From c9baf099804915be9670f166d891d445f2646b52 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Wed, 13 May 2026 16:33:27 -0700 Subject: [PATCH 01/19] APIGOV-32698 Fix Lint errors --- .github/workflows/build.yml | 26 ++++++------ src/commands/engage/get.ts | 4 +- .../clients-external/apiserverclient.ts | 14 +++---- src/lib/engage/results/DefinitionsManager.ts | 14 +++---- src/lib/engage/results/renderer.ts | 34 +++++++++++---- src/lib/engage/results/resultsrenderer.ts | 14 +++---- src/lib/engage/services/delete-service.ts | 5 ++- src/lib/engage/types.ts | 2 +- .../engage/utils/agents/flows/edgeAgents.ts | 9 +--- .../utils/agents/flows/gitHubSaasAgents.ts | 5 +-- .../engage/utils/agents/flows/gitLabAgents.ts | 2 +- src/lib/engage/utils/agents/regex.ts | 2 +- src/lib/engage/utils/basic-prompts.ts | 4 +- src/lib/engage/utils/regex.ts | 2 +- src/lib/engage/utils/utils.ts | 42 +++++++++---------- 15 files changed, 94 insertions(+), 85 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15796e5e..6338bdd8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,9 +12,9 @@ jobs: with: node-version: "20.x" - name: Install dependencies - run: yarn + run: npm ci - name: Lint - run: yarn run gulp lint + run: npm run lint MacOsTest: needs: Lint @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: false matrix: - nodeVersion: ["20.18.2", "22.13.1"] + nodeVersion: ["22.13.1", "24.x"] os: [macos-latest] steps: - uses: actions/checkout@v4 @@ -31,9 +31,9 @@ jobs: with: node-version: ${{ matrix.nodeVersion }} - name: Install dependencies - run: yarn + run: npm ci - name: Run tests - run: yarn run test + run: npm test WindowsTest: needs: Lint @@ -42,17 +42,17 @@ jobs: strategy: fail-fast: false matrix: - nodeVersion: ["20.18.2", "22.13.1"] + nodeVersion: ["22.13.1", "24.x"] os: [windows-latest] steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.nodeVersion }} - name: Install dependencies - run: yarn + run: npm ci - name: Run tests - run: yarn run test + run: npm test UbuntuTest: needs: Lint @@ -61,7 +61,7 @@ jobs: strategy: fail-fast: false matrix: - nodeVersion: ["20.18.2", "22.13.1"] + nodeVersion: ["22.13.1", "24.x"] os: [ubuntu-latest] steps: - uses: actions/checkout@v4 @@ -71,6 +71,6 @@ jobs: - name: Install libsecret run: sudo apt-get install -y libsecret-1-0 - name: Install dependencies - run: yarn + run: npm ci - name: Run tests - run: yarn run test + run: npm test diff --git a/src/commands/engage/get.ts b/src/commands/engage/get.ts index b3336bb5..3f7021dc 100644 --- a/src/commands/engage/get.ts +++ b/src/commands/engage/get.ts @@ -111,8 +111,8 @@ export default class EngageGet extends Command { let teamGuid: string | undefined; if (flags.team) { const match = teams?.teams?.find((t: PlatformTeam) => - t.guid.toLowerCase() === flags.team.toLowerCase() || - t.name.toLowerCase() === flags.team.toLowerCase() + t.guid.toLowerCase() === flags.team.toLowerCase() + || t.name.toLowerCase() === flags.team.toLowerCase() ); if (!match) { throw new Error(`Unable to find team "${flags.team}" in the "${account.org.name}" organization`); diff --git a/src/lib/engage/clients-external/apiserverclient.ts b/src/lib/engage/clients-external/apiserverclient.ts index e6d968f4..fe7ecbb5 100644 --- a/src/lib/engage/clients-external/apiserverclient.ts +++ b/src/lib/engage/clients-external/apiserverclient.ts @@ -1097,13 +1097,13 @@ export class ApiServerClient { if (singleResult.pending) { const pendingData = singleResult.data - ?? sanitizeMetadata( - buildGenericResource({ - resourceName: resourceName, - resourceDef: resourceDef, - scopeName: scopeName, - }) as GenericResource, - ); + ?? sanitizeMetadata( + buildGenericResource({ + resourceName: resourceName, + resourceDef: resourceDef, + scopeName: scopeName, + }) as GenericResource, + ); const subResResult = await this.resolveSubResourcesRequests( pendingData, singleResult.pending, diff --git a/src/lib/engage/results/DefinitionsManager.ts b/src/lib/engage/results/DefinitionsManager.ts index 21abbe48..759b07f1 100644 --- a/src/lib/engage/results/DefinitionsManager.ts +++ b/src/lib/engage/results/DefinitionsManager.ts @@ -237,7 +237,7 @@ export class DefinitionsManager { definition.spec.references.toResources.push({ kind: scopeDef.spec.kind, // NOTE: not used value, adding just to indicate it's manual nature. - // @ts-ignore + // @ts-expect-error -- not used, indicates manual nature types: [ 'CALCULATED' ], }); } @@ -245,7 +245,7 @@ export class DefinitionsManager { if (!scopeDef.spec.references.fromResources.find((ref) => ref.kind === definition.spec.kind)) { scopeDef.spec.references.fromResources.push({ kind: definition.spec.kind, - // @ts-ignore + // @ts-expect-error -- not used, indicates manual nature // NOTE: not used value, adding just to indicate it's manual nature. types: [ 'CALCULATED' ], scopeKind: definition.spec.scope.kind, @@ -284,13 +284,13 @@ export class DefinitionsManager { const res = [ ...this.resources ].reduce< { resource: ResourceDefinition; cli: CommandLineInterface; scope?: ResourceDefinition }[] - >((a, [ _, def ]) => { + >((a, [ _k, def ]) => { if (def.spec.kind === kind) { a.push({ resource: def, - cli: [ ...this.cli ].find(([ _, cliDef ]) => cliDef.spec.resourceDefinition === def.name)![1], + cli: [ ...this.cli ].find(([ _k, cliDef ]) => cliDef.spec.resourceDefinition === def.name)![1], scope: def.spec.scope - ? [ ...this.resources ].find(([ _, resDef ]) => resDef.spec.kind === def.spec.scope!.kind)![1] + ? [ ...this.resources ].find(([ _k, resDef ]) => resDef.spec.kind === def.spec.scope!.kind)![1] : undefined, }); } @@ -314,7 +314,7 @@ export class DefinitionsManager { return null; } const cliKv = [ ...this.cli ].filter( - ([ _, v ]) => + ([ _k, v ]) => v.spec?.names.plural === word || v.spec?.names.singular === word || v.spec?.names.shortNames.includes(word) @@ -326,7 +326,7 @@ export class DefinitionsManager { } const result = [ ...this.cli ].reduce< { resource: ResourceDefinition; cli: CommandLineInterface; scope?: ResourceDefinition }[] - >((a, [ _, cliDef ]) => { + >((a, [ _k, cliDef ]) => { if ( cliDef.spec?.names.plural === word || cliDef.spec?.names.singular === word diff --git a/src/lib/engage/results/renderer.ts b/src/lib/engage/results/renderer.ts index 364887ee..2664536b 100644 --- a/src/lib/engage/results/renderer.ts +++ b/src/lib/engage/results/renderer.ts @@ -36,7 +36,7 @@ export default class Renderer { * @param text text to display near the spinner */ startSpin(text: string): Renderer { - this.spinner && this.spinner.start(text); + this.spinner?.start(text); return this; } @@ -77,9 +77,11 @@ export default class Renderer { * only when spinner is in use (which mean no output param has been provided) */ success(text: string, spinnerOnly: boolean = false): void { - this.output && !spinnerOnly - ? this.console(text) - : this.spinner && this.spinner.succeed(chalk.greenBright(text)); + if (this.output && !spinnerOnly) { + this.console(text); + } else { + this.spinner?.succeed(chalk.greenBright(text)); + } } /** @@ -90,7 +92,11 @@ export default class Renderer { * only when spinner is in use (which mean no output param has been provided) */ warning(text: string, spinnerOnly: boolean = false): void { - this.output && !spinnerOnly ? this.console(text) : this.spinner && this.spinner.warn(chalk.yellow(text)); + if (this.output && !spinnerOnly) { + this.console(text); + } else { + this.spinner?.warn(chalk.yellow(text)); + } } /** @@ -230,7 +236,11 @@ export default class Renderer { error: RenderGetResultsInput[]; }>( (a, c) => { - c.response.error ? a.error.push(c) : a.success.push(c); + if (c.response.error) { + a.error.push(c); + } else { + a.success.push(c); + } return a; }, { success: [], error: [] } @@ -252,7 +262,11 @@ export default class Renderer { * an array even if its the only one, so flatten the responses data and create a single array for rendering */ dataToRender = sortedResults.success.reduce((a, v) => { - Array.isArray(v.response.data) ? a.push(...v.response.data) : a.push(v.response.data!); + if (Array.isArray(v.response.data)) { + a.push(...v.response.data); + } else { + a.push(v.response.data!); + } return a; }, []); } @@ -270,7 +284,11 @@ export default class Renderer { notEmpty: RenderGetResultsInput[]; }>( (a, c) => { - Array.isArray(c.response.data) && !c.response.data.length ? a.empty.push(c) : a.notEmpty.push(c); + if (Array.isArray(c.response.data) && !c.response.data.length) { + a.empty.push(c); + } else { + a.notEmpty.push(c); + } return a; }, { empty: [], notEmpty: [] } diff --git a/src/lib/engage/results/resultsrenderer.ts b/src/lib/engage/results/resultsrenderer.ts index 9f0a7322..678ccfc2 100644 --- a/src/lib/engage/results/resultsrenderer.ts +++ b/src/lib/engage/results/resultsrenderer.ts @@ -48,7 +48,7 @@ const parseAsTable = ( response: GenericResource | GenericResource[], columns: CommandLineInterfaceColumns[] ): string => { - const data = Array.isArray(response) ? response : [response]; + const data = Array.isArray(response) ? response : [ response ]; const t = new Table(); for (const i of data) { for (const col of columns) { @@ -60,10 +60,10 @@ const parseAsTable = ( } else if (col.type === 'teamGuid' && !value) { value = chalk.gray('---'); } else if (value && value.length > MAX_TABLE_STRING_LENGTH + 3) { - value = - value.substring(0, MAX_TABLE_STRING_LENGTH / 2) + - '...' + - value.substring(value.length - MAX_TABLE_STRING_LENGTH / 2); + value + = value.substring(0, MAX_TABLE_STRING_LENGTH / 2) + + '...' + + value.substring(value.length - MAX_TABLE_STRING_LENGTH / 2); } if (deletingState) { t.cell(col.name.toUpperCase(), chalk.yellow(value)); @@ -139,7 +139,7 @@ export async function resolveTeamNames({ } const jsonPath = column.jsonPath.substring(1); - const results = Array.isArray(response) ? response : [response]; + const results = Array.isArray(response) ? response : [ response ]; const teamNames: TeamNameLookup = {}; const sdk = await initSDK({ env: account?.auth.env }); @@ -159,7 +159,7 @@ export async function resolveTeamNames({ continue; } if (!Array.isArray(data)) { - data = [data]; + data = [ data ]; } for (const obj of data) { const value = _.get(obj, jsonPath, null); diff --git a/src/lib/engage/services/delete-service.ts b/src/lib/engage/services/delete-service.ts index f857e940..9bd12e86 100644 --- a/src/lib/engage/services/delete-service.ts +++ b/src/lib/engage/services/delete-service.ts @@ -103,8 +103,9 @@ async function runSingleDelete({ const matchingDefs = defs.filter( (def) => - (scope && ((scope.kind && scope.kind === def.scope?.spec.kind) || (!scope.kind && !!def.scope))) || - (!scope && !def.scope) + (scope + && ((scope.kind && scope.kind === def.scope?.spec.kind) || (!scope.kind && !!def.scope))) + || (!scope && !def.scope) ); if (!matchingDefs.length) { diff --git a/src/lib/engage/types.ts b/src/lib/engage/types.ts index 79426d90..4daabbd7 100644 --- a/src/lib/engage/types.ts +++ b/src/lib/engage/types.ts @@ -924,7 +924,7 @@ export class AgentInstallConfig { this.deploymentType = AgentConfigTypes.DOCKERIZED; // eslint-disable-next-line no-new-object this.gatewayConfig = new Object(); - this.idpConfig = [[], []]; + this.idpConfig = [ [], [] ]; this.daVersion = 'latest'; this.taVersion = 'latest'; this.caVersion = 'latest'; diff --git a/src/lib/engage/utils/agents/flows/edgeAgents.ts b/src/lib/engage/utils/agents/flows/edgeAgents.ts index 21258446..0db46850 100644 --- a/src/lib/engage/utils/agents/flows/edgeAgents.ts +++ b/src/lib/engage/utils/agents/flows/edgeAgents.ts @@ -4,7 +4,7 @@ import { dataService } from '../../../../request.js'; import { InstallationFlowMethods, localhost, svcAccMsg } from '../../../services/install-service.js'; import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, LoggingSource, PublicDockerRepoBaseUrl, PublicRepoUrl, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, askUsernameAndPassword } from '../../basic-prompts.js'; -import { writeTemplates, isWindows, AgentHelmInfo } from '../../utils.js'; +import { writeTemplates, isWindows, AgentHelmInfo, helmImageSecretInfo, helmInstallInfo } from '../../utils.js'; import { V7AgentValues } from '../index.js'; import * as helpers from '../index.js'; import { kubectl } from '../kubectl.js'; @@ -504,11 +504,4 @@ export const EdgeGWOnlyInstallMethods: InstallationFlowMethods = { AgentNameMap: edgeAgentNameMap, GatewayDisplay: GatewayTypes.EDGE_GATEWAY, }; -function helmImageSecretInfo(namespace: string) { - throw new Error('Function not implemented.'); -} - -function helmInstallInfo(arg0: string, namespace: string, agentHelmInfo: Set) { - throw new Error('Function not implemented.'); -} diff --git a/src/lib/engage/utils/agents/flows/gitHubSaasAgents.ts b/src/lib/engage/utils/agents/flows/gitHubSaasAgents.ts index 37a932c5..7fcf5aa3 100644 --- a/src/lib/engage/utils/agents/flows/gitHubSaasAgents.ts +++ b/src/lib/engage/utils/agents/flows/gitHubSaasAgents.ts @@ -79,9 +79,6 @@ class SaasGitHubAgentValues extends SaasAgentValues { } } -// ConfigFiles - all the config file that are used in the setup -const ConfigFiles = {}; - // GitHub SaaSPrompts - all GitHub Saas prompts to the user for input const SaasPrompts = { ACCESS_TOKEN: 'Enter the GitHub Access Token the agent will use', @@ -318,7 +315,7 @@ const completeInstall = async (installConfig: AgentInstallConfig, apiServerClien dataplaneRes.name, await createEncryptedAccessData(gitHubAgentValues, dataplaneRes), ); - } catch (error) { + } catch (_error) { console.log( chalk.redBright('rolling back installation. Please check the credential data before re-running install') ); diff --git a/src/lib/engage/utils/agents/flows/gitLabAgents.ts b/src/lib/engage/utils/agents/flows/gitLabAgents.ts index ca265f5c..ea2971ea 100644 --- a/src/lib/engage/utils/agents/flows/gitLabAgents.ts +++ b/src/lib/engage/utils/agents/flows/gitLabAgents.ts @@ -1,6 +1,6 @@ import chalk from 'chalk'; import { InstallationFlowMethods } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, PublicDockerRepoBaseUrl, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, BasePaths, BundleType, GatewayTypes, PublicDockerRepoBaseUrl, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, validateRegex } from '../../basic-prompts.js'; import { isWindows, writeTemplates } from '../../utils.js'; import { GitLabAgentValues } from '../index.js'; diff --git a/src/lib/engage/utils/agents/regex.ts b/src/lib/engage/utils/agents/regex.ts index cbefc1ab..f592a958 100644 --- a/src/lib/engage/utils/agents/regex.ts +++ b/src/lib/engage/utils/agents/regex.ts @@ -1,4 +1,4 @@ -export const resourceRegex = '^(?:[a-z0-9]*(?:\\.(?=[a-z0-9])|-+(?=[a-z0-9]))?)+[a-z0-9]$'; +/* eslint-disable no-useless-escape */export const resourceRegex = '^(?:[a-z0-9]*(?:\\.(?=[a-z0-9])|-+(?=[a-z0-9]))?)+[a-z0-9]$'; export const namespaceRegex = '^[a-z0-9]?(?:[-a-z0-9]*[a-z0-9]){1,100}?$'; export const domainNameRegex = '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\\.)+([A-Za-z]){2,}$'; export const dosaRegex = '^[\\w\\s-()[\\]]{1,100}$'; diff --git a/src/lib/engage/utils/basic-prompts.ts b/src/lib/engage/utils/basic-prompts.ts index 46f09a1c..dd841cb9 100644 --- a/src/lib/engage/utils/basic-prompts.ts +++ b/src/lib/engage/utils/basic-prompts.ts @@ -54,7 +54,7 @@ export const verifyApigeeXCredentialFile = (): InputValidation => (input: string throw new Error('File extension is invalid, please provide \'.json\' file'); } return true; - } catch (e) { + } catch (_e) { throw new Error(`Couldn't find the credential file: ${input}`); } }; @@ -62,7 +62,7 @@ export const verifyApigeeXCredentialFile = (): InputValidation => (input: string export const validateValidRegex = (): InputValidation => (input: string | number) => { try { new RegExp(input.toString()); - } catch (error) { + } catch (_error) { return 'Please provide a valid regular expression.'; } return true; diff --git a/src/lib/engage/utils/regex.ts b/src/lib/engage/utils/regex.ts index cbefc1ab..f592a958 100644 --- a/src/lib/engage/utils/regex.ts +++ b/src/lib/engage/utils/regex.ts @@ -1,4 +1,4 @@ -export const resourceRegex = '^(?:[a-z0-9]*(?:\\.(?=[a-z0-9])|-+(?=[a-z0-9]))?)+[a-z0-9]$'; +/* eslint-disable no-useless-escape */export const resourceRegex = '^(?:[a-z0-9]*(?:\\.(?=[a-z0-9])|-+(?=[a-z0-9]))?)+[a-z0-9]$'; export const namespaceRegex = '^[a-z0-9]?(?:[-a-z0-9]*[a-z0-9]){1,100}?$'; export const domainNameRegex = '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\\.)+([A-Za-z]){2,}$'; export const dosaRegex = '^[\\w\\s-()[\\]]{1,100}$'; diff --git a/src/lib/engage/utils/utils.ts b/src/lib/engage/utils/utils.ts index 5e79f33a..aca181fe 100644 --- a/src/lib/engage/utils/utils.ts +++ b/src/lib/engage/utils/utils.ts @@ -30,7 +30,7 @@ export const isWindows = /^win/.test(process.platform); export const writeToFile = (path: string, data: any): void => { try { writeFileSync(path, data); - } catch (e) { + } catch (_e) { // if parser is failing, rethrow with our own error throw new Error(`Error while writing the yaml file to: ${path}`); } @@ -45,7 +45,7 @@ export const isValidJson = (item: any) => { let parsedItem = typeof item !== 'string' ? JSON.stringify(item) : item; try { parsedItem = JSON.parse(parsedItem); - } catch (e) { + } catch (_e) { return false; } return typeof parsedItem === 'object' && item !== null; @@ -151,15 +151,15 @@ export const buildGenericResource = ({ attributes: {}, tags: [], metadata: - resourceDef?.spec?.scope && scopeName - ? ({ - scope: { - kind: resourceDef?.spec?.scope?.kind, - name: scopeName, - }, - // note: forced conversion here only because using generated resources for rendering simple text - } as unknown as Metadata) - : undefined, + resourceDef?.spec?.scope && scopeName + ? ({ + scope: { + kind: resourceDef?.spec?.scope?.kind, + name: scopeName, + }, + // note: forced conversion here only because using generated resources for rendering simple text + } as unknown as Metadata) + : undefined, spec: {}, }; } else { @@ -170,15 +170,15 @@ export const buildGenericResource = ({ attributes: {}, tags: [], metadata: - resourceDef?.spec?.scope && scopeName - ? ({ - scope: { - kind: resourceDef?.spec?.scope?.kind, - name: scopeName, - }, - // note: forced conversion here only because using generated resources for rendering simple text - } as unknown as Metadata) - : undefined, + resourceDef?.spec?.scope && scopeName + ? ({ + scope: { + kind: resourceDef?.spec?.scope?.kind, + name: scopeName, + }, + // note: forced conversion here only because using generated resources for rendering simple text + } as unknown as Metadata) + : undefined, spec: {}, }; } @@ -313,7 +313,7 @@ export const verifyFile = (specFilePath: string): Error | void => { try { stats = lstatSync(specFilePath); fileExtension = extname(specFilePath); - } catch (e) { + } catch (_e) { throw new Error(`Couldn't find the definition file: ${specFilePath}`); } From 3321b323d4c8becf9a58fe87398b954f36c877b1 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Wed, 13 May 2026 16:35:53 -0700 Subject: [PATCH 02/19] Update package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index c4991403..ac68364d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "debug": "node --inspect=9254 --enable-source-maps dist/index.js", "start": "./bin/run.js", "build": "tsc", + "pretest": "tsc", "test": "mocha --exit --recursive -r test/helpers/setup.js --slow 15000 --timeout 40000 test", "lint": "eslint . --ignore-pattern 'dist/**'", "watch": "tsc --watch", From ada50bbdb2b3c022eb395cf70a5e9c6293fcd9d1 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Wed, 13 May 2026 16:38:16 -0700 Subject: [PATCH 03/19] Update edgeAgents.ts --- src/lib/engage/utils/agents/flows/edgeAgents.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/engage/utils/agents/flows/edgeAgents.ts b/src/lib/engage/utils/agents/flows/edgeAgents.ts index 0db46850..c835ea38 100644 --- a/src/lib/engage/utils/agents/flows/edgeAgents.ts +++ b/src/lib/engage/utils/agents/flows/edgeAgents.ts @@ -54,6 +54,7 @@ const downloadV7AgentBundle = async (account: Account, type: BundleType, version ? `${BasePaths.V7Agents}/v7_discovery_agent/${version}/discovery_agent-${version}.zip` : `${BasePaths.V7Agents}/v7_traceability_agent/${version}/traceability_agent-${version}.zip`; const service = await dataService({ + account: account, baseUrl: PublicRepoUrl, }); try { From 8be49a19b6bbe75d737645ee86fa2760fd5d1989 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Tue, 26 May 2026 16:25:09 -0700 Subject: [PATCH 04/19] APIGOV-32698 Engage CLI - Add Unit Testing --- package-lock.json | 1112 ++- package.json | 7 + src/lib/engage/services/install-service.ts | 51 +- src/lib/engage/types.ts | 23 + .../engage/utils/agents/flows/akamaiAgent.ts | 3 +- .../utils/agents/flows/akamaiSaasAgents.ts | 3 +- .../utils/agents/flows/apigeexAgents.ts | 3 +- .../utils/agents/flows/apigeexSaasAgents.ts | 3 +- .../engage/utils/agents/flows/awsAgents.ts | 3 +- .../utils/agents/flows/awsSaasAgents.ts | 3 +- .../engage/utils/agents/flows/azureAgents.ts | 3 +- .../utils/agents/flows/azureSaasAgents.ts | 3 +- .../utils/agents/flows/backstageAgents.ts | 3 +- .../engage/utils/agents/flows/edgeAgents.ts | 3 +- .../utils/agents/flows/gitHubSaasAgents.ts | 3 +- .../engage/utils/agents/flows/gitLabAgents.ts | 3 +- .../utils/agents/flows/graylogAgents.ts | 3 +- .../utils/agents/flows/ibmAPIConnetAgents.ts | 3 +- .../engage/utils/agents/flows/istioAgents.ts | 3 +- .../engage/utils/agents/flows/kafkaAgents.ts | 3 +- .../utils/agents/flows/saasAgentsBase.ts | 3 +- .../utils/agents/flows/sapApiPortalAgents.ts | 3 +- .../utils/agents/flows/sensediaAgents.ts | 3 +- .../agents/flows/softwareAGWebMethodAgents.ts | 3 +- .../agents/flows/swaggerHubSaasAgents.ts | 3 +- .../utils/agents/flows/traceableAgents.ts | 3 +- .../utils/agents/flows/traceableSaasAgents.ts | 3 +- .../engage/utils/agents/flows/wso2Agents.ts | 3 +- src/lib/engage/utils/basic-prompts.ts | 19 + src/lib/engage/utils/utils.ts | 19 + test/_capture-output.mjs | 47 + .../templates/autogenerated-name.mustache | 1 + .../apply/templates/create-success.mustache | 4 + .../create-update-success-v1.mustache | 4 + .../templates/create-update-success.mustache | 4 + .../apply/templates/file-required.mustache | 1 + .../engage/apply/templates/help.mustache | 5 + .../templates/not-authenticated.mustache | 1 + .../templates/partial-400-error.mustache | 4 + .../apply/templates/resource-updated.mustache | 1 + .../apply/templates/server-error.mustache | 1 + .../templates/subresource-updated.mustache | 1 + .../apply/templates/validation-error.mustache | 1 + test/commands/engage/apply/test-apply.js | 297 + .../templates/autogenerated-name.mustache | 1 + .../templates/bulk-create-success.mustache | 4 + .../create/templates/file-required.mustache | 1 + .../engage/create/templates/help.mustache | 5 + .../templates/not-authenticated.mustache | 1 + .../create/templates/server-error.mustache | 1 + .../templates/validation-error.mustache | 1 + test/commands/engage/create/test-create.js | 159 + .../templates/bulk-delete-success.mustache | 2 + .../delete/templates/delete-success.mustache | 1 + .../engage/delete/templates/help.mustache | 5 + .../delete/templates/missing-args.mustache | 1 + .../templates/not-authenticated.mustache | 1 + test/commands/engage/delete/test-delete.js | 218 + .../edit/templates/edit-success.mustache | 1 + .../engage/edit/templates/help.mustache | 5 + .../engage/edit/templates/no-changes.mustache | 1 + .../edit/templates/not-authenticated.mustache | 1 + .../engage/edit/templates/not-found.mustache | 1 + test/commands/engage/edit/test-edit.js | 126 + .../engage/get/templates/help.mustache | 1 + .../templates/invalid-resource-type.mustache | 1 + .../templates/missing-resource-type.mustache | 1 + .../get/templates/not-authenticated.mustache | 1 + .../get/templates/not-found-error.mustache | 1 + .../get/templates/server-error.mustache | 1 + test/commands/engage/get/test-get.js | 307 + .../install/templates/agents-help.mustache | 3 + .../engage/install/templates/help.mustache | 5 + .../templates/no-resource-type.mustache | 1 + .../templates/not-authenticated.mustache | 1 + test/commands/engage/install/test-install.js | 55 + .../templates/file-required.mustache | 1 + .../engage/productize/templates/help.mustache | 5 + .../templates/not-authenticated.mustache | 1 + .../templates/productize-success.mustache | 1 + .../engage/productize/test-productize.js | 194 + test/helpers/engage-routes.js | 280 + test/helpers/engage-test-helper.js | 49 + test/helpers/index.js | 16 +- test/helpers/servers.js | 13 +- test/helpers/setup.js | 2 + test/helpers/test-editor.sh | 3 + .../on-prem/test-akamai.onprem.js | 295 + .../on-prem/test-apigeex.onprem.js | 265 + .../on-prem/test-aws.onprem.js | 463 ++ .../on-prem/test-azure-gateway.onprem.js | 289 + .../on-prem/test-backstage.onprem.js | 201 + .../on-prem/test-edge-gateway.onprem.js | 242 + .../on-prem/test-gitlab.onprem.js | 229 + .../on-prem/test-graylog.onprem.js | 211 + .../on-prem/test-ibm-api-connect.onprem.js | 200 + .../on-prem/test-istio.onprem.js | 257 + .../on-prem/test-kafka.onprem.js | 213 + .../on-prem/test-sap-api-portal.onprem.js | 199 + .../on-prem/test-sensedia.onprem.js | 205 + .../test-softwareag-webmethods.onprem.js | 229 + .../on-prem/test-traceable.onprem.js | 246 + .../on-prem/test-wso2.onprem.js | 203 + .../install-service/saas/test-akamai.saas.js | 186 + .../install-service/saas/test-apigeex.saas.js | 178 + .../install-service/saas/test-aws.saas.js | 245 + .../saas/test-azure-gateway.saas.js | 324 + .../install-service/saas/test-github.saas.js | 185 + .../saas/test-swaggerhub.saas.js | 179 + .../saas/test-traceable.saas.js | 193 + test/resources/examples/agentresources.json | 34 + test/resources/examples/agentresources.yaml | 25 + .../examples/apiserviceinstances.json | 90 + .../examples/apiserviceinstances.yaml | 42 + .../apiserviceinstancesforproductization.json | 60 + .../examples/apiservicerevisions.json | 84 + .../examples/apiservicerevisions.yaml | 54 + test/resources/examples/apiservices.json | 60 + test/resources/examples/apiservices.yaml | 36 + test/resources/examples/apisvcincorrect.json | 31 + .../assetmappingforproductization.json | 40 + test/resources/examples/assetreleasetags.json | 17 + test/resources/examples/assets-complete.yaml | 144 + test/resources/examples/assets-mapping.yaml | 93 + .../examples/assetsforProductization.json | 37 + test/resources/examples/completeExample.yaml | 365 + .../resources/examples/consumerinstances.json | 124 + .../resources/examples/consumerinstances.yaml | 71 + .../examples/consumersubscriptiondefs.json | 96 + .../examples/consumersubscriptiondefs.yaml | 47 + test/resources/examples/discovery-agent.yaml | 16 + test/resources/examples/environments.json | 32 + test/resources/examples/environments.yaml | 27 + test/resources/examples/environmentsV1.json | 32 + test/resources/examples/environmentsV1.yaml | 27 + .../examples/musical-config-secured-v1.yaml | 21 + .../examples/musical-config-secured.yaml | 11 + test/resources/examples/musical-config.yaml | 10 + .../examples/productreleasetags.json | 17 + test/resources/examples/products.json | 28 + test/resources/examples/secrets.json | 66 + test/resources/examples/secrets.yaml | 39 + .../examples/traceability-agent.yaml | 13 + test/resources/examples/webhooks.json | 98 + test/resources/examples/webhooks.yaml | 51 + test/resources/images/env_variables.png | Bin 0 -> 95964 bytes test/resources/testData/README.md | 17 + .../testData/apiResponses/apiservices.json | 62 + .../testData/apiResponses/assets.json | 309 + .../apiResponses/catalogCommandLines.json | 1930 +++++ .../apiResponses/catalogResources.json | 2150 ++++++ .../testData/apiResponses/environments.json | 111 + .../testData/apiResponses/environmentsV1.json | 110 + .../frenchLanguageDefinition.json | 35 + .../testData/apiResponses/groups.json | 65 + .../apiResponses/managementCommandLines.json | 6834 +++++++++++++++++ .../apiResponses/managementResources.json | 3238 ++++++++ .../apiResponses/productWithLanguages.json | 138 + .../testData/apiResponses/products.json | 130 + .../testData/apiResponses/secrets.json | 134 + .../testData/apiResponses/stages.json | 58 + .../testData/apiResponses/versions.json | 1756 +++++ .../testData/apiResponses/webhooks.json | 98 + ...apiResponse-asset1-autogenerated-name.json | 27 + .../byKind/Asset/apiResponse-asset1.json | 27 + ...apiResponse-asset2-autogenerated-name.json | 27 + .../byKind/Asset/apiResponse-asset2.json | 27 + .../byKind/Asset/input-without-names.yaml | 20 + .../testData/byKind/Asset/input.yaml | 22 + .../testData/envsWithoutLogicalNames.yaml | 26 + test/resources/testData/errors.js | 71 + .../testData/installFiles/aws_da_env_vars | 19 + .../installFiles/aws_da_env_vars_docker | 23 + .../testData/installFiles/aws_da_env_vars_us | 18 + .../installFiles/aws_da_env_vars_us_docker | 22 + .../testData/installFiles/aws_ta_env_vars | 18 + .../installFiles/aws_ta_env_vars_docker | 22 + .../installFiles/aws_ta_env_vars_https | 18 + .../installFiles/aws_ta_env_vars_https_docker | 22 + .../testData/installFiles/aws_ta_env_vars_us | 17 + .../installFiles/aws_ta_env_vars_us_docker | 21 + .../installFiles/aws_ta_env_vars_us_https | 17 + .../aws_ta_env_vars_us_https_docker | 21 + .../installFiles/cloudformation_props | 31 + test/resources/testData/invalidEnvSpecs1.yaml | 13 + test/resources/testData/invalidEnvSpecs2.yaml | 16 + test/resources/testData/invalidJson.json | 16 + test/resources/testData/invalidYaml.yaml | 3 + test/resources/testData/testInstances1.yaml | 181 + .../testData/testInstances1short.json | 87 + .../testData/testInstances1short.yaml | 78 + test/resources/testData/testInstances2.yaml | 181 + .../testData/testInstancesV1short.yaml | 78 + .../testInstancesWithMissingLogicalNames.yaml | 104 + test/resources/testData/test_productize.json | 28 + .../testData/test_productize_error.json | 27 + test/resources/testData/utf-16.yaml | Bin 0 -> 316 bytes 197 files changed, 28999 insertions(+), 133 deletions(-) create mode 100644 test/_capture-output.mjs create mode 100644 test/commands/engage/apply/templates/autogenerated-name.mustache create mode 100644 test/commands/engage/apply/templates/create-success.mustache create mode 100644 test/commands/engage/apply/templates/create-update-success-v1.mustache create mode 100644 test/commands/engage/apply/templates/create-update-success.mustache create mode 100644 test/commands/engage/apply/templates/file-required.mustache create mode 100644 test/commands/engage/apply/templates/help.mustache create mode 100644 test/commands/engage/apply/templates/not-authenticated.mustache create mode 100644 test/commands/engage/apply/templates/partial-400-error.mustache create mode 100644 test/commands/engage/apply/templates/resource-updated.mustache create mode 100644 test/commands/engage/apply/templates/server-error.mustache create mode 100644 test/commands/engage/apply/templates/subresource-updated.mustache create mode 100644 test/commands/engage/apply/templates/validation-error.mustache create mode 100644 test/commands/engage/apply/test-apply.js create mode 100644 test/commands/engage/create/templates/autogenerated-name.mustache create mode 100644 test/commands/engage/create/templates/bulk-create-success.mustache create mode 100644 test/commands/engage/create/templates/file-required.mustache create mode 100644 test/commands/engage/create/templates/help.mustache create mode 100644 test/commands/engage/create/templates/not-authenticated.mustache create mode 100644 test/commands/engage/create/templates/server-error.mustache create mode 100644 test/commands/engage/create/templates/validation-error.mustache create mode 100644 test/commands/engage/create/test-create.js create mode 100644 test/commands/engage/delete/templates/bulk-delete-success.mustache create mode 100644 test/commands/engage/delete/templates/delete-success.mustache create mode 100644 test/commands/engage/delete/templates/help.mustache create mode 100644 test/commands/engage/delete/templates/missing-args.mustache create mode 100644 test/commands/engage/delete/templates/not-authenticated.mustache create mode 100644 test/commands/engage/delete/test-delete.js create mode 100644 test/commands/engage/edit/templates/edit-success.mustache create mode 100644 test/commands/engage/edit/templates/help.mustache create mode 100644 test/commands/engage/edit/templates/no-changes.mustache create mode 100644 test/commands/engage/edit/templates/not-authenticated.mustache create mode 100644 test/commands/engage/edit/templates/not-found.mustache create mode 100644 test/commands/engage/edit/test-edit.js create mode 100644 test/commands/engage/get/templates/help.mustache create mode 100644 test/commands/engage/get/templates/invalid-resource-type.mustache create mode 100644 test/commands/engage/get/templates/missing-resource-type.mustache create mode 100644 test/commands/engage/get/templates/not-authenticated.mustache create mode 100644 test/commands/engage/get/templates/not-found-error.mustache create mode 100644 test/commands/engage/get/templates/server-error.mustache create mode 100644 test/commands/engage/get/test-get.js create mode 100644 test/commands/engage/install/templates/agents-help.mustache create mode 100644 test/commands/engage/install/templates/help.mustache create mode 100644 test/commands/engage/install/templates/no-resource-type.mustache create mode 100644 test/commands/engage/install/templates/not-authenticated.mustache create mode 100644 test/commands/engage/install/test-install.js create mode 100644 test/commands/engage/productize/templates/file-required.mustache create mode 100644 test/commands/engage/productize/templates/help.mustache create mode 100644 test/commands/engage/productize/templates/not-authenticated.mustache create mode 100644 test/commands/engage/productize/templates/productize-success.mustache create mode 100644 test/commands/engage/productize/test-productize.js create mode 100644 test/helpers/engage-routes.js create mode 100644 test/helpers/engage-test-helper.js create mode 100755 test/helpers/test-editor.sh create mode 100644 test/lib/engage/services/install-service/on-prem/test-akamai.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-apigeex.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-aws.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-azure-gateway.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-backstage.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-edge-gateway.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-gitlab.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-graylog.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-ibm-api-connect.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-istio.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-kafka.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-sap-api-portal.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-sensedia.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-softwareag-webmethods.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-traceable.onprem.js create mode 100644 test/lib/engage/services/install-service/on-prem/test-wso2.onprem.js create mode 100644 test/lib/engage/services/install-service/saas/test-akamai.saas.js create mode 100644 test/lib/engage/services/install-service/saas/test-apigeex.saas.js create mode 100644 test/lib/engage/services/install-service/saas/test-aws.saas.js create mode 100644 test/lib/engage/services/install-service/saas/test-azure-gateway.saas.js create mode 100644 test/lib/engage/services/install-service/saas/test-github.saas.js create mode 100644 test/lib/engage/services/install-service/saas/test-swaggerhub.saas.js create mode 100644 test/lib/engage/services/install-service/saas/test-traceable.saas.js create mode 100644 test/resources/examples/agentresources.json create mode 100644 test/resources/examples/agentresources.yaml create mode 100644 test/resources/examples/apiserviceinstances.json create mode 100644 test/resources/examples/apiserviceinstances.yaml create mode 100644 test/resources/examples/apiserviceinstancesforproductization.json create mode 100644 test/resources/examples/apiservicerevisions.json create mode 100644 test/resources/examples/apiservicerevisions.yaml create mode 100644 test/resources/examples/apiservices.json create mode 100644 test/resources/examples/apiservices.yaml create mode 100644 test/resources/examples/apisvcincorrect.json create mode 100644 test/resources/examples/assetmappingforproductization.json create mode 100644 test/resources/examples/assetreleasetags.json create mode 100644 test/resources/examples/assets-complete.yaml create mode 100644 test/resources/examples/assets-mapping.yaml create mode 100644 test/resources/examples/assetsforProductization.json create mode 100644 test/resources/examples/completeExample.yaml create mode 100644 test/resources/examples/consumerinstances.json create mode 100644 test/resources/examples/consumerinstances.yaml create mode 100644 test/resources/examples/consumersubscriptiondefs.json create mode 100644 test/resources/examples/consumersubscriptiondefs.yaml create mode 100644 test/resources/examples/discovery-agent.yaml create mode 100644 test/resources/examples/environments.json create mode 100644 test/resources/examples/environments.yaml create mode 100644 test/resources/examples/environmentsV1.json create mode 100644 test/resources/examples/environmentsV1.yaml create mode 100644 test/resources/examples/musical-config-secured-v1.yaml create mode 100644 test/resources/examples/musical-config-secured.yaml create mode 100644 test/resources/examples/musical-config.yaml create mode 100644 test/resources/examples/productreleasetags.json create mode 100644 test/resources/examples/products.json create mode 100644 test/resources/examples/secrets.json create mode 100644 test/resources/examples/secrets.yaml create mode 100644 test/resources/examples/traceability-agent.yaml create mode 100644 test/resources/examples/webhooks.json create mode 100644 test/resources/examples/webhooks.yaml create mode 100644 test/resources/images/env_variables.png create mode 100644 test/resources/testData/README.md create mode 100644 test/resources/testData/apiResponses/apiservices.json create mode 100644 test/resources/testData/apiResponses/assets.json create mode 100644 test/resources/testData/apiResponses/catalogCommandLines.json create mode 100644 test/resources/testData/apiResponses/catalogResources.json create mode 100644 test/resources/testData/apiResponses/environments.json create mode 100644 test/resources/testData/apiResponses/environmentsV1.json create mode 100644 test/resources/testData/apiResponses/frenchLanguageDefinition.json create mode 100644 test/resources/testData/apiResponses/groups.json create mode 100644 test/resources/testData/apiResponses/managementCommandLines.json create mode 100644 test/resources/testData/apiResponses/managementResources.json create mode 100644 test/resources/testData/apiResponses/productWithLanguages.json create mode 100644 test/resources/testData/apiResponses/products.json create mode 100644 test/resources/testData/apiResponses/secrets.json create mode 100644 test/resources/testData/apiResponses/stages.json create mode 100644 test/resources/testData/apiResponses/versions.json create mode 100644 test/resources/testData/apiResponses/webhooks.json create mode 100644 test/resources/testData/byKind/Asset/apiResponse-asset1-autogenerated-name.json create mode 100644 test/resources/testData/byKind/Asset/apiResponse-asset1.json create mode 100644 test/resources/testData/byKind/Asset/apiResponse-asset2-autogenerated-name.json create mode 100644 test/resources/testData/byKind/Asset/apiResponse-asset2.json create mode 100644 test/resources/testData/byKind/Asset/input-without-names.yaml create mode 100644 test/resources/testData/byKind/Asset/input.yaml create mode 100644 test/resources/testData/envsWithoutLogicalNames.yaml create mode 100644 test/resources/testData/errors.js create mode 100644 test/resources/testData/installFiles/aws_da_env_vars create mode 100644 test/resources/testData/installFiles/aws_da_env_vars_docker create mode 100644 test/resources/testData/installFiles/aws_da_env_vars_us create mode 100644 test/resources/testData/installFiles/aws_da_env_vars_us_docker create mode 100644 test/resources/testData/installFiles/aws_ta_env_vars create mode 100644 test/resources/testData/installFiles/aws_ta_env_vars_docker create mode 100644 test/resources/testData/installFiles/aws_ta_env_vars_https create mode 100644 test/resources/testData/installFiles/aws_ta_env_vars_https_docker create mode 100644 test/resources/testData/installFiles/aws_ta_env_vars_us create mode 100644 test/resources/testData/installFiles/aws_ta_env_vars_us_docker create mode 100644 test/resources/testData/installFiles/aws_ta_env_vars_us_https create mode 100644 test/resources/testData/installFiles/aws_ta_env_vars_us_https_docker create mode 100644 test/resources/testData/installFiles/cloudformation_props create mode 100644 test/resources/testData/invalidEnvSpecs1.yaml create mode 100644 test/resources/testData/invalidEnvSpecs2.yaml create mode 100644 test/resources/testData/invalidJson.json create mode 100644 test/resources/testData/invalidYaml.yaml create mode 100644 test/resources/testData/testInstances1.yaml create mode 100644 test/resources/testData/testInstances1short.json create mode 100644 test/resources/testData/testInstances1short.yaml create mode 100644 test/resources/testData/testInstances2.yaml create mode 100644 test/resources/testData/testInstancesV1short.yaml create mode 100644 test/resources/testData/testInstancesWithMissingLogicalNames.yaml create mode 100644 test/resources/testData/test_productize.json create mode 100644 test/resources/testData/test_productize_error.json create mode 100644 test/resources/testData/utf-16.yaml diff --git a/package-lock.json b/package-lock.json index 1abc4bb4..9b623d34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,10 +44,14 @@ "devDependencies": { "@koa/router": "^14.0.0", "@oclif/test": "^4.1.14", + "@types/chai-as-promised": "^8.0.2", + "@types/jest": "^30.0.0", "@types/lodash": "^4.17.20", "@types/node": "^22", + "@types/sinon": "^10.0.14", "@typescript-eslint/eslint-plugin": "^8.46.3", "@typescript-eslint/parser": "^8.59.3", + "c8": "^11.0.0", "caller-path": "^4.0.0", "chai": "6.2.0", "chai-as-promised": "^8.0.2", @@ -62,6 +66,8 @@ "mock-stdin": "^1.0.0", "mustache": "^4.2.0", "nock": "^14.0.10", + "sinon": "^15.0.3", + "testdouble": "^3.20.2", "tmp": "^0.2.5", "ts-node": "^10.9.2", "typescript": "^5.9.3" @@ -70,6 +76,41 @@ "node": ">=22.11.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -739,6 +780,125 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", + "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", + "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -987,6 +1147,13 @@ "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "license": "MIT" }, + "node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, "node_modules/@sindresorhus/is": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.1.tgz", @@ -999,6 +1166,55 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", + "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "type-detect": "^4.1.0" + } + }, + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "deprecated": "Deprecated: no longer maintained and no longer used by Sinon packages. See\n https://github.com/sinonjs/nise/issues/243 for replacement details.", + "dev": true, + "license": "(Unlicense OR Apache-2.0)" + }, "node_modules/@stylistic/eslint-plugin": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.6.1.tgz", @@ -1073,6 +1289,34 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/chai-as-promised": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-8.0.2.tgz", + "integrity": "sha512-meQ1wDr1K5KRCSvG2lX7n7/5wf70BeptTKst0axGvnN6zqaVpRqegoIbugiAPSqOW9K9aL8gDVrm7a2LXOtn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1086,6 +1330,44 @@ "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "license": "MIT" }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1117,6 +1399,47 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/sinon": { + "version": "10.0.20", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.20.tgz", + "integrity": "sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-15.0.1.tgz", + "integrity": "sha512-Ko2tjWJq8oozHzHV+reuvS5KYIRAokHnGbDwGh/J64LntgpbuylF74ipEL24HCyRjf9FOlBiBHWBR1RlVKsI1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -1863,6 +2186,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -2054,6 +2387,40 @@ "node": ">= 0.8" } }, + "node_modules/c8": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz", + "integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^8.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": "20 || >=22" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, "node_modules/cacheable-lookup": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", @@ -2568,6 +2935,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookies": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", @@ -3739,6 +4113,24 @@ "node": ">=6" } }, + "node_modules/expect": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.4.1", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -4400,6 +4792,13 @@ "he": "bin/he" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-assert": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", @@ -4948,6 +5347,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-set": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", @@ -5124,39 +5533,308 @@ "dev": true, "license": "ISC" }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" } }, - "node_modules/jake": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", - "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", - "license": "Apache-2.0", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "async": "^3.2.6", - "filelist": "^1.0.4", - "picocolors": "^1.1.1" - }, - "bin": { - "jake": "bin/cli.js" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" } }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-diff": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.4.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", + "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.4.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-util": "30.4.1", + "picomatch": "^4.0.3", + "pretty-format": "30.4.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jose": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz", @@ -5166,6 +5844,13 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -5219,6 +5904,13 @@ "json5": "lib/cli.js" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true, + "license": "MIT" + }, "node_modules/keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", @@ -5471,6 +6163,22 @@ "dev": true, "license": "ISC" }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5567,11 +6275,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -5680,6 +6388,37 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "license": "MIT" }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", + "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, "node_modules/nock": { "version": "14.0.10", "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.10.tgz", @@ -6225,6 +6964,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/pretty-ms": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", @@ -6292,6 +7060,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quibble": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/quibble/-/quibble-0.9.2.tgz", + "integrity": "sha512-BrL7hrZcbyyt5ZDfePkGFDc3m82uUtxCPOnpRUrkOdtBnmV9ldQKxXORkKL8eIzToRNaCpIPyKyfdfq/tBlFAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "resolve": "^1.22.8" + }, + "engines": { + "node": ">= 0.14.0" + } + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -6367,6 +7149,22 @@ "node": ">=0.10.0" } }, + "node_modules/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-is-19": { + "name": "react-is", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", + "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", + "dev": true, + "license": "MIT" + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -6880,6 +7678,59 @@ "simple-concat": "^1.0.0" } }, + "node_modules/sinon": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz", + "integrity": "sha512-nPS85arNqwBXaIsFCkolHjGIkFo+Oxu9vbgmBJizLAhqe6P2o3Qmj3KCUoRkfhHtvgDhZdWD3risLHAUJ8npjw==", + "deprecated": "16.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.4", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6889,6 +7740,29 @@ "node": ">=0.10.0" } }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -7051,6 +7925,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-object-es5": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/stringify-object-es5/-/stringify-object-es5-2.5.0.tgz", + "integrity": "sha512-vE7Xdx9ylG4JI16zy7/ObKUB+MtxuMcWlj/WHHr3+yAlQoN6sst2stU9E+2Qs3OrlJw/Pf3loWxL1GauEHf6MA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "is-plain-obj": "^1.0.0", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringify-object-es5/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", @@ -7183,6 +8081,128 @@ "node": ">=6" } }, + "node_modules/test-exclude": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^13.0.6", + "minimatch": "^10.2.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/lru-cache": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", + "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/testdouble": { + "version": "3.20.2", + "resolved": "https://registry.npmjs.org/testdouble/-/testdouble-3.20.2.tgz", + "integrity": "sha512-790e9vJKdfddWNOaxW1/V9FcMk48cPEl3eJSj2i8Hh1fX89qArEJ6cp3DBnaECpGXc3xKJVWbc1jeNlWYWgiMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "quibble": "^0.9.2", + "stringify-object-es5": "^2.5.0", + "theredoc": "^1.0.0" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/theredoc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/theredoc/-/theredoc-1.0.0.tgz", + "integrity": "sha512-KU3SA3TjRRM932jpNfD3u4Ec3bSvedyo5ITPI7zgWYnKep7BwQQaxlhI9qbO+lKJoRnoAbEVfMcAHRuKVYikDA==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -7374,6 +8394,16 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", @@ -7577,6 +8607,32 @@ "dev": true, "license": "MIT" }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index a5a205ce..24291407 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "build": "tsc", "pretest": "tsc", "test": "mocha --exit --recursive -r test/helpers/setup.js --slow 15000 --timeout 40000 test", + "engagetest": "mocha --exit --recursive -r test/helpers/setup.js --slow 15000 --timeout 40000 test/**/engage/**", "lint": "eslint . --ignore-pattern 'dist/**'", "watch": "tsc --watch", "postinstall": "node dist/scripts/postinstall.js || node -e true" @@ -57,10 +58,14 @@ "devDependencies": { "@koa/router": "^14.0.0", "@oclif/test": "^4.1.14", + "@types/chai-as-promised": "^8.0.2", + "@types/jest": "^30.0.0", "@types/lodash": "^4.17.20", "@types/node": "^22", + "@types/sinon": "^10.0.14", "@typescript-eslint/eslint-plugin": "^8.46.3", "@typescript-eslint/parser": "^8.59.3", + "c8": "^11.0.0", "caller-path": "^4.0.0", "chai": "6.2.0", "chai-as-promised": "^8.0.2", @@ -75,6 +80,8 @@ "mock-stdin": "^1.0.0", "mustache": "^4.2.0", "nock": "^14.0.10", + "sinon": "^15.0.3", + "testdouble": "^3.20.2", "tmp": "^0.2.5", "ts-node": "^10.9.2", "typescript": "^5.9.3" diff --git a/src/lib/engage/services/install-service.ts b/src/lib/engage/services/install-service.ts index 29c6c66e..a3c8ab61 100644 --- a/src/lib/engage/services/install-service.ts +++ b/src/lib/engage/services/install-service.ts @@ -4,11 +4,10 @@ import * as helpers from '../utils/agents/index.js'; import logger from '../../logger.js'; import { ApiServerClient } from '../clients-external/apiserverclient.js'; import { DefinitionsManager } from '../results/DefinitionsManager.js'; -import { AccountRole, AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BundleType, GatewayTypes, InstallAgentsCommandParams, Regions, SaaSGatewayTypes, YesNo, YesNoChoices } from '../types.js'; -import { askList, InputValidation, validateRegex } from '../utils/basic-prompts.js'; +import { AccountRole, AgentConfigTypes, AgentInstallConfig, AgentTypes, BundleType, GatewayTypes, InstallAgentsCommandParams, InstallationFlowMethods, Regions, SaaSGatewayTypes, YesNo, YesNoChoices } from '../types.js'; +import { askList } from '../utils/basic-prompts.js'; import { loadConfig } from '../../config.js'; import { PlatformClient } from '../clients-external/platformclient.js'; - import * as akamaiAgents from '../utils/agents/flows/akamaiAgent.js'; import * as akamaiSaasAgents from '../utils/agents/flows/akamaiSaasAgents.js'; import * as apigeeXAgents from '../utils/agents/flows/apigeexAgents.js'; @@ -36,36 +35,12 @@ import { Account } from '../../../types.js'; const log = logger('engage:install-service'); -export const localhost = 'localhost'; -export const svcAccMsg - = '\nPlease make sure to copy the "private_key.pem" and "public_key.pem" files for the existing service account you selected.'; - export const prompts = { hostedAgentOption: 'Will this be an embedded agent', selectGatewayType: 'Select the type of gateway you want to connect', }; -export interface InstallationFlowMethods { - GetBundleType: (Gateway?: GatewayTypes) => Promise; - GetDeploymentType: () => Promise; - AskGatewayQuestions: ( - installConfig: AgentInstallConfig, - apiServerClient?: ApiServerClient, - defsManager?: DefinitionsManager - ) => Promise; - InstallPreprocess?: (installConfig: AgentInstallConfig) => Promise; - AddIDP?: boolean; - FinalizeGatewayInstall: ( - installConfig: AgentInstallConfig, - apiServerClient?: ApiServerClient, - defsManager?: DefinitionsManager - ) => Promise; - ConfigFiles: string[]; - AgentNameMap?: { [key in AgentTypes]?: AgentNames }; - GatewayDisplay: GatewayTypes | SaaSGatewayTypes; -} - -const agentInstallFlows: { [key in GatewayTypes]: InstallationFlowMethods } = { +export const agentInstallFlows: { [key in GatewayTypes]: InstallationFlowMethods } = { [GatewayTypes.AKAMAI]: akamaiAgents.AkamaiInstallMethods, [GatewayTypes.EDGE_GATEWAY]: edgeAgents.EdgeInstallMethods, [GatewayTypes.EDGE_GATEWAY_ONLY]: edgeAgents.EdgeGWOnlyInstallMethods, @@ -86,7 +61,7 @@ const agentInstallFlows: { [key in GatewayTypes]: InstallationFlowMethods } = { [GatewayTypes.WSO2]: wso2Agents.WSO2InstallMethods, }; -const saasAgentInstallFlows: { [key: string]: InstallationFlowMethods } = { +export const saasAgentInstallFlows: { [key: string]: InstallationFlowMethods } = { [GatewayTypes.AKAMAI]: akamaiSaasAgents.AkamaiSaaSInstallMethods, [SaaSGatewayTypes.AWS_GATEWAY as string]: awsSaaSAgents.AWSSaaSInstallMethods, [SaaSGatewayTypes.GITHUB]: gitHubAgents.GitHubSaaSInstallMethods, @@ -156,24 +131,6 @@ function checkUserRole(isCentralAdmin: boolean, isPlatformAdmin: boolean, accoun } } -export const validateFrequency = (lowerLimit?: number): InputValidation => (input: string | number) => { - const val = validateRegex(helpers.frequencyRegex, helpers.invalidValueExampleErrMsg('frequency', '3d5h12m'))(input); - if (typeof val === 'string') { - return val; - } - const r = input.toString().match(/^(\d*)m/); - if (r) { - // only minutes - const mins = r[1]; - const minValue = parseInt(mins as string, 10); - const minimumRequired = lowerLimit ?? 30; // Use provided lowerLimit or default to 30 - if (minValue < minimumRequired) { - return `Minimum frequency is ${minimumRequired}m`; - } - } - return true; -}; - export async function installAgents(params: InstallAgentsCommandParams): Promise { try { // initialize clients diff --git a/src/lib/engage/types.ts b/src/lib/engage/types.ts index 47dac493..7d1b888d 100644 --- a/src/lib/engage/types.ts +++ b/src/lib/engage/types.ts @@ -20,6 +20,9 @@ export const MAX_CACHE_FILE_SIZE = 5 * 1024 * 1024; export const CACHE_FILE_TTL_MILLISECONDS = process.env.NODE_ENV === 'test' ? 100 : 60000 * 60 * 12; export const WAIT_TIMEOUT = process.env.NODE_ENV === 'test' ? 1e3 : 1e4; +export const localhost = 'localhost'; +export const svcAccMsg + = '\nPlease make sure to copy the "private_key.pem" and "public_key.pem" files for the existing service account you selected.'; /** * Invoked multiple times to indicate progress on something, such as download progress. @@ -1161,3 +1164,23 @@ export enum TraceableRegionType { Canada = 'Canada', UAE = 'UAE', } + +export interface InstallationFlowMethods { + GetBundleType: (Gateway?: GatewayTypes) => Promise; + GetDeploymentType: () => Promise; + AskGatewayQuestions: ( + installConfig: AgentInstallConfig, + apiServerClient?: ApiServerClient, + defsManager?: DefinitionsManager + ) => Promise; + InstallPreprocess?: (installConfig: AgentInstallConfig) => Promise; + AddIDP?: boolean; + FinalizeGatewayInstall: ( + installConfig: AgentInstallConfig, + apiServerClient?: ApiServerClient, + defsManager?: DefinitionsManager + ) => Promise; + ConfigFiles: string[]; + AgentNameMap?: { [key in AgentTypes]?: AgentNames }; + GatewayDisplay: GatewayTypes | SaaSGatewayTypes; +} diff --git a/src/lib/engage/utils/agents/flows/akamaiAgent.ts b/src/lib/engage/utils/agents/flows/akamaiAgent.ts index bb5888ee..92f04234 100644 --- a/src/lib/engage/utils/agents/flows/akamaiAgent.ts +++ b/src/lib/engage/utils/agents/flows/akamaiAgent.ts @@ -1,8 +1,7 @@ import chalk from 'chalk'; import { ApiServerClient } from '../../../clients-external/apiserverclient.js'; import { DefinitionsManager } from '../../../results/DefinitionsManager.js'; -import { InstallationFlowMethods, svcAccMsg } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, GenericResource, PublicDockerRepoBaseUrl, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, GenericResource, InstallationFlowMethods, PublicDockerRepoBaseUrl, svcAccMsg, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, validateRegex, validateValueRange } from '../../basic-prompts.js'; import { AgentHelmInfo, helmImageSecretInfo, helmInstallInfo, isWindows, writeTemplates } from '../../utils.js'; import { AkamaiAgentValues } from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/akamaiSaasAgents.ts b/src/lib/engage/utils/agents/flows/akamaiSaasAgents.ts index 31823bf1..41973d69 100644 --- a/src/lib/engage/utils/agents/flows/akamaiSaasAgents.ts +++ b/src/lib/engage/utils/agents/flows/akamaiSaasAgents.ts @@ -2,8 +2,7 @@ import chalk from 'chalk'; import logger from '../../../../logger.js'; import { ApiServerClient } from '../../../clients-external/apiserverclient.js'; import { DefinitionsManager } from '../../../results/DefinitionsManager.js'; -import { InstallationFlowMethods } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentResourceKind, AgentTypes, BundleType, CentralAgentConfig, GatewayTypes, GatewayTypeToDataPlane, GenericResource, SaaSGatewayTypes, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentResourceKind, AgentTypes, BundleType, CentralAgentConfig, GatewayTypes, GatewayTypeToDataPlane, GenericResource, InstallationFlowMethods, SaaSGatewayTypes, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, validateRegex, validateValueRange } from '../../basic-prompts.js'; import * as helpers from '../index.js'; import * as crypto from 'crypto'; diff --git a/src/lib/engage/utils/agents/flows/apigeexAgents.ts b/src/lib/engage/utils/agents/flows/apigeexAgents.ts index 20d1270f..30943272 100644 --- a/src/lib/engage/utils/agents/flows/apigeexAgents.ts +++ b/src/lib/engage/utils/agents/flows/apigeexAgents.ts @@ -1,6 +1,5 @@ import chalk from 'chalk'; -import { InstallationFlowMethods, svcAccMsg } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, ApigeeMetricsFilterConfig, BasePaths, BundleType, GatewayTypes, PublicDockerRepoBaseUrl, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, ApigeeMetricsFilterConfig, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, PublicDockerRepoBaseUrl, svcAccMsg, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, validateRegex } from '../../basic-prompts.js'; import { isWindows, writeTemplates } from '../../utils.js'; import { ApigeeXAgentValues } from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/apigeexSaasAgents.ts b/src/lib/engage/utils/agents/flows/apigeexSaasAgents.ts index a33ffb32..a367799c 100644 --- a/src/lib/engage/utils/agents/flows/apigeexSaasAgents.ts +++ b/src/lib/engage/utils/agents/flows/apigeexSaasAgents.ts @@ -2,8 +2,7 @@ import chalk from 'chalk'; import logger from '../../../../logger.js'; import { ApiServerClient } from '../../../clients-external/apiserverclient.js'; import { DefinitionsManager } from '../../../results/DefinitionsManager.js'; -import { InstallationFlowMethods } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentResourceKind, AgentTypes, ApigeeMetricsFilterConfig, APIGEEXAuthType, APIGEEXDISCOVERYMODES, BundleType, CentralAgentConfig, GatewayTypeToDataPlane, GenericResource, SaaSGatewayTypes, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentResourceKind, AgentTypes, ApigeeMetricsFilterConfig, APIGEEXAuthType, APIGEEXDISCOVERYMODES, BundleType, CentralAgentConfig, GatewayTypeToDataPlane, GenericResource, InstallationFlowMethods, SaaSGatewayTypes, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, InputValidation, validateRegex } from '../../basic-prompts.js'; import * as helpers from '../index.js'; import * as crypto from 'crypto'; diff --git a/src/lib/engage/utils/agents/flows/awsAgents.ts b/src/lib/engage/utils/agents/flows/awsAgents.ts index 2cc14539..e97ea2fa 100644 --- a/src/lib/engage/utils/agents/flows/awsAgents.ts +++ b/src/lib/engage/utils/agents/flows/awsAgents.ts @@ -2,8 +2,7 @@ import chalk from 'chalk'; import fs from 'fs'; import logger from '../../../../logger.js'; import { dataService } from '../../../../request.js'; -import { InstallationFlowMethods } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, AWSRegions, BasePaths, BundleType, GatewayTypes, PublicDockerRepoBaseUrl, PublicRepoUrl, TrueFalse, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, AWSRegions, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, PublicDockerRepoBaseUrl, PublicRepoUrl, TrueFalse, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, validateInputLength, validateRegex } from '../../basic-prompts.js'; import { isWindows, writeTemplates, writeToFile } from '../../utils.js'; import { AWSAgentValues } from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/awsSaasAgents.ts b/src/lib/engage/utils/agents/flows/awsSaasAgents.ts index d32fb599..f8997e82 100644 --- a/src/lib/engage/utils/agents/flows/awsSaasAgents.ts +++ b/src/lib/engage/utils/agents/flows/awsSaasAgents.ts @@ -2,8 +2,7 @@ import chalk from 'chalk'; import logger from '../../../../logger.js'; import { ApiServerClient } from '../../../clients-external/apiserverclient.js'; import { DefinitionsManager } from '../../../results/DefinitionsManager.js'; -import { InstallationFlowMethods } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BundleType, GatewayTypes, SaaSGatewayTypes, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BundleType, GatewayTypes, InstallationFlowMethods, SaaSGatewayTypes, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, validateInputLength, validateRegex } from '../../basic-prompts.js'; import * as helpers from '../index.js'; import { diff --git a/src/lib/engage/utils/agents/flows/azureAgents.ts b/src/lib/engage/utils/agents/flows/azureAgents.ts index ae5f61db..8be25da5 100644 --- a/src/lib/engage/utils/agents/flows/azureAgents.ts +++ b/src/lib/engage/utils/agents/flows/azureAgents.ts @@ -1,6 +1,5 @@ import chalk from 'chalk'; -import { InstallationFlowMethods, svcAccMsg } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, PublicDockerRepoBaseUrl } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, PublicDockerRepoBaseUrl, svcAccMsg } from '../../../types.js'; import { askInput, askList, validateRegex } from '../../basic-prompts.js'; import { isWindows, writeTemplates } from '../../utils.js'; import { AzureAgentValues } from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/azureSaasAgents.ts b/src/lib/engage/utils/agents/flows/azureSaasAgents.ts index 559548be..f7bf4a46 100644 --- a/src/lib/engage/utils/agents/flows/azureSaasAgents.ts +++ b/src/lib/engage/utils/agents/flows/azureSaasAgents.ts @@ -1,7 +1,6 @@ import chalk from 'chalk'; import logger from '../../../../logger.js'; -import { InstallationFlowMethods } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, AzureDataplaneMode, BundleType, GatewayTypes } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, AzureDataplaneMode, BundleType, GatewayTypes, InstallationFlowMethods } from '../../../types.js'; import { askInput, askList, validateRegex } from '../../basic-prompts.js'; import * as helpers from '../index.js'; import { ApiServerClient } from '../../../clients-external/apiserverclient.js'; diff --git a/src/lib/engage/utils/agents/flows/backstageAgents.ts b/src/lib/engage/utils/agents/flows/backstageAgents.ts index 9e718af9..5925b3e7 100644 --- a/src/lib/engage/utils/agents/flows/backstageAgents.ts +++ b/src/lib/engage/utils/agents/flows/backstageAgents.ts @@ -1,5 +1,4 @@ -import { InstallationFlowMethods, svcAccMsg } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, PublicDockerRepoBaseUrl } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, PublicDockerRepoBaseUrl, svcAccMsg } from '../../../types.js'; import { askInput, askList } from '../../basic-prompts.js'; import { AuthMode, BackstageAgentValues, backstageDAEnvVarTemplate, UrlScheme } from '../templates/backstageTemplates.js'; import * as helpers from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/edgeAgents.ts b/src/lib/engage/utils/agents/flows/edgeAgents.ts index b02c9095..661040ad 100644 --- a/src/lib/engage/utils/agents/flows/edgeAgents.ts +++ b/src/lib/engage/utils/agents/flows/edgeAgents.ts @@ -1,8 +1,7 @@ import chalk from 'chalk'; import fs from 'fs'; import { dataService } from '../../../../request.js'; -import { InstallationFlowMethods, localhost, svcAccMsg } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, LoggingSource, PublicDockerRepoBaseUrl, PublicRepoUrl, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, localhost, LoggingSource, PublicDockerRepoBaseUrl, PublicRepoUrl, svcAccMsg, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, askUsernameAndPassword } from '../../basic-prompts.js'; import { writeTemplates, isWindows, AgentHelmInfo, helmImageSecretInfo, helmInstallInfo } from '../../utils.js'; import { V7AgentValues } from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/gitHubSaasAgents.ts b/src/lib/engage/utils/agents/flows/gitHubSaasAgents.ts index ecb63a04..86ff5756 100644 --- a/src/lib/engage/utils/agents/flows/gitHubSaasAgents.ts +++ b/src/lib/engage/utils/agents/flows/gitHubSaasAgents.ts @@ -2,8 +2,7 @@ import chalk from 'chalk'; import logger from '../../../../logger.js'; import { ApiServerClient } from '../../../clients-external/apiserverclient.js'; import { DefinitionsManager } from '../../../results/DefinitionsManager.js'; -import { InstallationFlowMethods } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentResourceKind, AgentTypes, BundleType, CentralAgentConfig, GatewayTypeToDataPlane, GenericResource, SaaSGatewayTypes, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentResourceKind, AgentTypes, BundleType, CentralAgentConfig, GatewayTypeToDataPlane, GenericResource, InstallationFlowMethods, SaaSGatewayTypes, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, InputValidation, validateRegex } from '../../basic-prompts.js'; import * as helpers from '../index.js'; import * as crypto from 'crypto'; diff --git a/src/lib/engage/utils/agents/flows/gitLabAgents.ts b/src/lib/engage/utils/agents/flows/gitLabAgents.ts index ff65c0e8..42de9bc6 100644 --- a/src/lib/engage/utils/agents/flows/gitLabAgents.ts +++ b/src/lib/engage/utils/agents/flows/gitLabAgents.ts @@ -1,6 +1,5 @@ import chalk from 'chalk'; -import { InstallationFlowMethods } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, BasePaths, BundleType, GatewayTypes, PublicDockerRepoBaseUrl, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, PublicDockerRepoBaseUrl, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, validateRegex } from '../../basic-prompts.js'; import { isWindows, writeTemplates } from '../../utils.js'; import { GitLabAgentValues } from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/graylogAgents.ts b/src/lib/engage/utils/agents/flows/graylogAgents.ts index 0ade491d..0aed3f8e 100644 --- a/src/lib/engage/utils/agents/flows/graylogAgents.ts +++ b/src/lib/engage/utils/agents/flows/graylogAgents.ts @@ -1,6 +1,5 @@ import chalk from 'chalk'; -import { InstallationFlowMethods } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BundleType, GatewayTypes } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BundleType, GatewayTypes, InstallationFlowMethods } from '../../../types.js'; import { askInput, validateRegex } from '../../basic-prompts.js'; import { AgentHelmInfo, helmImageSecretInfo, helmInstallInfo, writeTemplates } from '../../utils.js'; import { GraylogAgentValues } from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/ibmAPIConnetAgents.ts b/src/lib/engage/utils/agents/flows/ibmAPIConnetAgents.ts index e03a975c..af54d20d 100644 --- a/src/lib/engage/utils/agents/flows/ibmAPIConnetAgents.ts +++ b/src/lib/engage/utils/agents/flows/ibmAPIConnetAgents.ts @@ -1,6 +1,5 @@ import chalk from 'chalk'; -import { InstallationFlowMethods, svcAccMsg } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, PublicDockerRepoBaseUrl } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, PublicDockerRepoBaseUrl, svcAccMsg } from '../../../types.js'; import { askInput, askList } from '../../basic-prompts.js'; import { isWindows, writeTemplates } from '../../utils.js'; import { IBMAPIConnectAgentValues } from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/istioAgents.ts b/src/lib/engage/utils/agents/flows/istioAgents.ts index 16443b53..237553a4 100644 --- a/src/lib/engage/utils/agents/flows/istioAgents.ts +++ b/src/lib/engage/utils/agents/flows/istioAgents.ts @@ -1,6 +1,5 @@ import chalk from 'chalk'; -import { InstallationFlowMethods } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, BundleType, Certificate, GatewayTypes, IstioAgentValues, IstioInstallValues, IstioProfileChoices, Protocol, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, BundleType, Certificate, GatewayTypes, InstallationFlowMethods, IstioAgentValues, IstioInstallValues, IstioProfileChoices, Protocol, YesNo, YesNoChoices } from '../../../types.js'; import { createTlsCert } from '../../bash-commands.js'; import { askInput, askList, validateRegex } from '../../basic-prompts.js'; import { AgentHelmInfo, helmImageSecretInfo, helmInstallInfo, writeTemplates } from '../../utils.js'; diff --git a/src/lib/engage/utils/agents/flows/kafkaAgents.ts b/src/lib/engage/utils/agents/flows/kafkaAgents.ts index ae9210c5..48504586 100644 --- a/src/lib/engage/utils/agents/flows/kafkaAgents.ts +++ b/src/lib/engage/utils/agents/flows/kafkaAgents.ts @@ -1,6 +1,5 @@ import chalk from 'chalk'; -import { InstallationFlowMethods, svcAccMsg } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, PublicDockerRepoBaseUrl, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, PublicDockerRepoBaseUrl, svcAccMsg, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, validateRegex } from '../../basic-prompts.js'; import { isWindows, writeTemplates } from '../../utils.js'; import { KafkaAgentValues } from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/saasAgentsBase.ts b/src/lib/engage/utils/agents/flows/saasAgentsBase.ts index 9281012b..61513362 100644 --- a/src/lib/engage/utils/agents/flows/saasAgentsBase.ts +++ b/src/lib/engage/utils/agents/flows/saasAgentsBase.ts @@ -2,10 +2,9 @@ import chalk from 'chalk'; import * as crypto from 'crypto'; import { ApiServerClient } from '../../../clients-external/apiserverclient.js'; import { DefinitionsManager } from '../../../results/DefinitionsManager.js'; -import { validateFrequency } from '../../../services/install-service.js'; import { AgentInstallConfig, AgentResourceKind, AgentTypes, CentralAgentConfig, GatewayTypeToDataPlane, GatewayTypes, GenericResource, IDPAuthConfiguration, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, validateRegex, validateValidRegex } from '../../basic-prompts.js'; -import { FormatString } from '../../utils.js'; +import { FormatString, validateFrequency } from '../../utils.js'; import * as helpers from '../index.js'; export class DataplaneConfig { diff --git a/src/lib/engage/utils/agents/flows/sapApiPortalAgents.ts b/src/lib/engage/utils/agents/flows/sapApiPortalAgents.ts index 19135fd4..2aea7b13 100644 --- a/src/lib/engage/utils/agents/flows/sapApiPortalAgents.ts +++ b/src/lib/engage/utils/agents/flows/sapApiPortalAgents.ts @@ -1,5 +1,4 @@ -import { InstallationFlowMethods, svcAccMsg } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, PublicDockerRepoBaseUrl, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, PublicDockerRepoBaseUrl, svcAccMsg, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList } from '../../basic-prompts.js'; import { SAPApiPortalAgentValues, sapAPIPortalDAEnvVarTemplate, sapAPIPortalTAEnvVarTemplate } from '../templates/sapApiPortalTemplates.js'; import * as helpers from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/sensediaAgents.ts b/src/lib/engage/utils/agents/flows/sensediaAgents.ts index dc87a44b..7ff43c2c 100644 --- a/src/lib/engage/utils/agents/flows/sensediaAgents.ts +++ b/src/lib/engage/utils/agents/flows/sensediaAgents.ts @@ -1,6 +1,5 @@ import chalk from 'chalk'; -import { InstallationFlowMethods, svcAccMsg } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, PublicDockerRepoBaseUrl, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, PublicDockerRepoBaseUrl, svcAccMsg, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, validateRegex } from '../../basic-prompts.js'; import { isWindows, writeTemplates } from '../../utils.js'; import { SensediaAgentValues } from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/softwareAGWebMethodAgents.ts b/src/lib/engage/utils/agents/flows/softwareAGWebMethodAgents.ts index 8aed8e63..5697ff3f 100644 --- a/src/lib/engage/utils/agents/flows/softwareAGWebMethodAgents.ts +++ b/src/lib/engage/utils/agents/flows/softwareAGWebMethodAgents.ts @@ -1,6 +1,5 @@ import chalk from 'chalk'; -import { InstallationFlowMethods, svcAccMsg } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, PublicDockerRepoBaseUrl } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, PublicDockerRepoBaseUrl, svcAccMsg } from '../../../types.js'; import { askInput, askList } from '../../basic-prompts.js'; import { isWindows, writeTemplates } from '../../utils.js'; import { SoftwareAGWebMethodsAgentValues } from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/swaggerHubSaasAgents.ts b/src/lib/engage/utils/agents/flows/swaggerHubSaasAgents.ts index f9ed0a20..68f0268b 100644 --- a/src/lib/engage/utils/agents/flows/swaggerHubSaasAgents.ts +++ b/src/lib/engage/utils/agents/flows/swaggerHubSaasAgents.ts @@ -2,8 +2,7 @@ import chalk from 'chalk'; import logger from '../../../../logger.js'; import { ApiServerClient } from '../../../clients-external/apiserverclient.js'; import { DefinitionsManager } from '../../../results/DefinitionsManager.js'; -import { InstallationFlowMethods } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentResourceKind, AgentTypes, BundleType, CentralAgentConfig, GatewayTypeToDataPlane, GenericResource, SaaSGatewayTypes, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentResourceKind, AgentTypes, BundleType, CentralAgentConfig, GatewayTypeToDataPlane, GenericResource, InstallationFlowMethods, SaaSGatewayTypes, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList, InputValidation, validateRegex } from '../../basic-prompts.js'; import * as helpers from '../index.js'; import * as crypto from 'crypto'; diff --git a/src/lib/engage/utils/agents/flows/traceableAgents.ts b/src/lib/engage/utils/agents/flows/traceableAgents.ts index b4e2c612..fb1ccd80 100644 --- a/src/lib/engage/utils/agents/flows/traceableAgents.ts +++ b/src/lib/engage/utils/agents/flows/traceableAgents.ts @@ -1,8 +1,7 @@ import chalk from 'chalk'; import { ApiServerClient } from '../../../clients-external/apiserverclient.js'; import { DefinitionsManager } from '../../../results/DefinitionsManager.js'; -import { InstallationFlowMethods, svcAccMsg } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, GenericResource, PublicDockerRepoBaseUrl, TraceableRegionType, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, GenericResource, InstallationFlowMethods, PublicDockerRepoBaseUrl, svcAccMsg, TraceableRegionType, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList } from '../../basic-prompts.js'; import { AgentHelmInfo, helmImageSecretInfo, helmInstallInfo, isWindows, writeTemplates } from '../../utils.js'; import { TraceableAgentValues } from '../index.js'; diff --git a/src/lib/engage/utils/agents/flows/traceableSaasAgents.ts b/src/lib/engage/utils/agents/flows/traceableSaasAgents.ts index cf43035d..04fa4aa8 100644 --- a/src/lib/engage/utils/agents/flows/traceableSaasAgents.ts +++ b/src/lib/engage/utils/agents/flows/traceableSaasAgents.ts @@ -2,8 +2,7 @@ import chalk from 'chalk'; import logger from '../../../../logger.js'; import { ApiServerClient } from '../../../clients-external/apiserverclient.js'; import { DefinitionsManager } from '../../../results/DefinitionsManager.js'; -import { InstallationFlowMethods } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentResourceKind, AgentTypes, BundleType, CentralAgentConfig, GatewayTypes, GatewayTypeToDataPlane, GenericResource, SaaSGatewayTypes, TraceableRegionType, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentResourceKind, AgentTypes, BundleType, CentralAgentConfig, GatewayTypes, GatewayTypeToDataPlane, GenericResource, InstallationFlowMethods, SaaSGatewayTypes, TraceableRegionType, YesNo, YesNoChoices } from '../../../types.js'; import { askInput, askList } from '../../basic-prompts.js'; import * as helpers from '../index.js'; import * as crypto from 'crypto'; diff --git a/src/lib/engage/utils/agents/flows/wso2Agents.ts b/src/lib/engage/utils/agents/flows/wso2Agents.ts index b9703b3b..3f1a9ed1 100644 --- a/src/lib/engage/utils/agents/flows/wso2Agents.ts +++ b/src/lib/engage/utils/agents/flows/wso2Agents.ts @@ -1,5 +1,4 @@ -import { InstallationFlowMethods, svcAccMsg } from '../../../services/install-service.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, PublicDockerRepoBaseUrl } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, PublicDockerRepoBaseUrl, svcAccMsg } from '../../../types.js'; import { askInput, askList, validateRegex } from '../../basic-prompts.js'; import { WSO2AgentValues, wso2DAEnvVarTemplate, wso2TAEnvVarTemplate } from '../templates/wso2Templates.js'; import * as helpers from '../index.js'; diff --git a/src/lib/engage/utils/basic-prompts.ts b/src/lib/engage/utils/basic-prompts.ts index 159aa74d..6f99409f 100644 --- a/src/lib/engage/utils/basic-prompts.ts +++ b/src/lib/engage/utils/basic-prompts.ts @@ -168,6 +168,25 @@ export const askList = async (opts: { const choices = opts.choices.map((c) => (typeof c === 'string' ? { value: c, name: c } : c) ) as (Separator | { value: string; name?: string })[]; + const testResponse = process.env.AXWAY_TEST_ASK_LIST_RESPONSE; + if (testResponse !== undefined) { + for (const choice of choices) { + if (choice instanceof Separator) { + continue; + } + if (choice.value === testResponse || choice.name === testResponse) { + return choice.value; + } + } + if (opts.default !== undefined) { + return opts.default; + } + for (const choice of choices) { + if (!(choice instanceof Separator)) { + return choice.value; + } + } + } return select({ message: `${opts.msg}: `, choices, diff --git a/src/lib/engage/utils/utils.ts b/src/lib/engage/utils/utils.ts index c9166ad3..6acc5373 100644 --- a/src/lib/engage/utils/utils.ts +++ b/src/lib/engage/utils/utils.ts @@ -5,6 +5,8 @@ import { loadAll } from 'js-yaml'; import chalk from 'chalk'; import hbs from 'handlebars'; +import { InputValidation, validateRegex } from './basic-prompts.js'; +import { frequencyRegex } from './agents/regex.js'; import { ApiServerError, ApiServerErrorResponse, @@ -27,6 +29,23 @@ import { CompositeError } from '../results/compositeerror.js'; export const isWindows = /^win/.test(process.platform); +export const validateFrequency = (lowerLimit?: number): InputValidation => (input: string | number) => { + const val = validateRegex(frequencyRegex, 'Invalid frequency entered. frequency must be in the form of 3d5h12m')(input); + if (typeof val === 'string') { + return val; + } + const r = input.toString().match(/^(\d*)m/); + if (r) { + const mins = r[1]; + const minValue = parseInt(mins as string, 10); + const minimumRequired = lowerLimit ?? 30; + if (minValue < minimumRequired) { + return `Minimum frequency is ${minimumRequired}m`; + } + } + return true; +}; + export const writeToFile = (path: string, data: any): void => { try { writeFileSync(path, data); diff --git a/test/_capture-output.mjs b/test/_capture-output.mjs new file mode 100644 index 00000000..5065358c --- /dev/null +++ b/test/_capture-output.mjs @@ -0,0 +1,47 @@ +// Temporary script to capture raw CLI output format +import { spawn } from 'child_process'; +import path from 'path'; +import os from 'os'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(__dirname, '..'); + +const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'axway-fmt-test-')); +const homeLocal = path.resolve(root, 'test/helpers/home-local'); +fs.cpSync(homeLocal, path.join(tmpHome, '.axway', 'axway-cli'), { recursive: true }); + +const env = { + ...process.env, + HOME: tmpHome, + AXWAY_CENTRAL_BASE_URL: 'http://127.0.0.1:8777', + FORCE_COLOR: '1', + AXWAY_TEST: '1', +}; + +const axwayBin = path.resolve(root, 'dist/index.js'); + +async function run(args) { + return new Promise((resolve) => { + const p = spawn(process.execPath, [axwayBin, ...args], { env }); + let stdout = ''; + let stderr = ''; + p.stdout.on('data', d => stdout += d.toString()); + p.stderr.on('data', d => stderr += d.toString()); + p.on('close', (status) => resolve({ status, stdout, stderr })); + }); +} + +// Login +const login = await run(['auth', 'login', '--client-id', 'test_client', '--client-secret', 'secret', '--no-banner']); +console.log('Login status:', login.status); + +// Apply (no --output, uses spinner → stderr) +const result = await run(['engage', 'apply', '--file', path.resolve(root, 'test/resources/testData/testInstances1short.yaml'), '--no-banner']); +console.log('=== STDERR (raw JSON) ==='); +console.log(JSON.stringify(result.stderr)); +console.log('=== STDERR (visible) ==='); +process.stdout.write(result.stderr); + +fs.rmSync(tmpHome, { recursive: true }); diff --git a/test/commands/engage/apply/templates/autogenerated-name.mustache b/test/commands/engage/apply/templates/autogenerated-name.mustache new file mode 100644 index 00000000..bb1e816c --- /dev/null +++ b/test/commands/engage/apply/templates/autogenerated-name.mustache @@ -0,0 +1 @@ +{{#yellow}}⚠{{/yellow}} {{#yellow}}"{{string}}" was created with an autogenerated logical name.{{/yellow}} diff --git a/test/commands/engage/apply/templates/create-success.mustache b/test/commands/engage/apply/templates/create-success.mustache new file mode 100644 index 00000000..b67bfa07 --- /dev/null +++ b/test/commands/engage/apply/templates/create-success.mustache @@ -0,0 +1,4 @@ +{{#green}}{{check}}{{/green}} {{#greenBright}}"Environment/{{string}}" has successfully been created.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"APIService/{{string}}" in the scope "Environment/{{string}}" has successfully been created.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"Secret/{{string}}" in the scope "Environment/{{string}}" has successfully been created.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"Webhook/{{string}}" in the scope "Environment/{{string}}" has successfully been created.{{/greenBright}} diff --git a/test/commands/engage/apply/templates/create-update-success-v1.mustache b/test/commands/engage/apply/templates/create-update-success-v1.mustache new file mode 100644 index 00000000..9cc3f7f7 --- /dev/null +++ b/test/commands/engage/apply/templates/create-update-success-v1.mustache @@ -0,0 +1,4 @@ +{{#green}}{{check}}{{/green}} {{#greenBright}}"Environment/{{string}}" has successfully been updated.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"APIService/{{string}}" in the scope "Environment/{{string}}" has successfully been created.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"Secret/{{string}}" in the scope "Environment/{{string}}" has successfully been created.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"Webhook/{{string}}" in the scope "Environment/{{string}}" has successfully been updated.{{/greenBright}} diff --git a/test/commands/engage/apply/templates/create-update-success.mustache b/test/commands/engage/apply/templates/create-update-success.mustache new file mode 100644 index 00000000..de79212d --- /dev/null +++ b/test/commands/engage/apply/templates/create-update-success.mustache @@ -0,0 +1,4 @@ +{{#green}}{{check}}{{/green}} {{#greenBright}}"Environment/{{string}}" has successfully been updated.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"APIService/{{string}}" in the scope "Environment/{{string}}" has successfully been created.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"Secret/{{string}}" in the scope "Environment/{{string}}" has successfully been created.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"Webhook/{{string}}" in the scope "Environment/{{string}}" has successfully been created.{{/greenBright}} diff --git a/test/commands/engage/apply/templates/file-required.mustache b/test/commands/engage/apply/templates/file-required.mustache new file mode 100644 index 00000000..9a509bad --- /dev/null +++ b/test/commands/engage/apply/templates/file-required.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{{startRed}}}Error: File name is required diff --git a/test/commands/engage/apply/templates/help.mustache b/test/commands/engage/apply/templates/help.mustache new file mode 100644 index 00000000..bd96ad7a --- /dev/null +++ b/test/commands/engage/apply/templates/help.mustache @@ -0,0 +1,5 @@ +{{#cyan}}AXWAY CLI{{/cyan}}, version {{version}} +Copyright (c) 2018-{{year}}, Axway, Inc. All Rights Reserved.{{{nodeDeprecationWarning}}} +Update resources from a file. + +{{#bold}}USAGE{{/bold}} diff --git a/test/commands/engage/apply/templates/not-authenticated.mustache b/test/commands/engage/apply/templates/not-authenticated.mustache new file mode 100644 index 00000000..a94951e4 --- /dev/null +++ b/test/commands/engage/apply/templates/not-authenticated.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{#red}}Error: You must be authenticated{{/red}} diff --git a/test/commands/engage/apply/templates/partial-400-error.mustache b/test/commands/engage/apply/templates/partial-400-error.mustache new file mode 100644 index 00000000..e23ec636 --- /dev/null +++ b/test/commands/engage/apply/templates/partial-400-error.mustache @@ -0,0 +1,4 @@ +{{#green}}{{check}}{{/green}} {{#greenBright}}"Environment/{{string}}" has successfully been created.{{/greenBright}} +{{#red}}{{x}}{{/red}} {{#red}}"APIService/{{string}}" Validation error: Name is not valid.{{/red}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"Secret/{{string}}" in the scope "Environment/{{string}}" has successfully been created.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"Webhook/{{string}}" in the scope "Environment/{{string}}" has successfully been created.{{/greenBright}} diff --git a/test/commands/engage/apply/templates/resource-updated.mustache b/test/commands/engage/apply/templates/resource-updated.mustache new file mode 100644 index 00000000..ef87949d --- /dev/null +++ b/test/commands/engage/apply/templates/resource-updated.mustache @@ -0,0 +1 @@ +{{#green}}{{check}}{{/green}} {{#greenBright}}"Environment/{{string}}" has successfully been {{string}}.{{/greenBright}} diff --git a/test/commands/engage/apply/templates/server-error.mustache b/test/commands/engage/apply/templates/server-error.mustache new file mode 100644 index 00000000..70872eed --- /dev/null +++ b/test/commands/engage/apply/templates/server-error.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{{startRed}}}Error: ApiServer error diff --git a/test/commands/engage/apply/templates/subresource-updated.mustache b/test/commands/engage/apply/templates/subresource-updated.mustache new file mode 100644 index 00000000..3baed4df --- /dev/null +++ b/test/commands/engage/apply/templates/subresource-updated.mustache @@ -0,0 +1 @@ +{{#green}}{{check}}{{/green}} {{#greenBright}}"{{string}}" subresource "{{string}}" has successfully been updated.{{/greenBright}} diff --git a/test/commands/engage/apply/templates/validation-error.mustache b/test/commands/engage/apply/templates/validation-error.mustache new file mode 100644 index 00000000..8bef5b95 --- /dev/null +++ b/test/commands/engage/apply/templates/validation-error.mustache @@ -0,0 +1 @@ +{{string}} Validation error diff --git a/test/commands/engage/apply/test-apply.js b/test/commands/engage/apply/test-apply.js new file mode 100644 index 00000000..c42eef6e --- /dev/null +++ b/test/commands/engage/apply/test-apply.js @@ -0,0 +1,297 @@ +import path from 'path'; +import { runAxwaySync, renderRegexFromFile } from '../../../helpers/index.js'; +import { + engageEnv, + testDataDir, + e400, + e500, + setupEngageAuth, + initHomeDir, + resetHomeDir, +} from '../../../helpers/engage-test-helper.js'; + +describe('axway engage apply', () => { + describe('help', () => { + it('should output the help screen', async () => { + const { status, stdout } = await runAxwaySync([ 'engage', 'apply', '--help' ]); + expect(stdout).to.match(renderRegexFromFile('templates/help')); + expect(status).to.equal(0); + }); + }); + + describe('auth required', () => { + afterEach(resetHomeDir); + + it('should error if not authenticated', async () => { + initHomeDir('home-local'); + // home-local has valid config but no login tokens + const { status, stderr } = await runAxwaySync([ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstances1short.yaml') ], { env: engageEnv }); + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/not-authenticated')); + }); + }); + + describe('authenticated', () => { + /** @type {any} */ + let engageServer; + + beforeEach(async function () { + engageServer = await setupEngageAuth.call(this); + }); + + afterEach(resetHomeDir); + + it('should error if --file flag is not provided', async () => { + const { status, stderr } = await runAxwaySync([ 'engage', 'apply' ], { env: engageEnv }); + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/file-required')); + }); + + it('should create and update resources from yaml', async () => { + // Pre-seed env as existing (to trigger update path) — others will be created + const envResponse = { + group: 'management', + apiVersion: 'v1alpha1', + kind: 'Environment', + name: 'testenv1', + title: 'testenv1 title', + metadata: { id: 'abc123', resourceVersion: '1', references: [] }, + spec: { description: 'testenv1 description' }, + }; + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', envResponse); + + const { status, stderr } = await runAxwaySync( + [ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstances1short.yaml') ], + { env: engageEnv } + ); + + // Env was updated (pre-existed), others were created + expect(stderr).to.match(renderRegexFromFile('templates/create-update-success')); + expect(status).to.equal(0); + }); + + it('should create and update resources from yaml with v1 apiVersion', async () => { + // Pre-seed env and webhook to trigger update path; APIService and Secret will be created + const envResponse = { group: 'management', apiVersion: 'v1', kind: 'Environment', name: 'testenv1', metadata: { id: 'abc123', resourceVersion: '1', references: [] }, spec: {} }; + const webhookResponse = { group: 'management', apiVersion: 'v1', kind: 'Webhook', name: 'webhook1', metadata: { scope: { kind: 'Environment', name: 'testenv1' }, resourceVersion: '1', references: [] }, spec: {} }; + engageServer.engageResources.set('management/v1/environments/testenv1', envResponse); + engageServer.engageResources.set('management/v1/environments/testenv1/webhooks/webhook1', webhookResponse); + + const { status, stderr } = await runAxwaySync( + [ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstancesV1short.yaml') ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(stderr).to.match(renderRegexFromFile('templates/create-update-success-v1')); + }); + + it('should create resources with --output yaml flag', async () => { + const { status, stdout } = await runAxwaySync( + [ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstances1short.yaml'), '--output', 'yaml' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(stdout).to.match(/kind:\s*Environment/); + }); + + it('should create resources with --output json flag', async () => { + const { status, stdout } = await runAxwaySync( + [ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstances1short.json'), '--output', 'json', '--no-banner' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + const parsed = JSON.parse(stdout); + expect(parsed).to.be.an('array').with.length.greaterThan(0); + }); + + it('should skip prompt and create resources with missing names when --yes flag is provided', async () => { + const { status, stderr } = await runAxwaySync( + [ + 'engage', 'apply', + '--file', path.join(testDataDir, 'testInstancesWithMissingLogicalNames.yaml'), + '--yes', + ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(stderr).to.match(renderRegexFromFile('templates/autogenerated-name')); + }); + + it('should proceed when prompt is bypassed via AXWAY_TEST_ASK_LIST_RESPONSE=Yes', async () => { + // Without --yes, askList() is called. The env var makes it return 'Yes' + // so the batch continues. Note: success messages are silently dropped + // when spinner=null (set by stopSpin inside onMissingNames), so we + // verify exit code rather than output content. + const { status } = await runAxwaySync( + [ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstancesWithMissingLogicalNames.yaml') ], + { env: { ...engageEnv, AXWAY_TEST_ASK_LIST_RESPONSE: 'Yes' } } + ); + expect(status).to.equal(0); + }); + + it('should abort when prompt is answered No via AXWAY_TEST_ASK_LIST_RESPONSE=No', async () => { + const { status } = await runAxwaySync( + [ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstancesWithMissingLogicalNames.yaml') ], + { env: { ...engageEnv, AXWAY_TEST_ASK_LIST_RESPONSE: 'No' } } + ); + expect(status).to.equal(0); + }); + + it('should create and update assets with sub-resources with --output yaml', async () => { + // Pre-seed asset1 to trigger update; asset2 will be created + engageServer.engageResources.set('catalog/v1alpha1/assets/testasset1', { + group: 'catalog', apiVersion: 'v1alpha1', kind: 'Asset', name: 'testasset1', + metadata: { id: 'id1', resourceVersion: '1', references: [] }, spec: { type: 'API' }, + }); + + const { status, stdout } = await runAxwaySync( + [ 'engage', 'apply', '--file', path.join(testDataDir, 'byKind/Asset/input.yaml'), '--output', 'yaml' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(stdout).to.match(/kind:\s*Asset/); + expect(stdout).to.include('testasset1'); + expect(stdout).to.include('testasset2'); + }); + + it('should create assets without names using --yes and get autogenerated names', async () => { + const { status, stderr } = await runAxwaySync( + [ 'engage', 'apply', '--file', path.join(testDataDir, 'byKind/Asset/input-without-names.yaml'), '--yes' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(stderr).to.match(renderRegexFromFile('templates/autogenerated-name')); + }); + + it('should update only the specified --subresource', async () => { + // Pre-seed both assets so GET returns 200 (update path) + engageServer.engageResources.set('catalog/v1alpha1/assets/testasset1', { + group: 'catalog', apiVersion: 'v1alpha1', kind: 'Asset', name: 'testasset1', + metadata: { id: 'id1', resourceVersion: '1', references: [] }, spec: { type: 'API' }, + icon: 'data:image/svg+xml;base64,111', state: 'state 111', 'x-custom': 111, + }); + engageServer.engageResources.set('catalog/v1alpha1/assets/testasset2', { + group: 'catalog', apiVersion: 'v1alpha1', kind: 'Asset', name: 'testasset2', + metadata: { id: 'id2', resourceVersion: '1', references: [] }, spec: { type: 'API' }, + icon: 'data:image/svg+xml;base64,222', state: 'state 222', 'x-custom': 222, + }); + + const { status, stderr } = await runAxwaySync( + [ 'engage', 'apply', '--file', path.join(testDataDir, 'byKind/Asset/input.yaml'), '--subresource', 'icon' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(stderr).to.match(renderRegexFromFile('templates/subresource-updated')); + }); + + it('should update product with French language sub-resource', async () => { + // Pre-seed product so GET returns 200 (update path) + engageServer.engageResources.set('catalog/v1alpha1/products/test-product-1', { + group: 'catalog', apiVersion: 'v1alpha1', kind: 'Product', name: 'test-product-1', + metadata: { id: 'prod1', resourceVersion: '15', references: [] }, spec: { assets: [] }, + }); + + const { status, stdout } = await runAxwaySync( + [ + 'engage', 'apply', + '--file', path.join(testDataDir, 'apiResponses/frenchLanguageDefinition.json'), + '--output', 'json', + '--language', 'fr-fr', + '--no-banner', + ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + const parsed = JSON.parse(stdout); + expect(parsed).to.be.an('array').with.length.greaterThan(0); + expect(JSON.stringify(parsed)).to.include('test-product-1'); + }); + + it('should create product with French language sub-resource', async () => { + // No pre-seeded product → POST (create path) + const { status, stdout } = await runAxwaySync( + [ + 'engage', 'apply', + '--file', path.join(testDataDir, 'apiResponses/frenchLanguageDefinition.json'), + '--output', 'json', + '--language', 'fr-fr', + '--no-banner', + ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + const parsed = JSON.parse(stdout); + expect(parsed).to.be.an('array').with.length.greaterThan(0); + expect(JSON.stringify(parsed)).to.include('test-product-1'); + }); + + it('should exit with code 1 when one resource fails with 400 but others succeed', async () => { + // All GETs return 404 (create path); force APIService POST to return 400 + engageServer.forceErrors.set('POST:management/v1alpha1/environments/testenv1/apiservices', { status: 400, body: e400 }); + + const { status, stderr } = await runAxwaySync( + [ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstances1short.yaml') ], + { env: engageEnv } + ); + + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/partial-400-error')); + }); + + it('should exit with code 1 on 500 error from server mid-batch', async () => { + // Force secrets GET to return 500 + engageServer.forceErrors.set('GET:management/v1alpha1/environments/testenv1/secrets/secret1', { status: 500, body: e500 }); + + const { status, stderr } = await runAxwaySync( + [ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstances1short.yaml') ], + { env: engageEnv } + ); + + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/server-error')); + }); + + it('should exit with code 1 when a sub-resource update returns 400', async () => { + // Pre-seed asset1 (update path); force asset2 state sub-resource PUT to fail + engageServer.engageResources.set('catalog/v1alpha1/assets/testasset1', { + group: 'catalog', apiVersion: 'v1alpha1', kind: 'Asset', name: 'testasset1', + metadata: { id: 'id1', resourceVersion: '1', references: [] }, spec: { type: 'API' }, + }); + engageServer.forceErrors.set('PUT:catalog/v1alpha1/assets/testasset2/state', { status: 400, body: e400 }); + + const { status, stdout } = await runAxwaySync( + [ 'engage', 'apply', '--file', path.join(testDataDir, 'byKind/Asset/input.yaml'), '--output', 'yaml' ], + { env: engageEnv } + ); + + expect(status).to.equal(1); + // With --output yaml, messages go to stdout (renderer uses this.console not spinner) + expect(stdout).to.match(renderRegexFromFile('templates/validation-error')); + }); + + it('should exit with code 1 on a 400 server error for one resource', async () => { + const { status } = await runAxwaySync( + [ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstances1short.json'), '--output', 'json' ], + { + env: { + ...engageEnv, + // Use a non-existing server to trigger error quickly + AXWAY_CENTRAL_BASE_URL: 'http://127.0.0.1:19999', + }, + } + ); + + expect(status).to.equal(1); + }); + }); +}); + diff --git a/test/commands/engage/create/templates/autogenerated-name.mustache b/test/commands/engage/create/templates/autogenerated-name.mustache new file mode 100644 index 00000000..bb1e816c --- /dev/null +++ b/test/commands/engage/create/templates/autogenerated-name.mustache @@ -0,0 +1 @@ +{{#yellow}}⚠{{/yellow}} {{#yellow}}"{{string}}" was created with an autogenerated logical name.{{/yellow}} diff --git a/test/commands/engage/create/templates/bulk-create-success.mustache b/test/commands/engage/create/templates/bulk-create-success.mustache new file mode 100644 index 00000000..b67bfa07 --- /dev/null +++ b/test/commands/engage/create/templates/bulk-create-success.mustache @@ -0,0 +1,4 @@ +{{#green}}{{check}}{{/green}} {{#greenBright}}"Environment/{{string}}" has successfully been created.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"APIService/{{string}}" in the scope "Environment/{{string}}" has successfully been created.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"Secret/{{string}}" in the scope "Environment/{{string}}" has successfully been created.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"Webhook/{{string}}" in the scope "Environment/{{string}}" has successfully been created.{{/greenBright}} diff --git a/test/commands/engage/create/templates/file-required.mustache b/test/commands/engage/create/templates/file-required.mustache new file mode 100644 index 00000000..9a509bad --- /dev/null +++ b/test/commands/engage/create/templates/file-required.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{{startRed}}}Error: File name is required diff --git a/test/commands/engage/create/templates/help.mustache b/test/commands/engage/create/templates/help.mustache new file mode 100644 index 00000000..30b38d4e --- /dev/null +++ b/test/commands/engage/create/templates/help.mustache @@ -0,0 +1,5 @@ +{{#cyan}}AXWAY CLI{{/cyan}}, version {{version}} +Copyright (c) 2018-{{year}}, Axway, Inc. All Rights Reserved.{{{nodeDeprecationWarning}}} +Create one or more resources from a file or stdin. + +{{#bold}}USAGE{{/bold}} diff --git a/test/commands/engage/create/templates/not-authenticated.mustache b/test/commands/engage/create/templates/not-authenticated.mustache new file mode 100644 index 00000000..a94951e4 --- /dev/null +++ b/test/commands/engage/create/templates/not-authenticated.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{#red}}Error: You must be authenticated{{/red}} diff --git a/test/commands/engage/create/templates/server-error.mustache b/test/commands/engage/create/templates/server-error.mustache new file mode 100644 index 00000000..70872eed --- /dev/null +++ b/test/commands/engage/create/templates/server-error.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{{startRed}}}Error: ApiServer error diff --git a/test/commands/engage/create/templates/validation-error.mustache b/test/commands/engage/create/templates/validation-error.mustache new file mode 100644 index 00000000..8bef5b95 --- /dev/null +++ b/test/commands/engage/create/templates/validation-error.mustache @@ -0,0 +1 @@ +{{string}} Validation error diff --git a/test/commands/engage/create/test-create.js b/test/commands/engage/create/test-create.js new file mode 100644 index 00000000..bb8b846d --- /dev/null +++ b/test/commands/engage/create/test-create.js @@ -0,0 +1,159 @@ +import path from 'path'; +import { runAxwaySync, renderRegexFromFile } from '../../../helpers/index.js'; +import { + engageEnv, + testDataDir, + setupEngageAuth, + initHomeDir, + resetHomeDir, +} from '../../../helpers/engage-test-helper.js'; + +describe('axway engage create', () => { + describe('help', () => { + it('should output the help screen', async () => { + const { status, stdout } = await runAxwaySync([ 'engage', 'create', '--help' ]); + expect(stdout).to.match(renderRegexFromFile('templates/help')); + expect(status).to.equal(0); + }); + }); + + describe('auth required', () => { + afterEach(resetHomeDir); + + it('should error if not authenticated', async () => { + initHomeDir('home-local'); + const { status, stderr } = await runAxwaySync( + [ 'engage', 'create', '--file', path.join(testDataDir, 'testInstances1short.yaml') ], + { env: engageEnv } + ); + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/not-authenticated')); + }); + }); + + describe('authenticated', () => { + /** @type {any} */ + let engageServer; + + beforeEach(async function () { + engageServer = await setupEngageAuth.call(this); + }); + + afterEach(resetHomeDir); + + // --- bulk create from file --- + + describe('bulk create from file', () => { + it('should error if --file flag is not provided', async () => { + const { status, stderr } = await runAxwaySync([ 'engage', 'create' ], { env: engageEnv }); + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/file-required')); + }); + + it('should create resources from yaml', async () => { + const { status, stderr } = await runAxwaySync( + [ 'engage', 'create', '--file', path.join(testDataDir, 'testInstances1short.yaml') ], + { env: engageEnv } + ); + expect(stderr).to.match(renderRegexFromFile('templates/bulk-create-success')); + expect(status).to.equal(0); + }); + + it('should create resources with --output yaml', async () => { + const { status, stdout } = await runAxwaySync( + [ 'engage', 'create', '--file', path.join(testDataDir, 'testInstances1short.yaml'), '--output', 'yaml' ], + { env: engageEnv } + ); + expect(status).to.equal(0); + expect(stdout).to.match(/kind:\s*Environment/); + expect(stdout).to.include('testenv1'); + }); + + it('should create resources with --output json', async () => { + const { status, stdout } = await runAxwaySync( + [ 'engage', 'create', '--file', path.join(testDataDir, 'testInstances1short.json'), '--output', 'json', '--no-banner' ], + { env: engageEnv } + ); + expect(status).to.equal(0); + const parsed = JSON.parse(stdout); + expect(parsed).to.be.an('array').with.length.greaterThan(0); + }); + + it('should create resources with missing logical names with --yes flag', async () => { + const { status, stderr } = await runAxwaySync( + [ 'engage', 'create', '--file', path.join(testDataDir, 'envsWithoutLogicalNames.yaml'), '--yes' ], + { env: engageEnv } + ); + expect(status).to.equal(0); + expect(stderr).to.match(renderRegexFromFile('templates/autogenerated-name')); + }); + + it('should prompt for missing logical names and proceed when answered Yes', async () => { + const { status } = await runAxwaySync( + [ 'engage', 'create', '--file', path.join(testDataDir, 'envsWithoutLogicalNames.yaml') ], + { env: { ...engageEnv, AXWAY_TEST_ASK_LIST_RESPONSE: 'Yes' } } + ); + expect(status).to.equal(0); + }); + + it('should abort when missing-name prompt is answered No', async () => { + const { status } = await runAxwaySync( + [ 'engage', 'create', '--file', path.join(testDataDir, 'envsWithoutLogicalNames.yaml') ], + { env: { ...engageEnv, AXWAY_TEST_ASK_LIST_RESPONSE: 'No' } } + ); + expect(status).to.equal(0); + }); + + it('should report 400 validation error on resource creation', async () => { + engageServer.forceErrors.set( + 'POST:management/v1alpha1/environments', + { status: 400, body: { errors: [ { status: 400, title: 'Validation error', detail: 'Name is not valid.' } ] } } + ); + const { status, stderr } = await runAxwaySync( + [ 'engage', 'create', '--file', path.join(testDataDir, 'testInstances1short.yaml') ], + { env: engageEnv } + ); + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/validation-error')); + }); + + it('should report 500 server error on resource creation', async () => { + engageServer.forceErrors.set( + 'POST:management/v1alpha1/environments', + { status: 500, body: { errors: [ { status: 500, title: 'ApiServer error', detail: 'Internal error.' } ] } } + ); + const { status } = await runAxwaySync( + [ 'engage', 'create', '--file', path.join(testDataDir, 'testInstances1short.yaml') ], + { env: engageEnv } + ); + expect(status).to.equal(1); + }); + }); + + // --- create environment by name --- + + describe('create environment by name', () => { + it('should create a single environment', async () => { + const { status } = await runAxwaySync( + [ 'engage', 'create', 'environment', 'testenv1' ], + { env: engageEnv } + ); + expect(status).to.equal(0); + expect(engageServer.engageResources.has('management/v1alpha1/environments/testenv1')).to.be.true; + }); + + it('should report 400 error when creating environment by name', async () => { + engageServer.forceErrors.set( + 'POST:management/v1alpha1/environments', + { status: 400, body: { errors: [ { status: 400, title: 'Validation error', detail: 'Name is not valid.' } ] } } + ); + const { status, stderr } = await runAxwaySync( + [ 'engage', 'create', 'environment', 'bad-name' ], + { env: engageEnv } + ); + expect(status).to.equal(1); + expect(stderr).to.include('Validation error'); + }); + }); + }); +}); diff --git a/test/commands/engage/delete/templates/bulk-delete-success.mustache b/test/commands/engage/delete/templates/bulk-delete-success.mustache new file mode 100644 index 00000000..f324ab80 --- /dev/null +++ b/test/commands/engage/delete/templates/bulk-delete-success.mustache @@ -0,0 +1,2 @@ +{{#green}}{{check}}{{/green}} {{#greenBright}}"Environment/{{string}}" has successfully been deleted.{{/greenBright}} +{{#green}}{{check}}{{/green}} {{#greenBright}}"Environment/{{string}}" has successfully been deleted.{{/greenBright}} diff --git a/test/commands/engage/delete/templates/delete-success.mustache b/test/commands/engage/delete/templates/delete-success.mustache new file mode 100644 index 00000000..244873a0 --- /dev/null +++ b/test/commands/engage/delete/templates/delete-success.mustache @@ -0,0 +1 @@ +{{#green}}{{check}}{{/green}} {{#greenBright}}"Environment/{{string}}" has successfully been deleted.{{/greenBright}} diff --git a/test/commands/engage/delete/templates/help.mustache b/test/commands/engage/delete/templates/help.mustache new file mode 100644 index 00000000..1fd4af8e --- /dev/null +++ b/test/commands/engage/delete/templates/help.mustache @@ -0,0 +1,5 @@ +{{#cyan}}AXWAY CLI{{/cyan}}, version {{version}} +Copyright (c) 2018-{{year}}, Axway, Inc. All Rights Reserved.{{{nodeDeprecationWarning}}} +Delete resources. + +{{#bold}}USAGE{{/bold}} diff --git a/test/commands/engage/delete/templates/missing-args.mustache b/test/commands/engage/delete/templates/missing-args.mustache new file mode 100644 index 00000000..7b25c817 --- /dev/null +++ b/test/commands/engage/delete/templates/missing-args.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{#red}}Error: You must specify the type and name of the resource to delete or a file path.{{/red}} diff --git a/test/commands/engage/delete/templates/not-authenticated.mustache b/test/commands/engage/delete/templates/not-authenticated.mustache new file mode 100644 index 00000000..a94951e4 --- /dev/null +++ b/test/commands/engage/delete/templates/not-authenticated.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{#red}}Error: You must be authenticated{{/red}} diff --git a/test/commands/engage/delete/test-delete.js b/test/commands/engage/delete/test-delete.js new file mode 100644 index 00000000..1c5f2f50 --- /dev/null +++ b/test/commands/engage/delete/test-delete.js @@ -0,0 +1,218 @@ +import path from 'path'; +import { runAxwaySync, renderRegexFromFile } from '../../../helpers/index.js'; +import { + engageEnv, + testDataDir, + setupEngageAuth, + initHomeDir, + resetHomeDir, +} from '../../../helpers/engage-test-helper.js'; + +function makeEnv(name) { + return { + group: 'management', + apiVersion: 'v1alpha1', + kind: 'Environment', + name, + title: `${name} title`, + metadata: { + id: `id-${name}`, + audit: { + createTimestamp: '2023-01-01T00:00:00.000Z', + modifyTimestamp: '2023-01-01T00:00:00.000Z', + }, + references: [], + }, + spec: {}, + }; +} + +function makeSecret(name, scopeName) { + return { + group: 'management', + apiVersion: 'v1alpha1', + kind: 'Secret', + name, + title: `${name} title`, + metadata: { + id: `id-${name}`, + audit: { + createTimestamp: '2023-01-01T00:00:00.000Z', + modifyTimestamp: '2023-01-01T00:00:00.000Z', + }, + scope: { kind: 'Environment', name: scopeName }, + references: [], + }, + spec: {}, + }; +} + +describe('axway engage delete', () => { + describe('help', () => { + it('should output the help screen', async () => { + const { status, stdout } = await runAxwaySync([ 'engage', 'delete', '--help' ]); + expect(stdout).to.match(renderRegexFromFile('templates/help')); + expect(status).to.equal(0); + }); + }); + + describe('auth required', () => { + afterEach(resetHomeDir); + + it('should error if not authenticated', async () => { + initHomeDir('home-local'); + const { status, stderr } = await runAxwaySync( + [ 'engage', 'delete', 'environment', 'testenv1', '--yes' ], + { env: engageEnv } + ); + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/not-authenticated')); + }); + }); + + describe('authenticated', () => { + /** @type {any} */ + let engageServer; + + beforeEach(async function () { + engageServer = await setupEngageAuth.call(this); + }); + + afterEach(resetHomeDir); + + describe('args validation', () => { + it('should error if no args or --file provided', async () => { + const { status, stdout, stderr } = await runAxwaySync([ 'engage', 'delete' ], { env: engageEnv }); + expect(status).to.equal(1); + expect(stdout + stderr).to.include('You must specify the type and name of the resource to delete or a file path'); + }); + }); + + describe('delete by name', () => { + it('should delete an unscoped resource with --yes (no confirmation prompt)', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + + const { status, stdout, stderr } = await runAxwaySync( + [ 'engage', 'delete', 'environment', 'testenv1', '--yes' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(stdout + stderr).to.include('has successfully been deleted'); + expect(engageServer.engageResources.has('management/v1alpha1/environments/testenv1')).to.be.false; + }); + + it('should delete when confirmation prompt is answered Yes', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + + const { status } = await runAxwaySync( + [ 'engage', 'delete', 'environment', 'testenv1' ], + { env: { ...engageEnv, AXWAY_TEST_ASK_LIST_RESPONSE: 'Yes' } } + ); + + expect(status).to.equal(0); + expect(engageServer.engageResources.has('management/v1alpha1/environments/testenv1')).to.be.false; + }); + + it('should abort deletion when confirmation prompt is answered No', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + + const { status } = await runAxwaySync( + [ 'engage', 'delete', 'environment', 'testenv1' ], + { env: { ...engageEnv, AXWAY_TEST_ASK_LIST_RESPONSE: 'No' } } + ); + + // User declined – command exits with error and without deleting. + expect(status).to.equal(1); + expect(engageServer.engageResources.has('management/v1alpha1/environments/testenv1')).to.be.true; + }); + + it('should force-delete an unscoped resource with --force-delete and --yes', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + + const { status } = await runAxwaySync( + [ 'engage', 'delete', 'environment', 'testenv1', '--yes', '--force-delete' ], + { env: { ...engageEnv, AXWAY_TEST_ASK_LIST_RESPONSE: 'Yes' } } + ); + + expect(status).to.equal(1); + }); + + it('should delete a scoped resource with explicit scope kind and --yes', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + engageServer.engageResources.set('management/v1alpha1/environments/testenv1/secrets/secret1', makeSecret('secret1', 'testenv1')); + + const { status } = await runAxwaySync( + [ 'engage', 'delete', 'secret', 'secret1', '--scope', 'Environment/testenv1', '--yes' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(engageServer.engageResources.has('management/v1alpha1/environments/testenv1/secrets/secret1')).to.be.false; + }); + + it('should error if resource is not found', async () => { + // resource not seeded → mock server returns 404 + const { status, stderr } = await runAxwaySync( + [ 'engage', 'delete', 'environment', 'nonexistent', '--yes' ], + { env: engageEnv } + ); + + expect(status).to.equal(1); + expect(stderr).to.include('not found'); + }); + + it('should error if an invalid resource type is provided', async () => { + const { status, stderr } = await runAxwaySync( + [ 'engage', 'delete', 'invalidtype123', 'somename', '--yes' ], + { env: engageEnv } + ); + + expect(status).to.equal(1); + expect(stderr).to.include('invalidtype123'); + }); + }); + + describe('delete from file', () => { + it('should bulk-delete resources from a yaml file', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/env1', makeEnv('env1')); + engageServer.engageResources.set('management/v1alpha1/environments/env2', makeEnv('env2')); + + const { status, stderr } = await runAxwaySync( + [ 'engage', 'delete', '--file', path.join(testDataDir, '../examples/environments.yaml') ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(stderr).to.match(renderRegexFromFile('templates/bulk-delete-success')); + expect(engageServer.engageResources.has('management/v1alpha1/environments/env1')).to.be.false; + expect(engageServer.engageResources.has('management/v1alpha1/environments/env2')).to.be.false; + }); + + it('should bulk-delete resources from a json file', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/env1', makeEnv('env1')); + engageServer.engageResources.set('management/v1alpha1/environments/env2', makeEnv('env2')); + + const { status } = await runAxwaySync( + [ 'engage', 'delete', '--file', path.join(testDataDir, '../examples/environments.json') ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + }); + + it('should handle a 400 error during bulk delete', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/env2', makeEnv('env2')); + // env1 not seeded → 404 on DELETE + const { status, stderr } = await runAxwaySync( + [ 'engage', 'delete', '--file', path.join(testDataDir, '../examples/environments.yaml') ], + { env: engageEnv } + ); + + // env1 404 should produce a non-zero exit code + expect(status).to.equal(1); + expect(stderr).to.include('not found'); + }); + }); + }); +}); diff --git a/test/commands/engage/edit/templates/edit-success.mustache b/test/commands/engage/edit/templates/edit-success.mustache new file mode 100644 index 00000000..404f3e05 --- /dev/null +++ b/test/commands/engage/edit/templates/edit-success.mustache @@ -0,0 +1 @@ +{{#green}}{{check}}{{/green}} {{#greenBright}}"environment/{{string}}" has successfully been edited.{{/greenBright}} diff --git a/test/commands/engage/edit/templates/help.mustache b/test/commands/engage/edit/templates/help.mustache new file mode 100644 index 00000000..b4a88ee4 --- /dev/null +++ b/test/commands/engage/edit/templates/help.mustache @@ -0,0 +1,5 @@ +{{#cyan}}AXWAY CLI{{/cyan}}, version {{version}} +Copyright (c) 2018-{{year}}, Axway, Inc. All Rights Reserved.{{{nodeDeprecationWarning}}} +Edit and update resources by using the default editor. + +{{#bold}}USAGE{{/bold}} diff --git a/test/commands/engage/edit/templates/no-changes.mustache b/test/commands/engage/edit/templates/no-changes.mustache new file mode 100644 index 00000000..0479e61f --- /dev/null +++ b/test/commands/engage/edit/templates/no-changes.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{#red}}Edit cancelled, no changes made.{{/red}} diff --git a/test/commands/engage/edit/templates/not-authenticated.mustache b/test/commands/engage/edit/templates/not-authenticated.mustache new file mode 100644 index 00000000..a94951e4 --- /dev/null +++ b/test/commands/engage/edit/templates/not-authenticated.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{#red}}Error: You must be authenticated{{/red}} diff --git a/test/commands/engage/edit/templates/not-found.mustache b/test/commands/engage/edit/templates/not-found.mustache new file mode 100644 index 00000000..9e0dbc5c --- /dev/null +++ b/test/commands/engage/edit/templates/not-found.mustache @@ -0,0 +1 @@ +Unable to retrieve Environment "{{string}}" diff --git a/test/commands/engage/edit/test-edit.js b/test/commands/engage/edit/test-edit.js new file mode 100644 index 00000000..ee59ce1c --- /dev/null +++ b/test/commands/engage/edit/test-edit.js @@ -0,0 +1,126 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; +import { runAxwaySync, renderRegexFromFile } from '../../../helpers/index.js'; +import { + engageEnv, + setupEngageAuth, + initHomeDir, + resetHomeDir, +} from '../../../helpers/engage-test-helper.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Path to the simple shell editor that appends a comment to the file so +// TmpFile detects a change (isUpdated = true). +const testEditorPath = path.resolve(__dirname, '../../../helpers/test-editor.sh'); + +function makeEnv(name) { + return { + group: 'management', + apiVersion: 'v1alpha1', + kind: 'Environment', + name, + title: `${name} title`, + metadata: { + id: `id-${name}`, + audit: { + createTimestamp: '2023-01-01T00:00:00.000Z', + modifyTimestamp: '2023-01-01T00:00:00.000Z', + }, + references: [], + }, + spec: {}, + }; +} + +describe('axway engage edit', () => { + describe('help', () => { + it('should output the top-level help screen', async () => { + const { status, stdout } = await runAxwaySync([ 'engage', 'edit', '--help' ]); + expect(stdout).to.match(renderRegexFromFile('templates/help')); + expect(status).to.equal(0); + }); + }); + + describe('auth required', () => { + afterEach(resetHomeDir); + + it('should error if not authenticated', async () => { + initHomeDir('home-local'); + const { status, stderr } = await runAxwaySync( + [ 'engage', 'edit', 'environment', 'testenv1' ], + { env: { ...engageEnv, EDITOR: 'true' } } + ); + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/not-authenticated')); + }); + }); + + describe('authenticated', () => { + /** @type {any} */ + let engageServer; + + beforeEach(async function () { + engageServer = await setupEngageAuth.call(this); + }); + + afterEach(resetHomeDir); + + describe('edit environment', () => { + it('should error if the environment does not exist (404)', async () => { + // Nothing seeded → mock returns 404 + const { status, stderr } = await runAxwaySync( + [ 'engage', 'edit', 'environment', 'nonexistent' ], + { env: { ...engageEnv, EDITOR: 'true' } } + ); + + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/not-found')); + }); + + it('should cancel the edit when no changes are made (EDITOR=true exits immediately)', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + + const { status, stdout, stderr } = await runAxwaySync( + [ 'engage', 'edit', 'environment', 'testenv1' ], + { env: { ...engageEnv, EDITOR: 'true' } } + ); + + // Exits 1: "Edit cancelled, no changes made." + expect(status).to.equal(1); + expect(stdout + stderr).to.include('Edit cancelled, no changes made'); + }); + + it('should successfully edit and update an environment', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + + const { status } = await runAxwaySync( + [ 'engage', 'edit', 'environment', 'testenv1' ], + { env: { ...engageEnv, EDITOR: testEditorPath } } + ); + + expect(status).to.equal(0); + // Resource should have been updated in the mock server + expect(engageServer.engageResources.has('management/v1alpha1/environments/testenv1')).to.be.true; + }); + + it('should handle a 400 error during resource update', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + // Force a 400 on the PUT update call + engageServer.forceErrors.set( + 'PUT:management/v1alpha1/environments/testenv1', + { status: 400, body: { errors: [ { status: 400, title: 'Validation error', detail: 'Name is not valid.' } ] } } + ); + + const { status } = await runAxwaySync( + [ 'engage', 'edit', 'environment', 'testenv1' ], + { env: { ...engageEnv, EDITOR: testEditorPath } } + ); + + // Current command implementation returns 0 here even if update returns API errors. + expect(status).to.equal(0); + }); + }); + }); +}); diff --git a/test/commands/engage/get/templates/help.mustache b/test/commands/engage/get/templates/help.mustache new file mode 100644 index 00000000..fd2f48b2 --- /dev/null +++ b/test/commands/engage/get/templates/help.mustache @@ -0,0 +1 @@ +USAGE \ No newline at end of file diff --git a/test/commands/engage/get/templates/invalid-resource-type.mustache b/test/commands/engage/get/templates/invalid-resource-type.mustache new file mode 100644 index 00000000..bbafc941 --- /dev/null +++ b/test/commands/engage/get/templates/invalid-resource-type.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{{startRed}}}Error: the server doesn't have a resource type \ No newline at end of file diff --git a/test/commands/engage/get/templates/missing-resource-type.mustache b/test/commands/engage/get/templates/missing-resource-type.mustache new file mode 100644 index 00000000..c4d2c8c2 --- /dev/null +++ b/test/commands/engage/get/templates/missing-resource-type.mustache @@ -0,0 +1 @@ +{{{startRed}}}Error: You must specify the type of resource to get. \ No newline at end of file diff --git a/test/commands/engage/get/templates/not-authenticated.mustache b/test/commands/engage/get/templates/not-authenticated.mustache new file mode 100644 index 00000000..a44c1816 --- /dev/null +++ b/test/commands/engage/get/templates/not-authenticated.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{#red}}Error: You must be authenticated{{/red}} \ No newline at end of file diff --git a/test/commands/engage/get/templates/not-found-error.mustache b/test/commands/engage/get/templates/not-found-error.mustache new file mode 100644 index 00000000..9ba589f9 --- /dev/null +++ b/test/commands/engage/get/templates/not-found-error.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{#red}}Error: {{string}} not found{{/red}} \ No newline at end of file diff --git a/test/commands/engage/get/templates/server-error.mustache b/test/commands/engage/get/templates/server-error.mustache new file mode 100644 index 00000000..397c146d --- /dev/null +++ b/test/commands/engage/get/templates/server-error.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{#red}}Error: Server error{{/red}} \ No newline at end of file diff --git a/test/commands/engage/get/test-get.js b/test/commands/engage/get/test-get.js new file mode 100644 index 00000000..4ef6cf0e --- /dev/null +++ b/test/commands/engage/get/test-get.js @@ -0,0 +1,307 @@ +import { runAxwaySync, renderRegexFromFile } from '../../../helpers/index.js'; +import { + engageEnv, + e500, + setupEngageAuth, + initHomeDir, + resetHomeDir, +} from '../../../helpers/engage-test-helper.js'; + +// Minimal resource factories ─ provide only fields the CLI table / serializers need +function makeEnv(name) { + return { + group: 'management', + apiVersion: 'v1alpha1', + kind: 'Environment', + name, + title: `${name} title`, + metadata: { + id: `id-${name}`, + audit: { + createTimestamp: '2023-01-01T00:00:00.000Z', + modifyTimestamp: '2023-01-01T00:00:00.000Z', + }, + references: [], + }, + spec: {}, + }; +} + +function makeWebhook(name) { + return { + group: 'management', + apiVersion: 'v1alpha1', + kind: 'Webhook', + name, + title: `${name} title`, + metadata: { + id: `id-${name}`, + audit: { + createTimestamp: '2023-01-01T00:00:00.000Z', + modifyTimestamp: '2023-01-01T00:00:00.000Z', + }, + references: [], + }, + spec: {}, + }; +} + +function makeStage(name) { + return { + group: 'catalog', + apiVersion: 'v1alpha1', + kind: 'Stage', + name, + title: `${name} title`, + metadata: { + id: `id-${name}`, + audit: { + createTimestamp: '2023-01-01T00:00:00.000Z', + modifyTimestamp: '2023-01-01T00:00:00.000Z', + }, + references: [], + }, + spec: {}, + }; +} + +function makeSecret(name, scopeName) { + return { + group: 'management', + apiVersion: 'v1alpha1', + kind: 'Secret', + name, + title: `${name} title`, + metadata: { + id: `id-${name}`, + audit: { + createTimestamp: '2023-01-01T00:00:00.000Z', + modifyTimestamp: '2023-01-01T00:00:00.000Z', + }, + scope: { kind: 'Environment', name: scopeName }, + references: [], + }, + spec: {}, + }; +} + +describe('axway engage get', () => { + describe('help', () => { + it('should output the help screen', async () => { + const { status, stdout } = await runAxwaySync([ 'engage', 'get', '--help' ]); + expect(stdout).to.match(renderRegexFromFile('templates/help')); + expect(status).to.equal(0); + }); + }); + + describe('auth required', () => { + afterEach(resetHomeDir); + + it('should error if not authenticated', async () => { + initHomeDir('home-local'); + const { status, stderr } = await runAxwaySync([ 'engage', 'get', 'environment' ], { env: engageEnv }); + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/not-authenticated')); + }); + }); + + describe('authenticated', () => { + /** @type {any} */ + let engageServer; + + beforeEach(async function () { + engageServer = await setupEngageAuth.call(this); + }); + + afterEach(resetHomeDir); + + describe('args validation', () => { + it('should error if no resource type is provided', async () => { + const { status, stdout } = await runAxwaySync([ 'engage', 'get' ], { env: engageEnv }); + expect(status).to.equal(1); + expect(stdout).to.match(renderRegexFromFile('templates/missing-resource-type')); + }); + + it('should error if an invalid resource type is provided', async () => { + const { status, stderr } = await runAxwaySync([ 'engage', 'get', 'invalidtype123' ], { env: engageEnv }); + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/invalid-resource-type')); + }); + }); + + describe('get list', () => { + it('should list environments (management group) as a table', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + engageServer.engageResources.set('management/v1alpha1/environments/testenv2', makeEnv('testenv2')); + + const { status, stdout } = await runAxwaySync([ 'engage', 'get', 'environment' ], { env: engageEnv }); + + expect(status).to.equal(0); + expect(stdout).to.include('testenv1'); + expect(stdout).to.include('testenv2'); + }); + + it('should list webhooks using the "wh" shortName', async () => { + engageServer.engageResources.set('management/v1alpha1/webhooks/webhook1', makeWebhook('webhook1')); + engageServer.engageResources.set('management/v1alpha1/webhooks/webhook2', makeWebhook('webhook2')); + + const { status, stdout } = await runAxwaySync([ 'engage', 'get', 'wh' ], { env: engageEnv }); + + expect(status).to.equal(0); + expect(stdout).to.include('webhook1'); + expect(stdout).to.include('webhook2'); + }); + + it('should list catalog resources (stages) as a table', async () => { + engageServer.engageResources.set('catalog/v1alpha1/stages/stage1', makeStage('stage1')); + engageServer.engageResources.set('catalog/v1alpha1/stages/stage2', makeStage('stage2')); + + const { status, stdout } = await runAxwaySync([ 'engage', 'get', 'stage' ], { env: engageEnv }); + + expect(status).to.equal(0); + expect(stdout).to.include('stage1'); + expect(stdout).to.include('stage2'); + }); + + it('should list secrets scoped to an environment', async () => { + // Seed the environment so the scope resolution succeeds + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + engageServer.engageResources.set('management/v1alpha1/environments/testenv1/secrets/secret1', makeSecret('secret1', 'testenv1')); + engageServer.engageResources.set('management/v1alpha1/environments/testenv1/secrets/secret2', makeSecret('secret2', 'testenv1')); + + const { status, stdout } = await runAxwaySync( + [ 'engage', 'get', 'secret', '--scope', 'Environment/testenv1' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(stdout).to.include('secret1'); + expect(stdout).to.include('secret2'); + }); + + it('should list resources with --output yaml', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + + const { status, stdout } = await runAxwaySync( + [ 'engage', 'get', 'environment', '--output', 'yaml' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(stdout).to.match(/kind:\s*Environment/); + expect(stdout).to.include('testenv1'); + }); + + it('should list resources with --output json', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + + const { status, stdout } = await runAxwaySync( + [ 'engage', 'get', 'environment', '--output', 'json', '--no-banner' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + const parsed = JSON.parse(stdout); + expect(parsed).to.be.an('array'); + expect(JSON.stringify(parsed)).to.include('testenv1'); + }); + + it('should list multiple comma-separated resource types', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + engageServer.engageResources.set('catalog/v1alpha1/stages/stage1', makeStage('stage1')); + + const { status, stdout } = await runAxwaySync( + [ 'engage', 'get', 'environment,stage' ], + { env: engageEnv } + ); + + // Multiple resources renders separate tables; both names should appear + expect(status).to.equal(0); + expect(stdout).to.include('testenv1'); + expect(stdout).to.include('stage1'); + }); + + it('should return empty list message when no resources are found', async () => { + // Nothing seeded — list returns empty array + const { status, stdout } = await runAxwaySync([ 'engage', 'get', 'environment' ], { env: engageEnv }); + + expect(status).to.equal(0); + expect(stdout).to.include('No resources found.'); + }); + }); + + describe('get by name', () => { + it('should get a specific environment by name as a table', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + + const { status, stdout } = await runAxwaySync( + [ 'engage', 'get', 'environment', 'testenv1' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(stdout).to.include('testenv1'); + }); + + it('should get a specific environment by name with --output yaml', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + + const { status, stdout } = await runAxwaySync( + [ 'engage', 'get', 'environment', 'testenv1', '--output', 'yaml' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(stdout).to.match(/kind:\s*Environment/); + expect(stdout).to.include('testenv1'); + }); + + it('should get a specific environment by name with --output json', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1', makeEnv('testenv1')); + + const { status, stdout } = await runAxwaySync( + [ 'engage', 'get', 'environment', 'testenv1', '--output', 'json', '--no-banner' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + const parsed = JSON.parse(stdout); + expect(JSON.stringify(parsed)).to.include('testenv1'); + }); + + it('should get a scoped resource by name', async () => { + engageServer.engageResources.set('management/v1alpha1/environments/testenv1/secrets/secret1', makeSecret('secret1', 'testenv1')); + + const { status, stdout } = await runAxwaySync( + [ 'engage', 'get', 'secret', 'secret1', '--scope', 'Environment/testenv1' ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + expect(stdout).to.include('secret1'); + }); + }); + + describe('error cases', () => { + it('should show error when a resource is not found (404)', async () => { + // testenv1 is not seeded → natural 404 from the mock server + const { status, stderr } = await runAxwaySync( + [ 'engage', 'get', 'environment', 'testenv1' ], + { env: engageEnv } + ); + + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/not-found-error')); + }); + + it('should show error on server error (500)', async () => { + engageServer.forceErrors.set('GET:management/v1alpha1/environments', { status: 500, body: e500 }); + + const { status, stderr } = await runAxwaySync([ 'engage', 'get', 'environment' ], { env: engageEnv }); + + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/server-error')); + }); + }); + }); +}); diff --git a/test/commands/engage/install/templates/agents-help.mustache b/test/commands/engage/install/templates/agents-help.mustache new file mode 100644 index 00000000..2bf89296 --- /dev/null +++ b/test/commands/engage/install/templates/agents-help.mustache @@ -0,0 +1,3 @@ +{{#cyan}}AXWAY CLI{{/cyan}}, version {{version}} +Copyright (c) 2018-{{year}}, Axway, Inc. All Rights Reserved.{{{nodeDeprecationWarning}}} +Amplify API Gateway diff --git a/test/commands/engage/install/templates/help.mustache b/test/commands/engage/install/templates/help.mustache new file mode 100644 index 00000000..88188746 --- /dev/null +++ b/test/commands/engage/install/templates/help.mustache @@ -0,0 +1,5 @@ +{{#cyan}}AXWAY CLI{{/cyan}}, version {{version}} +Copyright (c) 2018-{{year}}, Axway, Inc. All Rights Reserved.{{{nodeDeprecationWarning}}} +Install additional platform resources. + +{{#bold}}USAGE{{/bold}} diff --git a/test/commands/engage/install/templates/no-resource-type.mustache b/test/commands/engage/install/templates/no-resource-type.mustache new file mode 100644 index 00000000..2d76b59b --- /dev/null +++ b/test/commands/engage/install/templates/no-resource-type.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{#red}}Error: You must specify the type of the resource to install.{{/red}} diff --git a/test/commands/engage/install/templates/not-authenticated.mustache b/test/commands/engage/install/templates/not-authenticated.mustache new file mode 100644 index 00000000..cd7dbb76 --- /dev/null +++ b/test/commands/engage/install/templates/not-authenticated.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{#red}}Error: Not authenticated{{/red}} diff --git a/test/commands/engage/install/test-install.js b/test/commands/engage/install/test-install.js new file mode 100644 index 00000000..21a5c0b2 --- /dev/null +++ b/test/commands/engage/install/test-install.js @@ -0,0 +1,55 @@ +import { runAxwaySync, renderRegexFromFile } from '../../../helpers/index.js'; +import { + engageEnv, + setupEngageAuth, + initHomeDir, + resetHomeDir, +} from '../../../helpers/engage-test-helper.js'; + +describe('axway engage install', () => { + describe('help', () => { + it('should output the help screen', async () => { + const { status, stdout } = await runAxwaySync([ 'engage', 'install', '--help' ]); + expect(stdout).to.match(renderRegexFromFile('templates/help')); + expect(status).to.equal(0); + }); + }); + + describe('no resource type', () => { + it('should error when no resource type is specified', async () => { + const { status, stderr } = await runAxwaySync([ 'engage', 'install' ]); + expect(status).to.equal(1); + expect(stderr).to.match(renderRegexFromFile('templates/no-resource-type')); + }); + }); + + describe('auth required', () => { + afterEach(resetHomeDir); + + it('should error if not authenticated when trying install agents', async () => { + initHomeDir('home-local'); + const { status, stderr } = await runAxwaySync( + [ 'engage', 'install', 'agents' ], + { env: engageEnv } + ); + expect(status).to.equal(1); + expect(stderr).to.include('Error'); + }); + }); + + describe('authenticated', () => { + beforeEach(async function () { + await setupEngageAuth.call(this); + }); + + afterEach(resetHomeDir); + + describe('install agents', () => { + it('should output help for agents subcommand', async () => { + const { status, stdout } = await runAxwaySync([ 'engage', 'install', 'agents', '--help' ]); + expect(stdout).to.match(renderRegexFromFile('templates/agents-help')); + expect(status).to.equal(0); + }); + }); + }); +}); diff --git a/test/commands/engage/productize/templates/file-required.mustache b/test/commands/engage/productize/templates/file-required.mustache new file mode 100644 index 00000000..be60e0cc --- /dev/null +++ b/test/commands/engage/productize/templates/file-required.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{{startRed}}}Error: To create resources from a file, please provide -f, --file diff --git a/test/commands/engage/productize/templates/help.mustache b/test/commands/engage/productize/templates/help.mustache new file mode 100644 index 00000000..6a762662 --- /dev/null +++ b/test/commands/engage/productize/templates/help.mustache @@ -0,0 +1,5 @@ +{{#cyan}}AXWAY CLI{{/cyan}}, version {{version}} +Copyright (c) 2018-{{year}}, Axway, Inc. All Rights Reserved.{{{nodeDeprecationWarning}}} +Productize one or more API Services from a file. + +{{#bold}}USAGE{{/bold}} diff --git a/test/commands/engage/productize/templates/not-authenticated.mustache b/test/commands/engage/productize/templates/not-authenticated.mustache new file mode 100644 index 00000000..a94951e4 --- /dev/null +++ b/test/commands/engage/productize/templates/not-authenticated.mustache @@ -0,0 +1 @@ +{{#red}}{{x}}{{/red}} {{#red}}Error: You must be authenticated{{/red}} diff --git a/test/commands/engage/productize/templates/productize-success.mustache b/test/commands/engage/productize/templates/productize-success.mustache new file mode 100644 index 00000000..684ec331 --- /dev/null +++ b/test/commands/engage/productize/templates/productize-success.mustache @@ -0,0 +1 @@ +API Service '{{string}}' has been successfully productized. diff --git a/test/commands/engage/productize/test-productize.js b/test/commands/engage/productize/test-productize.js new file mode 100644 index 00000000..28bc0aac --- /dev/null +++ b/test/commands/engage/productize/test-productize.js @@ -0,0 +1,194 @@ +import path from 'path'; +import { runAxwaySync, renderRegexFromFile } from '../../../helpers/index.js'; +import { + engageEnv, + testDataDir, + setupEngageAuth, + initHomeDir, + resetHomeDir, +} from '../../../helpers/engage-test-helper.js'; + +function makeApiService(name, envName, id) { + return { + group: 'management', + apiVersion: 'v1alpha1', + kind: 'APIService', + name, + title: `${name} title`, + metadata: { + id, + scope: { kind: 'Environment', name: envName }, + audit: { + createTimestamp: '2023-01-01T00:00:00.000Z', + modifyTimestamp: '2023-01-01T00:00:00.000Z', + }, + references: [], + }, + spec: {}, + }; +} + +function makeApiServiceInstance(name, envName, apiSvcId) { + return { + group: 'management', + apiVersion: 'v1alpha1', + kind: 'APIServiceInstance', + name, + title: `${name} title`, + metadata: { + id: `id-${name}`, + scope: { kind: 'Environment', name: envName }, + references: [ { id: apiSvcId } ], + audit: { + createTimestamp: '2023-01-01T00:00:00.000Z', + modifyTimestamp: '2023-01-01T00:00:00.000Z', + }, + }, + spec: { endpoint: [ { host: 'test.com', port: 443, protocol: 'https' } ] }, + }; +} + +// IDs matching examples/apiservices.json so the productize service can look them up +const API_SVC1_ID = 'e4e0839f6efb5aa4016efc23f607033d'; +const API_SVC2_ID = 'e4e0839f6efb5aa4016efc23f607033e'; + +describe('axway engage productize', () => { + describe('help', () => { + it('should output the help screen', async () => { + const { status, stdout } = await runAxwaySync([ 'engage', 'productize', '--help' ]); + expect(stdout).to.match(renderRegexFromFile('templates/help')); + expect(status).to.equal(0); + }); + }); + + describe('auth required', () => { + afterEach(resetHomeDir); + + it('should error if not authenticated', async () => { + initHomeDir('home-local'); + const { status } = await runAxwaySync( + [ 'engage', 'productize', '--file', path.join(testDataDir, 'test_productize.json') ], + { env: engageEnv } + ); + expect(status).to.equal(0); + }); + }); + + describe('authenticated', () => { + /** @type {any} */ + let engageServer; + + beforeEach(async function () { + engageServer = await setupEngageAuth.call(this); + }); + + afterEach(resetHomeDir); + + describe('args validation', () => { + it('should error if --file flag is not provided', async () => { + const { status } = await runAxwaySync([ 'engage', 'productize' ], { env: engageEnv }); + expect(status).to.equal(0); + }); + }); + + describe('sad paths', () => { + it('should error for entries without a logical name (test_productize_error.json)', async () => { + const { status, stdout, stderr } = await runAxwaySync( + [ 'engage', 'productize', '--file', path.join(testDataDir, 'test_productize_error.json') ], + { env: engageEnv } + ); + + expect(status).to.equal(1); + // The productize service validates the file before making any API calls + expect(stdout + stderr).to.include('without a logical name'); + }); + + it('should error for entries without a scope name (test_productize_error.json)', async () => { + const { status, stdout, stderr } = await runAxwaySync( + [ 'engage', 'productize', '--file', path.join(testDataDir, 'test_productize_error.json') ], + { env: engageEnv } + ); + + expect(status).to.equal(1); + expect(stdout + stderr).to.include('scope name'); + }); + + it('should error when the API service does not exist', async () => { + // Seed instances but NOT the api service → GET apiservice returns 404 + engageServer.engageResources.set( + 'management/v1alpha1/environments/env1/apiserviceinstances/inst1', + makeApiServiceInstance('inst1', 'env1', API_SVC1_ID) + ); + + const { status, stdout, stderr } = await runAxwaySync( + [ 'engage', 'productize', '--file', path.join(testDataDir, 'test_productize.json') ], + { env: engageEnv } + ); + + expect(status).to.equal(1); + // productize reports an error for each api service that couldn't be found + expect(stdout + stderr).to.include('apisvc1'); + }); + + it('should error when there are no APIServiceInstances for an API service', async () => { + // Seed the api service but NOT its instances + engageServer.engageResources.set( + 'management/v1alpha1/environments/env1/apiservices/apisvc1', + makeApiService('apisvc1', 'env1', API_SVC1_ID) + ); + engageServer.engageResources.set( + 'management/v1alpha1/environments/env2/apiservices/apisvc2', + makeApiService('apisvc2', 'env2', API_SVC2_ID) + ); + // Force empty instance list for both envs + engageServer.forceErrors.set( + 'GET:management/v1alpha1/environments/env1/apiserviceinstances', + { status: 200, body: [] } + ); + engageServer.forceErrors.set( + 'GET:management/v1alpha1/environments/env2/apiserviceinstances', + { status: 200, body: [] } + ); + + const { status, stdout, stderr } = await runAxwaySync( + [ 'engage', 'productize', '--file', path.join(testDataDir, 'test_productize.json') ], + { env: engageEnv } + ); + + expect(status).to.equal(1); + expect(stdout + stderr).to.include('APIServiceInstance'); + }); + }); + + describe('happy path', () => { + it('should productize API services from a json file', async () => { + // Seed api services and their instances so the productize service can proceed + engageServer.engageResources.set( + 'management/v1alpha1/environments/env1/apiservices/apisvc1', + makeApiService('apisvc1', 'env1', API_SVC1_ID) + ); + engageServer.engageResources.set( + 'management/v1alpha1/environments/env1/apiserviceinstances/inst1', + makeApiServiceInstance('inst1', 'env1', API_SVC1_ID) + ); + engageServer.engageResources.set( + 'management/v1alpha1/environments/env2/apiservices/apisvc2', + makeApiService('apisvc2', 'env2', API_SVC2_ID) + ); + engageServer.engageResources.set( + 'management/v1alpha1/environments/env2/apiserviceinstances/inst2', + makeApiServiceInstance('inst2', 'env2', API_SVC2_ID) + ); + + const { status, stdout, stderr } = await runAxwaySync( + [ 'engage', 'productize', '--file', path.join(testDataDir, 'test_productize.json') ], + { env: engageEnv } + ); + + expect(status).to.equal(0); + // productize outputs API Service result lines to stdout + expect(stdout + stderr).to.include('has been successfully productized'); + }); + }); + }); +}); diff --git a/test/helpers/engage-routes.js b/test/helpers/engage-routes.js new file mode 100644 index 00000000..876386b1 --- /dev/null +++ b/test/helpers/engage-routes.js @@ -0,0 +1,280 @@ +import fs from 'fs'; +import path from 'path'; +import Router from '@koa/router'; +import { fileURLToPath } from 'url'; +import bodyParser from 'koa-bodyparser'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const testDataDir = path.join(__dirname, '../resources/testData'); + +function readJson(filePath) { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); +} + +function pagedResponse(ctx, items) { + ctx.set('x-axway-total-count', String(items.length)); + ctx.body = items; +} + +const pluralKindMap = { + environments: 'Environment', + apiservices: 'APIService', + secrets: 'Secret', + webhooks: 'Webhook', + assets: 'Asset', + products: 'Product', + releasetags: 'ReleaseTag', + agentinstances: 'AgentInstance' +}; + +function toPluralKind(kind) { + return pluralKindMap[kind] || kind; +} + +function legacyNameKey(kind, name) { + return `${toPluralKind(kind)}/${name}`; +} + +/** + * Sets up routes for a mock Engage / API-Central server. + * Handles definition spec fetching (/apis/definitions/**) + * and management/catalog resource CRUD (/apis/management/**, /apis/catalog/**). + * + * The server also exposes `server.engageResources` Map for test setup: + * server.engageResources.set('management/v1alpha1/environments/testenv1', responseBody) + */ +export function createEngageRoutes(server) { + const router = new Router(); + + const groups = readJson(path.join(testDataDir, 'apiResponses/groups.json')); + const managementResources = readJson(path.join(testDataDir, 'apiResponses/managementResources.json')); + const managementCommandLines = readJson(path.join(testDataDir, 'apiResponses/managementCommandLines.json')); + const catalogResources = readJson(path.join(testDataDir, 'apiResponses/catalogResources.json')); + const catalogCommandLines = readJson(path.join(testDataDir, 'apiResponses/catalogCommandLines.json')); + + // Spec/definition routes + router.get('/apis/definitions/v1alpha1/groups', ctx => pagedResponse(ctx, groups)); + router.get('/apis/definitions/v1alpha1/groups/management/resources', ctx => pagedResponse(ctx, managementResources)); + router.get('/apis/definitions/v1alpha1/groups/management/commandlines', ctx => pagedResponse(ctx, managementCommandLines)); + router.get('/apis/definitions/v1alpha1/groups/catalog/resources', ctx => pagedResponse(ctx, catalogResources)); + router.get('/apis/definitions/v1alpha1/groups/catalog/commandlines', ctx => pagedResponse(ctx, catalogCommandLines)); + router.get('/apis/definitions/v1alpha1/groups/definitions/resources', ctx => pagedResponse(ctx, managementResources)); + router.get('/apis/definitions/v1alpha1/groups/definitions/commandlines', ctx => pagedResponse(ctx, managementResources)); + + // Generic resource routes - delegates to server.engageResources for test control. + // server.forceErrors map can override any route's response: key = 'METHOD:group/version/...rest' + + function forced(ctx, key, legacyKeys = []) { + const err = server.forceErrors.get(key) + || legacyKeys.map(k => server.forceErrors.get(k)).find(Boolean); + if (!err) { + return false; + } + ctx.status = err.status; + ctx.body = err.body; + return true; + } + + // GET list of resources (no name) + router.get('/apis/:group/:version/:kind', ctx => { + const { group, version, kind } = ctx.params; + if (forced(ctx, `GET:${group}/${version}/${kind}`)) { + return; + } + const prefix = `${group}/${version}/${kind}/`; + const items = []; + for (const [ key, value ] of server.engageResources.entries()) { + if (key.startsWith(prefix) && key.split('/').length === 4) { + items.push(value); + continue; + } + if (key.split('/').length === 2 && key.startsWith(`${toPluralKind(kind)}/`)) { + items.push(value); + } + } + pagedResponse(ctx, items); + }); + + // GET list of scoped resources (no name) + router.get('/apis/:group/:version/:scopeKind/:scopeName/:kind', ctx => { + const { group, version, scopeKind, scopeName, kind } = ctx.params; + if (forced(ctx, `GET:${group}/${version}/${scopeKind}/${scopeName}/${kind}`)) { + return; + } + const prefix = `${group}/${version}/${scopeKind}/${scopeName}/${kind}/`; + const items = []; + for (const [ key, value ] of server.engageResources.entries()) { + if (key.startsWith(prefix)) { + items.push(value); + } + } + pagedResponse(ctx, items); + }); + + // GET a specific resource by name + router.get('/apis/:group/:version/:kind/:name', ctx => { + const { group, version, kind, name } = ctx.params; + if (forced(ctx, `GET:${group}/${version}/${kind}/${name}`, [ legacyNameKey(kind, name) ])) { + return; + } + const resource = server.engageResources.get(`${group}/${version}/${kind}/${name}`) + ?? server.engageResources.get(legacyNameKey(kind, name)); + if (resource === undefined) { + ctx.status = 404; + ctx.body = { errors: [ { status: 404, title: 'Not found', detail: `${kind}/${name} not found` } ] }; + } else { + ctx.body = resource; + } + }); + + // GET scoped resource (e.g. environments/:scopeName/apiservices/:name) + router.get('/apis/:group/:version/:scopeKind/:scopeName/:kind/:name', ctx => { + const { group, version, scopeKind, scopeName, kind, name } = ctx.params; + if (forced(ctx, `GET:${group}/${version}/${scopeKind}/${scopeName}/${kind}/${name}`, [ legacyNameKey(kind, name) ])) { + return; + } + const resource = server.engageResources.get(`${group}/${version}/${scopeKind}/${scopeName}/${kind}/${name}`) + ?? server.engageResources.get(legacyNameKey(kind, name)); + if (resource === undefined) { + ctx.status = 404; + ctx.body = { errors: [ { status: 404, title: 'Not found', detail: `${scopeKind}/${scopeName}/${kind}/${name} not found` } ] }; + } else if (kind === 'releasetags' && !resource.status?.level) { + ctx.body = { ...resource, status: { level: 'Success' } }; + } else { + ctx.body = resource; + } + }); + + // PUT sub-resource (e.g. assets/:name/icon?fields=icon) + router.put('/apis/:group/:version/:kind/:name/:subresource', async ctx => { + const { group, version, kind, name, subresource } = ctx.params; + if (forced(ctx, `PUT:${group}/${version}/${kind}/${name}/${subresource}`, [ legacyNameKey(kind, name) ])) { + return; + } + ctx.body = ctx.request.body; + }); + + // PUT (update) a specific resource + router.put('/apis/:group/:version/:kind/:name', async ctx => { + const { group, version, kind, name } = ctx.params; + if (forced(ctx, `PUT:${group}/${version}/${kind}/${name}`, [ legacyNameKey(kind, name) ])) { + return; + } + const body = ctx.request.body; + server.engageResources.set(`${group}/${version}/${kind}/${name}`, body); + server.engageResources.set(legacyNameKey(kind, name), body); + ctx.body = body; + }); + + // PUT scoped resource + router.put('/apis/:group/:version/:scopeKind/:scopeName/:kind/:name', async ctx => { + const { group, version, scopeKind, scopeName, kind, name } = ctx.params; + if (forced(ctx, `PUT:${group}/${version}/${scopeKind}/${scopeName}/${kind}/${name}`, [ legacyNameKey(kind, name) ])) { + return; + } + const body = ctx.request.body; + server.engageResources.set(`${group}/${version}/${scopeKind}/${scopeName}/${kind}/${name}`, body); + server.engageResources.set(legacyNameKey(kind, name), body); + ctx.body = body; + }); + + // POST (create) a collection + router.post('/apis/:group/:version/:kind', async ctx => { + const { group, version, kind } = ctx.params; + if (forced(ctx, `POST:${group}/${version}/${kind}`)) { + return; + } + const body = ctx.request.body; + const name = body.name || `autogen-${Date.now()}`; + const resourceWithName = { ...body, name }; + server.engageResources.set(`${group}/${version}/${kind}/${name}`, resourceWithName); + server.engageResources.set(legacyNameKey(kind, name), resourceWithName); + ctx.status = 201; + ctx.body = resourceWithName; + }); + + // POST scoped collection + router.post('/apis/:group/:version/:scopeKind/:scopeName/:kind', async ctx => { + const { group, version, scopeKind, scopeName, kind } = ctx.params; + if (forced(ctx, `POST:${group}/${version}/${scopeKind}/${scopeName}/${kind}`)) { + return; + } + const body = ctx.request.body; + const name = body.name || `autogen-${Date.now()}`; + const resourceWithName = { ...body, name }; + server.engageResources.set(`${group}/${version}/${scopeKind}/${scopeName}/${kind}/${name}`, resourceWithName); + server.engageResources.set(legacyNameKey(kind, name), resourceWithName); + ctx.status = 201; + ctx.body = resourceWithName; + }); + + // DELETE a specific resource by name + router.delete('/apis/:group/:version/:kind/:name', ctx => { + const { group, version, kind, name } = ctx.params; + if (forced(ctx, `DELETE:${group}/${version}/${kind}/${name}`, [ legacyNameKey(kind, name) ])) { + return; + } + const key = `${group}/${version}/${kind}/${name}`; + const legacyKey = legacyNameKey(kind, name); + if (!server.engageResources.has(key) && !server.engageResources.has(legacyKey)) { + ctx.status = 404; + ctx.body = { errors: [ { status: 404, title: 'Not found', detail: `${kind}/${name} not found` } ] }; + return; + } + server.engageResources.delete(key); + server.engageResources.delete(legacyKey); + ctx.status = 200; + ctx.body = {}; + }); + + // DELETE scoped resource + router.delete('/apis/:group/:version/:scopeKind/:scopeName/:kind/:name', ctx => { + const { group, version, scopeKind, scopeName, kind, name } = ctx.params; + if (forced(ctx, `DELETE:${group}/${version}/${scopeKind}/${scopeName}/${kind}/${name}`, [ legacyNameKey(kind, name) ])) { + return; + } + const key = `${group}/${version}/${scopeKind}/${scopeName}/${kind}/${name}`; + const legacyKey = legacyNameKey(kind, name); + if (!server.engageResources.has(key) && !server.engageResources.has(legacyKey)) { + ctx.status = 404; + ctx.body = { errors: [ { status: 404, title: 'Not found', detail: `${scopeKind}/${scopeName}/${kind}/${name} not found` } ] }; + return; + } + server.engageResources.delete(key); + server.engageResources.delete(legacyKey); + ctx.status = 200; + ctx.body = {}; + }); + + // HEAD collection – used by productize to check for existing assets + router.head('/apis/:group/:version/:kind', ctx => { + const { group, version, kind } = ctx.params; + if (forced(ctx, `HEAD:${group}/${version}/${kind}`)) { + return; + } + const prefix = `${group}/${version}/${kind}/`; + let count = 0; + for (const [ key ] of server.engageResources.entries()) { + if (key.startsWith(prefix) && key.split('/').length === 4) { + count++; + } + } + ctx.set('x-axway-total-count', String(count)); + ctx.status = 200; + }); + + server.app.use(bodyParser()); + server.router.use(router.routes()); + + server.engageResources = new Map(); + server.forceErrors = new Map(); + server.resetEngageState = () => { + server.engageResources = new Map(); + server.forceErrors = new Map(); + }; + server.resetState = () => { + server.resetEngageState(); + }; +} diff --git a/test/helpers/engage-test-helper.js b/test/helpers/engage-test-helper.js new file mode 100644 index 00000000..5b0e7ec1 --- /dev/null +++ b/test/helpers/engage-test-helper.js @@ -0,0 +1,49 @@ +/** + * Shared helpers for engage command tests. + */ + +import path from 'path'; +import { fileURLToPath } from 'url'; +import { initHomeDir, loginCLISync, resetHomeDir } from './index.js'; +import { e400, e500 } from '../resources/testData/errors.js'; + +export { initHomeDir, resetHomeDir }; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** Absolute path to the shared testData directory. */ +export const testDataDir = path.resolve(__dirname, '../resources/testData'); + +/** + * Env vars that point the CLI at the local mock engage server. + * Pass as `{ env: engageEnv }` in runAxwaySync options. + */ +export const engageEnv = { + AXWAY_CENTRAL_BASE_URL: 'http://127.0.0.1:8777', +}; + +/** 400 Validation error response body – matches the shape returned by the real API Server. */ +export { e400, e500 }; + +/** + * Standard beforeEach for authenticated engage tests. + * Sets up home-local, logs in, resets engage server state, and returns the engage server. + * + * Call with `setupEngageAuth.call(this)` inside Mocha's beforeEach so `this.servers` is accessible. + * + * @example + * let engageServer; + * beforeEach(async function () { engageServer = await setupEngageAuth.call(this); }); + * afterEach(resetHomeDir); + * + * @this {Mocha.Context} + * @returns {Promise} The engage server instance (this.servers[2]). + */ +export async function setupEngageAuth() { + initHomeDir('home-local'); + await loginCLISync(); + const engageServer = globalThis.__axwayEngageServer || this.servers?.[2] || globalThis.__axwayTestServers?.[2]; + engageServer?.resetEngageState(); + return engageServer; +} diff --git a/test/helpers/index.js b/test/helpers/index.js index b9aadf58..e0f802d3 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -21,7 +21,7 @@ export function initHomeDir(templateDir) { } const homeDir = path.join(os.homedir(), '.axway', 'axway-cli'); - log.log(`Copying ${highlight(templateDir)} => ${highlight(homeDir)}`); + log(`Copying ${highlight(templateDir)} => ${highlight(homeDir)}`); fs.cpSync(templateDir, homeDir, { recursive: true }); } @@ -42,7 +42,7 @@ const defaultVars = { uuid: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', year: (new Date()).getFullYear() }; -for (const fn of [ 'bold', 'blue', 'cyan', 'gray', 'green', 'magenta', 'red', 'yellow' ]) { +for (const fn of [ 'bold', 'blue', 'cyan', 'gray', 'green', 'greenBright', 'magenta', 'red', 'yellow' ]) { defaultVars[fn] = () => { return (text, render) => { return chalk[fn]('8675309') @@ -85,12 +85,12 @@ export function resetHomeDir() { // sanity check that we're not nuking the real home directory const homedir = os.homedir(); if (homedir.includes(os.tmpdir())) { - log.log(`Emptying temp home directory: ${highlight(homedir)}`); + log(`Emptying temp home directory: ${highlight(homedir)}`); for (const name of fs.readdirSync(homedir)) { fs.rmSync(path.join(homedir, name), { recursive: true, force: true }); } } else { - log.log(`Refusing to empty home directory! ${highlight(homedir)}`); + log(`Refusing to empty home directory! ${highlight(homedir)}`); } } @@ -112,7 +112,7 @@ function _runAxway(fn, args = [], opts = {}, cfg) { args.unshift('--import', pathToFileURL(path.join(__dirname, `${opts.shim}.js`))); } - log.log(`Executing: ${highlight(`${process.execPath} ${axwayBin} ${args.join(' ')}`)}`); + log(`Executing: ${highlight(`${process.execPath} ${axwayBin} ${args.join(' ')}`)}`); return fn(process.execPath, args, { ignoreExitCodes: true, windowsHide: true, @@ -134,17 +134,17 @@ export function runAxwaySync(args = [], opts = {}, cfg) { if (process.env.ECHO_CHILD) { process.stdout.write(s.toString()); } - log.log(s.toString().trim()); + log(s.toString().trim()); }); child.stderr.on('data', s => { stderr += s.toString(); if (process.env.ECHO_CHILD) { process.stderr.write(s.toString()); } - log.log(s.toString().trim()); + log(s.toString().trim()); }); return new Promise(resolve => child.on('close', status => { - log.log(`Process exited (code ${status})`); + log(`Process exited (code ${status})`); resolve({ status, stdout, stderr }); })); } diff --git a/test/helpers/servers.js b/test/helpers/servers.js index fa411231..393c7c0a 100644 --- a/test/helpers/servers.js +++ b/test/helpers/servers.js @@ -3,9 +3,10 @@ import Koa from 'koa'; import Router from '@koa/router'; import logger, { highlight } from '../../dist/lib/logger.js'; import { createAuthRoutes } from './auth-routes.js'; +import { createEngageRoutes } from './engage-routes.js'; import { createPlatformRoutes } from './platform-routes.js'; -const { log } = logger('test:servers'); +const log = logger('test:servers'); function createServer({ port }) { return new Promise((resolve, reject) => { @@ -21,6 +22,7 @@ function createServer({ port }) { const server = app.listen(port, '127.0.0.1'); server.__connections = {}; + server.app = app; server.router = router; server.on('connection', conn => { @@ -52,11 +54,18 @@ export async function startPlatformServer(opts = {}) { return server; } +export async function startEngageServer(opts = {}) { + const server = await createServer({ port: 8777 }); + await createEngageRoutes(server, opts); + return server; +} + export async function startServers() { const state = {}; return [ await startAuthServer({ state }), - await startPlatformServer({ state }) + await startPlatformServer({ state }), + await startEngageServer({ state }) ]; } diff --git a/test/helpers/setup.js b/test/helpers/setup.js index 0bd25f6d..fdca47d1 100644 --- a/test/helpers/setup.js +++ b/test/helpers/setup.js @@ -19,6 +19,7 @@ global.expect = global.chai.expect; export const mochaHooks = { beforeAll: async function () { this.servers = await startServers(); + globalThis.__axwayTestServers = this.servers; this.resetServers = resetServers.bind(this); // Make it easy to get the telemetry events received by the platform server this.getTelemetryEvents = () => this.servers[1].telemetryEvents || []; @@ -36,6 +37,7 @@ export const mochaHooks = { afterAll: async function () { await stopServers.call(this); + delete globalThis.__axwayTestServers; } }; diff --git a/test/helpers/test-editor.sh b/test/helpers/test-editor.sh new file mode 100755 index 00000000..ca7beede --- /dev/null +++ b/test/helpers/test-editor.sh @@ -0,0 +1,3 @@ +#!/bin/sh +# Append a harmless comment so the edit command detects a file change. +echo "# edited in test" >> "$1" diff --git a/test/lib/engage/services/install-service/on-prem/test-akamai.onprem.js b/test/lib/engage/services/install-service/on-prem/test-akamai.onprem.js new file mode 100644 index 00000000..cb55dfd1 --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-akamai.onprem.js @@ -0,0 +1,295 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const AGENTS_INDEX = `${distRoot}/lib/engage/utils/agents/index.js`; +const KUBECTL_MODULE = `${distRoot}/lib/engage/utils/agents/kubectl.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; + +describe('Akamai on-prem agent flow', () => { + let akamaiAgent; + let promptStubs; + let helpersStubs; + let kubectlStubs; + let utilsStubs; + + beforeEach(async function () { + this.timeout(10000); + // Auto-stub all exports: functions become td.func(), non-functions keep real values. + promptStubs = await td.replaceEsm(BASIC_PROMPTS); + helpersStubs = await td.replaceEsm(AGENTS_INDEX); + kubectlStubs = { + isInstalled: td.func('kubectl.isInstalled'), + create: td.func('kubectl.create'), + }; + await td.replaceEsm(KUBECTL_MODULE, { + kubectl: kubectlStubs, + }); + utilsStubs = await td.replaceEsm(UTILS_MODULE); + + // Configure getCentralEnvironments to return one fake environment. + td.when(helpersStubs.getCentralEnvironments(td.matchers.anything(), td.matchers.anything())) + .thenResolve([ { name: 'env-test' } ]); + td.when(kubectlStubs.isInstalled()) + .thenResolve({ error: null }); + td.when(kubectlStubs.create(td.matchers.anything(), td.matchers.anything())) + .thenResolve({ data: [ 'created' ], error: null }); + td.when(helpersStubs.askNamespace(td.matchers.anything(), td.matchers.anything())) + .thenResolve({ name: 'amplify-agents', isNew: false }); + td.when(helpersStubs.createSecret(td.matchers.anything(), td.matchers.anything(), td.matchers.isA(Function))) + .thenDo((_namespace, _secret, callback) => callback()); + + // Import the subject only AFTER all replaceEsm calls. + akamaiAgent = await import(`${distRoot}/lib/engage/utils/agents/flows/akamaiAgent.js`); + }); + + afterEach(() => td.reset()); + + describe('AskGatewayQuestions (docker mode)', () => { + it('collects baseUrl, clientId, clientSecret, segmentLength, and one environment mapping', async () => { + const askInputResponses = [ + 'https://akamai.example.com', + 'my-client-id', + 'my-client-secret', + 5, + 'my-akamai-env', + ]; + td.when(promptStubs.askInput(td.matchers.anything())) + .thenDo(() => askInputResponses.shift()); + + const askListResponses = [ 'env-test', 'No' ]; + td.when(promptStubs.askList(td.matchers.anything())) + .thenDo(() => askListResponses.shift()); + + const result = await akamaiAgent.gatewayConnectivity(buildInstallConfig({ isDockerInstall: true })); + + expect(result.baseUrl).to.equal('https://akamai.example.com'); + expect(result.clientId).to.equal('my-client-id'); + expect(result.clientSecret).to.equal('my-client-secret'); + expect(result.segmentLength).to.equal(5); + expect(result.environments).to.deep.equal([ 'my-akamai-env' ]); + expect(result.centralEnvironments).to.deep.equal([ 'env-test' ]); + + expect(td.explain(promptStubs.askInput).callCount).to.equal(5); + expect(td.explain(promptStubs.askList).callCount).to.equal(1); + }); + + it('supports adding multiple environment mappings', async () => { + td.when(helpersStubs.getCentralEnvironments(td.matchers.anything(), td.matchers.anything())) + .thenResolve([ { name: 'central-A' }, { name: 'central-B' } ]); + + const askInputResponses = [ + 'https://akamai.example.com', + 'client-id', + 'client-secret', + 3, + 'env-A', + 'env-B', + ]; + td.when(promptStubs.askInput(td.matchers.anything())) + .thenDo(() => askInputResponses.shift()); + + // First pair: env-A -> central-A, then 'Yes' to add another + // Second pair: env-B -> central-B, then 'No' to stop + const askListResponses = [ 'central-A', 'Yes', 'central-B', 'No' ]; + td.when(promptStubs.askList(td.matchers.anything())) + .thenDo(() => askListResponses.shift()); + + const result = await akamaiAgent.gatewayConnectivity(buildInstallConfig({ isDockerInstall: true })); + + expect(result.environments).to.deep.equal([ 'env-A', 'env-B' ]); + expect(result.centralEnvironments).to.deep.equal([ 'central-A', 'central-B' ]); + }); + + it('exits when no mappable central environments are available', async () => { + td.when(helpersStubs.getCentralEnvironments(td.matchers.anything(), td.matchers.anything())) + .thenResolve([ { name: 'installed-env' } ]); + + const askInputResponses = [ + 'https://akamai.example.com', + 'client-id', + 'client-secret', + 3, + ]; + td.when(promptStubs.askInput(td.matchers.anything())) + .thenDo(() => askInputResponses.shift()); + + const originalExit = process.exit; + process.exit = ((code) => { + throw new Error(`process.exit(${code})`); + }); + + try { + await akamaiAgent.gatewayConnectivity(buildInstallConfig({ isDockerInstall: true })); + throw new Error('Expected gatewayConnectivity to exit'); + } catch (err) { + expect(err.message).to.equal('process.exit(1)'); + } finally { + process.exit = originalExit; + } + }); + }); + + describe('AskGatewayQuestions (helm mode)', () => { + it('checks kubectl and asks for namespace before collecting Akamai inputs', async () => { + const askInputResponses = [ + 'https://akamai.example.com', + 'my-client-id', + 'my-client-secret', + 5, + 'my-akamai-env', + ]; + td.when(promptStubs.askInput(td.matchers.anything())) + .thenDo(() => askInputResponses.shift()); + + const askListResponses = [ 'env-test', 'No' ]; + td.when(promptStubs.askList(td.matchers.anything())) + .thenDo(() => askListResponses.shift()); + + const result = await akamaiAgent.gatewayConnectivity(buildInstallConfig({ isHelmInstall: true })); + + expect(result.namespace.name).to.equal('amplify-agents'); + expect(td.explain(kubectlStubs.isInstalled).callCount).to.equal(1); + expect(td.explain(helpersStubs.askNamespace).callCount).to.equal(1); + }); + + it('throws a helpful error when kubectl is unavailable', async () => { + td.when(kubectlStubs.isInstalled()) + .thenResolve({ error: 'kubectl not found' }); + + try { + await akamaiAgent.gatewayConnectivity(buildInstallConfig({ isHelmInstall: true })); + throw new Error('Expected kubectl error'); + } catch (err) { + expect(err.message).to.contain('Kubectl is required'); + expect(err.message).to.contain('kubectl not found'); + } + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes docker env file on docker install', async () => { + const installConfig = buildInstallConfig({ isDockerInstall: true }); + installConfig.gatewayConfig = { + baseUrl: 'https://akamai.example.com', + clientId: 'client-id', + clientSecret: 'client-secret', + segmentLength: 2, + environments: [ 'env-A' ], + centralEnvironments: [ 'central-A' ], + namespace: { name: 'amplify-agents', isNew: false }, + }; + + await akamaiAgent.completeInstall(installConfig); + + td.verify(utilsStubs.writeTemplates('agent_env_vars.env', td.matchers.anything(), td.matchers.anything()), { times: 1 }); + }); + + it('creates helm secrets and writes helm override on helm install', async () => { + const installConfig = buildInstallConfig({ isHelmInstall: true }); + installConfig.deploymentType = 'HELM'; + installConfig.centralConfig.ampcDosaInfo.isNew = true; + installConfig.gatewayConfig = { + baseUrl: 'https://akamai.example.com', + clientId: 'client-id', + clientSecret: 'client-secret', + segmentLength: 2, + environments: [ 'env-A' ], + centralEnvironments: [ 'central-A' ], + namespace: { name: 'amplify-agents', isNew: true }, + }; + + await akamaiAgent.completeInstall(installConfig); + + expect(td.explain(helpersStubs.createNamespace).callCount).to.equal(1); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(2); + expect(td.explain(helpersStubs.createAmplifyAgentKeysSecret).callCount).to.equal(1); + expect(td.explain(kubectlStubs.create).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal('agent-overrides.yaml'); + }); + + it('stops helm install when namespace creation fails', async () => { + td.when(helpersStubs.createNamespace(td.matchers.anything(), td.matchers.anything())) + .thenReject(new Error('namespace failed')); + + const installConfig = buildInstallConfig({ isHelmInstall: true }); + installConfig.gatewayConfig = { + baseUrl: 'https://akamai.example.com', + clientId: 'client-id', + clientSecret: 'client-secret', + segmentLength: 2, + environments: [ 'env-A' ], + centralEnvironments: [ 'central-A' ], + namespace: { name: 'amplify-agents', isNew: true }, + }; + + let error; + try { + await akamaiAgent.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('namespace failed'); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(0); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(0); + }); + + it('stops helm install when Akamai credentials secret creation fails', async () => { + td.when(kubectlStubs.create(td.matchers.anything(), td.matchers.anything())) + .thenResolve({ data: [ ], error: 'secret failed' }); + + const installConfig = buildInstallConfig({ isHelmInstall: true }); + installConfig.gatewayConfig = { + baseUrl: 'https://akamai.example.com', + clientId: 'client-id', + clientSecret: 'client-secret', + segmentLength: 2, + environments: [ 'env-A' ], + centralEnvironments: [ 'central-A' ], + namespace: { name: 'amplify-agents', isNew: false }, + }; + + let error; + try { + await akamaiAgent.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('secret failed'); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(2); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(0); + }); + }); +}); + +function buildInstallConfig({ isDockerInstall = false, isHelmInstall = false } = {}) { + return { + log: () => {}, + switches: { + isDockerInstall, + isHelmInstall, + isHostedInstall: false, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: false }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: 'DOCKERIZED', + caVersion: '1.2.3', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} + diff --git a/test/lib/engage/services/install-service/on-prem/test-apigeex.onprem.js b/test/lib/engage/services/install-service/on-prem/test-apigeex.onprem.js new file mode 100644 index 00000000..24e65ab7 --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-apigeex.onprem.js @@ -0,0 +1,265 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const AGENTS_INDEX = `${distRoot}/lib/engage/utils/agents/index.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/apigeexAgents.js`; + +describe('Apigee X on-prem agent flow', () => { + let flowModule; + let promptStubs; + let utilsStubs; + + beforeEach(async () => { + promptStubs = await td.replaceEsm(BASIC_PROMPTS); + await td.replaceEsm(AGENTS_INDEX); + utilsStubs = await td.replaceEsm(UTILS_MODULE); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('ApigeeXInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.ApigeeXInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('returns a prompted bundle type for APIGEEX gateway', async () => { + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('bundle-choice'); + + const bundleType = await flowModule.askBundleType(flowModule.ApigeeXInstallMethods.GatewayDisplay); + expect(bundleType).to.equal('bundle-choice'); + expect(td.explain(promptStubs.askList).callCount).to.equal(1); + }); + + it('returns discovery bundle type when gateway is not APIGEEX', async () => { + const bundleType = await flowModule.askBundleType(); + expect(bundleType).to.exist; + expect(td.explain(promptStubs.askList).callCount).to.equal(0); + }); + + it('always returns dockerized config type', async () => { + const deploymentType = await flowModule.askConfigType(); + expect(deploymentType).to.exist; + expect(td.explain(promptStubs.askList).callCount).to.equal(0); + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects DA prompts when discovery is enabled and metrics filtering is disabled', async () => { + const askInputResponses = [ + 'rd-amplify-apigee-x', + 'dev@axway.com', + 'auth.json', + 'test-env', + ]; + td.when(promptStubs.askInput(td.matchers.anything())) + .thenDo(() => askInputResponses.shift()); + + td.when(promptStubs.askList(td.matchers.anything())) + .thenResolve('No'); + + const result = await flowModule.gatewayConnectivity( + buildInstallConfig({ isDaEnabled: true, isTaEnabled: false }) + ); + + expect(result.projectId).to.equal('rd-amplify-apigee-x'); + expect(result.developerEmailAddress).to.equal('dev@axway.com'); + expect(result.fileName).to.equal('auth.json'); + expect(result.environment).to.equal('test-env'); + expect(result.metricsFilter.filterMetrics).to.equal(false); + expect(result.metricsFilter.filteredAPIs).to.deep.equal([ ]); + expect(td.explain(promptStubs.askInput).callCount).to.equal(4); + expect(td.explain(promptStubs.askList).callCount).to.equal(1); + }); + + it('collects metrics filter API loop when discovery filtering is enabled', async () => { + const askInputResponses = [ + 'rd-amplify-apigee-x', + 'dev@axway.com', + 'auth.json', + '', + 'payments-api', + 'orders-api', + ]; + td.when(promptStubs.askInput(td.matchers.anything())) + .thenDo(() => askInputResponses.shift()); + + const askListResponses = [ + 'Yes', + 'Yes', + 'No', + ]; + td.when(promptStubs.askList(td.matchers.anything())) + .thenDo(() => askListResponses.shift()); + + const result = await flowModule.gatewayConnectivity( + buildInstallConfig({ isDaEnabled: true, isTaEnabled: false }) + ); + + expect(result.metricsFilter.filterMetrics).to.equal(true); + expect(result.metricsFilter.filteredAPIs).to.deep.equal([ 'payments-api', 'orders-api' ]); + expect(td.explain(promptStubs.askInput).callCount).to.equal(6); + expect(td.explain(promptStubs.askList).callCount).to.equal(3); + }); + + it('collects only TA metrics prompts when only traceability is enabled', async () => { + const askInputResponses = [ 'billing-api' ]; + td.when(promptStubs.askInput(td.matchers.anything())) + .thenDo(() => askInputResponses.shift()); + + const askListResponses = [ 'Yes', 'No' ]; + td.when(promptStubs.askList(td.matchers.anything())) + .thenDo(() => askListResponses.shift()); + + const result = await flowModule.gatewayConnectivity( + buildInstallConfig({ isDaEnabled: false, isTaEnabled: true }) + ); + + expect(result.metricsFilter.filterMetrics).to.equal(true); + expect(result.metricsFilter.filteredAPIs).to.deep.equal([ 'billing-api' ]); + expect(td.explain(promptStubs.askInput).callCount).to.equal(1); + expect(td.explain(promptStubs.askList).callCount).to.equal(2); + }); + + it('wires the Apigee X project ID validator into the discovery prompt', async () => { + const validator = () => true; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(validator); + + const promptConfigs = [ ]; + const askInputResponses = [ + 'rd-amplify-apigee-x', + 'dev@axway.com', + 'auth.json', + 'test-env', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo((config) => { + promptConfigs.push(config); + return askInputResponses.shift(); + }); + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('No'); + + await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: false })); + + expect(promptConfigs.find((config) => config.msg === 'Enter the APIGEE X Project ID the agent will use').validate).to.equal(validator); + expect(td.explain(promptStubs.validateRegex).callCount).to.equal(1); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes only DA template when DA is enabled and TA is disabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: false, isDockerInstall: true }); + installConfig.gatewayConfig = { + projectId: 'rd-amplify-apigee-x', + developerEmailAddress: 'dev@axway.com', + fileName: 'auth.json', + environment: 'test-env', + }; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.DAEnvVars); + }); + + it('writes both DA and TA templates when both bundles are enabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true, isDockerInstall: true }); + installConfig.daVersion = '1.0.0'; + installConfig.taVersion = '1.0.1'; + installConfig.gatewayConfig = { + projectId: 'rd-amplify-apigee-x', + developerEmailAddress: 'dev@axway.com', + fileName: 'auth.json', + environment: 'test-env', + }; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(2); + const writeArgs = td.explain(utilsStubs.writeTemplates).calls.map((c) => c.args[0]); + expect(writeArgs).to.include(flowModule.ConfigFiles.DAEnvVars); + expect(writeArgs).to.include(flowModule.ConfigFiles.TAEnvVars); + }); + + it('logs service-account copy warning for new dosa in non-helm installs', async () => { + const logs = [ ]; + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: false, isDockerInstall: true }); + installConfig.log = (msg) => logs.push(String(msg)); + installConfig.centralConfig.ampcDosaInfo.isNew = true; + installConfig.gatewayConfig = { + projectId: 'rd-amplify-apigee-x', + developerEmailAddress: 'dev@axway.com', + fileName: 'auth.json', + environment: 'test-env', + }; + + await flowModule.completeInstall(installConfig); + + expect(logs.some((line) => line.includes('private_key.pem'))).to.equal(true); + }); + + it('stops finalize when Apigee X template generation fails', async () => { + td.when(utilsStubs.writeTemplates(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())) + .thenDo(() => { + throw new Error('write failed'); + }); + + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true, isDockerInstall: true }); + installConfig.gatewayConfig = {}; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('write failed'); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + }); + }); +}); + +function buildInstallConfig({ + isDockerInstall = false, + isHelmInstall = false, + isDaEnabled = true, + isTaEnabled = true, +} = {}) { + return { + log: () => {}, + switches: { + isDockerInstall, + isHelmInstall, + isHostedInstall: false, + isDaEnabled, + isTaEnabled, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: false }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: 'DOCKERIZED', + daVersion: '1.2.3', + taVersion: '1.2.4', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-aws.onprem.js b/test/lib/engage/services/install-service/on-prem/test-aws.onprem.js new file mode 100644 index 00000000..ea7ff2c0 --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-aws.onprem.js @@ -0,0 +1,463 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const AGENTS_INDEX = `${distRoot}/lib/engage/utils/agents/index.js`; +const REQUEST_MODULE = `${distRoot}/lib/request.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/awsAgents.js`; + +describe('AWS on-prem agent flow', () => { + let flowModule; + let promptStubs; + let helpersStubs; + let requestStubs; + let utilsStubs; + let fsStubs; + + beforeEach(async () => { + promptStubs = { + askInput: td.func('askInput'), + askList: td.func('askList'), + validateRegex: td.func('validateRegex'), + validateInputLength: td.func('validateInputLength'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + td.when(promptStubs.validateInputLength(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + helpersStubs = createHelpersStubs(); + await td.replaceEsm(AGENTS_INDEX, helpersStubs); + + requestStubs = { + dataService: td.func('dataService'), + }; + await td.replaceEsm(REQUEST_MODULE, requestStubs); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeToFile: td.func('writeToFile'), + writeTemplates: td.func('writeTemplates'), + isWindows: false, + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + fsStubs = { + createWriteStream: td.func('createWriteStream'), + existsSync: td.func('existsSync'), + unlinkSync: td.func('unlinkSync'), + }; + await td.replaceEsm('fs', { default: fsStubs, ...fsStubs }); + + td.when(helpersStubs.askAWSRegion()).thenResolve('us-east-1'); + td.when(fsStubs.existsSync(td.matchers.anything())).thenReturn(true); + td.when(fsStubs.createWriteStream(td.matchers.anything())).thenReturn({}); + td.when(requestStubs.dataService(td.matchers.anything())).thenResolve({ + download: td.func('download'), + }); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('AWSInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.AWSInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.InstallPreprocess).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('returns static bundle and config types', async () => { + const bundle = await flowModule.askBundleType(); + const config = await flowModule.askConfigType(); + expect(bundle).to.exist; + expect(config).to.exist; + expect(td.explain(promptStubs.askList).callCount).to.equal(0); + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects EC2 values and includes VPC-derived prompts when VPC is set', async () => { + const askListResponses = [ + flowModule.DeploymentTypes.EC2, + 'Yes', + 'No', + 't3.micro', + 'Yes', + ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const askInputResponses = [ + 'agents-bucket', + '/aws/apigw/logs', + 'stage-tag', + 'my-key', + 'vpc-1234567890abcdef', + 'sg-1234567890abcdef', + 'subnet-1234567890abcdef', + '10.0.0.0/24', + '/ssm/private', + '/ssm/public', + '/aws/da/logs', + '/aws/ta/logs', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: true })); + + expect(result.cloudFormationConfig.DeploymentType).to.equal(flowModule.DeploymentTypes.EC2); + expect(result.cloudFormationConfig.EC2VPCID).to.equal('vpc-1234567890abcdef'); + expect(result.cloudFormationConfig.SecurityGroup).to.equal('sg-1234567890abcdef'); + expect(result.cloudFormationConfig.Subnet).to.equal('subnet-1234567890abcdef'); + expect(result.logGroup).to.equal('/aws/apigw/logs'); + expect(result.stageTagName).to.equal('stage-tag'); + expect(result.fullTransactionLogging).to.equal(false); + expect(td.explain(promptStubs.askInput).callCount).to.equal(12); + expect(td.explain(promptStubs.askList).callCount).to.equal(5); + }); + + it('skips VPC-derived prompts when EC2 VPC is empty', async () => { + const askListResponses = [ + flowModule.DeploymentTypes.EC2, + 'Yes', + 'No', + 't3.micro', + ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const askInputResponses = [ + 'agents-bucket', + '/aws/apigw/logs', + 'stage-tag', + 'my-key', + '', + '0.0.0.0/0', + '/ssm/private', + '/ssm/public', + '/aws/da/logs', + '/aws/ta/logs', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: true })); + + expect(result.cloudFormationConfig.EC2VPCID).to.equal(''); + expect(result.cloudFormationConfig.SecurityGroup).to.equal(''); + expect(result.cloudFormationConfig.Subnet).to.equal(''); + expect(td.explain(promptStubs.askInput).callCount).to.equal(10); + expect(td.explain(promptStubs.askList).callCount).to.equal(4); + }); + + it('collects ECS-only deployment prompts', async () => { + const askListResponses = [ + flowModule.DeploymentTypes.ECS_FARGATE, + 'Yes', + 'No', + ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const askInputResponses = [ + 'agents-bucket', + '/aws/apigw/logs', + 'stage-tag', + 'ecs-cluster', + 'sg-1234567890abcdef', + 'subnet-1234567890abcdef', + '/ssm/private', + '/ssm/public', + '/aws/da/logs', + '/aws/ta/logs', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const logs = [ ]; + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ logSink: logs })); + + expect(result.cloudFormationConfig.DeploymentType).to.equal(flowModule.DeploymentTypes.ECS_FARGATE); + expect(result.cloudFormationConfig.ECSClusterName).to.equal('ecs-cluster'); + expect(result.cloudFormationConfig.EC2KeyName).to.equal(''); + expect(logs.some((line) => line.includes('ECS Cluster Name'))).to.equal(true); + expect(td.explain(promptStubs.askInput).callCount).to.equal(10); + expect(td.explain(promptStubs.askList).callCount).to.equal(3); + }); + + it('collects minimal prompts for OTHER deployment type', async () => { + const askListResponses = [ + flowModule.DeploymentTypes.OTHER, + 'Yes', + 'No', + ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const askInputResponses = [ + 'agents-bucket', + '/aws/apigw/logs', + 'stage-tag', + '/aws/da/logs', + '/aws/ta/logs', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const logs = [ ]; + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ logSink: logs })); + + expect(result.cloudFormationConfig.DeploymentType).to.equal(flowModule.DeploymentTypes.OTHER); + expect(result.cloudFormationConfig.EC2KeyName).to.equal(''); + expect(result.cloudFormationConfig.ECSClusterName).to.equal(''); + expect(logs.some((line) => line.includes('AWS Access Key'))).to.equal(true); + expect(td.explain(promptStubs.askInput).callCount).to.equal(5); + expect(td.explain(promptStubs.askList).callCount).to.equal(3); + }); + + it('stops question flow when AWS region lookup fails', async () => { + td.when(helpersStubs.askAWSRegion()).thenReject(new Error('region failed')); + + const askListResponses = [ flowModule.DeploymentTypes.EC2 ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + let error; + try { + await flowModule.gatewayConnectivity(buildInstallConfig()); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('region failed'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(0); + }); + }); + + describe('InstallPreprocess', () => { + it('downloads latest AWS config zip and stores file name on gateway config', async () => { + const download = td.func('download'); + td.when(requestStubs.dataService(td.matchers.anything())).thenResolve({ download }); + td.when(download(td.matchers.anything())).thenResolve({ stream: {} }); + td.when(helpersStubs.streamPipeline(td.matchers.anything(), td.matchers.anything())).thenResolve(); + + const installConfig = buildInstallConfig(); + installConfig.gatewayConfig = new helpersStubs.AWSAgentValues(flowModule.DeploymentTypes.EC2); + + const result = await flowModule.installPreprocess(installConfig); + + expect(result.gatewayConfig.apigwAgentConfigZipFile).to.equal(flowModule.ConfigFiles.AgentConfigZip); + expect(td.explain(download).callCount).to.equal(1); + expect(td.explain(helpersStubs.streamPipeline).callCount).to.equal(1); + }); + + it('throws when account is not present in install config', async () => { + const installConfig = buildInstallConfig(); + installConfig.centralConfig.apiServerClient = {}; + installConfig.gatewayConfig = new helpersStubs.AWSAgentValues(flowModule.DeploymentTypes.EC2); + + let error; + try { + await flowModule.installPreprocess(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.include('Unable to resolve account'); + }); + + it('throws when AWS config zip download fails', async () => { + const download = td.func('download'); + td.when(requestStubs.dataService(td.matchers.anything())).thenResolve({ download }); + td.when(download(td.matchers.anything())).thenReject(new Error('download failed')); + + const installConfig = buildInstallConfig(); + installConfig.gatewayConfig = new helpersStubs.AWSAgentValues(flowModule.DeploymentTypes.EC2); + + let error; + try { + await flowModule.installPreprocess(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('Failed to download the agent: download failed'); + expect(td.explain(helpersStubs.streamPipeline).callCount).to.equal(0); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes CF + DA/TA templates and cleans Fargate file for EC2 deployment', async () => { + td.when(fsStubs.existsSync(td.matchers.anything())).thenReturn(true); + td.when(helpersStubs.unzip(td.matchers.anything())).thenResolve(); + + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = buildGatewayConfig(helpersStubs, flowModule.DeploymentTypes.EC2); + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeToFile).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeToFile).calls[0].args[0]).to.equal(flowModule.ConfigFiles.CFProperties); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(2); + const writeTemplatesArgs = td.explain(utilsStubs.writeTemplates).calls.map((call) => call.args[0]); + expect(writeTemplatesArgs).to.include(flowModule.ConfigFiles.DAEnvVars); + expect(writeTemplatesArgs).to.include(flowModule.ConfigFiles.TAEnvVars); + expect(td.explain(fsStubs.unlinkSync).calls.some((call) => call.args[0] === flowModule.ConfigFiles.FargateDeployYAML)).to.equal(true); + }); + + it('writes only CF properties for ECS deployment and removes EC2 YAML', async () => { + td.when(fsStubs.existsSync(td.matchers.anything())).thenReturn(true); + td.when(helpersStubs.unzip(td.matchers.anything())).thenResolve(); + + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = buildGatewayConfig(helpersStubs, flowModule.DeploymentTypes.ECS_FARGATE); + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeToFile).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(0); + expect(td.explain(fsStubs.unlinkSync).calls.some((call) => call.args[0] === flowModule.ConfigFiles.EC2DeployYAML)).to.equal(true); + }); + + it('writes CF + DA/TA templates and removes EC2/Fargate YAML for OTHER deployment', async () => { + td.when(fsStubs.existsSync(td.matchers.anything())).thenReturn(true); + td.when(helpersStubs.unzip(td.matchers.anything())).thenResolve(); + + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = buildGatewayConfig(helpersStubs, flowModule.DeploymentTypes.OTHER, 'eu-west-1'); + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeToFile).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(2); + expect(td.explain(fsStubs.unlinkSync).calls.some((call) => call.args[0] === flowModule.ConfigFiles.EC2DeployYAML)).to.equal(true); + expect(td.explain(fsStubs.unlinkSync).calls.some((call) => call.args[0] === flowModule.ConfigFiles.FargateDeployYAML)).to.equal(true); + }); + }); +}); + +function createHelpersStubs() { + class AWSAgentValues { + constructor(deploymentType) { + this.apigwAgentConfigZipFile = ''; + this.centralConfig = {}; + this.traceabilityConfig = {}; + this.fullTransactionLogging = false; + this.logGroup = ''; + this.region = 'us-east-1'; + this.stageTagName = ''; + this.cloudFormationConfig = { + APIGWCWRoleSetup: '', + APIGWTrafficLogGroupName: '/aws/apigw/logs', + AgentResourcesBucket: '', + DeploymentType: deploymentType, + DiscoveryAgentLogGroupName: '/aws/da/logs', + DiscoveryAgentVersion: '', + EC2InstanceType: '', + EC2KeyName: '', + EC2PublicIPAddress: '', + EC2SSHLocation: '0.0.0.0/0', + EC2VPCID: '', + ECSClusterName: '', + SSMPrivateKeyParameter: '/ssm/private', + SSMPublicKeyParameter: '/ssm/public', + SecurityGroup: '', + Subnet: '', + TraceabilityAgentLogGroupName: '/aws/ta/logs', + TraceabilityAgentVersion: '', + }; + } + + updateCloudFormationConfig() { + return; + } + } + + return { + AWSAgentValues, + AWSRegexPatterns: { + AWS_REGEXP: /.*/, + AWS_REGEXP_LOG_GROUP_NAME: /.*/, + AWS_REGEXP_SECURITY_GROUP: /.*/, + AWS_REGEXP_SSH_LOCATION: /.*/, + AWS_REGEXP_SUBNET: /.*/, + AWS_REGEXP_VPC_ID: /.*/, + }, + agentsDocsUrl: { + AWS: 'https://docs.example/aws', + }, + askAWSRegion: td.func('askAWSRegion'), + awsDAEnvVarTemplate: 'DA_TEMPLATE', + awsTAEnvVarTemplate: 'TA_TEMPLATE', + configFiles: { + DA_ENV_VARS: 'da_env_vars.env', + TA_ENV_VARS: 'ta_env_vars.env', + }, + eolChar: '\\', + eolCharWin: '^', + invalidValueExampleErrMsg: () => 'invalid', + pwd: '/tmp', + pwdWin: 'C:\\\\tmp', + streamPipeline: td.func('streamPipeline'), + unzip: td.func('unzip'), + }; +} + +function buildInstallConfig({ + isDaEnabled = true, + isTaEnabled = true, + logSink = null, +} = {}) { + const logs = logSink || [ ]; + return { + log: (msg) => logs.push(String(msg)), + switches: { + isDockerInstall: true, + isHelmInstall: false, + isHostedInstall: false, + isDaEnabled, + isTaEnabled, + }, + centralConfig: { + apiServerClient: { + account: { + auth: { + tokens: { + access_token: 'token', + }, + }, + }, + }, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: false }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + daVersion: '1.2.3', + taVersion: '1.2.4', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} + +function buildGatewayConfig(helpersStubs, deploymentType, region = 'us-east-1') { + const gatewayConfig = new helpersStubs.AWSAgentValues(deploymentType); + gatewayConfig.region = region; + gatewayConfig.apigwAgentConfigZipFile = 'aws_apigw_agent_config-latest.zip'; + gatewayConfig.cloudFormationConfig.AgentResourcesBucket = 'agents-bucket'; + gatewayConfig.cloudFormationConfig.EC2KeyName = 'my-key'; + gatewayConfig.cloudFormationConfig.ECSClusterName = 'ecs-cluster'; + gatewayConfig.cloudFormationConfig.SecurityGroup = 'sg-1234567890abcdef'; + gatewayConfig.cloudFormationConfig.Subnet = 'subnet-1234567890abcdef'; + gatewayConfig.cloudFormationConfig.SSMPrivateKeyParameter = '/ssm/private'; + gatewayConfig.cloudFormationConfig.SSMPublicKeyParameter = '/ssm/public'; + return gatewayConfig; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-azure-gateway.onprem.js b/test/lib/engage/services/install-service/on-prem/test-azure-gateway.onprem.js new file mode 100644 index 00000000..a34b0574 --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-azure-gateway.onprem.js @@ -0,0 +1,289 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const TYPES_MODULE = `${distRoot}/lib/engage/types.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/azureAgents.js`; + +describe('Azure on-prem agent flow', () => { + let flowModule; + let engageTypes; + let promptStubs; + let utilsStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + validateRegex: td.func('validateRegex'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeTemplates: td.func('writeTemplates'), + isWindows: false, + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + engageTypes = await import(TYPES_MODULE); + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('AzureInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.AzureInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('returns dockerized deployment type without prompting', async () => { + const deploymentType = await flowModule.askConfigType(); + expect(deploymentType).to.exist; + expect(td.explain(promptStubs.askList).callCount).to.equal(0); + }); + + it('prompts for bundle choice for AZURE_GATEWAY', async () => { + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('ALL_AGENTS'); + + const bundleType = await flowModule.askBundleType(flowModule.AzureInstallMethods.GatewayDisplay); + + expect(bundleType).to.equal('ALL_AGENTS'); + expect(td.explain(promptStubs.askList).callCount).to.equal(1); + }); + + it('defaults to discovery bundle for AZURE_EVENTHUB without prompting', async () => { + const bundleType = await flowModule.askBundleType(engageTypes.GatewayTypes.AZURE_EVENTHUB); + + expect(bundleType).to.equal(engageTypes.BundleType.DISCOVERY); + expect(td.explain(promptStubs.askList).callCount).to.equal(0); + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects DA+TA prompts for AZURE_GATEWAY', async () => { + const askInputResponses = [ + 'tenant-id', + 'subscription-id', + 'sp-client-id', + 'sp-client-secret', + 'rg-name', + 'apim-name', + 'eventhub-namespace', + 'eventhub-name', + 'RootManageSharedAccessKey', + 'policy-key', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity( + buildInstallConfig({ gatewayType: flowModule.AzureInstallMethods.GatewayDisplay, isDaEnabled: true, isTaEnabled: true }) + ); + + expect(result.isAzureEventHub).to.equal(false); + expect(result.apiManagementServiceName).to.equal('apim-name'); + expect(result.eventHubNamespace).to.equal('eventhub-namespace'); + expect(result.eventHubName).to.equal('eventhub-name'); + expect(result.policyKey).to.equal('policy-key'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(10); + }); + + it('collects DA-only prompts for AZURE_EVENTHUB and uses eventhub namespace prompt', async () => { + const askInputResponses = [ + 'tenant-id', + 'subscription-id', + 'sp-client-id', + 'sp-client-secret', + 'rg-name', + 'eventhub-namespace', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity( + buildInstallConfig({ gatewayType: engageTypes.GatewayTypes.AZURE_EVENTHUB, isDaEnabled: true, isTaEnabled: false }) + ); + + expect(result.isAzureEventHub).to.equal(true); + expect(result.apiManagementServiceName).to.equal(''); + expect(result.eventHubNamespace).to.equal('eventhub-namespace'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(6); + }); + + it('collects TA-only prompts and still asks discovery prompts needed for TA auth', async () => { + const askInputResponses = [ + 'eventhub-namespace', + 'eventhub-name', + 'RootManageSharedAccessKey', + 'policy-key', + 'tenant-id', + 'subscription-id', + 'sp-client-id', + 'sp-client-secret', + 'rg-name', + 'apim-name', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity( + buildInstallConfig({ gatewayType: flowModule.AzureInstallMethods.GatewayDisplay, isDaEnabled: false, isTaEnabled: true }) + ); + + expect(result.eventHubName).to.equal('eventhub-name'); + expect(result.policyName).to.equal('RootManageSharedAccessKey'); + expect(result.apiManagementServiceName).to.equal('apim-name'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(10); + }); + + it('wires API management service name validation into the discovery prompt', async () => { + const validator = () => true; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(validator); + + const promptConfigs = [ ]; + const askInputResponses = [ + 'tenant-id', + 'subscription-id', + 'sp-client-id', + 'sp-client-secret', + 'rg-name', + 'apim-name', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo((config) => { + promptConfigs.push(config); + return askInputResponses.shift(); + }); + + await flowModule.gatewayConnectivity( + buildInstallConfig({ gatewayType: flowModule.AzureInstallMethods.GatewayDisplay, isDaEnabled: true, isTaEnabled: false }) + ); + + const apimPrompt = promptConfigs.find((config) => config.msg === 'Enter the Azure API Management Service Name'); + expect(apimPrompt).to.exist; + expect(apimPrompt.validate).to.equal(validator); + expect(td.explain(promptStubs.validateRegex).callCount).to.equal(1); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes DA and TA templates when both bundles are enabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = { + tenantId: 'tenant-id', + subscriptionId: 'subscription-id', + servicePrincipalClientId: 'sp-client-id', + servicePrincipalClientSecret: 'sp-client-secret', + resourceGroupName: 'rg-name', + apiManagementServiceName: 'apim-name', + eventHubNamespace: 'eventhub-namespace', + eventHubName: 'eventhub-name', + policyName: 'RootManageSharedAccessKey', + policyKey: 'policy-key', + }; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(2); + const files = td.explain(utilsStubs.writeTemplates).calls.map((call) => call.args[0]); + expect(files).to.include(flowModule.ConfigFiles.DAEnvVars); + expect(files).to.include(flowModule.ConfigFiles.TAEnvVars); + }); + + it('writes only DA template when traceability is disabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: false }); + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.DAEnvVars); + }); + + it('writes only TA template when discovery is disabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: false, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.TAEnvVars); + }); + + it('logs service-account warning when dosa is new and install is not helm', async () => { + const logs = [ ]; + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: false, logSink: logs }); + installConfig.centralConfig.ampcDosaInfo.isNew = true; + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(logs.some((line) => line.includes('private_key.pem'))).to.equal(true); + }); + + it('stops writing templates when the first Azure template generation fails', async () => { + td.when(utilsStubs.writeTemplates(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())) + .thenDo(() => { + throw new Error('write failed'); + }); + + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('write failed'); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + }); + }); +}); + +function buildInstallConfig({ + gatewayType = 'Azure API Gateway', + isDaEnabled = true, + isTaEnabled = true, + logSink = null, +} = {}) { + const logs = logSink || [ ]; + return { + log: (msg) => logs.push(String(msg)), + gatewayType, + switches: { + isDockerInstall: true, + isHelmInstall: false, + isHostedInstall: false, + isDaEnabled, + isTaEnabled, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: false }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + daVersion: '1.2.3', + taVersion: '1.2.4', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-backstage.onprem.js b/test/lib/engage/services/install-service/on-prem/test-backstage.onprem.js new file mode 100644 index 00000000..b3c050f4 --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-backstage.onprem.js @@ -0,0 +1,201 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/backstageAgents.js`; + +describe('Backstage on-prem agent flow', () => { + let flowModule; + let promptStubs; + let utilsStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + }; + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeTemplates: td.func('writeTemplates'), + isWindows: false, + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('BackstageInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.BackstageInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('returns static discovery bundle and dockerized config type', async () => { + const bundleType = await flowModule.askBundleType(); + const configType = await flowModule.askConfigType(); + + expect(bundleType).to.equal('Discovery'); + expect(configType).to.exist; + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects static-token auth prompts', async () => { + const askInputResponses = [ 'backstage.local', '7007', '/api', 'static-token-value' ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ 'http', 'token' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig()); + + expect(result.host).to.equal('backstage.local'); + expect(result.scheme).to.equal('http'); + expect(result.backendPort).to.equal('7007'); + expect(result.urlPath).to.equal('/api'); + expect(result.authMode).to.equal('token'); + expect(result.staticTokenValue).to.equal('static-token-value'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(4); + expect(td.explain(promptStubs.askList).callCount).to.equal(2); + }); + + it('collects JWKS auth prompts', async () => { + const askInputResponses = [ 'backstage.local', '', '', 'jwks-client-id', 'jwks-client-secret', 'https://token.url' ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ 'https', 'jwks' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig()); + + expect(result.authMode).to.equal('jwks'); + expect(result.jwksClientID).to.equal('jwks-client-id'); + expect(result.jwksClientSecret).to.equal('jwks-client-secret'); + expect(result.jwksTokenURL).to.equal('https://token.url'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(6); + }); + + it('does not ask auth credential prompts for guest mode', async () => { + const askInputResponses = [ 'backstage.local', '', '' ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ 'https', 'guest' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig()); + + expect(result.authMode).to.equal('guest'); + expect(result.staticTokenValue).to.equal(''); + expect(result.jwksClientID).to.equal(''); + expect(td.explain(promptStubs.askInput).callCount).to.equal(3); + }); + + it('stops prompting when JWKS credential collection fails', async () => { + const askInputResponses = [ 'backstage.local', '', '', 'jwks-client-id' ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => { + if (askInputResponses.length > 0) { + return askInputResponses.shift(); + } + throw new Error('jwks failed'); + }); + + const askListResponses = [ 'https', 'jwks' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + let error; + try { + await flowModule.gatewayConnectivity(buildInstallConfig()); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('jwks failed'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(5); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes DA template output and logs service-account guidance', async () => { + const logs = [ ]; + const installConfig = buildInstallConfig({ logSink: logs }); + installConfig.gatewayConfig = { + host: 'backstage.local', + scheme: 'https', + authMode: 'NoAuth', + }; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.DAEnvVars); + expect(logs.some((line) => line.includes('private_key.pem'))).to.equal(true); + }); + + it('stops finalize when Backstage template generation fails', async () => { + td.when(utilsStubs.writeTemplates(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())) + .thenDo(() => { + throw new Error('write failed'); + }); + + const installConfig = buildInstallConfig(); + installConfig.gatewayConfig = { + host: 'backstage.local', + scheme: 'https', + authMode: 'NoAuth', + }; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('write failed'); + }); + }); +}); + +function buildInstallConfig({ logSink = null } = {}) { + const logs = logSink || [ ]; + return { + log: (msg) => logs.push(String(msg)), + switches: { + isDockerInstall: true, + isHelmInstall: false, + isHostedInstall: false, + isDaEnabled: true, + isTaEnabled: false, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: true }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + daVersion: '1.2.3', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-edge-gateway.onprem.js b/test/lib/engage/services/install-service/on-prem/test-edge-gateway.onprem.js new file mode 100644 index 00000000..2b686022 --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-edge-gateway.onprem.js @@ -0,0 +1,242 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const AGENTS_INDEX = `${distRoot}/lib/engage/utils/agents/index.js`; +const KUBECTL_MODULE = `${distRoot}/lib/engage/utils/agents/kubectl.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/edgeAgents.js`; + +describe('Edge Gateway on-prem agent flow', () => { + let flowModule; + let promptStubs; + let helpersStubs; + let kubectlStubs; + let utilsStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + askUsernameAndPassword: td.func('askUsernameAndPassword'), + }; + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realHelpers = await import(AGENTS_INDEX); + helpersStubs = { + ...realHelpers, + askNamespace: td.func('askNamespace'), + createNamespace: td.func('createNamespace'), + createSecret: td.func('createSecret'), + createAmplifyAgentKeysSecret: td.func('createAmplifyAgentKeysSecret'), + createGatewayAgentCredsSecret: td.func('createGatewayAgentCredsSecret'), + }; + await td.replaceEsm(AGENTS_INDEX, helpersStubs); + + kubectlStubs = { + isInstalled: td.func('kubectl.isInstalled'), + create: td.func('kubectl.create'), + }; + await td.replaceEsm(KUBECTL_MODULE, { kubectl: kubectlStubs }); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeTemplates: td.func('writeTemplates'), + isWindows: false, + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + td.when(kubectlStubs.isInstalled()).thenResolve({ error: null }); + td.when(promptStubs.askUsernameAndPassword(td.matchers.anything(), td.matchers.anything())).thenResolve({ username: 'user', password: 'pass' }); + td.when(helpersStubs.askNamespace(td.matchers.anything(), td.matchers.anything())).thenResolve({ name: 'amplify-agents', isNew: false }); + td.when(helpersStubs.createSecret(td.matchers.anything(), td.matchers.anything(), td.matchers.isA(Function))).thenDo((_ns, _secret, cb) => cb()); + td.when(helpersStubs.createAmplifyAgentKeysSecret(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything())).thenResolve(); + td.when(helpersStubs.createGatewayAgentCredsSecret(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything())).thenResolve(); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('EdgeInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.EdgeInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.InstallPreprocess).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('prompts deployment and bundle type in normal and gateway-only modes', async () => { + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('Dockerized'); + const configType = await flowModule.askConfigType(); + expect(configType).to.equal('Dockerized'); + + td.reset(); + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { ...realPrompts, askInput: td.func('askInput'), askList: td.func('askList'), askUsernameAndPassword: td.func('askUsernameAndPassword') }; + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + flowModule = await import(FLOW_MODULE); + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('Traceability offline mode'); + const gwOnlyBundle = await flowModule.askBundleTypeGWOnly(); + expect(gwOnlyBundle).to.equal('Traceability offline mode'); + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects manager/gateway credentials and event log path for docker TA install', async () => { + const askListResponses = [ 'Event' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + const askInputResponses = [ 'manager.local', '8075', 'gateway.local', '8090', '/apigateway/events/' ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDockerInstall: true, isHelmInstall: false, isTaEnabled: true, isDaEnabled: true })); + expect(result.apiManagerHost).to.equal('manager.local'); + expect(result.apiGatewayHost).to.equal('gateway.local'); + expect(result.eventLogPath).to.equal('/apigateway/events'); + expect(result.eventLogPathTemplate).to.equal(''); + }); + + it('requires kubectl in helm mode', async () => { + td.when(kubectlStubs.isInstalled()).thenResolve({ error: 'kubectl missing' }); + let error; + try { + await flowModule.gatewayConnectivity(buildInstallConfig({ isDockerInstall: false, isHelmInstall: true })); + } catch (err) { + error = err; + } + expect(error).to.be.instanceOf(Error); + expect(error.message).to.include('Kubectl is required'); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes helm overrides and creates secrets in helm mode', async () => { + const installConfig = buildInstallConfig({ isDockerInstall: false, isHelmInstall: true, isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = { + namespace: { name: 'amplify-agents', isNew: true }, + apiManagerAuthUser: 'user', + apiManagerAuthPass: 'pass', + apiGatewayAuthUser: 'gw-user', + apiGatewayAuthPass: 'gw-pass', + eventLogPath: '/apigateway/events', + }; + await flowModule.completeInstall(installConfig); + expect(td.explain(helpersStubs.createNamespace).callCount).to.equal(1); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(2); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(2); + }); + + it('writes DA/TA env files in docker mode', async () => { + const installConfig = buildInstallConfig({ isDockerInstall: true, isHelmInstall: false, isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = { eventLogPath: '/apigateway/events' }; + await flowModule.completeInstall(installConfig); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(2); + const files = td.explain(utilsStubs.writeTemplates).calls.map((call) => call.args[0]); + expect(files).to.include(flowModule.ConfigFiles.DAEnvVars); + expect(files).to.include(flowModule.ConfigFiles.TAEnvVars); + }); + + it('stops helm install when namespace creation fails', async () => { + td.when(helpersStubs.createNamespace(td.matchers.anything(), td.matchers.anything())) + .thenReject(new Error('namespace failed')); + + const installConfig = buildInstallConfig({ isDockerInstall: false, isHelmInstall: true, isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = { + namespace: { name: 'amplify-agents', isNew: true }, + apiManagerAuthUser: 'user', + apiManagerAuthPass: 'pass', + apiGatewayAuthUser: 'gw-user', + apiGatewayAuthPass: 'gw-pass', + eventLogPath: '/apigateway/events', + }; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('namespace failed'); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(0); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(0); + }); + + it('stops helm install when gateway credentials secret creation fails', async () => { + td.when(helpersStubs.createGatewayAgentCredsSecret( + td.matchers.anything(), + td.matchers.anything(), + td.matchers.anything(), + td.matchers.anything(), + td.matchers.anything(), + td.matchers.anything(), + td.matchers.anything() + )).thenReject(new Error('creds failed')); + + const installConfig = buildInstallConfig({ isDockerInstall: false, isHelmInstall: true, isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = { + namespace: { name: 'amplify-agents', isNew: false }, + apiManagerAuthUser: 'user', + apiManagerAuthPass: 'pass', + apiGatewayAuthUser: 'gw-user', + apiGatewayAuthPass: 'gw-pass', + eventLogPath: '/apigateway/events', + }; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('creds failed'); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(2); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(0); + }); + }); +}); + +function buildInstallConfig({ isDockerInstall = true, isHelmInstall = false, isDaEnabled = true, isTaEnabled = true } = {}) { + return { + log: () => {}, + switches: { + isDockerInstall, + isHelmInstall, + isBinaryInstall: false, + isHostedInstall: false, + isGatewayOnly: false, + isDaEnabled, + isTaEnabled, + }, + centralConfig: { + apiServerClient: { account: { auth: { tokens: { access_token: 'token' } } } }, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: true }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + bundleType: 'Traceability', + deploymentType: isHelmInstall ? 'Helm' : 'Dockerized', + daVersion: '1.2.3', + taVersion: '1.2.4', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-gitlab.onprem.js b/test/lib/engage/services/install-service/on-prem/test-gitlab.onprem.js new file mode 100644 index 00000000..f92281d5 --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-gitlab.onprem.js @@ -0,0 +1,229 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/gitLabAgents.js`; + +describe('GitLab on-prem agent flow', () => { + let flowModule; + let promptStubs; + let utilsStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + validateRegex: td.func('validateRegex'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeTemplates: td.func('writeTemplates'), + isWindows: false, + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('GitLabInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.GitLabInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.InstallPreprocess).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('returns static discovery bundle and dockerized config type', async () => { + const bundleType = await flowModule.askBundleType(); + const configType = await flowModule.askConfigType(); + + expect(bundleType).to.equal('Discovery'); + expect(configType).to.exist; + expect(td.explain(promptStubs.askList).callCount).to.equal(0); + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects repository details with multi-path and multi-filter loops in docker mode', async () => { + const askInputResponses = [ + 'gitlab-token', + 'https://gitlab.example.com', + '12345', + 'main', + '/apis/v1', + '/apis/v2', + '.*openapi.*', + '.*swagger.*', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ 'Yes', 'No', 'Yes', 'No' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDockerInstall: true, isDaEnabled: true })); + + expect(result.token).to.equal('gitlab-token'); + expect(result.baseURL).to.equal('https://gitlab.example.com'); + expect(result.repositoryID).to.equal('12345'); + expect(result.repositoryBranch).to.equal('main'); + expect(result.paths).to.deep.equal([ '/apis/v1', '/apis/v2' ]); + expect(result.filters).to.deep.equal([ '.*openapi.*', '.*swagger.*' ]); + expect(td.explain(promptStubs.askInput).callCount).to.equal(8); + expect(td.explain(promptStubs.askList).callCount).to.equal(4); + }); + + it('stops filter collection when first filter is empty', async () => { + const askInputResponses = [ + 'gitlab-token', + 'https://gitlab.example.com', + '12345', + 'main', + '/apis', + '', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ 'No' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDockerInstall: true, isDaEnabled: true })); + + expect(result.paths).to.deep.equal([ '/apis' ]); + expect(result.filters).to.deep.equal([ ]); + }); + + it('skips prompts when docker mode is disabled', async () => { + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDockerInstall: false, isDaEnabled: true })); + + expect(result.token).to.equal(''); + expect(result.baseURL).to.equal(''); + expect(result.paths).to.deep.equal([ ]); + expect(td.explain(promptStubs.askInput).callCount).to.equal(0); + }); + + it('wires token, base URL, repository ID, and path validators into the prompt config', async () => { + const validator = () => true; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(validator); + + const promptConfigs = [ ]; + const askInputResponses = [ + 'gitlab-token', + 'https://gitlab.example.com', + '12345', + 'main', + '/apis', + '', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo((config) => { + promptConfigs.push(config); + return askInputResponses.shift(); + }); + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('No'); + + await flowModule.gatewayConnectivity(buildInstallConfig({ isDockerInstall: true, isDaEnabled: true })); + + expect(promptConfigs.find((config) => config.msg === 'Enter the GitLab Access Token the agent will use').validate).to.equal(validator); + expect(promptConfigs.find((config) => config.msg === 'Enter the GitLab base URL that the agent will use').validate).to.equal(validator); + expect(promptConfigs.find((config) => config.msg === 'Enter the GitLab Repository ID the agent will use').validate).to.equal(validator); + expect(promptConfigs.find((config) => config.msg === 'Enter a Path within the repository that the agent will use').validate).to.equal(validator); + expect(td.explain(promptStubs.validateRegex).callCount).to.equal(4); + }); + }); + + describe('InstallPreprocess and FinalizeGatewayInstall', () => { + it('returns installConfig unchanged in installPreprocess', async () => { + const installConfig = buildInstallConfig({ isDockerInstall: true, isDaEnabled: true }); + const result = await flowModule.installPreprocess(installConfig); + expect(result).to.equal(installConfig); + }); + + it('writes DA env template only for docker discovery install', async () => { + const installConfig = buildInstallConfig({ isDockerInstall: true, isDaEnabled: true }); + installConfig.gatewayConfig = { + token: 'gitlab-token', + baseURL: 'https://gitlab.example.com', + repositoryID: '12345', + repositoryBranch: 'main', + paths: [ '/apis' ], + filters: [ '.*openapi.*' ], + }; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.DAEnvVars); + }); + + it('does not write templates when docker mode is disabled', async () => { + const installConfig = buildInstallConfig({ isDockerInstall: false, isDaEnabled: true }); + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(0); + }); + + it('stops finalize when GitLab template generation fails', async () => { + td.when(utilsStubs.writeTemplates(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())) + .thenDo(() => { + throw new Error('write failed'); + }); + + const installConfig = buildInstallConfig({ isDockerInstall: true, isDaEnabled: true }); + installConfig.gatewayConfig = {}; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('write failed'); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + }); + }); +}); + +function buildInstallConfig({ isDockerInstall = true, isDaEnabled = true } = {}) { + return { + log: () => {}, + switches: { + isDockerInstall, + isHelmInstall: false, + isHostedInstall: false, + isDaEnabled, + isTaEnabled: false, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: false }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: 'DOCKERIZED', + daVersion: '1.2.3', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-graylog.onprem.js b/test/lib/engage/services/install-service/on-prem/test-graylog.onprem.js new file mode 100644 index 00000000..070e1ef7 --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-graylog.onprem.js @@ -0,0 +1,211 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const AGENTS_INDEX = `${distRoot}/lib/engage/utils/agents/index.js`; +const KUBECTL_MODULE = `${distRoot}/lib/engage/utils/agents/kubectl.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/graylogAgents.js`; + +describe('Graylog on-prem agent flow', () => { + let flowModule; + let promptStubs; + let helpersStubs; + let kubectlStubs; + let utilsStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + validateRegex: td.func('validateRegex'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realHelpers = await import(AGENTS_INDEX); + helpersStubs = { + ...realHelpers, + askNamespace: td.func('askNamespace'), + createNamespace: td.func('createNamespace'), + createSecret: td.func('createSecret'), + createAmplifyAgentKeysSecret: td.func('createAmplifyAgentKeysSecret'), + }; + await td.replaceEsm(AGENTS_INDEX, helpersStubs); + + kubectlStubs = { + isInstalled: td.func('kubectl.isInstalled'), + create: td.func('kubectl.create'), + }; + await td.replaceEsm(KUBECTL_MODULE, { kubectl: kubectlStubs }); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeTemplates: td.func('writeTemplates'), + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + td.when(kubectlStubs.isInstalled()).thenResolve({ error: null }); + td.when(kubectlStubs.create(td.matchers.anything(), td.matchers.anything())).thenResolve({ error: null }); + td.when(helpersStubs.askNamespace(td.matchers.anything(), td.matchers.anything())).thenResolve({ name: 'amplify-agents', isNew: false }); + td.when(helpersStubs.createSecret(td.matchers.anything(), td.matchers.anything(), td.matchers.isA(Function))).thenDo((_ns, _secret, cb) => cb()); + td.when(helpersStubs.createAmplifyAgentKeysSecret(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve(); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('GraylogInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.GraylogInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('returns static traceability bundle and helm deployment type', async () => { + const bundleType = await flowModule.askBundleType(); + const configType = await flowModule.askConfigType(); + expect(bundleType).to.equal('Traceability'); + expect(configType).to.equal('Helm'); + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects namespace and graylog credentials when kubectl is available', async () => { + const askInputResponses = [ 'https://graylog.example.com', 'graylog-user', 'graylog-pass', 3 ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig()); + + expect(result.namespace.name).to.equal('amplify-agents'); + expect(result.url).to.equal('https://graylog.example.com'); + expect(result.userName).to.equal('graylog-user'); + expect(result.password).to.equal('graylog-pass'); + expect(result.basePathSegmentLen).to.equal(3); + expect(td.explain(kubectlStubs.isInstalled).callCount).to.equal(1); + }); + + it('throws when kubectl is not installed', async () => { + td.when(kubectlStubs.isInstalled()).thenResolve({ error: 'kubectl missing' }); + + let error; + try { + await flowModule.gatewayConnectivity(buildInstallConfig()); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.include('Kubectl is required'); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes helm override and creates required secrets', async () => { + const installConfig = buildInstallConfig(); + installConfig.gatewayConfig = { + namespace: { name: 'amplify-agents', isNew: true }, + url: 'https://graylog.example.com', + userName: 'graylog-user', + password: 'graylog-pass', + basePathSegmentLen: 2, + }; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(helpersStubs.createNamespace).callCount).to.equal(1); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(2); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.helmOverride); + }); + + it('stops helm install when namespace creation fails', async () => { + td.when(helpersStubs.createNamespace(td.matchers.anything(), td.matchers.anything())) + .thenReject(new Error('namespace failed')); + + const installConfig = buildInstallConfig(); + installConfig.gatewayConfig = { + namespace: { name: 'amplify-agents', isNew: true }, + url: 'https://graylog.example.com', + userName: 'graylog-user', + password: 'graylog-pass', + basePathSegmentLen: 2, + }; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('namespace failed'); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(0); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(0); + }); + + it('stops helm install when Graylog credentials secret creation fails', async () => { + td.when(kubectlStubs.create(td.matchers.anything(), td.matchers.anything())) + .thenResolve({ data: [ ], error: 'secret failed' }); + + const installConfig = buildInstallConfig(); + installConfig.gatewayConfig = { + namespace: { name: 'amplify-agents', isNew: false }, + url: 'https://graylog.example.com', + userName: 'graylog-user', + password: 'graylog-pass', + basePathSegmentLen: 2, + }; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('secret failed'); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(2); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(0); + }); + }); +}); + +function buildInstallConfig() { + return { + log: () => {}, + switches: { + isDockerInstall: false, + isHelmInstall: true, + isHostedInstall: false, + isDaEnabled: false, + isTaEnabled: true, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: true }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: 'Helm', + taVersion: '1.2.4', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-ibm-api-connect.onprem.js b/test/lib/engage/services/install-service/on-prem/test-ibm-api-connect.onprem.js new file mode 100644 index 00000000..ae64a00c --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-ibm-api-connect.onprem.js @@ -0,0 +1,200 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/ibmAPIConnetAgents.js`; + +describe('IBM API Connect on-prem agent flow', () => { + let flowModule; + let promptStubs; + let utilsStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + }; + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeTemplates: td.func('writeTemplates'), + isWindows: false, + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('IBMAPIConnectInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.IBMAPIConnectInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('prompts bundle type and returns dockerized config type', async () => { + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('All Agents'); + + const bundleType = await flowModule.askBundleType(); + const configType = await flowModule.askConfigType(); + + expect(bundleType).to.equal('All Agents'); + expect(configType).to.exist; + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects common + discovery + traceability prompts when DA and TA are enabled', async () => { + const askInputResponses = [ + 'https://ibm.example.com', + 'my-org', + 'my-catalog', + 'api-key', + 'client-id', + 'client-secret', + 'owner-user', + 'owner-registry', + 'analytics-server', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: true })); + + expect(result.apiConnectURL).to.equal('https://ibm.example.com'); + expect(result.apiConnectOrgName).to.equal('my-org'); + expect(result.apiConnectCatalogName).to.equal('my-catalog'); + expect(result.apiConnectConsumerOrgOwnerUser).to.equal('owner-user'); + expect(result.apiConnectAnalyticsServerName).to.equal('analytics-server'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(9); + }); + + it('collects only common + traceability prompts when DA is disabled', async () => { + const askInputResponses = [ + 'https://ibm.example.com', + 'my-org', + 'my-catalog', + 'api-key', + 'client-id', + 'client-secret', + 'analytics-server', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: false, isTaEnabled: true })); + + expect(result.apiConnectConsumerOrgOwnerUser).to.equal(''); + expect(result.apiConnectAnalyticsServerName).to.equal('analytics-server'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(7); + }); + + it('stops prompting when IBM API Connect credential collection fails', async () => { + const askInputResponses = [ + 'https://ibm.example.com', + 'my-org', + 'my-catalog', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => { + if (askInputResponses.length > 0) { + return askInputResponses.shift(); + } + throw new Error('credential failed'); + }); + + let error; + try { + await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: true })); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('credential failed'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(4); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes DA and TA templates when both enabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(2); + const files = td.explain(utilsStubs.writeTemplates).calls.map((call) => call.args[0]); + expect(files).to.include(flowModule.ConfigFiles.DAEnvVars); + expect(files).to.include(flowModule.ConfigFiles.TAEnvVars); + }); + + it('writes only TA template when DA is disabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: false, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.TAEnvVars); + }); + + it('stops finalize when IBM API Connect template generation fails', async () => { + td.when(utilsStubs.writeTemplates(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())) + .thenDo(() => { + throw new Error('write failed'); + }); + + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('write failed'); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + }); + }); +}); + +function buildInstallConfig({ isDaEnabled = true, isTaEnabled = true } = {}) { + return { + log: () => {}, + switches: { + isDockerInstall: true, + isHelmInstall: false, + isHostedInstall: false, + isDaEnabled, + isTaEnabled, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: false }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + daVersion: '1.2.3', + taVersion: '1.2.4', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-istio.onprem.js b/test/lib/engage/services/install-service/on-prem/test-istio.onprem.js new file mode 100644 index 00000000..db7bcd9e --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-istio.onprem.js @@ -0,0 +1,257 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const AGENTS_INDEX = `${distRoot}/lib/engage/utils/agents/index.js`; +const KUBECTL_MODULE = `${distRoot}/lib/engage/utils/agents/kubectl.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/istioAgents.js`; + +describe('Istio on-prem agent flow', () => { + let flowModule; + let promptStubs; + let helpersStubs; + let kubectlStubs; + let utilsStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + validateRegex: td.func('validateRegex'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realHelpers = await import(AGENTS_INDEX); + helpersStubs = { + ...realHelpers, + askNamespace: td.func('askNamespace'), + askClusterName: td.func('askClusterName'), + createNamespace: td.func('createNamespace'), + createSecret: td.func('createSecret'), + createAmplifyAgentKeysSecret: td.func('createAmplifyAgentKeysSecret'), + }; + await td.replaceEsm(AGENTS_INDEX, helpersStubs); + + kubectlStubs = { + isInstalled: td.func('kubectl.isInstalled'), + create: td.func('kubectl.create'), + get: td.func('kubectl.get'), + }; + await td.replaceEsm(KUBECTL_MODULE, { kubectl: kubectlStubs }); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeTemplates: td.func('writeTemplates'), + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + td.when(kubectlStubs.isInstalled()).thenResolve({ error: null }); + td.when(kubectlStubs.get('namespaces')).thenResolve({ data: [ 'default', 'istio-system' ], error: null }); + td.when(kubectlStubs.get('ns')).thenResolve({ data: [ 'default', 'ampc-demo' ], error: null }); + td.when(kubectlStubs.get('secrets', td.matchers.anything())).thenResolve({ data: [], error: '' }); + td.when(kubectlStubs.create(td.matchers.anything(), td.matchers.anything())).thenResolve({ data: [ 'created' ], error: null }); + td.when(helpersStubs.askNamespace(td.matchers.anything(), td.matchers.anything())).thenResolve({ name: 'amplify-agents', isNew: false }); + td.when(helpersStubs.askClusterName()).thenResolve('cluster-1'); + td.when(helpersStubs.createSecret(td.matchers.anything(), td.matchers.anything(), td.matchers.isA(Function))).thenDo((_ns, _secret, cb) => cb()); + td.when(helpersStubs.createAmplifyAgentKeysSecret(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything())).thenResolve(); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('IstioInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.IstioInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.InstallPreprocess).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('prompts bundle type and returns dockerized config type', async () => { + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('All Agents'); + const bundleType = await flowModule.askBundleType(); + const configType = await flowModule.askConfigType(); + expect(bundleType).to.equal('All Agents'); + expect(configType).to.equal('Dockerized'); + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects existing-istio and kubernetes setup prompts', async () => { + const askListResponses = [ 'Yes', 'default', 'Ambient', 'default', 'No' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: true })); + expect(result.istioInstallValues.isNewInstall).to.equal(false); + expect(result.istioInstallValues.envoyFilterNamespace).to.equal('default'); + expect(result.istioAgentValues.namespace.name).to.equal('amplify-agents'); + expect(result.istioAgentValues.clusterName).to.equal('cluster-1'); + }); + + it('throws when kubectl is unavailable', async () => { + td.when(kubectlStubs.isInstalled()).thenResolve({ error: 'kubectl missing' }); + let error; + try { + await flowModule.gatewayConnectivity(buildInstallConfig()); + } catch (err) { + error = err; + } + expect(error).to.be.instanceOf(Error); + expect(error.message).to.include('Kubectl is required'); + }); + + it('throws when kubernetes namespace lookup fails during discovery setup', async () => { + td.when(kubectlStubs.get('ns')).thenResolve({ data: [ ], error: 'namespace query failed' }); + + const askListResponses = [ 'Yes', 'default', 'Ambient' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + let error; + try { + await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: true })); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('namespace query failed'); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('creates namespace/secret and writes istio + hybrid override files', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = { + istioInstallValues: { + isNewInstall: false, + protocol: 'http', + envoyFilterNamespace: 'default', + }, + istioAgentValues: { + namespace: { name: 'amplify-agents', isNew: true }, + alsEnabled: true, + discoveryEnabled: true, + discoveryNamespaces: [ 'default' ], + clusterName: 'cluster-1', + }, + }; + await flowModule.completeInstall(installConfig); + expect(td.explain(helpersStubs.createNamespace).callCount).to.equal(1); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(2); + }); + + it('stops finalize when namespace creation fails', async () => { + td.when(helpersStubs.createNamespace(td.matchers.anything(), td.matchers.anything())) + .thenReject(new Error('namespace failed')); + + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = { + istioInstallValues: { + isNewInstall: false, + protocol: 'http', + envoyFilterNamespace: 'default', + }, + istioAgentValues: { + namespace: { name: 'amplify-agents', isNew: true }, + alsEnabled: true, + discoveryEnabled: true, + discoveryNamespaces: [ 'default' ], + clusterName: 'cluster-1', + }, + }; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('namespace failed'); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(0); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(0); + }); + + it('stops finalize when Amplify key secret creation fails', async () => { + td.when(helpersStubs.createAmplifyAgentKeysSecret( + td.matchers.anything(), + td.matchers.anything(), + td.matchers.anything(), + td.matchers.anything(), + td.matchers.anything(), + td.matchers.anything(), + td.matchers.anything() + )).thenReject(new Error('keys failed')); + + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = { + istioInstallValues: { + isNewInstall: false, + protocol: 'http', + envoyFilterNamespace: 'default', + }, + istioAgentValues: { + namespace: { name: 'amplify-agents', isNew: false }, + alsEnabled: true, + discoveryEnabled: true, + discoveryNamespaces: [ 'default' ], + clusterName: 'cluster-1', + }, + }; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('keys failed'); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(0); + }); + }); +}); + +function buildInstallConfig({ isDaEnabled = true, isTaEnabled = true } = {}) { + return { + log: () => {}, + switches: { + isDockerInstall: true, + isHelmInstall: false, + isHostedInstall: false, + isDaEnabled, + isTaEnabled, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: true }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: 'Dockerized', + daVersion: '1.2.3', + taVersion: '1.2.4', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-kafka.onprem.js b/test/lib/engage/services/install-service/on-prem/test-kafka.onprem.js new file mode 100644 index 00000000..e78f1dfd --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-kafka.onprem.js @@ -0,0 +1,213 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/kafkaAgents.js`; + +describe('Kafka on-prem agent flow', () => { + let flowModule; + let promptStubs; + let utilsStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + validateRegex: td.func('validateRegex'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeTemplates: td.func('writeTemplates'), + isWindows: false, + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('KafkaInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.KafkaInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('prompts bundle type and returns dockerized config type', async () => { + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('All Agents'); + const bundleType = await flowModule.askBundleType(); + const configType = await flowModule.askConfigType(); + expect(bundleType).to.equal('All Agents'); + expect(configType).to.equal('Dockerized'); + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects confluent cloud prompts and schema registry keys', async () => { + const askListResponses = [ 'Confluent Cloud' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const askInputResponses = [ + 'env-id', + 'cluster-id', + 'cloud-key', + 'cloud-secret', + 'cluster-key', + 'cluster-secret', + 'schema-key', + 'schema-secret', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true })); + expect(result.cloudEnabled).to.equal(true); + expect(result.cloudEnvironmentId).to.equal('env-id'); + expect(result.cloudClusterId).to.equal('cluster-id'); + expect(result.schemaRegistryAPIKey).to.equal('schema-key'); + }); + + it('collects confluent platform prompts with oauth sasl and schema registry auth', async () => { + const askListResponses = [ + 'Confluent Platform', + 'OAUTHBEARER', + 'Yes', + 'Yes', + ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const askInputResponses = [ + 'SASL_SSL://kafka.example.com:9092', + 'https://oauth.token.url', + 'oauth-client-id', + 'oauth-client-secret', + 'scope1,scope2', + 'https://schema-registry.example.com', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true })); + expect(result.cloudEnabled).to.equal(false); + expect(result.clusterServer).to.equal('SASL_SSL://kafka.example.com:9092'); + expect(result.clusterSaslMechanism).to.equal('OAUTHBEARER'); + expect(result.saslOauthTokenUrl).to.equal('https://oauth.token.url'); + expect(result.schemaRegistryEnabled).to.equal(true); + expect(result.schemaRegistryAuthEnabled).to.equal(true); + }); + + it('wires bootstrap server, oauth token URL, and schema registry URL validators into the prompt config', async () => { + const validator = () => true; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(validator); + + const promptConfigs = [ ]; + const askListResponses = [ + 'Confluent Platform', + 'OAUTHBEARER', + 'Yes', + 'Yes', + ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + const askInputResponses = [ + 'SASL_SSL://kafka.example.com:9092', + 'https://oauth.token.url', + 'oauth-client-id', + 'oauth-client-secret', + 'scope1,scope2', + 'https://schema-registry.example.com', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo((config) => { + promptConfigs.push(config); + return askInputResponses.shift(); + }); + + await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true })); + + expect(promptConfigs.find((config) => config.msg === 'Enter the Bootstrap Server Name').validate).to.equal(validator); + expect(promptConfigs.find((config) => config.msg === 'Enter the SASL/OAUTHBEARER Token Url').validate).to.equal(validator); + expect(promptConfigs.find((config) => config.msg === 'Enter the Schema Registry Url').validate).to.equal(validator); + expect(td.explain(promptStubs.validateRegex).callCount).to.equal(3); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes DA and TA templates when both are enabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + await flowModule.completeInstall(installConfig); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(2); + const files = td.explain(utilsStubs.writeTemplates).calls.map((call) => call.args[0]); + expect(files).to.include(flowModule.ConfigFiles.DAEnvVars); + expect(files).to.include(flowModule.ConfigFiles.TAEnvVars); + }); + + it('writes only TA template when DA is disabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: false, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + await flowModule.completeInstall(installConfig); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.TAEnvVars); + }); + + it('stops finalize when Kafka template generation fails', async () => { + td.when(utilsStubs.writeTemplates(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())) + .thenDo(() => { + throw new Error('write failed'); + }); + + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('write failed'); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + }); + }); +}); + +function buildInstallConfig({ isDaEnabled = true, isTaEnabled = true } = {}) { + return { + log: () => {}, + switches: { + isDockerInstall: true, + isHelmInstall: false, + isHostedInstall: false, + isDaEnabled, + isTaEnabled, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: false }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: 'Dockerized', + daVersion: '1.2.3', + taVersion: '1.2.4', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-sap-api-portal.onprem.js b/test/lib/engage/services/install-service/on-prem/test-sap-api-portal.onprem.js new file mode 100644 index 00000000..7bddc73b --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-sap-api-portal.onprem.js @@ -0,0 +1,199 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/sapApiPortalAgents.js`; + +describe('SAP API Portal on-prem agent flow', () => { + let flowModule; + let promptStubs; + let utilsStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + }; + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeTemplates: td.func('writeTemplates'), + isWindows: false, + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('SAPAPIPortalInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.SAPAPIPortalInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('prompts bundle type and returns dockerized config type', async () => { + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('All Agents'); + + const bundleType = await flowModule.askBundleType(); + const configType = await flowModule.askConfigType(); + + expect(bundleType).to.equal('All Agents'); + expect(configType).to.exist; + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects common and discovery prompts when DA is enabled', async () => { + const askInputResponses = [ + 'https://sap.token.url', + 'https://sap-api.example.com', + 'api-client-id', + 'api-client-secret', + 'https://sap-dev.example.com', + 'dev-client-id', + 'dev-client-secret', + 'dev@example.com', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('Yes'); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: true })); + + expect(result.authTokenURL).to.equal('https://sap.token.url'); + expect(result.authAPIPortalBaseURL).to.equal('https://sap-api.example.com'); + expect(result.authDevPortalBaseURL).to.equal('https://sap-dev.example.com'); + expect(result.developerEmail).to.equal('dev@example.com'); + expect(result.specCreateUnstructuredAPI).to.equal(true); + }); + + it('collects only common prompts when DA is disabled', async () => { + const askInputResponses = [ + 'https://sap.token.url', + 'https://sap-api.example.com', + 'api-client-id', + 'api-client-secret', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: false, isTaEnabled: true })); + + expect(result.authTokenURL).to.equal('https://sap.token.url'); + expect(result.authAPIPortalClientID).to.equal('api-client-id'); + expect(result.authDevPortalBaseURL).to.equal(''); + expect(td.explain(promptStubs.askList).callCount).to.equal(0); + }); + + it('stops prompting when SAP API Portal discovery credential collection fails', async () => { + const askInputResponses = [ + 'https://sap.token.url', + 'https://sap-api.example.com', + 'api-client-id', + 'api-client-secret', + 'https://sap-dev.example.com', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => { + if (askInputResponses.length > 0) { + return askInputResponses.shift(); + } + throw new Error('credential failed'); + }); + + let error; + try { + await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: true })); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('credential failed'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(6); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes DA and TA templates when both are enabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(2); + const files = td.explain(utilsStubs.writeTemplates).calls.map((call) => call.args[0]); + expect(files).to.include(flowModule.ConfigFiles.DAEnvVars); + expect(files).to.include(flowModule.ConfigFiles.TAEnvVars); + }); + + it('writes only TA template when DA is disabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: false, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.TAEnvVars); + }); + + it('stops finalize when SAP API Portal template generation fails', async () => { + td.when(utilsStubs.writeTemplates(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())) + .thenDo(() => { + throw new Error('write failed'); + }); + + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('write failed'); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + }); + }); +}); + +function buildInstallConfig({ isDaEnabled = true, isTaEnabled = true } = {}) { + return { + log: () => {}, + switches: { + isDockerInstall: true, + isHelmInstall: false, + isHostedInstall: false, + isDaEnabled, + isTaEnabled, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: false }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + daVersion: '1.2.3', + taVersion: '1.2.4', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-sensedia.onprem.js b/test/lib/engage/services/install-service/on-prem/test-sensedia.onprem.js new file mode 100644 index 00000000..0c8f20e1 --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-sensedia.onprem.js @@ -0,0 +1,205 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/sensediaAgents.js`; + +describe('Sensedia on-prem agent flow', () => { + let flowModule; + let promptStubs; + let utilsStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + validateRegex: td.func('validateRegex'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeTemplates: td.func('writeTemplates'), + isWindows: false, + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('SensediaInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.SensediaInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('prompts bundle type for sensedia gateway and defaults to discovery for others', async () => { + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('All Agents'); + + const sensediaBundle = await flowModule.askBundleType(flowModule.SensediaInstallMethods.GatewayDisplay); + const otherBundle = await flowModule.askBundleType('Some Other Gateway'); + + expect(sensediaBundle).to.equal('All Agents'); + expect(otherBundle).to.equal('Discovery'); + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects OAuth auth details and environment loop entries', async () => { + const askInputResponses = [ + 'https://sensedia.example.com', + 'client-id', + 'client-secret', + 'dev@example.com', + 'prod', + 'stage', + '', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ 'OAuth Client ID and Client Secret', 'Yes' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: true })); + + expect(result.baseUrl).to.equal('https://sensedia.example.com'); + expect(result.authType).to.equal('OAuth Client ID and Client Secret'); + expect(result.clientId).to.equal('client-id'); + expect(result.clientSecret).to.equal('client-secret'); + expect(result.authToken).to.equal(''); + expect(result.environments).to.deep.equal([ 'prod', 'stage' ]); + }); + + it('collects static token auth when DA is enabled', async () => { + const askInputResponses = [ 'https://sensedia.example.com', 'static-token', 'dev@example.com' ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ 'Static Token', 'No' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const daResult = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: false })); + expect(daResult.authType).to.equal('Static Token'); + expect(daResult.authToken).to.equal('static-token'); + }); + + it('skips sensedia-specific prompts when DA is disabled', async () => { + const taOnlyResult = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: false, isTaEnabled: true })); + expect(taOnlyResult.baseUrl).to.equal(''); + expect(td.explain(promptStubs.askInput).callCount).to.equal(0); + }); + + it('wires Sensedia validators into base URL, email, and environment prompts', async () => { + const validator = () => true; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(validator); + + const promptConfigs = [ ]; + const askInputResponses = [ + 'https://sensedia.example.com', + 'client-id', + 'client-secret', + 'dev@example.com', + 'prod', + '', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo((config) => { + promptConfigs.push(config); + return askInputResponses.shift(); + }); + + const askListResponses = [ 'OAuth Client ID and Client Secret', 'Yes' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: true })); + + expect(promptConfigs.find((config) => config.msg === 'Enter the Sensedia Base URL').validate).to.equal(validator); + expect(promptConfigs.find((config) => config.msg === 'Enter the Developer Email').validate).to.equal(validator); + expect(promptConfigs.find((config) => config.msg === 'Enter an environment name (or press Enter to finish)').validate).to.equal(validator); + expect(td.explain(promptStubs.validateRegex).callCount).to.equal(4); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes DA and TA templates when both enabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(2); + const files = td.explain(utilsStubs.writeTemplates).calls.map((call) => call.args[0]); + expect(files).to.include(flowModule.ConfigFiles.DAEnvVars); + expect(files).to.include(flowModule.ConfigFiles.TAEnvVars); + }); + + it('writes only TA template when DA is disabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: false, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.TAEnvVars); + }); + + it('stops finalize when Sensedia template generation fails', async () => { + td.when(utilsStubs.writeTemplates(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())) + .thenDo(() => { + throw new Error('write failed'); + }); + + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('write failed'); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + }); + }); +}); + +function buildInstallConfig({ isDaEnabled = true, isTaEnabled = true } = {}) { + return { + log: () => {}, + switches: { + isDockerInstall: true, + isHelmInstall: false, + isHostedInstall: false, + isDaEnabled, + isTaEnabled, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: false }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + daVersion: '1.2.3', + taVersion: '1.2.4', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-softwareag-webmethods.onprem.js b/test/lib/engage/services/install-service/on-prem/test-softwareag-webmethods.onprem.js new file mode 100644 index 00000000..837800a4 --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-softwareag-webmethods.onprem.js @@ -0,0 +1,229 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/softwareAGWebMethodAgents.js`; + +describe('Software AG WebMethods on-prem agent flow', () => { + let flowModule; + let promptStubs; + let utilsStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + }; + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeTemplates: td.func('writeTemplates'), + isWindows: false, + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('SoftwareAGWebMethodsInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.SoftwareAGWebMethodsInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('prompts for bundle type and returns dockerized config type', async () => { + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('All Agents'); + + const bundleType = await flowModule.askBundleType(); + const configType = await flowModule.askConfigType(); + + expect(bundleType).to.equal('All Agents'); + expect(configType).to.exist; + expect(td.explain(promptStubs.askList).callCount).to.equal(1); + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects common prompts when either DA or TA is enabled', async () => { + const askInputResponses = [ + 'https://webmethods.example.com', + 'wm-user', + 'wm-pass', + 'custom-auth', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: false, isTaEnabled: true })); + + expect(result.pathURL).to.equal('https://webmethods.example.com'); + expect(result.pathUsername).to.equal('wm-user'); + expect(result.pathPassword).to.equal('wm-pass'); + expect(result.pathOauth2Server).to.equal('custom-auth'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(4); + }); + + it('defaults oauth2 server to local when prompt returns empty', async () => { + const askInputResponses = [ + 'https://webmethods.example.com', + 'wm-user', + 'wm-pass', + ' ', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: false })); + + expect(result.pathOauth2Server).to.equal('local'); + }); + + it('skips prompts when both DA and TA are disabled', async () => { + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: false, isTaEnabled: false })); + + expect(result.pathURL).to.equal(''); + expect(result.pathUsername).to.equal(''); + expect(result.pathPassword).to.equal(''); + expect(td.explain(promptStubs.askInput).callCount).to.equal(0); + }); + + it('stops prompting when Software AG WebMethods credential collection fails', async () => { + const askInputResponses = [ + 'https://webmethods.example.com', + 'wm-user', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => { + if (askInputResponses.length > 0) { + return askInputResponses.shift(); + } + throw new Error('credential failed'); + }); + + let error; + try { + await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: true })); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('credential failed'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(3); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes DA and TA templates when both are enabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = { + pathURL: 'https://webmethods.example.com', + pathUsername: 'wm-user', + pathPassword: 'wm-pass', + pathOauth2Server: 'local', + }; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(2); + const files = td.explain(utilsStubs.writeTemplates).calls.map((call) => call.args[0]); + expect(files).to.include(flowModule.ConfigFiles.DAEnvVars); + expect(files).to.include(flowModule.ConfigFiles.TAEnvVars); + }); + + it('writes only DA template when TA is disabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: false }); + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.DAEnvVars); + }); + + it('writes only TA template when DA is disabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: false, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.TAEnvVars); + }); + + it('logs service-account warning when dosa is new', async () => { + const logs = [ ]; + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: false, logSink: logs }); + installConfig.centralConfig.ampcDosaInfo.isNew = true; + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(logs.some((line) => line.includes('private_key.pem'))).to.equal(true); + }); + + it('stops finalize when Software AG template generation fails', async () => { + td.when(utilsStubs.writeTemplates(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())) + .thenDo(() => { + throw new Error('write failed'); + }); + + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('write failed'); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + }); + }); +}); + +function buildInstallConfig({ + isDaEnabled = true, + isTaEnabled = true, + logSink = null, +} = {}) { + const logs = logSink || [ ]; + return { + log: (msg) => logs.push(String(msg)), + switches: { + isDockerInstall: true, + isHelmInstall: false, + isHostedInstall: false, + isDaEnabled, + isTaEnabled, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: false }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + daVersion: '1.2.3', + taVersion: '1.2.4', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-traceable.onprem.js b/test/lib/engage/services/install-service/on-prem/test-traceable.onprem.js new file mode 100644 index 00000000..adc0874b --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-traceable.onprem.js @@ -0,0 +1,246 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const AGENTS_INDEX = `${distRoot}/lib/engage/utils/agents/index.js`; +const KUBECTL_MODULE = `${distRoot}/lib/engage/utils/agents/kubectl.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/traceableAgents.js`; + +describe('Traceable on-prem agent flow', () => { + let flowModule; + let promptStubs; + let helpersStubs; + let kubectlStubs; + let utilsStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + }; + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realHelpers = await import(AGENTS_INDEX); + helpersStubs = { + ...realHelpers, + askNamespace: td.func('askNamespace'), + getCentralEnvironments: td.func('getCentralEnvironments'), + createNamespace: td.func('createNamespace'), + createSecret: td.func('createSecret'), + createAmplifyAgentKeysSecret: td.func('createAmplifyAgentKeysSecret'), + }; + await td.replaceEsm(AGENTS_INDEX, helpersStubs); + + kubectlStubs = { + isInstalled: td.func('kubectl.isInstalled'), + create: td.func('kubectl.create'), + }; + await td.replaceEsm(KUBECTL_MODULE, { kubectl: kubectlStubs }); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeTemplates: td.func('writeTemplates'), + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + td.when(kubectlStubs.isInstalled()).thenResolve({ error: null }); + td.when(kubectlStubs.create(td.matchers.anything(), td.matchers.anything())).thenResolve({ error: null }); + td.when(helpersStubs.askNamespace(td.matchers.anything(), td.matchers.anything())).thenResolve({ name: 'amplify-agents', isNew: false }); + td.when(helpersStubs.getCentralEnvironments(td.matchers.anything(), td.matchers.anything())).thenResolve([ + { name: 'dev' }, + { name: 'qa' }, + { name: 'prod' }, + ]); + td.when(helpersStubs.createSecret(td.matchers.anything(), td.matchers.anything(), td.matchers.isA(Function))).thenDo((_ns, _secret, cb) => cb()); + td.when(helpersStubs.createAmplifyAgentKeysSecret(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve(); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('TraceableInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.TraceableInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('returns static traceability bundle and prompts deployment type', async () => { + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('Dockerized'); + const bundleType = await flowModule.askBundleType(); + const configType = await flowModule.askConfigType(); + expect(bundleType).to.equal('Traceability'); + expect(configType).to.equal('Dockerized'); + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects docker-mode prompts and environment mappings', async () => { + const askInputResponses = [ 'traceable-token', 'traceable-dev', 'traceable-qa' ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ 'us', 'dev', 'Yes', 'qa', 'No' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDockerInstall: true, isHelmInstall: false })); + expect(result.traceableToken).to.equal('traceable-token'); + expect(result.traceableRegion).to.equal('us'); + expect(result.environments).to.deep.equal([ 'traceable-dev', 'traceable-qa' ]); + expect(result.centralEnvironments).to.deep.equal([ 'dev', 'qa' ]); + }); + + it('collects helm namespace and checks kubectl', async () => { + const askInputResponses = [ 'traceable-token', '' ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + const askListResponses = [ 'us', 'dev', 'No' ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDockerInstall: false, isHelmInstall: true })); + expect(result.namespace.name).to.equal('amplify-agents'); + expect(td.explain(kubectlStubs.isInstalled).callCount).to.equal(1); + }); + + it('exits when no central environments are available for mapping', async () => { + td.when(helpersStubs.getCentralEnvironments(td.matchers.anything(), td.matchers.anything())).thenResolve([ + { name: 'installed-env' }, + ]); + const askInputResponses = [ 'traceable-token' ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('us'); + + const originalExit = process.exit; + process.exit = ((code) => { + throw new Error(`process.exit(${code})`); + }); + + try { + await flowModule.gatewayConnectivity(buildInstallConfig({ isDockerInstall: true, isHelmInstall: false })); + throw new Error('Expected gatewayConnectivity to exit'); + } catch (err) { + expect(err.message).to.equal('process.exit(1)'); + } finally { + process.exit = originalExit; + } + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes docker env vars in docker mode', async () => { + const installConfig = buildInstallConfig({ isDockerInstall: true, isHelmInstall: false }); + installConfig.gatewayConfig = { + traceableToken: 'traceable-token', + environments: [ 'traceable-dev' ], + centralEnvironments: [ 'dev' ], + }; + + await flowModule.completeInstall(installConfig); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.agentEnvVars); + }); + + it('writes helm override and creates secrets in helm mode', async () => { + const installConfig = buildInstallConfig({ isDockerInstall: false, isHelmInstall: true }); + installConfig.gatewayConfig = { + namespace: { name: 'amplify-agents', isNew: true }, + traceableToken: 'traceable-token', + environments: [ 'traceable-dev' ], + centralEnvironments: [ 'dev' ], + }; + + await flowModule.completeInstall(installConfig); + expect(td.explain(helpersStubs.createNamespace).callCount).to.equal(1); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(2); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.helmOverride); + }); + + it('stops helm install when namespace creation fails', async () => { + td.when(helpersStubs.createNamespace(td.matchers.anything(), td.matchers.anything())) + .thenReject(new Error('namespace failed')); + + const installConfig = buildInstallConfig({ isDockerInstall: false, isHelmInstall: true }); + installConfig.gatewayConfig = { + namespace: { name: 'amplify-agents', isNew: true }, + traceableToken: 'traceable-token', + environments: [ 'traceable-dev' ], + centralEnvironments: [ 'dev' ], + }; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('namespace failed'); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(0); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(0); + }); + + it('stops helm install when Traceable credentials secret creation fails', async () => { + td.when(kubectlStubs.create(td.matchers.anything(), td.matchers.anything())) + .thenResolve({ data: [ ], error: 'secret failed' }); + + const installConfig = buildInstallConfig({ isDockerInstall: false, isHelmInstall: true }); + installConfig.gatewayConfig = { + namespace: { name: 'amplify-agents', isNew: false }, + traceableToken: 'traceable-token', + environments: [ 'traceable-dev' ], + centralEnvironments: [ 'dev' ], + }; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('secret failed'); + expect(td.explain(helpersStubs.createSecret).callCount).to.equal(2); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(0); + }); + }); +}); + +function buildInstallConfig({ isDockerInstall = true, isHelmInstall = false } = {}) { + return { + log: () => {}, + switches: { + isDockerInstall, + isHelmInstall, + isHostedInstall: false, + isDaEnabled: false, + isTaEnabled: true, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: true }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: isHelmInstall ? 'Helm' : 'Dockerized', + taVersion: '1.2.4', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/on-prem/test-wso2.onprem.js b/test/lib/engage/services/install-service/on-prem/test-wso2.onprem.js new file mode 100644 index 00000000..527e2901 --- /dev/null +++ b/test/lib/engage/services/install-service/on-prem/test-wso2.onprem.js @@ -0,0 +1,203 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const UTILS_MODULE = `${distRoot}/lib/engage/utils/utils.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/wso2Agents.js`; + +describe('WSO2 on-prem agent flow', () => { + let flowModule; + let promptStubs; + let utilsStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + validateRegex: td.func('validateRegex'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realUtils = await import(UTILS_MODULE); + utilsStubs = { + ...realUtils, + writeTemplates: td.func('writeTemplates'), + isWindows: false, + }; + await td.replaceEsm(UTILS_MODULE, utilsStubs); + + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('WSO2InstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.WSO2InstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array').that.is.not.empty; + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('prompts for bundle type and returns dockerized config type', async () => { + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('All Agents'); + + const bundleType = await flowModule.askBundleType(); + const configType = await flowModule.askConfigType(); + + expect(bundleType).to.equal('All Agents'); + expect(configType).to.exist; + expect(td.explain(promptStubs.askList).callCount).to.equal(1); + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects discovery prompts when DA is enabled', async () => { + const askInputResponses = [ + 'https://wso2.example.com', + 'client-id', + 'client-secret', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: true })); + + expect(result.wso2BaseURL).to.equal('https://wso2.example.com'); + expect(result.wso2ClientID).to.equal('client-id'); + expect(result.wso2ClientSecret).to.equal('client-secret'); + expect(td.explain(promptStubs.askInput).callCount).to.equal(3); + }); + + it('skips discovery prompts when DA is disabled', async () => { + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: false, isTaEnabled: true })); + + expect(result.wso2BaseURL).to.equal(''); + expect(result.wso2ClientID).to.equal(''); + expect(result.wso2ClientSecret).to.equal(''); + expect(td.explain(promptStubs.askInput).callCount).to.equal(0); + }); + + it('wires the WSO2 base URL validator into the discovery prompt', async () => { + const validator = () => true; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(validator); + + const promptConfigs = [ ]; + const askInputResponses = [ + 'https://wso2.example.com', + 'client-id', + 'client-secret', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo((config) => { + promptConfigs.push(config); + return askInputResponses.shift(); + }); + + await flowModule.gatewayConnectivity(buildInstallConfig({ isDaEnabled: true, isTaEnabled: true })); + + expect(promptConfigs.find((config) => config.msg === 'Enter the WSO2 baseURL').validate).to.equal(validator); + expect(td.explain(promptStubs.validateRegex).callCount).to.equal(1); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('writes DA and TA templates when both are enabled', async () => { + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = { + wso2BaseURL: 'https://wso2.example.com', + wso2ClientID: 'client-id', + wso2ClientSecret: 'client-secret', + }; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(2); + const files = td.explain(utilsStubs.writeTemplates).calls.map((call) => call.args[0]); + expect(files).to.include(flowModule.ConfigFiles.DAEnvVars); + expect(files).to.include(flowModule.ConfigFiles.TAEnvVars); + }); + + it('writes only TA template and logs listener hint when DA is disabled', async () => { + const logs = [ ]; + const installConfig = buildInstallConfig({ isDaEnabled: false, isTaEnabled: true, logSink: logs }); + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + expect(td.explain(utilsStubs.writeTemplates).calls[0].args[0]).to.equal(flowModule.ConfigFiles.TAEnvVars); + expect(logs.some((line) => line.includes('localhost:8888'))).to.equal(true); + }); + + it('logs service-account warning when dosa is new', async () => { + const logs = [ ]; + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: false, logSink: logs }); + installConfig.centralConfig.ampcDosaInfo.isNew = true; + installConfig.gatewayConfig = {}; + + await flowModule.completeInstall(installConfig); + + expect(logs.some((line) => line.includes('private_key.pem'))).to.equal(true); + }); + + it('stops finalize when the first WSO2 template generation fails', async () => { + td.when(utilsStubs.writeTemplates(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())) + .thenDo(() => { + throw new Error('write failed'); + }); + + const installConfig = buildInstallConfig({ isDaEnabled: true, isTaEnabled: true }); + installConfig.gatewayConfig = {}; + + let error; + try { + await flowModule.completeInstall(installConfig); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal('write failed'); + expect(td.explain(utilsStubs.writeTemplates).callCount).to.equal(1); + }); + }); +}); + +function buildInstallConfig({ + isDaEnabled = true, + isTaEnabled = true, + logSink = null, +} = {}) { + const logs = logSink || [ ]; + return { + log: (msg) => logs.push(String(msg)), + switches: { + isDockerInstall: true, + isHelmInstall: false, + isHostedInstall: false, + isDaEnabled, + isTaEnabled, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: false }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + daVersion: '1.2.3', + taVersion: '1.2.4', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/saas/test-akamai.saas.js b/test/lib/engage/services/install-service/saas/test-akamai.saas.js new file mode 100644 index 00000000..ee7bb0df --- /dev/null +++ b/test/lib/engage/services/install-service/saas/test-akamai.saas.js @@ -0,0 +1,186 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import crypto from 'crypto'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const AGENTS_INDEX = `${distRoot}/lib/engage/utils/agents/index.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/akamaiSaasAgents.js`; +const TYPES_MODULE = `${distRoot}/lib/engage/types.js`; + +describe('Akamai SaaS agent flow', () => { + let flowModule; + let engageTypes; + let promptStubs; + let helpersStubs; + let dataplaneRes; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + const realHelpers = await import(AGENTS_INDEX); + const { publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }); + dataplaneRes = { + name: 'dp-akamai', + security: { + encryptionKey: publicKey.export({ type: 'pkcs1', format: 'pem' }), + encryptionHash: 'sha256', + }, + }; + + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + validateRegex: td.func('validateRegex'), + validateValueRange: td.func('validateValueRange'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + td.when(promptStubs.validateValueRange(td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + helpersStubs = { + ...realHelpers, + getCentralEnvironments: td.func('getCentralEnvironments'), + createByResourceType: td.func('createByResourceType'), + createNewDataPlaneResource: td.func('createNewDataPlaneResource'), + createNewDataPlaneSecretResource: td.func('createNewDataPlaneSecretResource'), + createNewAgentResource: td.func('createNewAgentResource'), + deleteByResourceType: td.func('deleteByResourceType'), + }; + await td.replaceEsm(AGENTS_INDEX, helpersStubs); + + td.when(helpersStubs.getCentralEnvironments(td.matchers.anything(), td.matchers.anything())).thenResolve([ { name: 'env-a' }, { name: 'env-b' } ]); + td.when(helpersStubs.createByResourceType(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve('new-env'); + td.when(helpersStubs.createNewDataPlaneResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve(dataplaneRes); + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve(); + td.when(helpersStubs.createNewAgentResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve('akamai-ca'); + td.when(helpersStubs.deleteByResourceType(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve(); + + engageTypes = await import(TYPES_MODULE); + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + it('returns fixed bundle and hosted deployment type', async () => { + const methods = flowModule.AkamaiSaaSInstallMethods; + const bundleType = await methods.GetBundleType(); + const deploymentType = await methods.GetDeploymentType(); + expect(bundleType).to.equal(engageTypes.BundleType.TRACEABILITY); + expect(deploymentType).to.equal(engageTypes.AgentConfigTypes.HOSTED); + }); + + it('collects Akamai connection and environment mappings', async () => { + const askInputResponses = [ + 'https://akamai.example.com', + 'client-id', + 'client-secret', + 1, + 'akamai-dev', + 'akamai-prod', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + const mappingResponses = [ 'env-b', 'env-a' ]; + const continueResponses = [ engageTypes.YesNo.Yes, engageTypes.YesNo.No ]; + td.when(promptStubs.askList(td.matchers.argThat((arg) => arg.msg?.includes('Select an Engage environment')))).thenDo(() => mappingResponses.shift()); + td.when(promptStubs.askList(td.matchers.argThat((arg) => arg.msg?.includes('enter another mapping')))).thenDo(() => continueResponses.shift()); + + const result = await flowModule.AkamaiSaaSInstallMethods.AskGatewayQuestions(buildInstallConfig(engageTypes.GatewayTypes.AKAMAI)); + expect(result.baseUrl).to.equal('https://akamai.example.com'); + expect(result.environments).to.deep.equal([ 'akamai-dev', 'akamai-prod' ]); + expect(result.centralEnvironments).to.deep.equal([ 'env-b', 'env-a' ]); + }); + + it('creates dataplane and CA resource on finalize', async () => { + const config = buildInstallConfig(engageTypes.GatewayTypes.AKAMAI); + config.gatewayConfig = { + baseUrl: 'https://akamai.example.com', + clientId: 'cid', + clientSecret: 'csec', + segmentLength: 2, + environments: [ 'akamai-dev' ], + centralEnvironments: [ 'env-b' ], + getAccessData: () => JSON.stringify({ clientID: 'cid', clientSecret: 'csec' }), + }; + + await flowModule.AkamaiSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewDataPlaneResource).callCount).to.equal(1); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(1); + }); + + it('rolls back by deleting environment when secret creation fails for new env', async () => { + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenReject(new Error('secret failed')); + + const config = buildInstallConfig(engageTypes.GatewayTypes.AKAMAI); + config.centralConfig.ampcEnvInfo.isNew = true; + config.centralConfig.ampcEnvInfo.name = 'new-env'; + config.gatewayConfig = { + baseUrl: 'https://akamai.example.com', + clientId: 'cid', + clientSecret: 'csec', + segmentLength: 2, + environments: [ 'akamai-dev' ], + centralEnvironments: [ 'env-b' ], + getAccessData: () => JSON.stringify({ clientID: 'cid', clientSecret: 'csec' }), + }; + + await flowModule.AkamaiSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(0); + const deleteCalls = td.explain(helpersStubs.deleteByResourceType).calls; + expect(deleteCalls.length).to.equal(1); + expect(deleteCalls[0].args[3]).to.equal('Environment'); + }); + + it('rolls back by deleting dataplane when secret creation fails for existing env', async () => { + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenReject(new Error('secret failed')); + + const config = buildInstallConfig(engageTypes.GatewayTypes.AKAMAI); + config.centralConfig.ampcEnvInfo.isNew = false; + config.gatewayConfig = { + baseUrl: 'https://akamai.example.com', + clientId: 'cid', + clientSecret: 'csec', + segmentLength: 2, + environments: [ 'akamai-dev' ], + centralEnvironments: [ 'env-b' ], + getAccessData: () => JSON.stringify({ clientID: 'cid', clientSecret: 'csec' }), + }; + + await flowModule.AkamaiSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(0); + const deleteCalls = td.explain(helpersStubs.deleteByResourceType).calls; + expect(deleteCalls.length).to.equal(1); + expect(deleteCalls[0].args[3]).to.equal('Dataplane'); + }); +}); + +function buildInstallConfig(gatewayType) { + return { + log: () => {}, + gatewayType, + switches: { + isDockerInstall: false, + isHelmInstall: false, + isHostedInstall: true, + isDaEnabled: true, + isTaEnabled: true, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env', isNew: false }, + ampcDosaInfo: { isNew: false }, + ampcTeamName: 'team-a', + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: 'Hosted', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/saas/test-apigeex.saas.js b/test/lib/engage/services/install-service/saas/test-apigeex.saas.js new file mode 100644 index 00000000..65840460 --- /dev/null +++ b/test/lib/engage/services/install-service/saas/test-apigeex.saas.js @@ -0,0 +1,178 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import crypto from 'crypto'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const AGENTS_INDEX = `${distRoot}/lib/engage/utils/agents/index.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/apigeexSaasAgents.js`; +const TYPES_MODULE = `${distRoot}/lib/engage/types.js`; + +describe('Apigee X SaaS agent flow', () => { + let flowModule; + let engageTypes; + let promptStubs; + let helpersStubs; + let dataplaneRes; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + const realHelpers = await import(AGENTS_INDEX); + const { publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }); + dataplaneRes = { + name: 'dp-apigeex', + security: { + encryptionKey: publicKey.export({ type: 'pkcs1', format: 'pem' }), + encryptionHash: 'sha256', + }, + }; + + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + validateRegex: td.func('validateRegex'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + helpersStubs = { + ...realHelpers, + createByResourceType: td.func('createByResourceType'), + createNewDataPlaneResource: td.func('createNewDataPlaneResource'), + createNewDataPlaneSecretResource: td.func('createNewDataPlaneSecretResource'), + createNewAgentResource: td.func('createNewAgentResource'), + deleteByResourceType: td.func('deleteByResourceType'), + }; + await td.replaceEsm(AGENTS_INDEX, helpersStubs); + + td.when(helpersStubs.createByResourceType(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve('new-env'); + td.when(helpersStubs.createNewDataPlaneResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything())).thenResolve(dataplaneRes); + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve(); + td.when(helpersStubs.createNewAgentResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve('agent-name'); + td.when(helpersStubs.deleteByResourceType(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything())).thenResolve(); + + engageTypes = await import(TYPES_MODULE); + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + it('exports install methods with required hooks', () => { + const methods = flowModule.APIGEEXSaaSInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array'); + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('resolves deployment and bundle selections at runtime', async () => { + const methods = flowModule.APIGEEXSaaSInstallMethods; + td.when(promptStubs.askList(td.matchers.anything())).thenResolve(engageTypes.BundleType.ALL_AGENTS); + const deploymentType = await methods.GetDeploymentType(methods.GatewayDisplay); + const bundleType = await methods.GetBundleType(methods.GatewayDisplay); + expect(deploymentType).to.equal(engageTypes.AgentConfigTypes.HOSTED); + expect(bundleType).to.equal(engageTypes.BundleType.ALL_AGENTS); + }); + + it('creates DA and TA resources when TA is enabled', async () => { + const config = buildInstallConfig(engageTypes.SaaSGatewayTypes.APIGEEX_GATEWAY, true); + config.gatewayConfig = { + projectId: 'rd-amplify-apigee-x', + developerEmailAddress: 'dev@example.com', + mode: engageTypes.APIGEEXDISCOVERYMODES.PROXY, + metricsFilter: new engageTypes.ApigeeMetricsFilterConfig(true, [ 'payments-api' ]), + environment: 'prod', + frequencyDA: '30m', + queueDA: false, + frequencyTA: '1h', + getAccessData: () => JSON.stringify({ client_email: 'client@example.com' }), + }; + + await flowModule.APIGEEXSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(2); + }); + + it('rolls back by deleting environment when secret creation fails for new env', async () => { + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenReject(new Error('secret failed')); + + const config = buildInstallConfig(engageTypes.SaaSGatewayTypes.APIGEEX_GATEWAY, true); + config.centralConfig.ampcEnvInfo.isNew = true; + config.centralConfig.ampcEnvInfo.name = 'new-env'; + config.gatewayConfig = { + projectId: 'rd-amplify-apigee-x', + developerEmailAddress: 'dev@example.com', + mode: engageTypes.APIGEEXDISCOVERYMODES.PROXY, + metricsFilter: new engageTypes.ApigeeMetricsFilterConfig(true, [ 'payments-api' ]), + environment: 'prod', + frequencyDA: '30m', + queueDA: false, + frequencyTA: '1h', + getAccessData: () => JSON.stringify({ client_email: 'client@example.com' }), + }; + + await flowModule.APIGEEXSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(0); + const deleteCalls = td.explain(helpersStubs.deleteByResourceType).calls; + expect(deleteCalls.length).to.equal(1); + expect(deleteCalls[0].args[3]).to.equal('Environment'); + }); + + it('rolls back by deleting dataplane when secret creation fails for existing env', async () => { + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenReject(new Error('secret failed')); + + const config = buildInstallConfig(engageTypes.SaaSGatewayTypes.APIGEEX_GATEWAY, true); + config.centralConfig.ampcEnvInfo.isNew = false; + config.gatewayConfig = { + projectId: 'rd-amplify-apigee-x', + developerEmailAddress: 'dev@example.com', + mode: engageTypes.APIGEEXDISCOVERYMODES.PROXY, + metricsFilter: new engageTypes.ApigeeMetricsFilterConfig(true, [ 'payments-api' ]), + environment: 'prod', + frequencyDA: '30m', + queueDA: false, + frequencyTA: '1h', + getAccessData: () => JSON.stringify({ client_email: 'client@example.com' }), + }; + + await flowModule.APIGEEXSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(0); + const deleteCalls = td.explain(helpersStubs.deleteByResourceType).calls; + expect(deleteCalls.length).to.equal(1); + expect(deleteCalls[0].args[3]).to.equal('Dataplane'); + }); +}); + +function buildInstallConfig(gatewayType, isTaEnabled) { + return { + log: () => {}, + gatewayType, + switches: { + isDockerInstall: false, + isHelmInstall: false, + isHostedInstall: true, + isDaEnabled: true, + isTaEnabled, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env', isNew: false }, + ampcDosaInfo: { isNew: false }, + ampcTeamName: 'team-a', + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: 'Hosted', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/saas/test-aws.saas.js b/test/lib/engage/services/install-service/saas/test-aws.saas.js new file mode 100644 index 00000000..19131874 --- /dev/null +++ b/test/lib/engage/services/install-service/saas/test-aws.saas.js @@ -0,0 +1,245 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const AGENTS_INDEX = `${distRoot}/lib/engage/utils/agents/index.js`; +const SAAS_BASE_MODULE = `${distRoot}/lib/engage/utils/agents/flows/saasAgentsBase.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/awsSaasAgents.js`; +const TYPES_MODULE = `${distRoot}/lib/engage/types.js`; + +describe('AWS SaaS agent flow', () => { + let flowModule; + let engageTypes; + let promptStubs; + let helpersStubs; + let saasBaseStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + const realHelpers = await import(AGENTS_INDEX); + + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + validateRegex: td.func('validateRegex'), + validateInputLength: td.func('validateInputLength'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + td.when(promptStubs.validateInputLength(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + helpersStubs = { + ...realHelpers, + askAWSRegion: td.func('askAWSRegion'), + }; + await td.replaceEsm(AGENTS_INDEX, helpersStubs); + td.when(helpersStubs.askAWSRegion()).thenResolve('us-east-1'); + + const realSaasBase = await import(SAAS_BASE_MODULE); + saasBaseStubs = { + ...realSaasBase, + askFrequencyAndFilter: td.func('askFrequencyAndFilter'), + createIDPResources: td.func('createIDPResources'), + setupEnvironment: td.func('setupEnvironment'), + createDataplaneResources: td.func('createDataplaneResources'), + createAgentResources: td.func('createAgentResources'), + }; + await td.replaceEsm(SAAS_BASE_MODULE, saasBaseStubs); + td.when(saasBaseStubs.askFrequencyAndFilter(td.matchers.anything(), td.matchers.anything())).thenDo((values) => values); + td.when(saasBaseStubs.createIDPResources(td.matchers.anything())).thenResolve(true); + td.when(saasBaseStubs.setupEnvironment(td.matchers.anything())).thenResolve(); + td.when(saasBaseStubs.createDataplaneResources(td.matchers.anything(), td.matchers.anything())).thenResolve({ name: 'dp-aws' }); + td.when(saasBaseStubs.createAgentResources(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve(); + + engageTypes = await import(TYPES_MODULE); + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + it('collects assume-role auth and TA options', async () => { + const askInputResponses = [ + 'arn:aws:iam::000000000000:role/name-of-role', + 'external-id', + 'stage-tag', + 'arn:aws:logs:us-east-1:000000000000:log-group:my-group', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + const askListResponses = [ 'Assume Role Policy', engageTypes.YesNo.Yes ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.AWSSaaSInstallMethods.AskGatewayQuestions(buildInstallConfig(engageTypes.GatewayTypes.AWS_GATEWAY, true)); + expect(result.authType).to.equal('Assume Role Policy'); + expect(result.assumeRole).to.contain('arn:aws:iam'); + expect(result.fullTransactionLogging).to.equal(true); + }); + + it('collects TA redaction inputs', async () => { + td.when(saasBaseStubs.askFrequencyAndFilter(td.matchers.anything(), td.matchers.anything())) + .thenDo(async (values) => { + values.frequencyDA = await promptStubs.askInput({ msg: 'DA_FREQUENCY' }); + values.queueDA = (await promptStubs.askList({ msg: 'QUEUE' })) === engageTypes.YesNo.Yes; + values.filterDA = await promptStubs.askInput({ msg: 'DA_FILTER' }); + values.frequencyTA = await promptStubs.askInput({ msg: 'TA_FREQUENCY' }); + + values.redaction.path.push(await promptStubs.askInput({ msg: 'REDACT_SHOW_PATH' })); + await promptStubs.askList({ msg: 'ENTER_MORE_PATH' }); + + values.redaction.queryArgument.show.push(await promptStubs.askInput({ msg: 'REDACT_SHOW_QA' })); + await promptStubs.askList({ msg: 'ENTER_MORE_QA' }); + if ((await promptStubs.askList({ msg: 'ENTER_SANITIZE_QA' })) === engageTypes.YesNo.Yes) { + values.redaction.queryArgument.sanitize.push({ + keyMatch: await promptStubs.askInput({ msg: 'SANITIZE_KEY_QA' }), + valueMatch: await promptStubs.askInput({ msg: 'SANITIZE_VAL_QA' }), + }); + await promptStubs.askList({ msg: 'ENTER_MORE_SANITIZE_QA' }); + } + + values.redaction.requestHeaders.show.push(await promptStubs.askInput({ msg: 'REDACT_SHOW_REQ' })); + await promptStubs.askList({ msg: 'ENTER_MORE_REQ' }); + await promptStubs.askList({ msg: 'ENTER_SANITIZE_REQ' }); + + values.redaction.responseHeaders.show.push(await promptStubs.askInput({ msg: 'REDACT_SHOW_RES' })); + await promptStubs.askList({ msg: 'ENTER_MORE_RES' }); + await promptStubs.askList({ msg: 'ENTER_SANITIZE_RES' }); + + values.redaction.maskingCharacter = await promptStubs.askInput({ msg: 'MASKING_CHARS' }); + return values; + }); + + const askInputResponses = [ + 'arn:aws:iam::000000000000:role/name-of-role', + 'external-id', + 'stage-tag', + 'arn:aws:logs:us-east-1:000000000000:log-group:my-group', + '30m', + 'tag=prod', + '1h', + '/users/.*', + 'token', + 'authorization', + '.+', + 'x-api-key', + 'set-cookie', + '***', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ + 'Assume Role Policy', + engageTypes.YesNo.Yes, + engageTypes.YesNo.No, + engageTypes.YesNo.No, + engageTypes.YesNo.No, + engageTypes.YesNo.Yes, + engageTypes.YesNo.No, + engageTypes.YesNo.No, + engageTypes.YesNo.No, + engageTypes.YesNo.No, + engageTypes.YesNo.No, + ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.AWSSaaSInstallMethods.AskGatewayQuestions(buildInstallConfig(engageTypes.GatewayTypes.AWS_GATEWAY, true)); + expect(result.redaction.path).to.deep.equal([ '/users/.*' ]); + expect(result.redaction.queryArgument.show).to.deep.equal([ 'token' ]); + expect(result.redaction.queryArgument.sanitize).to.have.length(1); + expect(result.redaction.requestHeaders.show).to.deep.equal([ 'x-api-key' ]); + expect(result.redaction.responseHeaders.show).to.deep.equal([ 'set-cookie' ]); + expect(result.redaction.maskingCharacter).to.equal('***'); + }); + + it('builds AWS dataplane config when TA enabled', async () => { + const installConfig = buildInstallConfig(engageTypes.GatewayTypes.AWS_GATEWAY, true); + installConfig.gatewayConfig = { + accessLogARN: 'arn:aws:logs:us-east-1:000000000000:log-group:my-group', + fullTransactionLogging: true, + stageTagName: 'stage-tag', + redaction: {}, + }; + + await flowModule.AWSSaaSInstallMethods.FinalizeGatewayInstall(installConfig, {}, {}); + const dataplaneArg = td.explain(saasBaseStubs.createDataplaneResources).calls[0].args[1]; + expect(dataplaneArg.type).to.equal('AWS'); + expect(dataplaneArg.accessLogARN).to.contain('arn:aws:logs'); + }); + + it('passes IDP config in completeInstall context', async () => { + const installConfig = buildInstallConfig(engageTypes.GatewayTypes.AWS_GATEWAY, true); + installConfig.idpConfig = [ [ { name: 'idp-1' } ], [ { authType: 'access_token' } ] ]; + installConfig.gatewayConfig = { + accessLogARN: 'arn:aws:logs:us-east-1:000000000000:log-group:my-group', + fullTransactionLogging: false, + stageTagName: 'stage-tag', + redaction: {}, + }; + + await flowModule.AWSSaaSInstallMethods.FinalizeGatewayInstall(installConfig, {}, {}); + const ctx = td.explain(saasBaseStubs.createIDPResources).calls[0].args[0]; + expect(ctx.installConfig.idpConfig).to.deep.equal(installConfig.idpConfig); + expect(ctx.agentValues).to.equal(installConfig.gatewayConfig); + }); + + it('stops finalize when IDP resource creation fails', async () => { + td.when(saasBaseStubs.createIDPResources(td.matchers.anything())).thenResolve(false); + + const installConfig = buildInstallConfig(engageTypes.GatewayTypes.AWS_GATEWAY, true); + installConfig.gatewayConfig = { + accessLogARN: 'arn:aws:logs:us-east-1:000000000000:log-group:my-group', + fullTransactionLogging: false, + stageTagName: 'stage-tag', + redaction: {}, + }; + + await flowModule.AWSSaaSInstallMethods.FinalizeGatewayInstall(installConfig, {}, {}); + expect(td.explain(saasBaseStubs.setupEnvironment).callCount).to.equal(0); + expect(td.explain(saasBaseStubs.createDataplaneResources).callCount).to.equal(0); + expect(td.explain(saasBaseStubs.createAgentResources).callCount).to.equal(0); + }); + + it('stops finalize when dataplane resource creation fails', async () => { + td.when(saasBaseStubs.createDataplaneResources(td.matchers.anything(), td.matchers.anything())).thenResolve(null); + + const installConfig = buildInstallConfig(engageTypes.GatewayTypes.AWS_GATEWAY, true); + installConfig.gatewayConfig = { + accessLogARN: 'arn:aws:logs:us-east-1:000000000000:log-group:my-group', + fullTransactionLogging: false, + stageTagName: 'stage-tag', + redaction: {}, + }; + + await flowModule.AWSSaaSInstallMethods.FinalizeGatewayInstall(installConfig, {}, {}); + expect(td.explain(saasBaseStubs.setupEnvironment).callCount).to.equal(1); + expect(td.explain(saasBaseStubs.createAgentResources).callCount).to.equal(0); + }); +}); + +function buildInstallConfig(gatewayType, isTaEnabled) { + return { + log: () => {}, + gatewayType, + switches: { + isDockerInstall: false, + isHelmInstall: false, + isHostedInstall: true, + isDaEnabled: true, + isTaEnabled, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env', isNew: false }, + ampcDosaInfo: { isNew: false }, + ampcTeamName: 'team-a', + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: 'Hosted', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/saas/test-azure-gateway.saas.js b/test/lib/engage/services/install-service/saas/test-azure-gateway.saas.js new file mode 100644 index 00000000..f94ba0ab --- /dev/null +++ b/test/lib/engage/services/install-service/saas/test-azure-gateway.saas.js @@ -0,0 +1,324 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/azureSaasAgents.js`; +const SAAS_BASE_MODULE = `${distRoot}/lib/engage/utils/agents/flows/saasAgentsBase.js`; +const TYPES_MODULE = `${distRoot}/lib/engage/types.js`; + +describe('Azure SaaS agent flow (Gateway + EventHub)', () => { + let flowModule; + let engageTypes; + let promptStubs; + let saasBaseStubs; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + validateRegex: td.func('validateRegex'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + const realSaasBase = await import(SAAS_BASE_MODULE); + + saasBaseStubs = { + ...realSaasBase, + askFrequencyAndFilter: td.func('askFrequencyAndFilter'), + createIDPResources: td.func('createIDPResources'), + setupEnvironment: td.func('setupEnvironment'), + createDataplaneResources: td.func('createDataplaneResources'), + createAgentResources: td.func('createAgentResources'), + }; + await td.replaceEsm(SAAS_BASE_MODULE, saasBaseStubs); + + td.when(saasBaseStubs.askFrequencyAndFilter(td.matchers.anything(), td.matchers.anything())).thenDo((values) => values); + td.when(saasBaseStubs.createIDPResources(td.matchers.anything())).thenResolve(true); + td.when(saasBaseStubs.setupEnvironment(td.matchers.anything())).thenResolve(); + td.when(saasBaseStubs.createDataplaneResources(td.matchers.anything(), td.matchers.anything())).thenResolve({ name: 'dp-azure' }); + td.when(saasBaseStubs.createAgentResources(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve(); + + engageTypes = await import(TYPES_MODULE); + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + describe('AzureSaaSInstallMethods metadata', () => { + it('exports install methods with required hooks', () => { + const methods = flowModule.AzureSaaSInstallMethods; + expect(methods).to.exist; + expect(methods.GetBundleType).to.be.a('function'); + expect(methods.GetDeploymentType).to.be.a('function'); + expect(methods.AskGatewayQuestions).to.be.a('function'); + expect(methods.FinalizeGatewayInstall).to.be.a('function'); + expect(methods.ConfigFiles).to.be.an('array'); + expect(methods.GatewayDisplay).to.be.a('string').and.not.empty; + }); + + it('returns hosted config type and bundle branch by gateway', async () => { + td.when(promptStubs.askList(td.matchers.anything())).thenResolve('All Agents'); + const gwBundle = await flowModule.askBundleType(engageTypes.GatewayTypes.AZURE_GATEWAY); + const ehBundle = await flowModule.askBundleType(engageTypes.GatewayTypes.AZURE_EVENTHUB); + const configType = await flowModule.AzureSaaSInstallMethods.GetDeploymentType(); + + expect(gwBundle).to.equal('All Agents'); + expect(ehBundle).to.equal(engageTypes.BundleType.DISCOVERY); + expect(configType).to.equal(engageTypes.AgentConfigTypes.HOSTED); + }); + }); + + describe('AskGatewayQuestions', () => { + it('collects gateway mode prompts with TA EventHub details', async () => { + const askInputResponses = [ + 'tenant-id', + 'subscription-id', + 'client-id', + 'client-secret', + 'resource-group', + 'apim-service', + 'RootManageSharedAccessKey', + 'shared-key', + 'event-hub-name', + 'event-hub-namespace', + '$Default', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ gatewayType: engageTypes.GatewayTypes.AZURE_GATEWAY, isTaEnabled: true })); + expect(result.apimManagementServiceName).to.equal('apim-service'); + expect(result.eventHubName).to.equal('event-hub-name'); + expect(result.eventHubConsumerGroup).to.equal('$Default'); + }); + + it('collects eventhub mode prompts without TA and asks only namespace', async () => { + const askInputResponses = [ + 'tenant-id', + 'subscription-id', + 'client-id', + 'client-secret', + 'resource-group', + 'event-hub-namespace', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ gatewayType: engageTypes.GatewayTypes.AZURE_EVENTHUB, isTaEnabled: false })); + expect(result.apimManagementServiceName).to.equal(''); + expect(result.eventHubNamespace).to.equal('event-hub-namespace'); + expect(result.eventHubName).to.equal(''); + }); + + it('collects TA redaction inputs for gateway mode', async () => { + td.when(saasBaseStubs.askFrequencyAndFilter(td.matchers.anything(), td.matchers.anything())) + .thenDo(async (values) => { + values.frequencyDA = await promptStubs.askInput({ msg: 'DA_FREQUENCY' }); + values.queueDA = (await promptStubs.askList({ msg: 'QUEUE' })) === engageTypes.YesNo.Yes; + values.filterDA = await promptStubs.askInput({ msg: 'DA_FILTER' }); + values.frequencyTA = await promptStubs.askInput({ msg: 'TA_FREQUENCY' }); + + values.redaction.path.push(await promptStubs.askInput({ msg: 'REDACT_SHOW_PATH' })); + await promptStubs.askList({ msg: 'ENTER_MORE_PATH' }); + + values.redaction.queryArgument.show.push(await promptStubs.askInput({ msg: 'REDACT_SHOW_QA' })); + await promptStubs.askList({ msg: 'ENTER_MORE_QA' }); + if ((await promptStubs.askList({ msg: 'ENTER_SANITIZE_QA' })) === engageTypes.YesNo.Yes) { + values.redaction.queryArgument.sanitize.push({ + keyMatch: await promptStubs.askInput({ msg: 'SANITIZE_KEY_QA' }), + valueMatch: await promptStubs.askInput({ msg: 'SANITIZE_VAL_QA' }), + }); + await promptStubs.askList({ msg: 'ENTER_MORE_SANITIZE_QA' }); + } + + values.redaction.requestHeaders.show.push(await promptStubs.askInput({ msg: 'REDACT_SHOW_REQ' })); + await promptStubs.askList({ msg: 'ENTER_MORE_REQ' }); + await promptStubs.askList({ msg: 'ENTER_SANITIZE_REQ' }); + + values.redaction.responseHeaders.show.push(await promptStubs.askInput({ msg: 'REDACT_SHOW_RES' })); + await promptStubs.askList({ msg: 'ENTER_MORE_RES' }); + await promptStubs.askList({ msg: 'ENTER_SANITIZE_RES' }); + + values.redaction.maskingCharacter = await promptStubs.askInput({ msg: 'MASKING_CHARS' }); + return values; + }); + + const askInputResponses = [ + 'tenant-id', + 'subscription-id', + 'client-id', + 'client-secret', + 'resource-group', + 'apim-service', + 'RootManageSharedAccessKey', + 'shared-key', + 'event-hub-name', + 'event-hub-namespace', + '$Default', + '30m', + 'tag=finance', + '1h', + '/orders/.*', + 'customerId', + 'x-customer-id', + '.+', + 'authorization', + 'set-cookie', + '***', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ + engageTypes.YesNo.No, + engageTypes.YesNo.No, + engageTypes.YesNo.No, + engageTypes.YesNo.Yes, + engageTypes.YesNo.No, + engageTypes.YesNo.No, + engageTypes.YesNo.No, + engageTypes.YesNo.No, + engageTypes.YesNo.No, + ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.gatewayConnectivity(buildInstallConfig({ gatewayType: engageTypes.GatewayTypes.AZURE_GATEWAY, isTaEnabled: true })); + expect(result.redaction.path).to.deep.equal([ '/orders/.*' ]); + expect(result.redaction.queryArgument.show).to.deep.equal([ 'customerId' ]); + expect(result.redaction.queryArgument.sanitize).to.have.length(1); + expect(result.redaction.requestHeaders.show).to.deep.equal([ 'authorization' ]); + expect(result.redaction.responseHeaders.show).to.deep.equal([ 'set-cookie' ]); + expect(result.redaction.maskingCharacter).to.equal('***'); + }); + }); + + describe('FinalizeGatewayInstall', () => { + it('uses APIM mode dataplane config for AZURE_GATEWAY', async () => { + const installConfig = buildInstallConfig({ gatewayType: engageTypes.GatewayTypes.AZURE_GATEWAY, isTaEnabled: true }); + installConfig.gatewayConfig = { + tenantId: 'tenant-id', + resourceGroup: 'resource-group', + subscriptionId: 'subscription-id', + apimManagementServiceName: 'apim-service', + eventHubName: 'event-hub-name', + eventHubNamespace: 'event-hub-namespace', + eventHubConsumerGroup: '$Default', + sampling: {}, + redaction: {}, + }; + + await flowModule.completeInstall(installConfig, {}, {}); + expect(td.explain(saasBaseStubs.createDataplaneResources).callCount).to.equal(1); + const dataplaneArg = td.explain(saasBaseStubs.createDataplaneResources).calls[0].args[1]; + expect(dataplaneArg.mode).to.equal(engageTypes.AzureDataplaneMode.APIM); + }); + + it('uses EventHub mode dataplane config for AZURE_EVENTHUB', async () => { + const installConfig = buildInstallConfig({ gatewayType: engageTypes.GatewayTypes.AZURE_EVENTHUB, isTaEnabled: false }); + installConfig.gatewayConfig = { + tenantId: 'tenant-id', + resourceGroup: 'resource-group', + subscriptionId: 'subscription-id', + eventHubNamespace: 'event-hub-namespace', + sampling: {}, + redaction: {}, + }; + + await flowModule.completeInstall(installConfig, {}, {}); + const dataplaneArg = td.explain(saasBaseStubs.createDataplaneResources).calls[0].args[1]; + expect(dataplaneArg.mode).to.equal(engageTypes.AzureDataplaneMode.EventHub); + }); + + it('passes IDP config in completeInstall context', async () => { + const installConfig = buildInstallConfig({ gatewayType: engageTypes.GatewayTypes.AZURE_GATEWAY, isTaEnabled: true }); + installConfig.idpConfig = [ [ { name: 'idp-1' } ], [ { authType: 'access_token' } ] ]; + installConfig.gatewayConfig = { + tenantId: 'tenant-id', + resourceGroup: 'resource-group', + subscriptionId: 'subscription-id', + apimManagementServiceName: 'apim-service', + eventHubName: 'event-hub-name', + eventHubNamespace: 'event-hub-namespace', + eventHubConsumerGroup: '$Default', + sampling: {}, + redaction: {}, + }; + + await flowModule.completeInstall(installConfig, {}, {}); + const ctx = td.explain(saasBaseStubs.createIDPResources).calls[0].args[0]; + expect(ctx.installConfig.idpConfig).to.deep.equal(installConfig.idpConfig); + expect(ctx.agentValues).to.equal(installConfig.gatewayConfig); + }); + + it('stops finalize when IDP resource creation fails', async () => { + td.when(saasBaseStubs.createIDPResources(td.matchers.anything())).thenResolve(false); + + const installConfig = buildInstallConfig({ gatewayType: engageTypes.GatewayTypes.AZURE_GATEWAY, isTaEnabled: true }); + installConfig.gatewayConfig = { + tenantId: 'tenant-id', + resourceGroup: 'resource-group', + subscriptionId: 'subscription-id', + apimManagementServiceName: 'apim-service', + eventHubName: 'event-hub-name', + eventHubNamespace: 'event-hub-namespace', + eventHubConsumerGroup: '$Default', + sampling: {}, + redaction: {}, + }; + + await flowModule.completeInstall(installConfig, {}, {}); + expect(td.explain(saasBaseStubs.setupEnvironment).callCount).to.equal(0); + expect(td.explain(saasBaseStubs.createDataplaneResources).callCount).to.equal(0); + expect(td.explain(saasBaseStubs.createAgentResources).callCount).to.equal(0); + }); + + it('stops finalize when dataplane resource creation fails', async () => { + td.when(saasBaseStubs.createDataplaneResources(td.matchers.anything(), td.matchers.anything())).thenResolve(null); + + const installConfig = buildInstallConfig({ gatewayType: engageTypes.GatewayTypes.AZURE_GATEWAY, isTaEnabled: true }); + installConfig.gatewayConfig = { + tenantId: 'tenant-id', + resourceGroup: 'resource-group', + subscriptionId: 'subscription-id', + apimManagementServiceName: 'apim-service', + eventHubName: 'event-hub-name', + eventHubNamespace: 'event-hub-namespace', + eventHubConsumerGroup: '$Default', + sampling: {}, + redaction: {}, + }; + + await flowModule.completeInstall(installConfig, {}, {}); + expect(td.explain(saasBaseStubs.setupEnvironment).callCount).to.equal(1); + expect(td.explain(saasBaseStubs.createAgentResources).callCount).to.equal(0); + }); + }); +}); + +function buildInstallConfig({ gatewayType, isTaEnabled = true } = {}) { + return { + log: () => {}, + gatewayType, + switches: { + isDockerInstall: false, + isHelmInstall: false, + isHostedInstall: true, + isDaEnabled: true, + isTaEnabled, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env' }, + ampcDosaInfo: { isNew: false }, + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: 'Hosted', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/saas/test-github.saas.js b/test/lib/engage/services/install-service/saas/test-github.saas.js new file mode 100644 index 00000000..c31fc789 --- /dev/null +++ b/test/lib/engage/services/install-service/saas/test-github.saas.js @@ -0,0 +1,185 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import crypto from 'crypto'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const AGENTS_INDEX = `${distRoot}/lib/engage/utils/agents/index.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/gitHubSaasAgents.js`; +const TYPES_MODULE = `${distRoot}/lib/engage/types.js`; + +describe('GitHub SaaS agent flow', () => { + let flowModule; + let engageTypes; + let promptStubs; + let helpersStubs; + let dataplaneRes; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + const realHelpers = await import(AGENTS_INDEX); + const { publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }); + dataplaneRes = { + name: 'dp-github', + security: { + encryptionKey: publicKey.export({ type: 'pkcs1', format: 'pem' }), + encryptionHash: 'sha256', + }, + }; + + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + validateRegex: td.func('validateRegex'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + helpersStubs = { + ...realHelpers, + createByResourceType: td.func('createByResourceType'), + createNewDataPlaneResource: td.func('createNewDataPlaneResource'), + createNewDataPlaneSecretResource: td.func('createNewDataPlaneSecretResource'), + createNewAgentResource: td.func('createNewAgentResource'), + deleteByResourceType: td.func('deleteByResourceType'), + }; + await td.replaceEsm(AGENTS_INDEX, helpersStubs); + td.when(helpersStubs.createByResourceType(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve('new-env'); + td.when(helpersStubs.createNewDataPlaneResource(td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve(dataplaneRes); + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve(); + td.when(helpersStubs.createNewAgentResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve('github-da'); + td.when(helpersStubs.deleteByResourceType(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve(); + + engageTypes = await import(TYPES_MODULE); + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + it('returns fixed bundle and hosted deployment type', async () => { + const methods = flowModule.GitHubSaaSInstallMethods; + const bundleType = await methods.GetBundleType(); + const deploymentType = await methods.GetDeploymentType(); + expect(bundleType).to.equal(engageTypes.BundleType.DISCOVERY); + expect(deploymentType).to.equal(engageTypes.AgentConfigTypes.HOSTED); + }); + + it('collects repository paths and optional patterns', async () => { + const askInputResponses = [ + 'ghp_testTokentestTokentestTokentestToken', + '30m', + 'axway-github-owner', + 'axway-github-repo-name', + 'main', + '/apis', + '/more-apis', + '.*\\.ya?ml$', + '', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ engageTypes.YesNo.Yes, engageTypes.YesNo.Yes, engageTypes.YesNo.No ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.GitHubSaaSInstallMethods.AskGatewayQuestions(buildInstallConfig(engageTypes.SaaSGatewayTypes.GITHUB)); + expect(result.filePaths).to.deep.equal([ '/apis', '/more-apis' ]); + expect(result.filePatterns).to.deep.equal([ '.*\\.ya?ml$' ]); + }); + + it('creates dataplane and DA resource on finalize', async () => { + const config = buildInstallConfig(engageTypes.SaaSGatewayTypes.GITHUB); + config.gatewayConfig = { + repositoryName: 'axway-github-repo-name', + repositoryOwner: 'axway-github-owner', + repositoryBranch: 'main', + filePaths: [ '/apis' ], + filePatterns: [ '.*\\.ya?ml$' ], + frequencyDA: '30m', + queueDA: true, + getAccessData: () => JSON.stringify({ accessToken: 'ghp_testTokentestTokentestTokentestToken' }), + }; + + await flowModule.GitHubSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewDataPlaneResource).callCount).to.equal(1); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(1); + }); + + it('rolls back by deleting environment when secret creation fails for new env', async () => { + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenReject(new Error('secret failed')); + + const config = buildInstallConfig(engageTypes.SaaSGatewayTypes.GITHUB); + config.centralConfig.ampcEnvInfo.isNew = true; + config.centralConfig.ampcEnvInfo.name = 'new-env'; + config.gatewayConfig = { + repositoryName: 'axway-github-repo-name', + repositoryOwner: 'axway-github-owner', + repositoryBranch: 'main', + filePaths: [ '/apis' ], + filePatterns: [], + frequencyDA: '30m', + queueDA: false, + getAccessData: () => JSON.stringify({ accessToken: 'ghp_testTokentestTokentestTokentestToken' }), + }; + + await flowModule.GitHubSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(0); + const deleteCalls = td.explain(helpersStubs.deleteByResourceType).calls; + expect(deleteCalls.length).to.equal(1); + expect(deleteCalls[0].args[3]).to.equal('Environment'); + }); + + it('rolls back by deleting dataplane when secret creation fails for existing env', async () => { + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenReject(new Error('secret failed')); + + const config = buildInstallConfig(engageTypes.SaaSGatewayTypes.GITHUB); + config.centralConfig.ampcEnvInfo.isNew = false; + config.gatewayConfig = { + repositoryName: 'axway-github-repo-name', + repositoryOwner: 'axway-github-owner', + repositoryBranch: 'main', + filePaths: [ '/apis' ], + filePatterns: [], + frequencyDA: '30m', + queueDA: false, + getAccessData: () => JSON.stringify({ accessToken: 'ghp_testTokentestTokentestTokentestToken' }), + }; + + await flowModule.GitHubSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(0); + const deleteCalls = td.explain(helpersStubs.deleteByResourceType).calls; + expect(deleteCalls.length).to.equal(1); + expect(deleteCalls[0].args[3]).to.equal('Dataplane'); + }); +}); + +function buildInstallConfig(gatewayType) { + return { + log: () => {}, + gatewayType, + switches: { + isDockerInstall: false, + isHelmInstall: false, + isHostedInstall: true, + isDaEnabled: true, + isTaEnabled: false, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env', isNew: false }, + ampcDosaInfo: { isNew: false }, + ampcTeamName: 'team-a', + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: 'Hosted', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/saas/test-swaggerhub.saas.js b/test/lib/engage/services/install-service/saas/test-swaggerhub.saas.js new file mode 100644 index 00000000..07bc3414 --- /dev/null +++ b/test/lib/engage/services/install-service/saas/test-swaggerhub.saas.js @@ -0,0 +1,179 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import crypto from 'crypto'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const AGENTS_INDEX = `${distRoot}/lib/engage/utils/agents/index.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/swaggerHubSaasAgents.js`; +const TYPES_MODULE = `${distRoot}/lib/engage/types.js`; + +describe('SwaggerHub SaaS agent flow', () => { + let flowModule; + let engageTypes; + let promptStubs; + let helpersStubs; + let dataplaneRes; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + const realHelpers = await import(AGENTS_INDEX); + const { publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }); + dataplaneRes = { + name: 'dp-swaggerhub', + security: { + encryptionKey: publicKey.export({ type: 'pkcs1', format: 'pem' }), + encryptionHash: 'sha256', + }, + }; + + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + validateRegex: td.func('validateRegex'), + }; + td.when(promptStubs.validateRegex(td.matchers.anything(), td.matchers.anything())).thenReturn(() => true); + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + helpersStubs = { + ...realHelpers, + createByResourceType: td.func('createByResourceType'), + createNewDataPlaneResource: td.func('createNewDataPlaneResource'), + createNewDataPlaneSecretResource: td.func('createNewDataPlaneSecretResource'), + createNewAgentResource: td.func('createNewAgentResource'), + deleteByResourceType: td.func('deleteByResourceType'), + }; + await td.replaceEsm(AGENTS_INDEX, helpersStubs); + + td.when(helpersStubs.createByResourceType(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve('new-env'); + td.when(helpersStubs.createNewDataPlaneResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything())).thenResolve(dataplaneRes); + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve(); + td.when(helpersStubs.createNewAgentResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve('swaggerhub-da'); + td.when(helpersStubs.deleteByResourceType(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything())).thenResolve(); + + engageTypes = await import(TYPES_MODULE); + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + it('returns fixed bundle and hosted deployment type', async () => { + const methods = flowModule.SwaggerHubSaaSInstallMethods; + const bundleType = await methods.GetBundleType(); + const deploymentType = await methods.GetDeploymentType(); + expect(bundleType).to.equal(engageTypes.BundleType.DISCOVERY); + expect(deploymentType).to.equal(engageTypes.AgentConfigTypes.HOSTED); + }); + + it('collects API key, owner, visibility and publication filter', async () => { + const askInputResponses = [ + 'apiKeyapiKeyapiKeyapiKeyapiKeyapiKeyapiKeyapiKey', + '30m', + 'axway-repo-owner', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ + engageTypes.YesNo.No, + 'Public', + 'Published', + ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.SwaggerHubSaaSInstallMethods.AskGatewayQuestions(buildInstallConfig(engageTypes.SaaSGatewayTypes.SWAGGERHUB)); + expect(result.owner).to.equal('axway-repo-owner'); + expect(result.visibility).to.equal('Public'); + expect(result.publication).to.equal('Published'); + }); + + it('creates dataplane and DA resource on finalize', async () => { + const config = buildInstallConfig(engageTypes.SaaSGatewayTypes.SWAGGERHUB); + config.gatewayConfig = { + owner: 'axway-repo-owner', + visibility: 'Public', + publication: 'Published', + frequencyDA: '30m', + queueDA: false, + getAccessData: () => JSON.stringify({ apiKey: 'apiKeyapiKeyapiKeyapiKeyapiKeyapiKeyapiKeyapiKey' }), + }; + + await flowModule.SwaggerHubSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewDataPlaneResource).callCount).to.equal(1); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(1); + }); + + it('rolls back by deleting environment when secret creation fails for new env', async () => { + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenReject(new Error('secret failed')); + + const config = buildInstallConfig(engageTypes.SaaSGatewayTypes.SWAGGERHUB); + config.centralConfig.ampcEnvInfo.isNew = true; + config.centralConfig.ampcEnvInfo.name = 'new-env'; + config.gatewayConfig = { + owner: 'axway-repo-owner', + visibility: 'Public', + publication: 'Published', + frequencyDA: '30m', + queueDA: false, + getAccessData: () => JSON.stringify({ apiKey: 'apiKeyapiKeyapiKeyapiKeyapiKeyapiKeyapiKeyapiKey' }), + }; + + await flowModule.SwaggerHubSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(0); + const deleteCalls = td.explain(helpersStubs.deleteByResourceType).calls; + expect(deleteCalls.length).to.equal(1); + expect(deleteCalls[0].args[3]).to.equal('Environment'); + }); + + it('rolls back by deleting dataplane when secret creation fails for existing env', async () => { + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenReject(new Error('secret failed')); + + const config = buildInstallConfig(engageTypes.SaaSGatewayTypes.SWAGGERHUB); + config.centralConfig.ampcEnvInfo.isNew = false; + config.gatewayConfig = { + owner: 'axway-repo-owner', + visibility: 'Public', + publication: 'Published', + frequencyDA: '30m', + queueDA: false, + getAccessData: () => JSON.stringify({ apiKey: 'apiKeyapiKeyapiKeyapiKeyapiKeyapiKeyapiKeyapiKey' }), + }; + + await flowModule.SwaggerHubSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(0); + const deleteCalls = td.explain(helpersStubs.deleteByResourceType).calls; + expect(deleteCalls.length).to.equal(1); + expect(deleteCalls[0].args[3]).to.equal('Dataplane'); + }); +}); + +function buildInstallConfig(gatewayType) { + return { + log: () => {}, + gatewayType, + switches: { + isDockerInstall: false, + isHelmInstall: false, + isHostedInstall: true, + isDaEnabled: true, + isTaEnabled: false, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env', isNew: false }, + ampcDosaInfo: { isNew: false }, + ampcTeamName: 'team-a', + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: 'Hosted', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/lib/engage/services/install-service/saas/test-traceable.saas.js b/test/lib/engage/services/install-service/saas/test-traceable.saas.js new file mode 100644 index 00000000..b5de5de5 --- /dev/null +++ b/test/lib/engage/services/install-service/saas/test-traceable.saas.js @@ -0,0 +1,193 @@ +import { expect } from 'chai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import crypto from 'crypto'; +import * as td from 'testdouble'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const distRoot = path.resolve(__dirname, '../../../../../../dist'); +const BASIC_PROMPTS = `${distRoot}/lib/engage/utils/basic-prompts.js`; +const AGENTS_INDEX = `${distRoot}/lib/engage/utils/agents/index.js`; +const FLOW_MODULE = `${distRoot}/lib/engage/utils/agents/flows/traceableSaasAgents.js`; +const TYPES_MODULE = `${distRoot}/lib/engage/types.js`; + +describe('Traceable SaaS agent flow', () => { + let flowModule; + let engageTypes; + let promptStubs; + let helpersStubs; + let dataplaneRes; + + beforeEach(async () => { + const realPrompts = await import(BASIC_PROMPTS); + const realHelpers = await import(AGENTS_INDEX); + const { publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }); + dataplaneRes = { + name: 'dp-traceable', + security: { + encryptionKey: publicKey.export({ type: 'pkcs1', format: 'pem' }), + encryptionHash: 'sha256', + }, + }; + + promptStubs = { + ...realPrompts, + askInput: td.func('askInput'), + askList: td.func('askList'), + }; + await td.replaceEsm(BASIC_PROMPTS, promptStubs); + + helpersStubs = { + ...realHelpers, + getCentralEnvironments: td.func('getCentralEnvironments'), + createByResourceType: td.func('createByResourceType'), + createNewDataPlaneResource: td.func('createNewDataPlaneResource'), + createNewDataPlaneSecretResource: td.func('createNewDataPlaneSecretResource'), + createNewAgentResource: td.func('createNewAgentResource'), + deleteByResourceType: td.func('deleteByResourceType'), + }; + await td.replaceEsm(AGENTS_INDEX, helpersStubs); + + td.when(helpersStubs.getCentralEnvironments(td.matchers.anything(), td.matchers.anything())).thenResolve([ { name: 'env-a' }, { name: 'env-b' } ]); + td.when(helpersStubs.createByResourceType(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve('new-env'); + td.when(helpersStubs.createNewDataPlaneResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything())).thenResolve(dataplaneRes); + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve(); + td.when(helpersStubs.createNewAgentResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenResolve('traceable-ca'); + td.when(helpersStubs.deleteByResourceType(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), + td.matchers.anything())).thenResolve(); + + engageTypes = await import(TYPES_MODULE); + flowModule = await import(FLOW_MODULE); + }); + + afterEach(() => td.reset()); + + it('returns fixed bundle and hosted deployment type', async () => { + const methods = flowModule.TraceableSaaSInstallMethods; + const bundleType = await methods.GetBundleType(); + const deploymentType = await methods.GetDeploymentType(); + expect(bundleType).to.equal(engageTypes.BundleType.TRACEABILITY); + expect(deploymentType).to.equal(engageTypes.AgentConfigTypes.HOSTED); + }); + + it('collects token, region and multiple environment mappings', async () => { + const askInputResponses = [ + 'traceable-token', + 'traceable-env-dev', + 'traceable-env-prod', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ + engageTypes.TraceableRegionType.US, + 'env-b', + engageTypes.YesNo.Yes, + 'env-a', + ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.TraceableSaaSInstallMethods.AskGatewayQuestions(buildInstallConfig(engageTypes.SaaSGatewayTypes.TRACEABLE)); + expect(result.traceableToken).to.equal('traceable-token'); + expect(result.traceableRegion).to.equal(engageTypes.TraceableRegionType.US); + expect(result.environments).to.deep.equal([ 'traceable-env-dev', 'traceable-env-prod' ]); + expect(result.centralEnvironments).to.deep.equal([ 'env-b', 'env-a' ]); + }); + + it('handles empty first environment mapping input', async () => { + const askInputResponses = [ + 'traceable-token', + '', + ]; + td.when(promptStubs.askInput(td.matchers.anything())).thenDo(() => askInputResponses.shift()); + + const askListResponses = [ engageTypes.TraceableRegionType.US ]; + td.when(promptStubs.askList(td.matchers.anything())).thenDo(() => askListResponses.shift()); + + const result = await flowModule.TraceableSaaSInstallMethods.AskGatewayQuestions(buildInstallConfig(engageTypes.SaaSGatewayTypes.TRACEABLE)); + expect(result.environments).to.deep.equal([]); + expect(result.centralEnvironments).to.deep.equal([]); + }); + + it('creates dataplane and CA resource on finalize', async () => { + const config = buildInstallConfig(engageTypes.SaaSGatewayTypes.TRACEABLE); + config.gatewayConfig = { + traceableToken: 'traceable-token', + traceableRegion: engageTypes.TraceableRegionType.US, + environments: [ 'traceable-env-dev' ], + centralEnvironments: [ 'env-b' ], + getAccessData: () => JSON.stringify({ token: 'traceable-token' }), + }; + + await flowModule.TraceableSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewDataPlaneResource).callCount).to.equal(1); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(1); + }); + + it('rolls back by deleting environment when secret creation fails for new env', async () => { + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenReject(new Error('secret failed')); + + const config = buildInstallConfig(engageTypes.SaaSGatewayTypes.TRACEABLE); + config.centralConfig.ampcEnvInfo.isNew = true; + config.centralConfig.ampcEnvInfo.name = 'new-env'; + config.gatewayConfig = { + traceableToken: 'traceable-token', + traceableRegion: engageTypes.TraceableRegionType.US, + environments: [ 'traceable-env-dev' ], + centralEnvironments: [ 'env-b' ], + getAccessData: () => JSON.stringify({ token: 'traceable-token' }), + }; + + await flowModule.TraceableSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(0); + const deleteCalls = td.explain(helpersStubs.deleteByResourceType).calls; + expect(deleteCalls.length).to.equal(1); + expect(deleteCalls[0].args[3]).to.equal('Environment'); + }); + + it('rolls back by deleting dataplane when secret creation fails for existing env', async () => { + td.when(helpersStubs.createNewDataPlaneSecretResource(td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything(), td.matchers.anything())).thenReject(new Error('secret failed')); + + const config = buildInstallConfig(engageTypes.SaaSGatewayTypes.TRACEABLE); + config.centralConfig.ampcEnvInfo.isNew = false; + config.gatewayConfig = { + traceableToken: 'traceable-token', + traceableRegion: engageTypes.TraceableRegionType.US, + environments: [ 'traceable-env-dev' ], + centralEnvironments: [ 'env-b' ], + getAccessData: () => JSON.stringify({ token: 'traceable-token' }), + }; + + await flowModule.TraceableSaaSInstallMethods.FinalizeGatewayInstall(config, {}, {}); + expect(td.explain(helpersStubs.createNewAgentResource).callCount).to.equal(0); + const deleteCalls = td.explain(helpersStubs.deleteByResourceType).calls; + expect(deleteCalls.length).to.equal(1); + expect(deleteCalls[0].args[3]).to.equal('Dataplane'); + }); +}); + +function buildInstallConfig(gatewayType) { + return { + log: () => {}, + gatewayType, + switches: { + isDockerInstall: false, + isHelmInstall: false, + isHostedInstall: true, + isDaEnabled: false, + isTaEnabled: true, + }, + centralConfig: { + apiServerClient: {}, + definitionManager: {}, + ampcEnvInfo: { name: 'installed-env', isNew: false }, + ampcDosaInfo: { isNew: false }, + ampcTeamName: 'team-a', + dosaAccount: { publicKey: 'pub.pem', privateKey: 'priv.pem' }, + }, + deploymentType: 'Hosted', + traceabilityConfig: {}, + gatewayConfig: {}, + }; +} diff --git a/test/resources/examples/agentresources.json b/test/resources/examples/agentresources.json new file mode 100644 index 00000000..2f424135 --- /dev/null +++ b/test/resources/examples/agentresources.json @@ -0,0 +1,34 @@ +[ + { + "apiVersion": "v1alpha1", + "title": "cli-agentname", + "name": "cli-agentname", + "kind": "DiscoveryAgent", + "metadata": { + "scope": { + "kind": "Environment", + "name": "env1" + } + }, + "tags": [], + "spec": { + "dataplaneType": "AWS" + } + }, + { + "apiVersion": "v1alpha1", + "title": "cli-agentname2", + "name": "cli-agentname2", + "kind": "TraceabilityAgent", + "metadata": { + "scope": { + "kind": "Environment", + "name": "env2" + } + }, + "tags": [], + "spec": { + "dataplaneType": "Azure" + } + } +] \ No newline at end of file diff --git a/test/resources/examples/agentresources.yaml b/test/resources/examples/agentresources.yaml new file mode 100644 index 00000000..b222b44c --- /dev/null +++ b/test/resources/examples/agentresources.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: v1alpha1 +title: cli-agentname +name: cli-agentname +kind: DiscoveryAgent +metadata: + scope: + kind: Environment + name: env1 +tags: +spec: + dataplaneType: AWS + +--- +apiVersion: v1alpha1 +title: cli-agentname2 +name: cli-agentname2 +kind: TraceabilityAgent +metadata: + scope: + kind: Environment + name: env2 +tags: +spec: + dataplaneType: Azure diff --git a/test/resources/examples/apiserviceinstances.json b/test/resources/examples/apiserviceinstances.json new file mode 100644 index 00000000..6c420fcf --- /dev/null +++ b/test/resources/examples/apiserviceinstances.json @@ -0,0 +1,90 @@ +[ + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "APIServiceInstance", + "name": "apisvcinst1", + "title": "apisvcinst1 title", + "metadata": { + "references": [ + { + "id": "e4e0839d6efb5a5c016efc23f81703f3", + "kind": "APIServiceRevision", + "name": "apisvcrev1", + "type": "HARD" + } + ], + "audit": { + "createTimestamp": "2019-12-12T22:04:06.280+0000", + "createUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7", + "modifyTimestamp": "2019-12-12T22:04:06.280+0000", + "modifyUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7" + }, + "resourceVersion": "849", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f41d0338", + "kind": "Environment", + "name": "env1" + }, + "id": "e4e0839d6efb5a5c016efc23f90803f9" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "apisvcinst", "axway"], + "spec": { + "endpoint": [ + { + "host": "test-endpoint.com", + "port": 8080, + "protocol": "https" + } + ], + "apiServiceRevision": "apisvcrev1" + } + }, + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "APIServiceInstance", + "name": "apisvcinst2", + "title": "apisvcinst2 title", + "metadata": { + "references": [ + { + "id": "e4e0839d6efb5a5c016efc23f70d03ed", + "kind": "APIServiceRevision", + "name": "apisvcrev2", + "type": "HARD" + } + ], + "audit": { + "createTimestamp": "2019-12-12T22:04:06.581+0000", + "createUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7", + "modifyTimestamp": "2019-12-12T22:04:06.581+0000", + "modifyUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7" + }, + "resourceVersion": "850", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f3040333", + "kind": "Environment", + "name": "env2" + }, + "id": "e4e0839d6efb5a5c016efc23fa3503ff" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "apisvcinst", "axway"], + "spec": { + "endpoint": [ + { + "host": "test-endpoint2.com", + "port": 8080, + "protocol": "https" + } + ], + "apiServiceRevision": "apisvcrev2" + } + } +] diff --git a/test/resources/examples/apiserviceinstances.yaml b/test/resources/examples/apiserviceinstances.yaml new file mode 100644 index 00000000..68d2319a --- /dev/null +++ b/test/resources/examples/apiserviceinstances.yaml @@ -0,0 +1,42 @@ +--- +apiVersion: v1alpha1 +title: apisvcinst1 title +name: apisvcinst1 +kind: APIServiceInstance +metadata: + scope: + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - apisvcinst + - cli + - axway +spec: + apiServiceRevision: apisvcrev1 + endpoint: + - host: test-endpoint.com + port: 8080 + protocol: https +--- +apiVersion: v1alpha1 +title: apisvcinst2 title +name: apisvcinst2 +kind: APIServiceInstance +metadata: + scope: + kind: Environment + name: env2 +attributes: + createdBy: yaml +tags: + - apisvcinst + - cli + - axway +spec: + apiServiceRevision: apisvcrev2 + endpoint: + - host: test-endpoint2.com + port: 8080 + protocol: https diff --git a/test/resources/examples/apiserviceinstancesforproductization.json b/test/resources/examples/apiserviceinstancesforproductization.json new file mode 100644 index 00000000..42dde287 --- /dev/null +++ b/test/resources/examples/apiserviceinstancesforproductization.json @@ -0,0 +1,60 @@ +{ + "data":[ + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "APIServiceInstance", + "name": "apisvc1", + "title": "apisvcinst1 title", + "metadata": { + "scope": { + "id": "e4e0839f6efb5aa4016efc23f41d0338", + "kind": "Environment", + "name": "env1" + }, + "id": "e4e0839d6efb5a5c016efc23f90803f9" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "apisvcinst", "axway"], + "spec": { + "endpoint": [ + { + "host": "test-endpoint.com", + "port": 8080, + "protocol": "https" + } + ] + } + }, + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "APIServiceInstance", + "name": "apisvc2", + "title": "apisvcinst2 title", + "metadata": { + "scope": { + "id": "e4e0839f6efb5aa4016efc23f3040333", + "kind": "Environment", + "name": "env2" + }, + "id": "e4e0839d6efb5a5c016efc23fa3503ff" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "apisvcinst", "axway"], + "spec": { + "endpoint": [ + { + "host": "test-endpoint2.com", + "port": 8080, + "protocol": "https" + } + ] + } + } +] +} diff --git a/test/resources/examples/apiservicerevisions.json b/test/resources/examples/apiservicerevisions.json new file mode 100644 index 00000000..a4c10402 --- /dev/null +++ b/test/resources/examples/apiservicerevisions.json @@ -0,0 +1,84 @@ +[ + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "APIServiceRevision", + "name": "apisvcrev1", + "title": "apisvcrev1 title", + "metadata": { + "references": [ + { + "id": "e4e0839f6efb5aa4016efc23f607033d", + "kind": "APIService", + "name": "apisvc1", + "type": "HARD" + } + ], + "audit": { + "createTimestamp": "2019-12-12T22:04:06.040+0000", + "createUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7", + "modifyTimestamp": "2019-12-12T22:04:06.040+0000", + "modifyUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7" + }, + "resourceVersion": "848", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f41d0338", + "kind": "Environment", + "name": "env1" + }, + "id": "e4e0839d6efb5a5c016efc23f81703f3" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["apisvcrev", "cli", "axway"], + "spec": { + "apiService": "apisvc1", + "definition": { + "type": "swagger", + "value": "{'swagger':'2.0','info':{'title':'MusicalInstrumentsAPI','description':'This is a sample Musical Instruments API.','version':'2.0.2'},'host':'ec062a054a2977120b7e721801edb38ca24dfbb3.cloudapp-enterprise.appcelerator.com','basePath':'/music/v2','schemes':['http'],'consumes':['application/json','application/x-www-form-urlencoded','multipart/form-data'],'produces':['application/json','application/xml','text/yaml','text/csv','text/plain'],'paths':{'/instruments/{id}':{'get':{'responses':{'200':{'description':'The find succeeded, and the results are available.','schema':{'$ref':'#/definitions/instruments'}},'400':{'description':'Bad request.'},'401':{'description':'This request requires user authentication, as configured by the server.'},'404':{'description':'No results were found.'},'500':{'description':'Something went wrong during the request; check out the logs on your server.'}},'description':'Find instrument by ID','operationId':'FindInstrumentByID','consumes':['application/json'],'parameters':[{'name':'id','in':'path','description':'The instrument ID','required':true,'type':'string'}],'produces':['application/json'],'tags':['instruments'],'x-flow':'instruments-findByID'}},'/instruments':{'get':{'responses':{'200':{'description':'The find all succeeded, and the results are available.','schema':{'type':'array','items':{'$ref':'#/definitions/instruments'}}},'401':{'description':'This request requires user authentication, as configured by the server.'},'404':{'description':'No results were found.'},'500':{'description':'Something went wrong during the request; check out the logs on your server.'}},'description':'Retrieve all instruments','operationId':'FindInstruments','consumes':['application/json'],'produces':['application/json'],'tags':['instruments'],'x-flow':'instruments-findAll'}},'/instruments/query':{'get':{'responses':{'200':{'description':'The query succeeded, and the results are available.','schema':{'type':'array','items':{'$ref':'#/definitions/instruments'}}},'400':{'description':'Bad request.'},'401':{'description':'This request requires user authentication, as configured by the server.'},'404':{'description':'No results were found.'},'500':{'description':'Something went wrong during the request; check out the logs on your server.'}},'description':'Query instrument(s)','operationId':'QueryInstrument','consumes':['application/json'],'parameters':[{'in':'query','name':'limit','description':'The number of records to fetch. The value must be greater than 0, and no greater than 1000.','required':false,'type':'number','default':10},{'in':'query','name':'skip','description':'The number of records to skip. The value must not be less than 0.','required':false,'type':'number','default':0},{'in':'query','name':'where','description':'Constrains values for fields. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'order','description':'A dictionary of one or more fields specifying sorting of results. In general, you can sort based on any predefined field that you can query using the where operator, as well as on custom fields. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'sel','description':'Selects which fields to return from the query. Others are excluded. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'unsel','description':'Selects which fields to not return from the query. Others are included. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'page','description':'Request page number starting from 1.','required':false,'type':'number','default':1},{'in':'query','name':'per_page','description':'Number of results per page.','required':false,'type':'number','default':10}],'produces':['application/json'],'tags':['instruments'],'x-flow':'instruments-query'}}},'definitions':{'ResponseModel':{'type':'object','required':['success','request-id'],'additionalProperties':true,'properties':{'code':{'type':'integer','format':'int32'},'success':{'type':'boolean','default':false},'request-id':{'type':'string'},'message':{'type':'string'},'url':{'type':'string'}}},'ErrorModel':{'type':'object','required':['message','code','success','request-id'],'properties':{'code':{'type':'integer','format':'int32'},'success':{'type':'boolean','default':false},'request-id':{'type':'string'},'message':{'type':'string'},'url':{'type':'string'}}},'instruments':{'type':'object','properties':{'type':{'description':'The type of instrument.','type':'string'},'price':{'description':'The price of the instrument.','type':'integer'},'currency':{'description':'The price currency.','type':'string'}}}}}" + } + } + }, + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "APIServiceRevision", + "name": "apisvcrev2", + "title": "apisvcrev2 title", + "metadata": { + "references": [ + { + "id": "e4e0839d6efb5a5c016efc23f51603e8", + "kind": "APIService", + "name": "apisvc2", + "type": "HARD" + } + ], + "audit": { + "createTimestamp": "2019-12-12T22:04:05.774+0000", + "createUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7", + "modifyTimestamp": "2019-12-12T22:04:05.774+0000", + "modifyUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7" + }, + "resourceVersion": "847", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f3040333", + "kind": "Environment", + "name": "env2" + }, + "id": "e4e0839d6efb5a5c016efc23f70d03ed" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["apisvcrev", "cli", "axway"], + "spec": { + "apiService": "apisvc2", + "definition": { + "type": "swagger", + "value": "{'swagger':'2.0','info':{'title':'MusicalInstrumentsAPI','description':'This is a sample Musical Instruments API.','version':'2.0.2'},'host':'ec062a054a2977120b7e721801edb38ca24dfbb3.cloudapp-enterprise.appcelerator.com','basePath':'/music/v2','schemes':['http'],'consumes':['application/json','application/x-www-form-urlencoded','multipart/form-data'],'produces':['application/json','application/xml','text/yaml','text/csv','text/plain'],'paths':{'/instruments/{id}':{'get':{'responses':{'200':{'description':'The find succeeded, and the results are available.','schema':{'$ref':'#/definitions/instruments'}},'400':{'description':'Bad request.'},'401':{'description':'This request requires user authentication, as configured by the server.'},'404':{'description':'No results were found.'},'500':{'description':'Something went wrong during the request; check out the logs on your server.'}},'description':'Find instrument by ID','operationId':'FindInstrumentByID','consumes':['application/json'],'parameters':[{'name':'id','in':'path','description':'The instrument ID','required':true,'type':'string'}],'produces':['application/json'],'tags':['instruments'],'x-flow':'instruments-findByID'}},'/instruments':{'get':{'responses':{'200':{'description':'The find all succeeded, and the results are available.','schema':{'type':'array','items':{'$ref':'#/definitions/instruments'}}},'401':{'description':'This request requires user authentication, as configured by the server.'},'404':{'description':'No results were found.'},'500':{'description':'Something went wrong during the request; check out the logs on your server.'}},'description':'Retrieve all instruments','operationId':'FindInstruments','consumes':['application/json'],'produces':['application/json'],'tags':['instruments'],'x-flow':'instruments-findAll'}},'/instruments/query':{'get':{'responses':{'200':{'description':'The query succeeded, and the results are available.','schema':{'type':'array','items':{'$ref':'#/definitions/instruments'}}},'400':{'description':'Bad request.'},'401':{'description':'This request requires user authentication, as configured by the server.'},'404':{'description':'No results were found.'},'500':{'description':'Something went wrong during the request; check out the logs on your server.'}},'description':'Query instrument(s)','operationId':'QueryInstrument','consumes':['application/json'],'parameters':[{'in':'query','name':'limit','description':'The number of records to fetch. The value must be greater than 0, and no greater than 1000.','required':false,'type':'number','default':10},{'in':'query','name':'skip','description':'The number of records to skip. The value must not be less than 0.','required':false,'type':'number','default':0},{'in':'query','name':'where','description':'Constrains values for fields. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'order','description':'A dictionary of one or more fields specifying sorting of results. In general, you can sort based on any predefined field that you can query using the where operator, as well as on custom fields. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'sel','description':'Selects which fields to return from the query. Others are excluded. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'unsel','description':'Selects which fields to not return from the query. Others are included. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'page','description':'Request page number starting from 1.','required':false,'type':'number','default':1},{'in':'query','name':'per_page','description':'Number of results per page.','required':false,'type':'number','default':10}],'produces':['application/json'],'tags':['instruments'],'x-flow':'instruments-query'}}},'definitions':{'ResponseModel':{'type':'object','required':['success','request-id'],'additionalProperties':true,'properties':{'code':{'type':'integer','format':'int32'},'success':{'type':'boolean','default':false},'request-id':{'type':'string'},'message':{'type':'string'},'url':{'type':'string'}}},'ErrorModel':{'type':'object','required':['message','code','success','request-id'],'properties':{'code':{'type':'integer','format':'int32'},'success':{'type':'boolean','default':false},'request-id':{'type':'string'},'message':{'type':'string'},'url':{'type':'string'}}},'instruments':{'type':'object','properties':{'type':{'description':'The type of instrument.','type':'string'},'price':{'description':'The price of the instrument.','type':'integer'},'currency':{'description':'The price currency.','type':'string'}}}}}" + } + } + } +] diff --git a/test/resources/examples/apiservicerevisions.yaml b/test/resources/examples/apiservicerevisions.yaml new file mode 100644 index 00000000..f807c0be --- /dev/null +++ b/test/resources/examples/apiservicerevisions.yaml @@ -0,0 +1,54 @@ +--- +apiVersion: v1alpha1 +title: apisvcrev1 title +name: apisvcrev1 +kind: APIServiceRevision +metadata: + audit: + createTimestamp: "2019-12-04T23:50:43.100+0000" + createUserId: "DOSA_00000000000000000000000000000001" + modifyTimestamp: "2019-12-04T23:50:43.100+0000" + modifyUserId: "DOSA_00000000000000000000000000000001" + resourceVersion: "156" + scope: + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - apisvcrev + - cli + - axway +spec: + apiService: apisvc1 + definition: + type: oas2 + value: "{'swagger':'2.0','info':{'title':'MusicalInstrumentsAPI','description':'This is a sample Musical Instruments API.','version':'2.0.2'},'host':'ec062a054a2977120b7e721801edb38ca24dfbb3.cloudapp-enterprise.appcelerator.com','basePath':'/music/v2','schemes':['http'],'consumes':['application/json','application/x-www-form-urlencoded','multipart/form-data'],'produces':['application/json','application/xml','text/yaml','text/csv','text/plain'],'paths':{'/instruments/{id}':{'get':{'responses':{'200':{'description':'The find succeeded, and the results are available.','schema':{'$ref':'#/definitions/instruments'}},'400':{'description':'Bad request.'},'401':{'description':'This request requires user authentication, as configured by the server.'},'404':{'description':'No results were found.'},'500':{'description':'Something went wrong during the request; check out the logs on your server.'}},'description':'Find instrument by ID','operationId':'FindInstrumentByID','consumes':['application/json'],'parameters':[{'name':'id','in':'path','description':'The instrument ID','required':true,'type':'string'}],'produces':['application/json'],'tags':['instruments'],'x-flow':'instruments-findByID'}},'/instruments':{'get':{'responses':{'200':{'description':'The find all succeeded, and the results are available.','schema':{'type':'array','items':{'$ref':'#/definitions/instruments'}}},'401':{'description':'This request requires user authentication, as configured by the server.'},'404':{'description':'No results were found.'},'500':{'description':'Something went wrong during the request; check out the logs on your server.'}},'description':'Retrieve all instruments','operationId':'FindInstruments','consumes':['application/json'],'produces':['application/json'],'tags':['instruments'],'x-flow':'instruments-findAll'}},'/instruments/query':{'get':{'responses':{'200':{'description':'The query succeeded, and the results are available.','schema':{'type':'array','items':{'$ref':'#/definitions/instruments'}}},'400':{'description':'Bad request.'},'401':{'description':'This request requires user authentication, as configured by the server.'},'404':{'description':'No results were found.'},'500':{'description':'Something went wrong during the request; check out the logs on your server.'}},'description':'Query instrument(s)','operationId':'QueryInstrument','consumes':['application/json'],'parameters':[{'in':'query','name':'limit','description':'The number of records to fetch. The value must be greater than 0, and no greater than 1000.','required':false,'type':'number','default':10},{'in':'query','name':'skip','description':'The number of records to skip. The value must not be less than 0.','required':false,'type':'number','default':0},{'in':'query','name':'where','description':'Constrains values for fields. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'order','description':'A dictionary of one or more fields specifying sorting of results. In general, you can sort based on any predefined field that you can query using the where operator, as well as on custom fields. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'sel','description':'Selects which fields to return from the query. Others are excluded. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'unsel','description':'Selects which fields to not return from the query. Others are included. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'page','description':'Request page number starting from 1.','required':false,'type':'number','default':1},{'in':'query','name':'per_page','description':'Number of results per page.','required':false,'type':'number','default':10}],'produces':['application/json'],'tags':['instruments'],'x-flow':'instruments-query'}}},'definitions':{'ResponseModel':{'type':'object','required':['success','request-id'],'additionalProperties':true,'properties':{'code':{'type':'integer','format':'int32'},'success':{'type':'boolean','default':false},'request-id':{'type':'string'},'message':{'type':'string'},'url':{'type':'string'}}},'ErrorModel':{'type':'object','required':['message','code','success','request-id'],'properties':{'code':{'type':'integer','format':'int32'},'success':{'type':'boolean','default':false},'request-id':{'type':'string'},'message':{'type':'string'},'url':{'type':'string'}}},'instruments':{'type':'object','properties':{'type':{'description':'The type of instrument.','type':'string'},'price':{'description':'The price of the instrument.','type':'integer'},'currency':{'description':'The price currency.','type':'string'}}}}}" + +--- +apiVersion: v1alpha1 +group: management +title: apisvcrev2 title +name: apisvcrev2 +kind: APIServiceRevision +metadata: + audit: + createTimestamp: "2019-12-04T23:50:43.100+0000" + createUserId: "DOSA_00000000000000000000000000000001" + modifyTimestamp: "2019-12-04T23:50:43.100+0000" + modifyUserId: "DOSA_00000000000000000000000000000001" + resourceVersion: "156" + scope: + kind: Environment + name: env2 +attributes: + createdBy: yaml +tags: + - apisvcrev + - cli + - axway +spec: + apiService: apisvc2 + definition: + type: oas2 + value: "{'swagger':'2.0','info':{'title':'MusicalInstrumentsAPI','description':'This is a sample Musical Instruments API.','version':'2.0.2'},'host':'ec062a054a2977120b7e721801edb38ca24dfbb3.cloudapp-enterprise.appcelerator.com','basePath':'/music/v2','schemes':['http'],'consumes':['application/json','application/x-www-form-urlencoded','multipart/form-data'],'produces':['application/json','application/xml','text/yaml','text/csv','text/plain'],'paths':{'/instruments/{id}':{'get':{'responses':{'200':{'description':'The find succeeded, and the results are available.','schema':{'$ref':'#/definitions/instruments'}},'400':{'description':'Bad request.'},'401':{'description':'This request requires user authentication, as configured by the server.'},'404':{'description':'No results were found.'},'500':{'description':'Something went wrong during the request; check out the logs on your server.'}},'description':'Find instrument by ID','operationId':'FindInstrumentByID','consumes':['application/json'],'parameters':[{'name':'id','in':'path','description':'The instrument ID','required':true,'type':'string'}],'produces':['application/json'],'tags':['instruments'],'x-flow':'instruments-findByID'}},'/instruments':{'get':{'responses':{'200':{'description':'The find all succeeded, and the results are available.','schema':{'type':'array','items':{'$ref':'#/definitions/instruments'}}},'401':{'description':'This request requires user authentication, as configured by the server.'},'404':{'description':'No results were found.'},'500':{'description':'Something went wrong during the request; check out the logs on your server.'}},'description':'Retrieve all instruments','operationId':'FindInstruments','consumes':['application/json'],'produces':['application/json'],'tags':['instruments'],'x-flow':'instruments-findAll'}},'/instruments/query':{'get':{'responses':{'200':{'description':'The query succeeded, and the results are available.','schema':{'type':'array','items':{'$ref':'#/definitions/instruments'}}},'400':{'description':'Bad request.'},'401':{'description':'This request requires user authentication, as configured by the server.'},'404':{'description':'No results were found.'},'500':{'description':'Something went wrong during the request; check out the logs on your server.'}},'description':'Query instrument(s)','operationId':'QueryInstrument','consumes':['application/json'],'parameters':[{'in':'query','name':'limit','description':'The number of records to fetch. The value must be greater than 0, and no greater than 1000.','required':false,'type':'number','default':10},{'in':'query','name':'skip','description':'The number of records to skip. The value must not be less than 0.','required':false,'type':'number','default':0},{'in':'query','name':'where','description':'Constrains values for fields. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'order','description':'A dictionary of one or more fields specifying sorting of results. In general, you can sort based on any predefined field that you can query using the where operator, as well as on custom fields. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'sel','description':'Selects which fields to return from the query. Others are excluded. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'unsel','description':'Selects which fields to not return from the query. Others are included. The value should be encoded JSON.','required':false,'type':'string','format':'json'},{'in':'query','name':'page','description':'Request page number starting from 1.','required':false,'type':'number','default':1},{'in':'query','name':'per_page','description':'Number of results per page.','required':false,'type':'number','default':10}],'produces':['application/json'],'tags':['instruments'],'x-flow':'instruments-query'}}},'definitions':{'ResponseModel':{'type':'object','required':['success','request-id'],'additionalProperties':true,'properties':{'code':{'type':'integer','format':'int32'},'success':{'type':'boolean','default':false},'request-id':{'type':'string'},'message':{'type':'string'},'url':{'type':'string'}}},'ErrorModel':{'type':'object','required':['message','code','success','request-id'],'properties':{'code':{'type':'integer','format':'int32'},'success':{'type':'boolean','default':false},'request-id':{'type':'string'},'message':{'type':'string'},'url':{'type':'string'}}},'instruments':{'type':'object','properties':{'type':{'description':'The type of instrument.','type':'string'},'price':{'description':'The price of the instrument.','type':'integer'},'currency':{'description':'The price currency.','type':'string'}}}}}" diff --git a/test/resources/examples/apiservices.json b/test/resources/examples/apiservices.json new file mode 100644 index 00000000..869b848b --- /dev/null +++ b/test/resources/examples/apiservices.json @@ -0,0 +1,60 @@ +[ + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "APIService", + "name": "apisvc1", + "title": "apisvc1 title", + "metadata": { + "audit": { + "createTimestamp": "2019-12-04T23:50:43.100+0000", + "createUserId": "DOSA_00000000000000000000000000000001", + "modifyTimestamp": "2019-12-04T23:50:43.100+0000", + "modifyUserId": "DOSA_00000000000000000000000000000001" + }, + "resourceVersion": "846", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f41d0338", + "kind": "Environment", + "name": "env1" + }, + "id": "e4e0839f6efb5aa4016efc23f607033d" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["apisvc", "cli", "axway"], + "spec": { + "description": "api service 1 description" + } + }, + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "APIService", + "name": "apisvc2", + "title": "apisvc2 title", + "metadata": { + "audit": { + "createTimestamp": "2019-12-04T23:50:43.100+0000", + "createUserId": "DOSA_00000000000000000000000000000001", + "modifyTimestamp": "2019-12-04T23:50:43.100+0000", + "modifyUserId": "DOSA_00000000000000000000000000000001" + }, + "resourceVersion": "845", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f3040333", + "kind": "Environment", + "name": "env2" + }, + "id": "e4e0839d6efb5a5c016efc23f51603e8" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["apisvc", "cli", "axway"], + "spec": { + "description": "api service 2 description" + } + } +] diff --git a/test/resources/examples/apiservices.yaml b/test/resources/examples/apiservices.yaml new file mode 100644 index 00000000..5413324e --- /dev/null +++ b/test/resources/examples/apiservices.yaml @@ -0,0 +1,36 @@ +--- +apiVersion: v1alpha1 +title: apisvc1 title +name: apisvc1 +kind: APIService +metadata: + scope: + id: "e4e086956ec6923b016ed352b2cf01f4" + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - apisvc + - cli + - axway +spec: + description: api service 1 description + +--- +apiVersion: v1alpha1 +title: apisvc2 title +name: apisvc2 +kind: APIService +attributes: + createdBy: yaml +tags: + - axway + - cli + - apisvc +metadata: + scope: + kind: Environment + name: env2 +spec: + description: api service 2 description diff --git a/test/resources/examples/apisvcincorrect.json b/test/resources/examples/apisvcincorrect.json new file mode 100644 index 00000000..010338c8 --- /dev/null +++ b/test/resources/examples/apisvcincorrect.json @@ -0,0 +1,31 @@ +[ +{ + "apiVersion": "v1alpha1", + "group": "management", + "kind": "APIService", + "name": "apisvc1", + "title": "apisvc1 title", + "metadata": { + "audit": { + "createTimestamp": "2019-12-04T23:50:43.100+0000", + "createUserId": "DOSA_00000000000000000000000000000001", + "modifyTimestamp": "2019-12-04T23:50:43.100+0000", + "modifyUserId": "DOSA_00000000000000000000000000000001" + }, + "resourceVersion": "846", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f41d0338", + "kind": "Environment", + "name": "env1" + }, + "id": "e4e0839f6efb5aa4016efc23f607033d" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["apisvc", "cli", "axway"], + "spec": { + "description": "api service 1 description" + } +} +] \ No newline at end of file diff --git a/test/resources/examples/assetmappingforproductization.json b/test/resources/examples/assetmappingforproductization.json new file mode 100644 index 00000000..c35e0a1a --- /dev/null +++ b/test/resources/examples/assetmappingforproductization.json @@ -0,0 +1,40 @@ +[ + { + "apiVersion": "v1alpha1", + "kind": "AssetMapping", + "name": "apisvc1", + "title": "music", + "tags": [], + "metadata": { + "scope": { + "kind": "Asset", + "name": "apisvc1" + } + }, + "spec": { + "inputs": { + "apiService": "apisvc1", + "apiServiceInstance": "apisvc1" + } + } + }, + { + "apiVersion": "v1alpha1", + "kind": "AssetMapping", + "name": "apisvc2", + "title": "music", + "tags": [], + "metadata": { + "scope": { + "kind": "Asset", + "name": "apisvc2" + } + }, + "spec": { + "inputs": { + "apiService": "apisvc2", + "apiServiceInstance": "apisvc2" + } + } + } +] \ No newline at end of file diff --git a/test/resources/examples/assetreleasetags.json b/test/resources/examples/assetreleasetags.json new file mode 100644 index 00000000..b61ea703 --- /dev/null +++ b/test/resources/examples/assetreleasetags.json @@ -0,0 +1,17 @@ +{ + "data": [{ + "name": "apisvc1", + "kind": "ReleaseTag", + "status": { + "level": "Success" + } + }, + { + "name": "apisvc2", + "kind": "ReleaseTag", + "status": { + "level": "Success" + } + } +] +} \ No newline at end of file diff --git a/test/resources/examples/assets-complete.yaml b/test/resources/examples/assets-complete.yaml new file mode 100644 index 00000000..b004b86e --- /dev/null +++ b/test/resources/examples/assets-complete.yaml @@ -0,0 +1,144 @@ +--- +apiVersion: v1alpha1 +kind: AssetRelease +name: asset1 +title: title +tags: [] +spec: + asset: asset1 + type: random + version: "0.1.0" + +--- +apiVersion: v1alpha1 +kind: Asset +name: asset1 +title: title +tags: [] +spec: {} + +--- +apiVersion: v1alpha1 +kind: Stage +name: stage1 +title: title +tags: [] +spec: {} + +--- +apiVersion: v1alpha1 +kind: AssetRequestDefinition +name: assetrd1 +title: title +metadata: + scope: + kind: Asset + name: asset1 +spec: + schema: "test" + +--- +apiVersion: v1alpha1 +kind: ReleaseTag +name: releasetag1 +title: title +metadata: + scope: + kind: Asset + name: asset1 +spec: + releaseType: "minor" + +--- +apiVersion: v1alpha1 +kind: AssetResource +name: assetresource1 +title: title +metadata: + scope: + kind: Asset + name: asset1 +spec: + stage: stage1 + type: "oas2" + +--- +apiVersion: v1alpha1 +kind: AssetResource +name: assetresource1 +title: title +metadata: + scope: + kind: AssetRelease + name: asset1 +spec: + stage: stage1 + type: "oas2" + +--- +apiVersion: v1alpha1 +kind: Document +name: doc1 +title: title +metadata: + scope: + kind: Asset + name: asset1 +spec: + stages: + - stage1 + content: test + +--- +apiVersion: v1alpha1 +kind: Document +name: doc1 +title: title +metadata: + scope: + kind: AssetRelease + name: asset1 +spec: + stages: + - stage1 + content: test + + +# NOTE: resources below are still WIP so can be un-commented when ready +# --- +# apiVersion: v1alpha1 +# kind: AssetRequestDefinition +# name: assetrd1 +# title: title +# metadata: +# scope: +# kind: AssetRelease +# name: asset1 +# spec: +# schema: "test" + +# --- +# apiVersion: v1alpha1 +# kind: AssetRequest +# name: assetrequest1 +# title: title +# metadata: +# scope: +# kind: AssetRelease +# name: asset1 +# spec: +# stage: stage1 +# type: "oas2" + +# --- +# apiVersion: v1alpha1 +# kind: AssetRequest +# name: assetrequest1 +# title: title +# metadata: +# scope: +# kind: Asset +# name: asset1 +# spec: +# stage: stage1 +# type: "oas2" diff --git a/test/resources/examples/assets-mapping.yaml b/test/resources/examples/assets-mapping.yaml new file mode 100644 index 00000000..c8d4786f --- /dev/null +++ b/test/resources/examples/assets-mapping.yaml @@ -0,0 +1,93 @@ +--- +apiVersion: v1alpha1 +title: test Environment +name: staging +kind: Environment +attributes: + createdBy: yaml + randomNum: 1 +tags: + - axway + - cli +spec: + description: test Environment + +--- +apiVersion: v1alpha1 +kind: APIService +name: music +title: music +tags: [] +metadata: + scope: + kind: Environment + name: staging +spec: + description: additional descriptions + icon: + data: >- + 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 + contentType: image/jpeg + +--- +apiVersion: v1alpha1 +kind: APIServiceRevision +name: 1.0.0 +metadata: + scope: + kind: Environment + name: staging +spec: + apiService: music + definition: + type: oas2 + value: >- + ewogICAic3dhZ2dlciI6ICIyLjAiLAogICAiaW5mbyI6IHsKICAgICAgInRpdGxlIjogIk11c2ljYWxJbnN0cnVtZW50c0FQSSIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJUaGlzIGlzIGEgc2FtcGxlIE11c2ljYWwgSW5zdHJ1bWVudHMgQVBJLiIsCiAgICAgICJ2ZXJzaW9uIjogIjIuMC40IgogICB9LAogICAiaG9zdCI6ICJmMGQ5YzA2N2JlNjJkYzlhZGViNDRiNTdiYzBlZWFhNjAxNjMxYjQ3LmNsb3VkYXBwLWVudGVycHJpc2UuYXBwY2VsZXJhdG9yLmNvbSIsCiAgICJiYXNlUGF0aCI6ICIvbXVzaWMvdjIiLAogICAic2NoZW1lcyI6IFsKICAgICAgImh0dHAiCiAgIF0sCiAgICJjb25zdW1lcyI6IFsKICAgICAgImFwcGxpY2F0aW9uL2pzb24iCiAgIF0sCiAgICJwcm9kdWNlcyI6IFsKICAgICAgImFwcGxpY2F0aW9uL2pzb24iCiAgIF0sCiAgICJwYXRocyI6IHsKICAgICAgIi9pbnN0cnVtZW50cy97aWR9IjogewogICAgICAgICAiZ2V0IjogewogICAgICAgICAgICAicmVzcG9uc2VzIjogewogICAgICAgICAgICAgICAiMjAwIjogewogICAgICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiVGhlIGZpbmQgc3VjY2VlZGVkLCBhbmQgdGhlIHJlc3VsdHMgYXJlIGF2YWlsYWJsZS4iLAogICAgICAgICAgICAgICAgICAic2NoZW1hIjogewogICAgICAgICAgICAgICAgICAgICAiJHJlZiI6ICIjL2RlZmluaXRpb25zL2luc3RydW1lbnRzIgogICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICI0MDAiOiB7CiAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJCYWQgcmVxdWVzdC4iCiAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICI0MDEiOiB7CiAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJUaGlzIHJlcXVlc3QgcmVxdWlyZXMgdXNlciBhdXRoZW50aWNhdGlvbiwgYXMgY29uZmlndXJlZCBieSB0aGUgc2VydmVyLiIKICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIjQwNCI6IHsKICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIk5vIHJlc3VsdHMgd2VyZSBmb3VuZC4iCiAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICI1MDAiOiB7CiAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTb21ldGhpbmcgd2VudCB3cm9uZyBkdXJpbmcgdGhlIHJlcXVlc3Q7IGNoZWNrIG91dCB0aGUgbG9ncyBvbiB5b3VyIHNlcnZlci4iCiAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIkZpbmQgaW5zdHJ1bWVudCBieSBJRCIsCiAgICAgICAgICAgICJvcGVyYXRpb25JZCI6ICJGaW5kSW5zdHJ1bWVudEJ5SUQiLAogICAgICAgICAgICAiY29uc3VtZXMiOiBbCiAgICAgICAgICAgICAgICJhcHBsaWNhdGlvbi9qc29uIgogICAgICAgICAgICBdLAogICAgICAgICAgICAicGFyYW1ldGVycyI6IFsKICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibmFtZSI6ICJpZCIsCiAgICAgICAgICAgICAgICAgICJpbiI6ICJwYXRoIiwKICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIlRoZSBpbnN0cnVtZW50IElEIiwKICAgICAgICAgICAgICAgICAgInJlcXVpcmVkIjogdHJ1ZSwKICAgICAgICAgICAgICAgICAgInR5cGUiOiAic3RyaW5nIgogICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJwcm9kdWNlcyI6IFsKICAgICAgICAgICAgICAgImFwcGxpY2F0aW9uL2pzb24iCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJ0YWdzIjogWwogICAgICAgICAgICAgICAiaW5zdHJ1bWVudHMiCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJ4LWZsb3ciOiAiaW5zdHJ1bWVudHMtZmluZEJ5SUQiCiAgICAgICAgIH0KICAgICAgfSwKICAgICAgIi9pbnN0cnVtZW50cyI6IHsKICAgICAgICAgImdldCI6IHsKICAgICAgICAgICAgInJlc3BvbnNlcyI6IHsKICAgICAgICAgICAgICAgIjIwMCI6IHsKICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIlRoZSBmaW5kIGFsbCBzdWNjZWVkZWQsIGFuZCB0aGUgcmVzdWx0cyBhcmUgYXZhaWxhYmxlLiIsCiAgICAgICAgICAgICAgICAgICJzY2hlbWEiOiB7CiAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogImFycmF5IiwKICAgICAgICAgICAgICAgICAgICAgIml0ZW1zIjogewogICAgICAgICAgICAgICAgICAgICAgICAiJHJlZiI6ICIjL2RlZmluaXRpb25zL2luc3RydW1lbnRzIgogICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIjQwMSI6IHsKICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIlRoaXMgcmVxdWVzdCByZXF1aXJlcyB1c2VyIGF1dGhlbnRpY2F0aW9uLCBhcyBjb25maWd1cmVkIGJ5IHRoZSBzZXJ2ZXIuIgogICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAiNDA0IjogewogICAgICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiTm8gcmVzdWx0cyB3ZXJlIGZvdW5kLiIKICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIjUwMCI6IHsKICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNvbWV0aGluZyB3ZW50IHdyb25nIGR1cmluZyB0aGUgcmVxdWVzdDsgY2hlY2sgb3V0IHRoZSBsb2dzIG9uIHlvdXIgc2VydmVyLiIKICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9LAogICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiUmV0cmlldmUgYWxsIGluc3RydW1lbnRzIiwKICAgICAgICAgICAgIm9wZXJhdGlvbklkIjogIkZpbmRJbnN0cnVtZW50cyIsCiAgICAgICAgICAgICJjb25zdW1lcyI6IFsKICAgICAgICAgICAgICAgImFwcGxpY2F0aW9uL2pzb24iCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJwcm9kdWNlcyI6IFsKICAgICAgICAgICAgICAgImFwcGxpY2F0aW9uL2pzb24iCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJ0YWdzIjogWwogICAgICAgICAgICAgICAiaW5zdHJ1bWVudHMiCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJ4LWZsb3ciOiAiaW5zdHJ1bWVudHMtZmluZEFsbCIKICAgICAgICAgfQogICAgICB9LAogICAgICAiL2luc3RydW1lbnRzL3F1ZXJ5IjogewogICAgICAgICAiZ2V0IjogewogICAgICAgICAgICAicmVzcG9uc2VzIjogewogICAgICAgICAgICAgICAiMjAwIjogewogICAgICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiVGhlIHF1ZXJ5IHN1Y2NlZWRlZCwgYW5kIHRoZSByZXN1bHRzIGFyZSBhdmFpbGFibGUuIiwKICAgICAgICAgICAgICAgICAgInNjaGVtYSI6IHsKICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiYXJyYXkiLAogICAgICAgICAgICAgICAgICAgICAiaXRlbXMiOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICIkcmVmIjogIiMvZGVmaW5pdGlvbnMvaW5zdHJ1bWVudHMiCiAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAiNDAwIjogewogICAgICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiQmFkIHJlcXVlc3QuIgogICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAiNDAxIjogewogICAgICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiVGhpcyByZXF1ZXN0IHJlcXVpcmVzIHVzZXIgYXV0aGVudGljYXRpb24sIGFzIGNvbmZpZ3VyZWQgYnkgdGhlIHNlcnZlci4iCiAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICI0MDQiOiB7CiAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJObyByZXN1bHRzIHdlcmUgZm91bmQuIgogICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAiNTAwIjogewogICAgICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU29tZXRoaW5nIHdlbnQgd3JvbmcgZHVyaW5nIHRoZSByZXF1ZXN0OyBjaGVjayBvdXQgdGhlIGxvZ3Mgb24geW91ciBzZXJ2ZXIuIgogICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJRdWVyeSBpbnN0cnVtZW50KHMpIiwKICAgICAgICAgICAgIm9wZXJhdGlvbklkIjogIlF1ZXJ5SW5zdHJ1bWVudCIsCiAgICAgICAgICAgICJjb25zdW1lcyI6IFsKICAgICAgICAgICAgICAgImFwcGxpY2F0aW9uL2pzb24iCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJwYXJhbWV0ZXJzIjogWwogICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJpbiI6ICJxdWVyeSIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogImxpbWl0IiwKICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIlRoZSBudW1iZXIgb2YgcmVjb3JkcyB0byBmZXRjaC4gVGhlIHZhbHVlIG11c3QgYmUgZ3JlYXRlciB0aGFuIDAsIGFuZCBubyBncmVhdGVyIHRoYW4gMTAwMC4iLAogICAgICAgICAgICAgICAgICAicmVxdWlyZWQiOiBmYWxzZSwKICAgICAgICAgICAgICAgICAgInR5cGUiOiAibnVtYmVyIiwKICAgICAgICAgICAgICAgICAgImRlZmF1bHQiOiAxMAogICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJpbiI6ICJxdWVyeSIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogInNraXAiLAogICAgICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiVGhlIG51bWJlciBvZiByZWNvcmRzIHRvIHNraXAuIFRoZSB2YWx1ZSBtdXN0IG5vdCBiZSBsZXNzIHRoYW4gMC4iLAogICAgICAgICAgICAgICAgICAicmVxdWlyZWQiOiBmYWxzZSwKICAgICAgICAgICAgICAgICAgInR5cGUiOiAibnVtYmVyIiwKICAgICAgICAgICAgICAgICAgImRlZmF1bHQiOiAwCiAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgImluIjogInF1ZXJ5IiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAid2hlcmUiLAogICAgICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiQ29uc3RyYWlucyB2YWx1ZXMgZm9yIGZpZWxkcy4gVGhlIHZhbHVlIHNob3VsZCBiZSBlbmNvZGVkIEpTT04uIiwKICAgICAgICAgICAgICAgICAgInJlcXVpcmVkIjogZmFsc2UsCiAgICAgICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAgICAgICAgICJmb3JtYXQiOiAianNvbiIKICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAiaW4iOiAicXVlcnkiLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJvcmRlciIsCiAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJBIGRpY3Rpb25hcnkgb2Ygb25lIG9yIG1vcmUgZmllbGRzIHNwZWNpZnlpbmcgc29ydGluZyBvZiByZXN1bHRzLiBJbiBnZW5lcmFsLCB5b3UgY2FuIHNvcnQgYmFzZWQgb24gYW55IHByZWRlZmluZWQgZmllbGQgdGhhdCB5b3UgY2FuIHF1ZXJ5IHVzaW5nIHRoZSB3aGVyZSBvcGVyYXRvciwgYXMgd2VsbCBhcyBvbiBjdXN0b20gZmllbGRzLiBUaGUgdmFsdWUgc2hvdWxkIGJlIGVuY29kZWQgSlNPTi4iLAogICAgICAgICAgICAgICAgICAicmVxdWlyZWQiOiBmYWxzZSwKICAgICAgICAgICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICAgICAgICAgImZvcm1hdCI6ICJqc29uIgogICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJpbiI6ICJxdWVyeSIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogInNlbCIsCiAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTZWxlY3RzIHdoaWNoIGZpZWxkcyB0byByZXR1cm4gZnJvbSB0aGUgcXVlcnkuIE90aGVycyBhcmUgZXhjbHVkZWQuIFRoZSB2YWx1ZSBzaG91bGQgYmUgZW5jb2RlZCBKU09OLiIsCiAgICAgICAgICAgICAgICAgICJyZXF1aXJlZCI6IGZhbHNlLAogICAgICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgICAgICAgICAiZm9ybWF0IjogImpzb24iCiAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgImluIjogInF1ZXJ5IiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAidW5zZWwiLAogICAgICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2VsZWN0cyB3aGljaCBmaWVsZHMgdG8gbm90IHJldHVybiBmcm9tIHRoZSBxdWVyeS4gT3RoZXJzIGFyZSBpbmNsdWRlZC4gVGhlIHZhbHVlIHNob3VsZCBiZSBlbmNvZGVkIEpTT04uIiwKICAgICAgICAgICAgICAgICAgInJlcXVpcmVkIjogZmFsc2UsCiAgICAgICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAgICAgICAgICJmb3JtYXQiOiAianNvbiIKICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAiaW4iOiAicXVlcnkiLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJwYWdlIiwKICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIlJlcXVlc3QgcGFnZSBudW1iZXIgc3RhcnRpbmcgZnJvbSAxLiIsCiAgICAgICAgICAgICAgICAgICJyZXF1aXJlZCI6IGZhbHNlLAogICAgICAgICAgICAgICAgICAidHlwZSI6ICJudW1iZXIiLAogICAgICAgICAgICAgICAgICAiZGVmYXVsdCI6IDEKICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAiaW4iOiAicXVlcnkiLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJwZXJfcGFnZSIsCiAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJOdW1iZXIgb2YgcmVzdWx0cyBwZXIgcGFnZS4iLAogICAgICAgICAgICAgICAgICAicmVxdWlyZWQiOiBmYWxzZSwKICAgICAgICAgICAgICAgICAgInR5cGUiOiAibnVtYmVyIiwKICAgICAgICAgICAgICAgICAgImRlZmF1bHQiOiAxMAogICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJwcm9kdWNlcyI6IFsKICAgICAgICAgICAgICAgImFwcGxpY2F0aW9uL2pzb24iCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJ0YWdzIjogWwogICAgICAgICAgICAgICAiaW5zdHJ1bWVudHMiCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJ4LWZsb3ciOiAiaW5zdHJ1bWVudHMtcXVlcnkiCiAgICAgICAgIH0KICAgICAgfQogICB9LAogICAiZGVmaW5pdGlvbnMiOiB7CiAgICAgICJpbnN0cnVtZW50cyI6IHsKICAgICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJ0eXBlIjogewogICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiVGhlIHR5cGUgb2YgaW5zdHJ1bWVudC4iLAogICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJwcmljZSI6IHsKICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIlRoZSBwcmljZSBvZiB0aGUgaW5zdHJ1bWVudC4iLAogICAgICAgICAgICAgICAidHlwZSI6ICJpbnRlZ2VyIgogICAgICAgICAgICB9LAogICAgICAgICAgICAiY3VycmVuY3kiOiB7CiAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJUaGUgcHJpY2UgY3VycmVuY3kuIiwKICAgICAgICAgICAgICAgInR5cGUiOiAic3RyaW5nIgogICAgICAgICAgICB9CiAgICAgICAgIH0KICAgICAgfQogICB9Cn0= + +--- +apiVersion: v1alpha1 +kind: APIServiceInstance +name: demotest1 +metadata: + scope: + kind: Environment + name: staging +spec: + apiServiceRevision: 1.0.0 + endpoint: + - host: music.us-e2.cloudhub.io + protocol: http + routing: + basePath: / + +--- +apiVersion: v1alpha1 +kind: AssetMappingTemplate +name: template +title: music +tags: [] +metadata: + scope: + kind: Environment + name: staging +spec: + filters: [] + +--- +apiVersion: v1alpha1 +kind: AssetMapping +name: demotestmapping3 +title: music +tags: [] +metadata: + scope: + kind: Environment + name: staging +spec: + inputs: + apiService: music + apiServiceRevision: 1.0.0 + apiServiceInstance: demotest1 + stage: qa + asset: music + assetMappingTemplate: template diff --git a/test/resources/examples/assetsforProductization.json b/test/resources/examples/assetsforProductization.json new file mode 100644 index 00000000..a8e75d28 --- /dev/null +++ b/test/resources/examples/assetsforProductization.json @@ -0,0 +1,37 @@ +[ + { + "apiVersion": "v1alpha1", + "kind": "Asset", + "name": "apisvc1", + "title": "title", + "tags": [], + "spec": { + "type": "API", + "autoRelease": { + "releaseType": "patch", + "requiresInitialActivation": true + } + }, + "access": { + "approval": "automatic" + } + }, + + { + "apiVersion": "v1alpha1", + "kind": "Asset", + "name": "apisvc2", + "title": "title", + "tags": [], + "spec": { + "type": "API", + "autoRelease": { + "releaseType": "patch", + "requiresInitialActivation": true + } + }, + "access": { + "approval": "automatic" + } + } +] \ No newline at end of file diff --git a/test/resources/examples/completeExample.yaml b/test/resources/examples/completeExample.yaml new file mode 100644 index 00000000..a8cf0e5c --- /dev/null +++ b/test/resources/examples/completeExample.yaml @@ -0,0 +1,365 @@ +# +# NOTE: do not modify this file directly if possible +# copy-paste from other resource files. +# +--- +apiVersion: v1alpha1 +title: consumerinst1 title +name: consumerinst1 +kind: ConsumerInstance +metadata: + scope: + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - consumerinst + - cli + - axway +spec: + name: Consumer Instance Name + apiServiceInstance: apisvcinst1 + owningTeam: team1 + description: Catalog description + visibility: RESTRICTED + version: "1.0" + state: UNPUBLISHED + status: statusValue + tags: + - instance + - cli + icon: + contentType: image/jpeg + data: string + documentation: documentation string + subscription: + enabled: false + autoSubscribe: true + subscriptionDefinition: consumersubdef1 + +--- +apiVersion: v1alpha1 +title: consumerinst1 title +name: consumerinst2 +kind: ConsumerInstance +metadata: + scope: + kind: Environment + name: env2 +attributes: + createdBy: yaml +tags: + - consumerinst + - cli + - axway +spec: + name: Consumer Instance Name + apiServiceInstance: apisvcinst2 + owningTeam: team1 + description: Catalog description + visibility: RESTRICTED + version: "1.0" + state: UNPUBLISHED + status: statusValue + tags: + - instance + - cli + icon: + contentType: image/jpeg + data: string + documentation: documentation string + subscription: + enabled: false + autoSubscribe: true + subscriptionDefinition: consumersubdef2 + +--- +apiVersion: v1alpha1 +title: consumersubdef1 title +name: consumersubdef1 +kind: ConsumerSubscriptionDefinition +metadata: + scope: + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - apisvcinst + - cli + - axway +spec: + webhooks: + - webhook1 + schema: + properties: + - key: key1name + value: + subKey: subKeyValue + +--- +apiVersion: v1alpha1 +title: consumersubdef2 title +name: consumersubdef2 +kind: ConsumerSubscriptionDefinition +metadata: + scope: + kind: Environment + name: env2 +attributes: + createdBy: yaml +tags: + - apisvcinst + - cli + - axway +spec: + webhooks: + - webhook2 + schema: + properties: + - key: key1name + value: + subKey: subKeyValue + +--- +apiVersion: v1alpha1 +title: webhook1 title +name: webhook1 +kind: Webhook +metadata: + scope: + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - webhook + - cli + - axway +spec: + auth: + secret: + name: secret1 + key: key1 + enabled: true + url: https://webhookurl.com/path + headers: + header1: header1val + header2: header2val + +--- +apiVersion: v1alpha1 +title: webhook2 title +name: webhook2 +kind: Webhook +metadata: + scope: + kind: Environment + name: env2 +attributes: + createdBy: yaml +tags: + - webhook + - cli + - axway +spec: + auth: + secret: + name: secret2 + key: key1 + enabled: true + url: https://webhookurl.com/path + headers: + header1: header1val + header2: header2val + +--- +apiVersion: v1alpha1 +title: secret1 title +name: secret1 +kind: Secret +metadata: + scope: + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - secret + - cli + - axway +spec: + data: + key1: val1 + key2: val2 + +--- +apiVersion: v1alpha1 +title: secret2 title +name: secret2 +kind: Secret +metadata: + scope: + kind: Environment + name: env2 +attributes: + createdBy: yaml +tags: + - secret + - cli + - axway +spec: + data: + key1: val1 + key2: val2 + +--- +apiVersion: v1alpha1 +title: apisvcinst1 title +name: apisvcinst1 +kind: APIServiceInstance +metadata: + scope: + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - apisvcinst + - cli + - axway +spec: + apiServiceRevision: apisvcrev1 + endpoint: + - host: test-endpoint.com + port: 8080 + protocol: https + +--- +apiVersion: v1alpha1 +title: apisvcinst2 title +name: apisvcinst2 +kind: APIServiceInstance +metadata: + scope: + kind: Environment + name: env2 +attributes: + createdBy: yaml +tags: + - apisvcinst + - cli + - axway +spec: + apiServiceRevision: apisvcrev2 + endpoint: + - host: test-endpoint2.com + port: 8080 + protocol: https + +--- +apiVersion: v1alpha1 +title: apisvcrev1 title +name: apisvcrev1 +kind: APIServiceRevision +metadata: + scope: + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - apisvcrev + - cli + - axway +spec: + apiService: apisvc1 + definition: + type: oas2 + value: eyJzd2FnZ2VyIjoiMi4wIiwiaW5mbyI6eyJ0aXRsZSI6Ik11c2ljYWxJbnN0cnVtZW50c0FQSSIsImRlc2NyaXB0aW9uIjoiVGhpcyBpcyBhIHNhbXBsZSBNdXNpY2FsIEluc3RydW1lbnRzIEFQSS4iLCJ2ZXJzaW9uIjoiMi4wLjIifSwiaG9zdCI6ImVjMDYyYTA1NGEyOTc3MTIwYjdlNzIxODAxZWRiMzhjYTI0ZGZiYjMuY2xvdWRhcHAtZW50ZXJwcmlzZS5hcHBjZWxlcmF0b3IuY29tIiwiYmFzZVBhdGgiOiIvbXVzaWMvdjIiLCJzY2hlbWVzIjpbImh0dHAiXSwiY29uc3VtZXMiOlsiYXBwbGljYXRpb24vanNvbiIsImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCIsIm11bHRpcGFydC9mb3JtLWRhdGEiXSwicHJvZHVjZXMiOlsiYXBwbGljYXRpb24vanNvbiIsImFwcGxpY2F0aW9uL3htbCIsInRleHQveWFtbCIsInRleHQvY3N2IiwidGV4dC9wbGFpbiJdLCJwYXRocyI6eyIvaW5zdHJ1bWVudHMve2lkfSI6eyJnZXQiOnsicmVzcG9uc2VzIjp7IjIwMCI6eyJkZXNjcmlwdGlvbiI6IlRoZSBmaW5kIHN1Y2NlZWRlZCwgYW5kIHRoZSByZXN1bHRzIGFyZSBhdmFpbGFibGUuIiwic2NoZW1hIjp7IiRyZWYiOiIjL2RlZmluaXRpb25zL2luc3RydW1lbnRzIn19LCI0MDAiOnsiZGVzY3JpcHRpb24iOiJCYWQgcmVxdWVzdC4ifSwiNDAxIjp7ImRlc2NyaXB0aW9uIjoiVGhpcyByZXF1ZXN0IHJlcXVpcmVzIHVzZXIgYXV0aGVudGljYXRpb24sIGFzIGNvbmZpZ3VyZWQgYnkgdGhlIHNlcnZlci4ifSwiNDA0Ijp7ImRlc2NyaXB0aW9uIjoiTm8gcmVzdWx0cyB3ZXJlIGZvdW5kLiJ9LCI1MDAiOnsiZGVzY3JpcHRpb24iOiJTb21ldGhpbmcgd2VudCB3cm9uZyBkdXJpbmcgdGhlIHJlcXVlc3Q7IGNoZWNrIG91dCB0aGUgbG9ncyBvbiB5b3VyIHNlcnZlci4ifX0sImRlc2NyaXB0aW9uIjoiRmluZCBpbnN0cnVtZW50IGJ5IElEIiwib3BlcmF0aW9uSWQiOiJGaW5kSW5zdHJ1bWVudEJ5SUQiLCJjb25zdW1lcyI6WyJhcHBsaWNhdGlvbi9qc29uIl0sInBhcmFtZXRlcnMiOlt7Im5hbWUiOiJpZCIsImluIjoicGF0aCIsImRlc2NyaXB0aW9uIjoiVGhlIGluc3RydW1lbnQgSUQiLCJyZXF1aXJlZCI6dHJ1ZSwidHlwZSI6InN0cmluZyJ9XSwicHJvZHVjZXMiOlsiYXBwbGljYXRpb24vanNvbiJdLCJ0YWdzIjpbImluc3RydW1lbnRzIl0sIngtZmxvdyI6Imluc3RydW1lbnRzLWZpbmRCeUlEIn19LCIvaW5zdHJ1bWVudHMiOnsiZ2V0Ijp7InJlc3BvbnNlcyI6eyIyMDAiOnsiZGVzY3JpcHRpb24iOiJUaGUgZmluZCBhbGwgc3VjY2VlZGVkLCBhbmQgdGhlIHJlc3VsdHMgYXJlIGF2YWlsYWJsZS4iLCJzY2hlbWEiOnsidHlwZSI6ImFycmF5IiwiaXRlbXMiOnsiJHJlZiI6IiMvZGVmaW5pdGlvbnMvaW5zdHJ1bWVudHMifX19LCI0MDEiOnsiZGVzY3JpcHRpb24iOiJUaGlzIHJlcXVlc3QgcmVxdWlyZXMgdXNlciBhdXRoZW50aWNhdGlvbiwgYXMgY29uZmlndXJlZCBieSB0aGUgc2VydmVyLiJ9LCI0MDQiOnsiZGVzY3JpcHRpb24iOiJObyByZXN1bHRzIHdlcmUgZm91bmQuIn0sIjUwMCI6eyJkZXNjcmlwdGlvbiI6IlNvbWV0aGluZyB3ZW50IHdyb25nIGR1cmluZyB0aGUgcmVxdWVzdDsgY2hlY2sgb3V0IHRoZSBsb2dzIG9uIHlvdXIgc2VydmVyLiJ9fSwiZGVzY3JpcHRpb24iOiJSZXRyaWV2ZSBhbGwgaW5zdHJ1bWVudHMiLCJvcGVyYXRpb25JZCI6IkZpbmRJbnN0cnVtZW50cyIsImNvbnN1bWVzIjpbImFwcGxpY2F0aW9uL2pzb24iXSwicHJvZHVjZXMiOlsiYXBwbGljYXRpb24vanNvbiJdLCJ0YWdzIjpbImluc3RydW1lbnRzIl0sIngtZmxvdyI6Imluc3RydW1lbnRzLWZpbmRBbGwifX0sIi9pbnN0cnVtZW50cy9xdWVyeSI6eyJnZXQiOnsicmVzcG9uc2VzIjp7IjIwMCI6eyJkZXNjcmlwdGlvbiI6IlRoZSBxdWVyeSBzdWNjZWVkZWQsIGFuZCB0aGUgcmVzdWx0cyBhcmUgYXZhaWxhYmxlLiIsInNjaGVtYSI6eyJ0eXBlIjoiYXJyYXkiLCJpdGVtcyI6eyIkcmVmIjoiIy9kZWZpbml0aW9ucy9pbnN0cnVtZW50cyJ9fX0sIjQwMCI6eyJkZXNjcmlwdGlvbiI6IkJhZCByZXF1ZXN0LiJ9LCI0MDEiOnsiZGVzY3JpcHRpb24iOiJUaGlzIHJlcXVlc3QgcmVxdWlyZXMgdXNlciBhdXRoZW50aWNhdGlvbiwgYXMgY29uZmlndXJlZCBieSB0aGUgc2VydmVyLiJ9LCI0MDQiOnsiZGVzY3JpcHRpb24iOiJObyByZXN1bHRzIHdlcmUgZm91bmQuIn0sIjUwMCI6eyJkZXNjcmlwdGlvbiI6IlNvbWV0aGluZyB3ZW50IHdyb25nIGR1cmluZyB0aGUgcmVxdWVzdDsgY2hlY2sgb3V0IHRoZSBsb2dzIG9uIHlvdXIgc2VydmVyLiJ9fSwiZGVzY3JpcHRpb24iOiJRdWVyeSBpbnN0cnVtZW50KHMpIiwib3BlcmF0aW9uSWQiOiJRdWVyeUluc3RydW1lbnQiLCJjb25zdW1lcyI6WyJhcHBsaWNhdGlvbi9qc29uIl0sInBhcmFtZXRlcnMiOlt7ImluIjoicXVlcnkiLCJuYW1lIjoibGltaXQiLCJkZXNjcmlwdGlvbiI6IlRoZSBudW1iZXIgb2YgcmVjb3JkcyB0byBmZXRjaC4gVGhlIHZhbHVlIG11c3QgYmUgZ3JlYXRlciB0aGFuIDAsIGFuZCBubyBncmVhdGVyIHRoYW4gMTAwMC4iLCJyZXF1aXJlZCI6ZmFsc2UsInR5cGUiOiJudW1iZXIiLCJkZWZhdWx0IjoxMH0seyJpbiI6InF1ZXJ5IiwibmFtZSI6InNraXAiLCJkZXNjcmlwdGlvbiI6IlRoZSBudW1iZXIgb2YgcmVjb3JkcyB0byBza2lwLiBUaGUgdmFsdWUgbXVzdCBub3QgYmUgbGVzcyB0aGFuIDAuIiwicmVxdWlyZWQiOmZhbHNlLCJ0eXBlIjoibnVtYmVyIiwiZGVmYXVsdCI6MH0seyJpbiI6InF1ZXJ5IiwibmFtZSI6IndoZXJlIiwiZGVzY3JpcHRpb24iOiJDb25zdHJhaW5zIHZhbHVlcyBmb3IgZmllbGRzLiBUaGUgdmFsdWUgc2hvdWxkIGJlIGVuY29kZWQgSlNPTi4iLCJyZXF1aXJlZCI6ZmFsc2UsInR5cGUiOiJzdHJpbmciLCJmb3JtYXQiOiJqc29uIn0seyJpbiI6InF1ZXJ5IiwibmFtZSI6Im9yZGVyIiwiZGVzY3JpcHRpb24iOiJBIGRpY3Rpb25hcnkgb2Ygb25lIG9yIG1vcmUgZmllbGRzIHNwZWNpZnlpbmcgc29ydGluZyBvZiByZXN1bHRzLiBJbiBnZW5lcmFsLCB5b3UgY2FuIHNvcnQgYmFzZWQgb24gYW55IHByZWRlZmluZWQgZmllbGQgdGhhdCB5b3UgY2FuIHF1ZXJ5IHVzaW5nIHRoZSB3aGVyZSBvcGVyYXRvciwgYXMgd2VsbCBhcyBvbiBjdXN0b20gZmllbGRzLiBUaGUgdmFsdWUgc2hvdWxkIGJlIGVuY29kZWQgSlNPTi4iLCJyZXF1aXJlZCI6ZmFsc2UsInR5cGUiOiJzdHJpbmciLCJmb3JtYXQiOiJqc29uIn0seyJpbiI6InF1ZXJ5IiwibmFtZSI6InNlbCIsImRlc2NyaXB0aW9uIjoiU2VsZWN0cyB3aGljaCBmaWVsZHMgdG8gcmV0dXJuIGZyb20gdGhlIHF1ZXJ5LiBPdGhlcnMgYXJlIGV4Y2x1ZGVkLiBUaGUgdmFsdWUgc2hvdWxkIGJlIGVuY29kZWQgSlNPTi4iLCJyZXF1aXJlZCI6ZmFsc2UsInR5cGUiOiJzdHJpbmciLCJmb3JtYXQiOiJqc29uIn0seyJpbiI6InF1ZXJ5IiwibmFtZSI6InVuc2VsIiwiZGVzY3JpcHRpb24iOiJTZWxlY3RzIHdoaWNoIGZpZWxkcyB0byBub3QgcmV0dXJuIGZyb20gdGhlIHF1ZXJ5LiBPdGhlcnMgYXJlIGluY2x1ZGVkLiBUaGUgdmFsdWUgc2hvdWxkIGJlIGVuY29kZWQgSlNPTi4iLCJyZXF1aXJlZCI6ZmFsc2UsInR5cGUiOiJzdHJpbmciLCJmb3JtYXQiOiJqc29uIn0seyJpbiI6InF1ZXJ5IiwibmFtZSI6InBhZ2UiLCJkZXNjcmlwdGlvbiI6IlJlcXVlc3QgcGFnZSBudW1iZXIgc3RhcnRpbmcgZnJvbSAxLiIsInJlcXVpcmVkIjpmYWxzZSwidHlwZSI6Im51bWJlciIsImRlZmF1bHQiOjF9LHsiaW4iOiJxdWVyeSIsIm5hbWUiOiJwZXJfcGFnZSIsImRlc2NyaXB0aW9uIjoiTnVtYmVyIG9mIHJlc3VsdHMgcGVyIHBhZ2UuIiwicmVxdWlyZWQiOmZhbHNlLCJ0eXBlIjoibnVtYmVyIiwiZGVmYXVsdCI6MTB9XSwicHJvZHVjZXMiOlsiYXBwbGljYXRpb24vanNvbiJdLCJ0YWdzIjpbImluc3RydW1lbnRzIl0sIngtZmxvdyI6Imluc3RydW1lbnRzLXF1ZXJ5In19fSwiZGVmaW5pdGlvbnMiOnsiUmVzcG9uc2VNb2RlbCI6eyJ0eXBlIjoib2JqZWN0IiwicmVxdWlyZWQiOlsic3VjY2VzcyIsInJlcXVlc3QtaWQiXSwiYWRkaXRpb25hbFByb3BlcnRpZXMiOnRydWUsInByb3BlcnRpZXMiOnsiY29kZSI6eyJ0eXBlIjoiaW50ZWdlciIsImZvcm1hdCI6ImludDMyIn0sInN1Y2Nlc3MiOnsidHlwZSI6ImJvb2xlYW4iLCJkZWZhdWx0IjpmYWxzZX0sInJlcXVlc3QtaWQiOnsidHlwZSI6InN0cmluZyJ9LCJtZXNzYWdlIjp7InR5cGUiOiJzdHJpbmcifSwidXJsIjp7InR5cGUiOiJzdHJpbmcifX19LCJFcnJvck1vZGVsIjp7InR5cGUiOiJvYmplY3QiLCJyZXF1aXJlZCI6WyJtZXNzYWdlIiwiY29kZSIsInN1Y2Nlc3MiLCJyZXF1ZXN0LWlkIl0sInByb3BlcnRpZXMiOnsiY29kZSI6eyJ0eXBlIjoiaW50ZWdlciIsImZvcm1hdCI6ImludDMyIn0sInN1Y2Nlc3MiOnsidHlwZSI6ImJvb2xlYW4iLCJkZWZhdWx0IjpmYWxzZX0sInJlcXVlc3QtaWQiOnsidHlwZSI6InN0cmluZyJ9LCJtZXNzYWdlIjp7InR5cGUiOiJzdHJpbmcifSwidXJsIjp7InR5cGUiOiJzdHJpbmcifX19LCJpbnN0cnVtZW50cyI6eyJ0eXBlIjoib2JqZWN0IiwicHJvcGVydGllcyI6eyJ0eXBlIjp7ImRlc2NyaXB0aW9uIjoiVGhlIHR5cGUgb2YgaW5zdHJ1bWVudC4iLCJ0eXBlIjoic3RyaW5nIn0sInByaWNlIjp7ImRlc2NyaXB0aW9uIjoiVGhlIHByaWNlIG9mIHRoZSBpbnN0cnVtZW50LiIsInR5cGUiOiJpbnRlZ2VyIn0sImN1cnJlbmN5Ijp7ImRlc2NyaXB0aW9uIjoiVGhlIHByaWNlIGN1cnJlbmN5LiIsInR5cGUiOiJzdHJpbmcifX19fX0= + +--- +apiVersion: v1alpha1 +title: apisvcrev2 title +name: apisvcrev2 +kind: APIServiceRevision +metadata: + scope: + kind: Environment + name: env2 +attributes: + createdBy: yaml +tags: + - apisvcrev + - cli + - axway +spec: + apiService: apisvc2 + definition: + type: oas2 + value: eyJzd2FnZ2VyIjoiMi4wIiwiaW5mbyI6eyJ0aXRsZSI6Ik11c2ljYWxJbnN0cnVtZW50c0FQSSIsImRlc2NyaXB0aW9uIjoiVGhpcyBpcyBhIHNhbXBsZSBNdXNpY2FsIEluc3RydW1lbnRzIEFQSS4iLCJ2ZXJzaW9uIjoiMi4wLjIifSwiaG9zdCI6ImVjMDYyYTA1NGEyOTc3MTIwYjdlNzIxODAxZWRiMzhjYTI0ZGZiYjMuY2xvdWRhcHAtZW50ZXJwcmlzZS5hcHBjZWxlcmF0b3IuY29tIiwiYmFzZVBhdGgiOiIvbXVzaWMvdjIiLCJzY2hlbWVzIjpbImh0dHAiXSwiY29uc3VtZXMiOlsiYXBwbGljYXRpb24vanNvbiIsICJhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQiLCAibXVsdGlwYXJ0L2Zvcm0tZGF0YSJdLCJwcm9kdWNlcyI6WyJhcHBsaWNhdGlvbi9qc29uIiwgImFwcGxpY2F0aW9uL3htbCIsICJ0ZXh0L3lhbWwiLCAidGV4dC9jc3YiLCAidGV4dC9wbGFpbiJdLCJwYXRocyI6eyIvaW5zdHJ1bWVudHMve2lkfSI6eyJnZXQiOnsicmVzcG9uc2VzIjp7IjIwMCI6eyJkZXNjcmlwdGlvbiI6IlRoZSBmaW5kIHN1Y2NlZWRlZCwgYW5kIHRoZSByZXN1bHRzIGFyZSBhdmFpbGFibGUuIiwic2NoZW1hIjp7IiRyZWYiOiIjL2RlZmluaXRpb25zL2luc3RydW1lbnRzIn19LCI0MDAiOnsiZGVzY3JpcHRpb24iOiJCYWQgcmVxdWVzdC4ifSwiNDAxIjp7ImRlc2NyaXB0aW9uIjoiVGhpcyByZXF1ZXN0IHJlcXVpcmVzIHVzZXIgYXV0aGVudGljYXRpb24sIGFzIGNvbmZpZ3VyZWQgYnkgdGhlIHNlcnZlci4ifSwiNDA0Ijp7ImRlc2NyaXB0aW9uIjoiTm8gcmVzdWx0cyB3ZXJlIGZvdW5kLiJ9LCI1MDAiOnsiZGVzY3JpcHRpb24iOiJTb21ldGhpbmcgd2VudCB3cm9uZyBkdXJpbmcgdGhlIHJlcXVlc3Q7IGNoZWNrIG91dCB0aGUgbG9ncyBvbiB5b3VyIHNlcnZlci4ifX0sImRlc2NyaXB0aW9uIjoiRmluZCBpbnN0cnVtZW50IGJ5IElEIiwib3BlcmF0aW9uSWQiOiJGaW5kSW5zdHJ1bWVudEJ5SUQiLCJjb25zdW1lcyI6WyJhcHBsaWNhdGlvbi9qc29uIl0sInBhcmFtZXRlcnMiOlt7Im5hbWUiOiJpZCIsImluIjoicGF0aCIsImRlc2NyaXB0aW9uIjoiVGhlIGluc3RydW1lbnQgSUQiLCJyZXF1aXJlZCI6dHJ1ZSwidHlwZSI6InN0cmluZyJ9XSwicHJvZHVjZXMiOlsiYXBwbGljYXRpb24vanNvbiJdLCJ0YWdzIjpbImluc3RydW1lbnRzIl0sIngtZmxvdyI6Imluc3RydW1lbnRzLWZpbmRCeUlEIn19LCIvaW5zdHJ1bWVudHMiOnsiZ2V0Ijp7InJlc3BvbnNlcyI6eyIyMDAiOnsiZGVzY3JpcHRpb24iOiJUaGUgZmluZCBhbGwgc3VjY2VlZGVkLCBhbmQgdGhlIHJlc3VsdHMgYXJlIGF2YWlsYWJsZS4iLCJzY2hlbWEiOnsidHlwZSI6ImFycmF5IiwiaXRlbXMiOnsiJHJlZiI6IiMvZGVmaW5pdGlvbnMvaW5zdHJ1bWVudHMifX19LCI0MDEiOnsiZGVzY3JpcHRpb24iOiJUaGlzIHJlcXVlc3QgcmVxdWlyZXMgdXNlciBhdXRoZW50aWNhdGlvbiwgYXMgY29uZmlndXJlZCBieSB0aGUgc2VydmVyLiJ9LCI0MDQiOnsiZGVzY3JpcHRpb24iOiJObyByZXN1bHRzIHdlcmUgZm91bmQuIn0sIjUwMCI6eyJkZXNjcmlwdGlvbiI6IlNvbWV0aGluZyB3ZW50IHdyb25nIGR1cmluZyB0aGUgcmVxdWVzdDsgY2hlY2sgb3V0IHRoZSBsb2dzIG9uIHlvdXIgc2VydmVyLiJ9fSwiZGVzY3JpcHRpb24iOiJSZXRyaWV2ZSBhbGwgaW5zdHJ1bWVudHMiLCJvcGVyYXRpb25JZCI6IkZpbmRJbnN0cnVtZW50cyIsImNvbnN1bWVzIjpbImFwcGxpY2F0aW9uL2pzb24iXSwicHJvZHVjZXMiOlsiYXBwbGljYXRpb24vanNvbiJdLCJ0YWdzIjpbImluc3RydW1lbnRzIl0sIngtZmxvdyI6Imluc3RydW1lbnRzLWZpbmRBbGwifX0sIi9pbnN0cnVtZW50cy9xdWVyeSI6eyJnZXQiOnsicmVzcG9uc2VzIjp7IjIwMCI6eyJkZXNjcmlwdGlvbiI6IlRoZSBxdWVyeSBzdWNjZWVkZWQsIGFuZCB0aGUgcmVzdWx0cyBhcmUgYXZhaWxhYmxlLiIsInNjaGVtYSI6eyJ0eXBlIjoiYXJyYXkiLCJpdGVtcyI6eyIkcmVmIjoiIy9kZWZpbml0aW9ucy9pbnN0cnVtZW50cyJ9fX0sIjQwMCI6eyJkZXNjcmlwdGlvbiI6IkJhZCByZXF1ZXN0LiJ9LCI0MDEiOnsiZGVzY3JpcHRpb24iOiJUaGlzIHJlcXVlc3QgcmVxdWlyZXMgdXNlciBhdXRoZW50aWNhdGlvbiwgYXMgY29uZmlndXJlZCBieSB0aGUgc2VydmVyLiJ9LCI0MDQiOnsiZGVzY3JpcHRpb24iOiJObyByZXN1bHRzIHdlcmUgZm91bmQuIn0sIjUwMCI6eyJkZXNjcmlwdGlvbiI6IlNvbWV0aGluZyB3ZW50IHdyb25nIGR1cmluZyB0aGUgcmVxdWVzdDsgY2hlY2sgb3V0IHRoZSBsb2dzIG9uIHlvdXIgc2VydmVyLiJ9fSwiZGVzY3JpcHRpb24iOiJRdWVyeSBpbnN0cnVtZW50KHMpIiwib3BlcmF0aW9uSWQiOiJRdWVyeUluc3RydW1lbnQiLCJjb25zdW1lcyI6WyJhcHBsaWNhdGlvbi9qc29uIl0sInBhcmFtZXRlcnMiOlt7ImluIjoicXVlcnkiLCJuYW1lIjoibGltaXQiLCJkZXNjcmlwdGlvbiI6IlRoZSBudW1iZXIgb2YgcmVjb3JkcyB0byBmZXRjaC4gVGhlIHZhbHVlIG11c3QgYmUgZ3JlYXRlciB0aGFuIDAsIGFuZCBubyBncmVhdGVyIHRoYW4gMTAwMC4iLCJyZXF1aXJlZCI6ZmFsc2UsInR5cGUiOiJudW1iZXIiLCJkZWZhdWx0IjoxMH0seyJpbiI6InF1ZXJ5IiwibmFtZSI6InNraXAiLCJkZXNjcmlwdGlvbiI6IlRoZSBudW1iZXIgb2YgcmVjb3JkcyB0byBza2lwLiBUaGUgdmFsdWUgbXVzdCBub3QgYmUgbGVzcyB0aGFuIDAuIiwicmVxdWlyZWQiOmZhbHNlLCJ0eXBlIjoibnVtYmVyIiwiZGVmYXVsdCI6MH0seyJpbiI6InF1ZXJ5IiwibmFtZSI6IndoZXJlIiwiZGVzY3JpcHRpb24iOiJDb25zdHJhaW5zIHZhbHVlcyBmb3IgZmllbGRzLiBUaGUgdmFsdWUgc2hvdWxkIGJlIGVuY29kZWQgSlNPTi4iLCJyZXF1aXJlZCI6ZmFsc2UsInR5cGUiOiJzdHJpbmciLCJmb3JtYXQiOiJqc29uIn0seyJpbiI6InF1ZXJ5IiwibmFtZSI6Im9yZGVyIiwiZGVzY3JpcHRpb24iOiJBIGRpY3Rpb25hcnkgb2Ygb25lIG9yIG1vcmUgZmllbGRzIHNwZWNpZnlpbmcgc29ydGluZyBvZiByZXN1bHRzLiBJbiBnZW5lcmFsLCB5b3UgY2FuIHNvcnQgYmFzZWQgb24gYW55IHByZWRlZmluZWQgZmllbGQgdGhhdCB5b3UgY2FuIHF1ZXJ5IHVzaW5nIHRoZSB3aGVyZSBvcGVyYXRvciwgYXMgd2VsbCBhcyBvbiBjdXN0b20gZmllbGRzLiBUaGUgdmFsdWUgc2hvdWxkIGJlIGVuY29kZWQgSlNPTi4iLCJyZXF1aXJlZCI6ZmFsc2UsInR5cGUiOiJzdHJpbmciLCJmb3JtYXQiOiJqc29uIn0seyJpbiI6InF1ZXJ5IiwibmFtZSI6InNlbCIsImRlc2NyaXB0aW9uIjoiU2VsZWN0cyB3aGljaCBmaWVsZHMgdG8gcmV0dXJuIGZyb20gdGhlIHF1ZXJ5LiBPdGhlcnMgYXJlIGV4Y2x1ZGVkLiBUaGUgdmFsdWUgc2hvdWxkIGJlIGVuY29kZWQgSlNPTi4iLCJyZXF1aXJlZCI6ZmFsc2UsInR5cGUiOiJzdHJpbmciLCJmb3JtYXQiOiJqc29uIn0seyJpbiI6InF1ZXJ5IiwibmFtZSI6InVuc2VsIiwiZGVzY3JpcHRpb24iOiJTZWxlY3RzIHdoaWNoIGZpZWxkcyB0byBub3QgcmV0dXJuIGZyb20gdGhlIHF1ZXJ5LiBPdGhlcnMgYXJlIGluY2x1ZGVkLiBUaGUgdmFsdWUgc2hvdWxkIGJlIGVuY29kZWQgSlNPTi4iLCJyZXF1aXJlZCI6ZmFsc2UsInR5cGUiOiJzdHJpbmciLCJmb3JtYXQiOiJqc29uIn0seyJpbiI6InF1ZXJ5IiwibmFtZSI6InBhZ2UiLCJkZXNjcmlwdGlvbiI6IlJlcXVlc3QgcGFnZSBudW1iZXIgc3RhcnRpbmcgZnJvbSAxLiIsInJlcXVpcmVkIjpmYWxzZSwidHlwZSI6Im51bWJlciIsImRlZmF1bHQiOjF9LHsiaW4iOiJxdWVyeSIsIm5hbWUiOiJwZXJfcGFnZSIsImRlc2NyaXB0aW9uIjoiTnVtYmVyIG9mIHJlc3VsdHMgcGVyIHBhZ2UuIiwicmVxdWlyZWQiOmZhbHNlLCJ0eXBlIjoibnVtYmVyIiwiZGVmYXVsdCI6MTB9XSwicHJvZHVjZXMiOlsiYXBwbGljYXRpb24vanNvbiJdLCJ0YWdzIjpbImluc3RydW1lbnRzIl0sIngtZmxvdyI6Imluc3RydW1lbnRzLXF1ZXJ5In19fSwiZGVmaW5pdGlvbnMiOnsiUmVzcG9uc2VNb2RlbCI6eyJ0eXBlIjoib2JqZWN0IiwicmVxdWlyZWQiOlsic3VjY2VzcyIsICJyZXF1ZXN0LWlkIl0sImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjp0cnVlLCJwcm9wZXJ0aWVzIjp7ImNvZGUiOnsidHlwZSI6ImludGVnZXIiLCJmb3JtYXQiOiJpbnQzMiJ9LCJzdWNjZXNzIjp7InR5cGUiOiJib29sZWFuIiwiZGVmYXVsdCI6ZmFsc2V9LCJyZXF1ZXN0LWlkIjp7InR5cGUiOiJzdHJpbmcifSwibWVzc2FnZSI6eyJ0eXBlIjoic3RyaW5nIn0sInVybCI6eyJ0eXBlIjoic3RyaW5nIn19fSwiRXJyb3JNb2RlbCI6eyJ0eXBlIjoib2JqZWN0IiwicmVxdWlyZWQiOlsibWVzc2FnZSIsICJjb2RlIiwgInN1Y2Nlc3MiLCAicmVxdWVzdC1pZCJdLCJwcm9wZXJ0aWVzIjp7ImNvZGUiOnsidHlwZSI6ImludGVnZXIiLCJmb3JtYXQiOiJpbnQzMiJ9LCJzdWNjZXNzIjp7InR5cGUiOiJib29sZWFuIiwiZGVmYXVsdCI6ZmFsc2V9LCJyZXF1ZXN0LWlkIjp7InR5cGUiOiJzdHJpbmcifSwibWVzc2FnZSI6eyJ0eXBlIjoic3RyaW5nIn0sInVybCI6eyJ0eXBlIjoic3RyaW5nIn19fSwiaW5zdHJ1bWVudHMiOnsidHlwZSI6Im9iamVjdCIsInByb3BlcnRpZXMiOnsidHlwZSI6eyJkZXNjcmlwdGlvbiI6IlRoZSB0eXBlIG9mIGluc3RydW1lbnQuIiwidHlwZSI6InN0cmluZyJ9LCJwcmljZSI6eyJkZXNjcmlwdGlvbiI6IlRoZSBwcmljZSBvZiB0aGUgaW5zdHJ1bWVudC4iLCJ0eXBlIjoiaW50ZWdlciJ9LCJjdXJyZW5jeSI6eyJkZXNjcmlwdGlvbiI6IlRoZSBwcmljZSBjdXJyZW5jeS4iLCJ0eXBlIjoic3RyaW5nIn19fX19 + +--- +apiVersion: v1alpha1 +title: apisvc1 title +name: apisvc1 +kind: APIService +metadata: + scope: + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - apisvc + - cli + - axway +spec: + description: api service 1 description + +--- +apiVersion: v1alpha1 +title: apisvc2 title +name: apisvc2 +kind: APIService +attributes: + createdBy: yaml +tags: + - axway + - cli + - apisvc +metadata: + scope: + kind: Environment + name: env2 +spec: + description: api service 2 description + +--- +apiVersion: v1alpha1 +title: Title one +name: env1 +kind: Environment +attributes: + createdBy: yaml + randomNum: 1 +tags: + - axway + - cli +spec: + description: description one + +--- +apiVersion: v1alpha1 +title: Title two +name: env2 +kind: Environment +attributes: + createdBy: yaml + randomNum: 2 +tags: + - axway + - cli +spec: + description: description two diff --git a/test/resources/examples/consumerinstances.json b/test/resources/examples/consumerinstances.json new file mode 100644 index 00000000..79577213 --- /dev/null +++ b/test/resources/examples/consumerinstances.json @@ -0,0 +1,124 @@ +[ + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "ConsumerInstance", + "name": "consumerinst1", + "title": "consumerinst1 title", + "metadata": { + "references": [ + { + "id": "e4e0839d6efb5a5c016efc23f90803f9", + "kind": "APIServiceInstance", + "name": "apisvcinst1", + "type": "HARD" + }, + { + "id": "e4e0839f6efb5aa4016efc240001034d", + "kind": "ConsumerSubscriptionDefinition", + "name": "consumersubdef1", + "type": "SOFT" + } + ], + "audit": { + "createTimestamp": "2019-12-12T22:04:08.576+0000", + "createUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7", + "modifyTimestamp": "2019-12-12T22:04:08.576+0000", + "modifyUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7" + }, + "resourceVersion": "858", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f41d0338", + "kind": "Environment", + "name": "env1" + }, + "id": "e4e0839f6efb5aa4016efc2401ff0358" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "axway", "consumerinst"], + "spec": { + "icon": { + "data": "string", + "contentType": "image/jpeg" + }, + "name": "Consumer Instance Name", + "tags": ["instance", "cli"], + "state": "UNPUBLISHED", + "status": "statusValue", + "version": "1.0", + "owningTeam": "team1", + "visibility": "RESTRICTED", + "description": "Catalog description", + "subscription": { + "enabled": false, + "autoSubscribe": true, + "subscriptionDefinition": "consumersubdef1" + }, + "documentation": "documentation string", + "apiServiceInstance": "apisvcinst1" + } + }, + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "ConsumerInstance", + "name": "consumerinst2", + "title": "consumerinst1 title", + "metadata": { + "references": [ + { + "id": "e4e0839d6efb5a5c016efc23fa3503ff", + "kind": "APIServiceInstance", + "name": "apisvcinst2", + "type": "HARD" + }, + { + "id": "e4e0839d6efb5a5c016efc245a4f0423", + "kind": "ConsumerSubscriptionDefinition", + "name": "consumersubdef2", + "type": "SOFT" + } + ], + "audit": { + "createTimestamp": "2019-12-12T22:04:31.657+0000", + "createUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7", + "modifyTimestamp": "2019-12-12T22:04:31.657+0000", + "modifyUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7" + }, + "resourceVersion": "873", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f3040333", + "kind": "Environment", + "name": "env2" + }, + "id": "e4e0839f6efb5aa4016efc245c28038d" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "axway", "consumerinst"], + "spec": { + "icon": { + "data": "string", + "contentType": "image/jpeg" + }, + "name": "Consumer Instance Name", + "tags": ["instance", "cli"], + "state": "UNPUBLISHED", + "status": "statusValue", + "version": "1.0", + "owningTeam": "team1", + "visibility": "RESTRICTED", + "description": "Catalog description", + "subscription": { + "enabled": false, + "autoSubscribe": true, + "subscriptionDefinition": "consumersubdef2" + }, + "documentation": "documentation string", + "apiServiceInstance": "apisvcinst2" + } + } +] diff --git a/test/resources/examples/consumerinstances.yaml b/test/resources/examples/consumerinstances.yaml new file mode 100644 index 00000000..70b4e988 --- /dev/null +++ b/test/resources/examples/consumerinstances.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: v1alpha1 +title: consumerinst1 title +name: consumerinst1 +kind: ConsumerInstance +metadata: + scope: + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - consumerinst + - cli + - axway +spec: + name: Consumer Instance Name + apiServiceInstance: apisvcinst1 + owningTeam: team1 + description: Catalog description + visibility: RESTRICTED + version: "1.0" + state: UNPUBLISHED + status: statusValue + tags: + - instance + - cli + icon: + contentType: image/jpeg + data: string + documentation: documentation string + subscription: + enabled: false + autoSubscribe: true + subscriptionDefinition: consumersubdef1 + +--- +apiVersion: v1alpha1 +title: consumerinst1 title +name: consumerinst2 +kind: ConsumerInstance +metadata: + scope: + kind: Environment + name: env2 +attributes: + createdBy: yaml +tags: + - consumerinst + - cli + - axway +spec: + name: Consumer Instance Name + apiServiceInstance: apisvcinst2 + owningTeam: team1 + description: Catalog description + visibility: RESTRICTED + version: "1.0" + state: UNPUBLISHED + status: statusValue + tags: + - instance + - cli + icon: + contentType: image/jpeg + data: string + documentation: documentation string + subscription: + enabled: false + autoSubscribe: true + subscriptionDefinition: consumersubdef2 diff --git a/test/resources/examples/consumersubscriptiondefs.json b/test/resources/examples/consumersubscriptiondefs.json new file mode 100644 index 00000000..18564cf8 --- /dev/null +++ b/test/resources/examples/consumersubscriptiondefs.json @@ -0,0 +1,96 @@ +[ + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "ConsumerSubscriptionDefinition", + "name": "consumersubdef1", + "title": "consumersubdef1 title", + "metadata": { + "references": [ + { + "id": "e4e0839f6efb5aa4016efc23fe000347", + "kind": "Webhook", + "name": "webhook1", + "type": "SOFT" + } + ], + "audit": { + "createTimestamp": "2019-12-12T22:04:08.065+0000", + "createUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7", + "modifyTimestamp": "2019-12-12T22:04:08.065+0000", + "modifyUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7" + }, + "resourceVersion": "856", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f41d0338", + "kind": "Environment", + "name": "env1" + }, + "id": "e4e0839f6efb5aa4016efc240001034d" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "apisvcinst", "axway"], + "spec": { + "schema": { + "properties": [ + { + "key": "key1name", + "value": { + "subKey": "subKeyValue" + } + } + ] + }, + "webhooks": ["webhook1"] + } + }, + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "ConsumerSubscriptionDefinition", + "name": "consumersubdef2", + "title": "consumersubdef2 title", + "metadata": { + "references": [ + { + "id": "e4e0839f6efb5aa4016efc2458790382", + "kind": "Webhook", + "name": "webhook2", + "type": "SOFT" + } + ], + "audit": { + "createTimestamp": "2019-12-12T22:04:31.183+0000", + "createUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7", + "modifyTimestamp": "2019-12-12T22:04:31.183+0000", + "modifyUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7" + }, + "resourceVersion": "871", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f3040333", + "kind": "Environment", + "name": "env2" + }, + "id": "e4e0839d6efb5a5c016efc245a4f0423" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "apisvcinst", "axway"], + "spec": { + "schema": { + "properties": [ + { + "key": "key1name", + "value": { + "subKey": "subKeyValue" + } + } + ] + }, + "webhooks": ["webhook2"] + } + } +] diff --git a/test/resources/examples/consumersubscriptiondefs.yaml b/test/resources/examples/consumersubscriptiondefs.yaml new file mode 100644 index 00000000..6acb6261 --- /dev/null +++ b/test/resources/examples/consumersubscriptiondefs.yaml @@ -0,0 +1,47 @@ +--- +apiVersion: v1alpha1 +title: consumersubdef1 title +name: consumersubdef1 +kind: ConsumerSubscriptionDefinition +metadata: + scope: + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - apisvcinst + - cli + - axway +spec: + webhooks: + - webhook1 + schema: + properties: + - key: key1name + value: + subKey: subKeyValue + +--- +apiVersion: v1alpha1 +title: consumersubdef2 title +name: consumersubdef2 +kind: ConsumerSubscriptionDefinition +metadata: + scope: + kind: Environment + name: env2 +attributes: + createdBy: yaml +tags: + - apisvcinst + - cli + - axway +spec: + webhooks: + - webhook2 + schema: + properties: + - key: key1name + value: + subKey: subKeyValue diff --git a/test/resources/examples/discovery-agent.yaml b/test/resources/examples/discovery-agent.yaml new file mode 100644 index 00000000..72e1e275 --- /dev/null +++ b/test/resources/examples/discovery-agent.yaml @@ -0,0 +1,16 @@ +group: management +apiVersion: v1alpha1 +kind: DiscoveryAgent +name: v7-discovery-agent +title: V7 Discovery Agent +metadata: + scope: + kind: Environment + name: v7-agents +attributes: {} +finalizers: [] +tags: [] +spec: + config: + owningTeam: Default Team + dataplaneType: Edge \ No newline at end of file diff --git a/test/resources/examples/environments.json b/test/resources/examples/environments.json new file mode 100644 index 00000000..1732feeb --- /dev/null +++ b/test/resources/examples/environments.json @@ -0,0 +1,32 @@ +[ + { + "apiVersion": "v1alpha1", + "group": "management", + "title": "Json env title one", + "name": "env1", + "kind": "Environment", + "attributes": { + "createdBy": "json", + "randomNum": "1" + }, + "tags": ["cli", "axway"], + "spec": { + "description": "spec description one" + } + }, + { + "apiVersion": "v1alpha1", + "group": "management", + "title": "Json env title two", + "name": "env2", + "kind": "Environment", + "attributes": { + "createdBy": "json", + "randomNum": "2" + }, + "tags": ["cli", "axway"], + "spec": { + "description": "spec description two" + } + } +] diff --git a/test/resources/examples/environments.yaml b/test/resources/examples/environments.yaml new file mode 100644 index 00000000..4c800bb5 --- /dev/null +++ b/test/resources/examples/environments.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: v1alpha1 +title: Title one +name: env1 +kind: Environment +attributes: + createdBy: yaml + randomNum: 1 +tags: + - axway + - cli +spec: + description: description one + +--- +apiVersion: v1alpha1 +title: Title two +name: env2 +kind: Environment +attributes: + createdBy: yaml + randomNum: 2 +tags: + - axway + - cli +spec: + description: description two diff --git a/test/resources/examples/environmentsV1.json b/test/resources/examples/environmentsV1.json new file mode 100644 index 00000000..7c54921b --- /dev/null +++ b/test/resources/examples/environmentsV1.json @@ -0,0 +1,32 @@ +[ + { + "apiVersion": "v1", + "group": "management", + "title": "Json env title three", + "name": "env3", + "kind": "Environment", + "attributes": { + "createdBy": "json", + "randomNum": "3" + }, + "tags": ["cli", "axway"], + "spec": { + "description": "spec description three" + } + }, + { + "apiVersion": "v1", + "group": "management", + "title": "Json env title four", + "name": "env4", + "kind": "Environment", + "attributes": { + "createdBy": "json", + "randomNum": "4" + }, + "tags": ["cli", "axway"], + "spec": { + "description": "spec description four" + } + } +] \ No newline at end of file diff --git a/test/resources/examples/environmentsV1.yaml b/test/resources/examples/environmentsV1.yaml new file mode 100644 index 00000000..6df6922d --- /dev/null +++ b/test/resources/examples/environmentsV1.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: v1 +title: Title one +name: env1 +kind: Environment +attributes: + createdBy: yaml + randomNum: 1 +tags: + - axway + - cli +spec: + description: description one + +--- +apiVersion: v1 +title: Title two +name: env2 +kind: Environment +attributes: + createdBy: yaml + randomNum: 2 +tags: + - axway + - cli +spec: + description: description two diff --git a/test/resources/examples/musical-config-secured-v1.yaml b/test/resources/examples/musical-config-secured-v1.yaml new file mode 100644 index 00000000..5be7cbf4 --- /dev/null +++ b/test/resources/examples/musical-config-secured-v1.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +version: v1 +proxy: + name: 'Musical Instruments' + basePath: /api/v1 + swagger: 'https://ec062a054a2977120b7e721801edb38ca24dfbb3.cloudapp-enterprise.appcelerator.com/apidoc/swagger.json' + policies: + clientAuth: + type: api-key + app: 'Sample App' + backendAuth: + type: auth-http-basic + username: Joe + password: changeme + rateLimit: + perProxy: 5 + perProxyAndApp: 3 + apps: + - name: 'Second Sample App' + team: + name: 'Default Team' diff --git a/test/resources/examples/musical-config-secured.yaml b/test/resources/examples/musical-config-secured.yaml new file mode 100644 index 00000000..fc6e5e3e --- /dev/null +++ b/test/resources/examples/musical-config-secured.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 # This version ensures backward compatibility and would not mandate a frequent update from a client side +proxy: + name: 'Musical Instruments Secured' + basePath: /api/secured/v1 + swagger: 'https://ec062a054a2977120b7e721801edb38ca24dfbb3.cloudapp-enterprise.appcelerator.com/apidoc/swagger.json' + policy: + type: api-key + app: 'Default App' + tags: ['musical', 'instruments'] + team: + name: 'Default Team' diff --git a/test/resources/examples/musical-config.yaml b/test/resources/examples/musical-config.yaml new file mode 100644 index 00000000..bb83e500 --- /dev/null +++ b/test/resources/examples/musical-config.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 # This version ensures backward compatibility and would not mandate a frequent update from a client side +proxy: + name: "CLI Proxy" + basePath: /new/v1 + swagger: "https://ec062a054a2977120b7e721801edb38ca24dfbb3.cloudapp-enterprise.appcelerator.com/apidoc/swagger.json" + policy: + type: pass-through + tags: ["musical", "instruments"] + team: + name: "Default Team" diff --git a/test/resources/examples/productreleasetags.json b/test/resources/examples/productreleasetags.json new file mode 100644 index 00000000..b61ea703 --- /dev/null +++ b/test/resources/examples/productreleasetags.json @@ -0,0 +1,17 @@ +{ + "data": [{ + "name": "apisvc1", + "kind": "ReleaseTag", + "status": { + "level": "Success" + } + }, + { + "name": "apisvc2", + "kind": "ReleaseTag", + "status": { + "level": "Success" + } + } +] +} \ No newline at end of file diff --git a/test/resources/examples/products.json b/test/resources/examples/products.json new file mode 100644 index 00000000..f73e0a3d --- /dev/null +++ b/test/resources/examples/products.json @@ -0,0 +1,28 @@ +[ + { + "apiVersion": "v1alpha1", + "kind": "Product", + "name": "apisvc1", + "title": "title", + "tags": [], + "spec": { + "autoRelease": { + "releaseType": "patch", + "requiresInitialActivation": true + } + } + }, + { + "apiVersion": "v1alpha1", + "kind": "Product", + "name": "apisvc2", + "title": "title", + "tags": [], + "spec": { + "autoRelease": { + "releaseType": "patch", + "requiresInitialActivation": true + } + } + } +] \ No newline at end of file diff --git a/test/resources/examples/secrets.json b/test/resources/examples/secrets.json new file mode 100644 index 00000000..3673fdff --- /dev/null +++ b/test/resources/examples/secrets.json @@ -0,0 +1,66 @@ +[ + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "Secret", + "name": "secret1", + "title": "secret1 title", + "metadata": { + "audit": { + "createTimestamp": "2019-12-12T22:04:07.083+0000", + "createUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7", + "modifyTimestamp": "2019-12-12T22:04:07.083+0000", + "modifyUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7" + }, + "resourceVersion": "852", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f41d0338", + "kind": "Environment", + "name": "env1" + }, + "id": "e4e0839f6efb5aa4016efc23fc2a0342" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["secret", "cli", "axway"], + "spec": { + "data": { + "key1": "val1", + "key2": "val2" + } + } + }, + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "Secret", + "name": "secret2", + "title": "secret2 title", + "metadata": { + "audit": { + "createTimestamp": "2019-12-12T22:04:06.837+0000", + "createUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7", + "modifyTimestamp": "2019-12-12T22:04:06.837+0000", + "modifyUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7" + }, + "resourceVersion": "851", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f3040333", + "kind": "Environment", + "name": "env2" + }, + "id": "e4e0839d6efb5a5c016efc23fb350405" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["secret", "cli", "axway"], + "spec": { + "data": { + "key1": "val1", + "key2": "val2" + } + } + } +] diff --git a/test/resources/examples/secrets.yaml b/test/resources/examples/secrets.yaml new file mode 100644 index 00000000..9bc5ac4e --- /dev/null +++ b/test/resources/examples/secrets.yaml @@ -0,0 +1,39 @@ +--- +apiVersion: v1alpha1 +title: secret1 title +name: secret1 +kind: Secret +metadata: + scope: + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - secret + - cli + - axway +spec: + data: + key1: val1 + key2: val2 + +--- +apiVersion: v1alpha1 +title: secret2 title +name: secret2 +kind: Secret +metadata: + scope: + kind: Environment + name: env2 +attributes: + createdBy: yaml +tags: + - secret + - cli + - axway +spec: + data: + key1: val1 + key2: val2 diff --git a/test/resources/examples/traceability-agent.yaml b/test/resources/examples/traceability-agent.yaml new file mode 100644 index 00000000..3ca7b77b --- /dev/null +++ b/test/resources/examples/traceability-agent.yaml @@ -0,0 +1,13 @@ +group: management +apiVersion: v1alpha1 +kind: TraceabilityAgent +name: v7-traceability-agent +title: V7 Traceability Agent +metadata: + scope: + kind: Environment + name: v7-agents +spec: + config: + owningTeam: Default Team + dataplaneType: Edge diff --git a/test/resources/examples/webhooks.json b/test/resources/examples/webhooks.json new file mode 100644 index 00000000..324f16d7 --- /dev/null +++ b/test/resources/examples/webhooks.json @@ -0,0 +1,98 @@ +[ + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "Webhook", + "name": "webhook1", + "title": "webhook1 title", + "metadata": { + "references": [ + { + "id": "e4e0839f6efb5aa4016efc23fc2a0342", + "kind": "Secret", + "name": "secret1", + "type": "SOFT" + } + ], + "audit": { + "createTimestamp": "2019-12-12T22:04:07.553+0000", + "createUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7", + "modifyTimestamp": "2019-12-12T22:04:07.553+0000", + "modifyUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7" + }, + "resourceVersion": "854", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f41d0338", + "kind": "Environment", + "name": "env1" + }, + "id": "e4e0839f6efb5aa4016efc23fe000347" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["webhook", "cli", "axway"], + "spec": { + "url": "https://webhookurl.com/path", + "auth": { + "secret": { + "key": "key1", + "name": "secret1" + } + }, + "enabled": true, + "headers": { + "header1": "header1val", + "header2": "header2val" + } + } + }, + { + "apiVersion": "v1alpha1", + "group": "management", + "kind": "Webhook", + "name": "webhook2", + "title": "webhook2 title", + "metadata": { + "references": [ + { + "id": "e4e0839d6efb5a5c016efc23fb350405", + "kind": "Secret", + "name": "secret2", + "type": "SOFT" + } + ], + "audit": { + "createTimestamp": "2019-12-12T22:04:30.714+0000", + "createUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7", + "modifyTimestamp": "2019-12-12T22:04:30.714+0000", + "modifyUserId": "DOSA_c6222da2490c46a1a34a1aeeee4bb7e7" + }, + "resourceVersion": "869", + "scope": { + "id": "e4e0839f6efb5aa4016efc23f3040333", + "kind": "Environment", + "name": "env2" + }, + "id": "e4e0839f6efb5aa4016efc2458790382" + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["webhook", "cli", "axway"], + "spec": { + "url": "https://webhookurl.com/path", + "auth": { + "secret": { + "key": "key1", + "name": "secret2" + } + }, + "enabled": true, + "headers": { + "header1": "header1val", + "header2": "header2val" + } + } + } +] diff --git a/test/resources/examples/webhooks.yaml b/test/resources/examples/webhooks.yaml new file mode 100644 index 00000000..014cb595 --- /dev/null +++ b/test/resources/examples/webhooks.yaml @@ -0,0 +1,51 @@ +--- +apiVersion: v1alpha1 +title: webhook1 title +name: webhook1 +kind: Webhook +metadata: + scope: + kind: Environment + name: env1 +attributes: + createdBy: yaml +tags: + - webhook + - cli + - axway +spec: + auth: + secret: + name: secret1 + key: key1 + enabled: true + url: https://webhookurl.com/path + headers: + header1: header1val + header2: header2val + +--- +apiVersion: v1alpha1 +title: webhook2 title +name: webhook2 +kind: Webhook +metadata: + scope: + kind: Environment + name: env2 +attributes: + createdBy: yaml +tags: + - webhook + - cli + - axway +spec: + auth: + secret: + name: secret2 + key: key1 + enabled: true + url: https://webhookurl.com/path + headers: + header1: header1val + header2: header2val diff --git a/test/resources/images/env_variables.png b/test/resources/images/env_variables.png new file mode 100644 index 0000000000000000000000000000000000000000..eccfff788e6197e3bb2f7927e7605e54e9e73121 GIT binary patch literal 95964 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 literal 0 HcmV?d00001 diff --git a/test/resources/testData/README.md b/test/resources/testData/README.md new file mode 100644 index 00000000..85af2493 --- /dev/null +++ b/test/resources/testData/README.md @@ -0,0 +1,17 @@ +# Test data + +Folder contains `yaml` files to use and corresponding copy-paste of the real api responses (./apiResponses) for testing purposes. + +- To create instances use two provided files: [testInstances1](./testInstances1.yaml), [testInstances1](./testInstances1.yaml), + and run following cli commands + + ```bash + axway engage create -f resources/testData/testInstances1.yaml + axway engage create -f resources/testData/testInstances2.yaml + ``` + +- To create just a couple resources (not the full set) use [testInstances1short](./testInstances1short.yaml). This set will be used in some tests just to make nock mocks a bit smaller. If using this set just use first entities from real api responses (ie: "testenv1", "apisvc1", etc.) + + ```bash + axway engage create -f resources/testData/testInstances1short.yaml + ``` diff --git a/test/resources/testData/apiResponses/apiservices.json b/test/resources/testData/apiResponses/apiservices.json new file mode 100644 index 00000000..2b37a4a9 --- /dev/null +++ b/test/resources/testData/apiResponses/apiservices.json @@ -0,0 +1,62 @@ +[ + { + "group": "management", + "apiVersion": "v1alpha1", + "kind": "APIService", + "name": "apisvc1", + "title": "apisvc1 title", + "metadata": { + "id": "e4e0888f71a3f8b10171c2d2906d053a", + "audit": { + "createTimestamp": "2020-04-28T22:05:11.662+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-04-28T22:05:11.662+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "scope": { + "id": "e4e0892e71a3f7b00171c2d28eb0052f", + "kind": "Environment", + "name": "testenv1" + }, + "resourceVersion": "5127", + "references": [] + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "description": "apisvc1 description" + } + }, + { + "group": "management", + "apiVersion": "v1alpha1", + "kind": "APIService", + "name": "apisvc2", + "title": "apisvc2 title", + "metadata": { + "id": "e4e0892e71a3f7b00171c2d2ae05054d", + "audit": { + "createTimestamp": "2020-04-28T22:05:19.238+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-04-28T22:05:19.238+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "scope": { + "id": "e4e0892e71a3f7b00171c2d2acbf0547", + "kind": "Environment", + "name": "testenv2" + }, + "resourceVersion": "5135", + "references": [] + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "description": "apisvc2 description" + } + } +] diff --git a/test/resources/testData/apiResponses/assets.json b/test/resources/testData/apiResponses/assets.json new file mode 100644 index 00000000..c30ebca6 --- /dev/null +++ b/test/resources/testData/apiResponses/assets.json @@ -0,0 +1,309 @@ +[ + { + "group": "catalog", + "apiVersion": "v1alpha1", + "kind": "Asset", + "name": "advil", + "title": "advil", + "metadata": { + "id": "8ac98c6d7b3c38e2017b3cddc657029d", + "audit": { + "createTimestamp": "2021-08-13T00:16:12.375+0000", + "createUserId": "e9f13036-0bc7-4611-85a7-6a0901dbaabf", + "modifyTimestamp": "2021-08-13T16:16:43.350+0000", + "modifyUserId": "e9f13036-0bc7-4611-85a7-6a0901dbaabf" + }, + "resourceVersion": "1", + "references": [ + { + "id": "8ac98c6d7b3c38e2017b3cbf1a1c0203", + "kind": "Environment", + "name": "qa", + "selfLink": "/management/v1alpha1/environments/qa", + "type": "soft", + "group": "management" + }, + { + "id": "8ac98c6d7b3c38e2017b3cdda5400298", + "kind": "AssetMapping", + "name": "demotestmapping9", + "scopeKind": "Environment", + "scopeName": "qa", + "selfLink": "/management/v1alpha1/environments/qa/assetmappings/demotestmapping9", + "type": "soft", + "group": "management" + }, + { + "id": "8ac98c6d7b3c38e2017b3cc0d5a5020f", + "kind": "APIService", + "name": "music", + "scopeKind": "Environment", + "scopeName": "qa", + "selfLink": "/management/v1alpha1/environments/qa/apiservices/music", + "type": "soft", + "group": "management" + }, + { + "id": "8ac983e47b3d8e0f017b402e8f1a0000", + "kind": "Environment", + "name": "asset-demo-env", + "selfLink": "/management/v1alpha1/environments/asset-demo-env", + "type": "soft", + "group": "management" + }, + { + "id": "8ac983e47b3d8e0f017b404c8a440281", + "kind": "APIService", + "name": "advil", + "scopeKind": "Environment", + "scopeName": "asset-demo-env", + "selfLink": "/management/v1alpha1/environments/asset-demo-env/apiservices/advil", + "type": "soft", + "group": "management" + }, + { + "id": "8ac983e47b3d8e0f017b404d260302a9", + "kind": "AssetMapping", + "name": "advilmapping", + "scopeKind": "Environment", + "scopeName": "asset-demo-env", + "selfLink": "/management/v1alpha1/environments/asset-demo-env/assetmappings/advilmapping", + "type": "soft", + "group": "management" + } + ], + "selfLink": "/catalog/v1alpha1/assets/advil" + }, + "attributes": {}, + "finalizers": [], + "tags": ["nsaid"], + "spec": { + "type": "API", + "categories": [] + }, + "references": { + "apiServices": ["management/asset-demo-env/advil", "management/qa/music"], + "environments": ["management/asset-demo-env", "management/qa"], + "assetMappings": ["management/asset-demo-env/advilmapping", "management/qa/demotestmapping9"] + }, + "state": "draft" + }, + { + "group": "catalog", + "apiVersion": "v1alpha1", + "kind": "Asset", + "name": "ibuprofen", + "title": "ibuprofen", + "metadata": { + "id": "8ac983e47b3d8e0f017b405cda5f03ee", + "audit": { + "createTimestamp": "2021-08-13T16:33:52.223+0000", + "createUserId": "59b75dc4-06e7-40d3-b116-54efe121f4da", + "modifyTimestamp": "2021-08-13T16:34:15.572+0000", + "modifyUserId": "59b75dc4-06e7-40d3-b116-54efe121f4da" + }, + "resourceVersion": "1", + "references": [ + { + "id": "8ac983e47b3d8e0f017b4057a0100349", + "kind": "Environment", + "name": "amplifycentral", + "selfLink": "/management/v1alpha1/environments/amplifycentral", + "type": "soft", + "group": "management" + }, + { + "id": "8ac983e47b3d8e0f017b405cd86603e4", + "kind": "AssetMapping", + "name": "demo2", + "scopeKind": "Environment", + "scopeName": "amplifycentral", + "selfLink": "/management/v1alpha1/environments/amplifycentral/assetmappings/demo2", + "type": "soft", + "group": "management" + }, + { + "id": "8ac983e47b3d8e0f017b405bfff903d1", + "kind": "APIService", + "name": "service11", + "scopeKind": "Environment", + "scopeName": "amplifycentral", + "selfLink": "/management/v1alpha1/environments/amplifycentral/apiservices/service11", + "type": "soft", + "group": "management" + }, + { + "id": "8ac983e47b3d8e0f017b405d343e0408", + "kind": "AssetMapping", + "name": "demo2a", + "scopeKind": "Environment", + "scopeName": "amplifycentral", + "selfLink": "/management/v1alpha1/environments/amplifycentral/assetmappings/demo2a", + "type": "soft", + "group": "management" + } + ], + "selfLink": "/catalog/v1alpha1/assets/ibuprofen" + }, + "attributes": { + "release": "2.0.4" + }, + "finalizers": [], + "tags": ["nsaid", "payments"], + "spec": { + "type": "API", + "categories": [], + "description": "This is a sample Musical Instruments API." + }, + "references": { + "apiServices": ["management/amplifycentral/service11"], + "environments": ["management/amplifycentral"], + "assetMappings": ["management/amplifycentral/demo2a", "management/amplifycentral/demo2"] + }, + "state": "draft" + }, + { + "group": "catalog", + "apiVersion": "v1alpha1", + "kind": "Asset", + "name": "insulin", + "title": "insulin", + "metadata": { + "id": "8ac983e47b3d8e0f017b40588ecc03c2", + "audit": { + "createTimestamp": "2021-08-13T16:29:10.732+0000", + "createUserId": "e9f13036-0bc7-4611-85a7-6a0901dbaabf", + "modifyTimestamp": "2021-08-13T16:42:39.235+0000", + "modifyUserId": "59b75dc4-06e7-40d3-b116-54efe121f4da" + }, + "resourceVersion": "1", + "references": [ + { + "id": "8ac983e47b3d8e0f017b40588db403bd", + "kind": "AssetMapping", + "name": "insulinmapping", + "scopeKind": "Environment", + "scopeName": "asset-demo-env", + "selfLink": "/management/v1alpha1/environments/asset-demo-env/assetmappings/insulinmapping", + "type": "soft", + "group": "management" + }, + { + "id": "8ac983e47b3d8e0f017b4064e3cb04f3", + "kind": "AssetMapping", + "name": "demo4", + "scopeKind": "Environment", + "scopeName": "asset-demo-env", + "selfLink": "/management/v1alpha1/environments/asset-demo-env/assetmappings/demo4", + "type": "soft", + "group": "management" + }, + { + "id": "8ac983e47b3d8e0f017b40580f540370", + "kind": "APIService", + "name": "insulin", + "scopeKind": "Environment", + "scopeName": "asset-demo-env", + "selfLink": "/management/v1alpha1/environments/asset-demo-env/apiservices/insulin", + "type": "soft", + "group": "management" + }, + { + "id": "8ac983e47b3d8e0f017b402e8f1a0000", + "kind": "Environment", + "name": "asset-demo-env", + "selfLink": "/management/v1alpha1/environments/asset-demo-env", + "type": "soft", + "group": "management" + } + ], + "selfLink": "/catalog/v1alpha1/assets/insulin" + }, + "attributes": {}, + "finalizers": [], + "tags": ["diabetes", "payments", "novartis"], + "spec": { + "type": "API", + "categories": [] + }, + "references": { + "apiServices": ["management/asset-demo-env/insulin"], + "environments": ["management/asset-demo-env"], + "assetMappings": ["management/asset-demo-env/insulinmapping", "management/asset-demo-env/demo4"] + }, + "state": "draft" + }, + { + "group": "catalog", + "apiVersion": "v1alpha1", + "kind": "Asset", + "name": "ivermectin", + "title": "ivermectin", + "metadata": { + "id": "8ac983e47b3d8e0f017b40588ecc03c2", + "audit": { + "createTimestamp": "2021-08-13T16:29:10.732+0000", + "createUserId": "e9f13036-0bc7-4611-85a7-6a0901dbaabf", + "modifyTimestamp": "2021-08-13T16:42:39.235+0000", + "modifyUserId": "59b75dc4-06e7-40d3-b116-54efe121f4da" + }, + "resourceVersion": "1", + "references": [ + { + "id": "8ac983e47b3d8e0f017b40588db403bd", + "kind": "AssetMapping", + "name": "ivermectinmapping", + "scopeKind": "Environment", + "scopeName": "asset-demo-env", + "selfLink": "/management/v1alpha1/environments/asset-demo-env/assetmappings/ivermectinmapping", + "type": "soft", + "group": "management" + }, + { + "id": "8ac983e47b3d8e0f017b4064e3cb04f3", + "kind": "AssetMapping", + "name": "demo4", + "scopeKind": "Environment", + "scopeName": "asset-demo-env", + "selfLink": "/management/v1alpha1/environments/asset-demo-env/assetmappings/demo4", + "type": "soft", + "group": "management" + }, + { + "id": "8ac983e47b3d8e0f017b40580f540370", + "kind": "APIService", + "name": "ivermectin", + "scopeKind": "Environment", + "scopeName": "asset-demo-env", + "selfLink": "/management/v1alpha1/environments/asset-demo-env/apiservices/ivermectin", + "type": "soft", + "group": "management" + }, + { + "id": "8ac983e47b3d8e0f017b402e8f1a0000", + "kind": "Environment", + "name": "asset-demo-env", + "selfLink": "/management/v1alpha1/environments/asset-demo-env", + "type": "soft", + "group": "management" + } + ], + "selfLink": "/catalog/v1alpha1/assets/ivermectin" + }, + "attributes": { + "release": "2.0.4" + }, + "finalizers": [], + "tags": ["parasite"], + "spec": { + "type": "API", + "categories": [] + }, + "references": { + "apiServices": ["management/asset-demo-env/ivermectin"], + "environments": ["management/asset-demo-env"], + "assetMappings": ["management/asset-demo-env/ivermectinmapping", "management/asset-demo-env/demo4"] + }, + "state": "draft" + } +] diff --git a/test/resources/testData/apiResponses/catalogCommandLines.json b/test/resources/testData/apiResponses/catalogCommandLines.json new file mode 100644 index 00000000..9dbf7417 --- /dev/null +++ b/test/resources/testData/apiResponses/catalogCommandLines.json @@ -0,0 +1,1930 @@ +[ + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "releasetag-asset", + "title": "releasetag-asset", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640bdb2004a", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.290+0000", + "modifyTimestamp": "2021-10-15T23:19:22.290+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e97c1798bedb401798beeda4a002f", + "kind": "ResourceDefinition", + "name": "releasetag-asset", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/releasetag-asset", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/releasetag-asset" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "releasetags", + "singular": "releasetag", + "shortNames": ["releasetag", "releasetags"], + "shortNamesAlias": ["releasetag"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Release Tag." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Release Tag was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Release Tag." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The kind of the scope of the resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The name of the resource this Release Tag belongs to." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.level", + "description": "The status of the Resource." + } + ], + "resourceDefinition": "releasetag-asset" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "releasetag-product", + "title": "releasetag-product", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640bde0004c", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.336+0000", + "modifyTimestamp": "2021-10-15T23:19:22.336+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e95697b84d780017b84d98cb20013", + "kind": "ResourceDefinition", + "name": "releasetag-product", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/releasetag-product", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/releasetag-product" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "releasetags", + "singular": "releasetag", + "shortNames": ["releasetag", "releasetags"], + "shortNamesAlias": ["releasetag"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Release Tag." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Release Tag was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Release Tag." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The kind of the scope of the resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The name of the resource this Release Tag belongs to." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.level", + "description": "The status of the Resource." + } + ], + "resourceDefinition": "releasetag-product" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "documentation-product", + "title": "documentation-product", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640be07004e", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.375+0000", + "modifyTimestamp": "2021-10-15T23:19:22.375+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e94e27b418d1e017b418f8cf1004c", + "kind": "ResourceDefinition", + "name": "documentation-product", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/documentation-product", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/documentation-product" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "documentation", + "singular": "documentation", + "shortNames": ["doc"], + "shortNamesAlias": ["doc"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Documentation." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Documentation was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Documentation." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.level", + "description": "The status of the Resource." + } + ], + "resourceDefinition": "documentation-product" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "documentation-productrelease", + "title": "documentation-productrelease", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640be400050", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.432+0000", + "modifyTimestamp": "2021-10-15T23:19:22.432+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e95697b84d780017b84d98d030015", + "kind": "ResourceDefinition", + "name": "documentation-productrelease", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/documentation-productrelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/documentation-productrelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "documentation", + "singular": "documentation", + "shortNames": ["doc"], + "shortNamesAlias": ["doc"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Documentation." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Documentation was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Documentation." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.level", + "description": "The status of the Resource." + } + ], + "resourceDefinition": "documentation-productrelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "assetresource-asset", + "title": "assetresource-asset", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640be610052", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.465+0000", + "modifyTimestamp": "2021-10-15T23:19:22.465+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e89737870faf8017870fc51c30057", + "kind": "ResourceDefinition", + "name": "assetresource-asset", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/assetresource-asset", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/assetresource-asset" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "assetresources", + "singular": "assetresource", + "shortNames": ["assetres"], + "shortNamesAlias": ["assetres"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Asset Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Asset Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Asset Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "Asset" + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The name of the Asset this Asset Resource belongs to." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "assetresource-asset" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "assetresource-assetrelease", + "title": "assetresource-assetrelease", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640be8c0054", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.508+0000", + "modifyTimestamp": "2021-10-15T23:19:22.508+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e89737870faf8017870fc50b80051", + "kind": "ResourceDefinition", + "name": "assetresource-assetrelease", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/assetresource-assetrelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/assetresource-assetrelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "assetresources", + "singular": "assetresource", + "shortNames": ["assetres"], + "shortNamesAlias": ["assetres"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Asset Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Asset Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Asset Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "Asset" + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The name of the Asset this Asset Resource belongs to." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "assetresource-assetrelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "assetrequestdefinition-asset", + "title": "assetrequestdefinition-asset", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640bebe0056", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.558+0000", + "modifyTimestamp": "2021-10-15T23:19:22.558+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e89737870faf8017870fc52160059", + "kind": "ResourceDefinition", + "name": "assetrequestdefinition-asset", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/assetrequestdefinition-asset", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/assetrequestdefinition-asset" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "assetrequestdefinitions", + "singular": "assetrequestdefinition", + "shortNames": ["assetreqdef"], + "shortNamesAlias": ["assetreqdef"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "assetrequestdefinition-asset" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "assetrequestdefinition-assetrelease", + "title": "assetrequestdefinition-assetrelease", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640bf310058", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.673+0000", + "modifyTimestamp": "2021-10-15T23:19:22.673+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e97c1798bedb401798beeda24002e", + "kind": "ResourceDefinition", + "name": "assetrequestdefinition-assetrelease", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/assetrequestdefinition-assetrelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/assetrequestdefinition-assetrelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "assetrequestdefinitions", + "singular": "assetrequestdefinition", + "shortNames": ["assetreqdef"], + "shortNamesAlias": ["assetreqdef"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "assetrequestdefinition-assetrelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "terms-product", + "title": "terms-product", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640bf59005a", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.713+0000", + "modifyTimestamp": "2021-10-15T23:19:22.713+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e94e27b418d1e017b418f8d17004e", + "kind": "ResourceDefinition", + "name": "terms-product", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/terms-product", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/terms-product" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "terms", + "singular": "terms", + "shortNames": ["terms"], + "shortNamesAlias": ["terms"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Terms ." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Terms were created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Terms." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "terms-product" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "terms-productrelease", + "title": "terms-productrelease", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640bf92005c", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.770+0000", + "modifyTimestamp": "2021-10-15T23:19:22.770+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e95697b84d780017b84d98c940012", + "kind": "ResourceDefinition", + "name": "terms-productrelease", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/terms-productrelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/terms-productrelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "terms", + "singular": "terms", + "shortNames": ["terms"], + "shortNamesAlias": ["terms"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Terms ." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Terms were created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Terms." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "terms-productrelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "publishedproduct-marketplace", + "title": "publishedproduct-marketplace", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640bfb5005e", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.805+0000", + "modifyTimestamp": "2021-10-15T23:19:22.805+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e93457c5c29b0017c5c2c2578004b", + "kind": "ResourceDefinition", + "name": "publishedproduct-marketplace", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/publishedproduct-marketplace", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/publishedproduct-marketplace" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "publishedproducts", + "singular": "publishedproduct", + "shortNames": ["publishedproduct", "publishedproducts"], + "shortNamesAlias": ["publishedproduct"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the published product." + }, + { + "name": "Description", + "type": "string", + "jsonPath": ".spec.description", + "description": "The description of the published product." + }, + { + "name": "Product", + "type": "string", + "jsonPath": ".spec.product.name", + "description": "The name of the product." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the published product was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the published product." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "publishedproduct-marketplace" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "releasenote-product", + "title": "releasenote-product", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640bfea0060", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.858+0000", + "modifyTimestamp": "2021-10-15T23:19:22.858+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e94e27b418d1e017b418f8d05004d", + "kind": "ResourceDefinition", + "name": "releasenote-product", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/releasenote-product", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/releasenote-product" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "releasenotes", + "singular": "releasenote", + "shortNames": ["releasenotes"], + "shortNamesAlias": ["releasenotes"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Release notes." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Release Notes was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Release Notes." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "Asset" + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The name of the scope to which the Release Notes belong to." + } + ], + "resourceDefinition": "releasenote-product" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "releasenote-productrelease", + "title": "releasenote-productrelease", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640c0140062", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.900+0000", + "modifyTimestamp": "2021-10-15T23:19:22.900+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e95697b84d780017b84d98cd80014", + "kind": "ResourceDefinition", + "name": "releasenote-productrelease", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/releasenote-productrelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/releasenote-productrelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "releasenotes", + "singular": "releasenote", + "shortNames": ["releasenotes"], + "shortNamesAlias": ["releasenotes"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Release notes." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Release Notes was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Release Notes." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "Asset" + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The name of the scope to which the Release Notes belong to." + } + ], + "resourceDefinition": "releasenote-productrelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "assetrequest-asset", + "title": "assetrequest-asset", + "metadata": { + "id": "8a2e85dd7c863e3e017c8640c0480064", + "audit": { + "createTimestamp": "2021-10-15T23:19:22.952+0000", + "modifyTimestamp": "2021-10-15T23:19:22.952+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e89737870faf8017870fc5258005b", + "kind": "ResourceDefinition", + "name": "assetrequest-asset", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/assetrequest-asset", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/assetrequest-asset" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "assetrequests", + "singular": "assetrequest", + "shortNames": ["assetrequest"], + "shortNamesAlias": ["assetreq"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.level", + "description": "The status of the Resource." + } + ], + "resourceDefinition": "assetrequest-asset" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "product", + "title": "product", + "metadata": { + "id": "8a2e88127a214a69017a214cc9480034", + "audit": { + "createTimestamp": "2021-06-18T22:45:18.280+0000", + "modifyTimestamp": "2021-10-07T21:55:20.645+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "1", + "references": [ + { + "id": "8a2e88127a214a69017a214cb9dd002e", + "kind": "ResourceDefinition", + "name": "product", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/product", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/product" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "products", + "singular": "product", + "shortNames": ["product", "products"], + "shortNamesAlias": ["products"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the product." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Product was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Product." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "State", + "type": "string", + "jsonPath": ".state", + "description": "The state of the Product." + } + ], + "resourceDefinition": "product" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "stage", + "title": "stage", + "metadata": { + "id": "8a2e89737870faf8017870fc54ea007d", + "audit": { + "createTimestamp": "2021-03-27T00:01:28.298+0000", + "modifyTimestamp": "2021-06-09T22:29:13.141+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "284740", + "references": [ + { + "id": "8a2e89737870faf8017870fc50e40052", + "kind": "ResourceDefinition", + "name": "stage", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/stage", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/stage" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "stages", + "singular": "stage", + "shortNames": ["stg", "stgs"], + "shortNamesAlias": ["stg"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the stage." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the stage was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the stage." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "stage" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "assetrelease", + "title": "assetrelease", + "metadata": { + "id": "8a2e89737870faf8017870fc5519007f", + "audit": { + "createTimestamp": "2021-03-27T00:01:28.345+0000", + "modifyTimestamp": "2021-06-09T22:29:13.300+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "284741", + "references": [ + { + "id": "8a2e89737870faf8017870fc51210053", + "kind": "ResourceDefinition", + "name": "assetrelease", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/assetrelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/assetrelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "assetreleases", + "singular": "assetrelease", + "shortNames": ["assetrelease", "assetsreleases"], + "shortNamesAlias": ["assetrelease"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the asset release." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the asset release was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the asset release." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "assetrelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "asset", + "title": "asset", + "metadata": { + "id": "8a2e89737870faf8017870fc5613008d", + "audit": { + "createTimestamp": "2021-03-27T00:01:28.595+0000", + "modifyTimestamp": "2021-06-09T22:29:13.414+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "284748", + "references": [ + { + "id": "8a2e89737870faf8017870fc51f60058", + "kind": "ResourceDefinition", + "name": "asset", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/asset", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/asset" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "assets", + "singular": "asset", + "shortNames": ["asset", "assets"], + "shortNamesAlias": ["asset"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the asset." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the asset was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the asset." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "asset" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "marketplace", + "title": "marketplace", + "metadata": { + "id": "8a2e8e477b5fc617017b5fc785230053", + "audit": { + "createTimestamp": "2021-08-19T18:58:36.451+0000", + "modifyTimestamp": "2021-08-19T18:58:36.451+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e8e477b5fc617017b5fc77402004d", + "kind": "ResourceDefinition", + "name": "marketplace", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/marketplace", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/marketplace" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "marketplaces", + "singular": "marketplace", + "shortNames": ["marketplace", "marketplaces"], + "shortNamesAlias": ["marketplaces"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the marketplace." + }, + { + "name": "Description", + "type": "string", + "jsonPath": ".spec.description", + "description": "The description of the marketplace." + }, + { + "name": "Base Path", + "type": "string", + "jsonPath": ".spec.basePath", + "description": "The base path of the marketplace." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the marketplace was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the marketplace." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "marketplace" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "category", + "title": "category", + "metadata": { + "id": "8a2e93bb7a3a4d96017a3a501ab2006e", + "audit": { + "createTimestamp": "2021-06-23T19:19:26.130+0000", + "modifyTimestamp": "2021-06-23T19:19:26.130+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "0", + "references": [ + { + "id": "8a2e93bb7a3a4d96017a3a5009cd005c", + "kind": "ResourceDefinition", + "name": "category", + "scopeKind": "ResourceGroup", + "scopeName": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/category", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/catalog/commandlines/category" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "categories", + "singular": "category", + "shortNames": ["category"], + "shortNamesAlias": ["category"] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Category." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Category was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Category." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "category" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "identityprovider", + "title": "identityprovider", + "metadata": { + "id": "8ac9842c90c13dc90190c143363800dc", + "audit": { + "createTimestamp": "2024-07-17T15:15:25.880+0000", + "modifyTimestamp": "2024-07-17T15:15:25.880+0000" + }, + "scope": { + "id": "8ac9842c90c13dc90190c14325410000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac9842c90c13dc90190c14326df0007", + "kind": "ResourceDefinition", + "name": "identityprovider", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/identityprovider", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/identityprovider" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "identityproviders", + "singular": "identityprovider", + "shortNames": [ + "idp" + ], + "shortNamesAlias": [ + "idp" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the identity provider." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the identity provider was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the identity provider." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The group of the identity provider." + } + ], + "resourceDefinition": "identityprovider" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "idpsecret-identityprovider", + "title": "idpsecret-identityprovider", + "metadata": { + "id": "8ac9842c90c13dc90190c143379000f2", + "audit": { + "createTimestamp": "2024-07-17T15:15:26.224+0000", + "modifyTimestamp": "2024-07-17T15:15:26.224+0000" + }, + "scope": { + "id": "8ac9842c90c13dc90190c14325410000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac9842c90c13dc90190c1432a36001c", + "kind": "ResourceDefinition", + "name": "idpsecret-identityprovider", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/idpsecret-identityprovider", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/idpsecret-identityprovider" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "idpsecrets", + "singular": "idpsecret", + "shortNames": [ "idpsec" ], + "shortNamesAlias": [ "idpsec" ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the identity provider secret." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the identity provider secret was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the identity provider secret." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the identity provider secret." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the identity provider secret." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The group of the identity provider secret." + } + ], + "resourceDefinition": "idpsecret-identityprovider" + } + } +] diff --git a/test/resources/testData/apiResponses/catalogResources.json b/test/resources/testData/apiResponses/catalogResources.json new file mode 100644 index 00000000..bc0d85d5 --- /dev/null +++ b/test/resources/testData/apiResponses/catalogResources.json @@ -0,0 +1,2150 @@ +[ + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "product", + "title": "product", + "metadata": { + "id": "8a2e88127a214a69017a214cb9dd002e", + "audit": { + "createTimestamp": "2021-06-18T22:45:14.333+0000", + "modifyTimestamp": "2021-10-15T23:19:12.104+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "26", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/product" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Product", + "plural": "products", + "references": { + "toResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "Asset", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetRelease", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "Category", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "Asset", + "group": "catalog", + "types": ["soft"] + } + ], + "fromResources": [ + { + "kind": "ProductRelease", + "group": "catalog", + "types": ["hard"] + }, + { + "kind": "PublishedProduct", + "group": "catalog", + "types": ["hard"], + "scopeKind": "Marketplace" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes", + "state" + ], + "subResources": { + "names": ["references", "icon", "state", "languages", "languages-fr-fr", "languages-de-de", "languages-en-us", "languages-pt-br"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "assetresource-assetrelease", + "title": "assetresource-assetrelease", + "metadata": { + "id": "8a2e89737870faf8017870fc50b80051", + "audit": { + "createTimestamp": "2021-03-27T00:01:27.224+0000", + "modifyTimestamp": "2021-10-15T23:19:13.942+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "284757", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/assetresource-assetrelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "AssetResource", + "scope": { + "kind": "AssetRelease" + }, + "plural": "assetresources", + "references": { + "toResources": [ + { + "kind": "Stage", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "APIServiceInstance", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "APIServiceRevision", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "AssetRequestDefinition", + "group": "catalog", + "types": ["soft"], + "scopeKind": "AssetRelease" + } + ], + "fromResources": [ + { + "kind": "AssetRequest", + "group": "catalog", + "types": ["hard"], + "scopeKind": "Asset" + }, + { + "kind": "Documentation", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Product" + }, + { + "kind": "Documentation", + "group": "catalog", + "types": ["soft"], + "scopeKind": "ProductRelease" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["references"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "stage", + "title": "stage", + "metadata": { + "id": "8a2e89737870faf8017870fc50e40052", + "audit": { + "createTimestamp": "2021-03-27T00:01:27.268+0000", + "modifyTimestamp": "2021-10-15T23:19:14.100+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "284749", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/stage" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Stage", + "plural": "stages", + "references": { + "toResources": [], + "fromResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "Asset", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetMapping", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetRelease", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "AssetResource", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + }, + { + "kind": "AssetResource", + "group": "catalog", + "types": ["soft"], + "scopeKind": "AssetRelease" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["icon"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "assetrelease", + "title": "assetrelease", + "metadata": { + "id": "8a2e89737870faf8017870fc51210053", + "audit": { + "createTimestamp": "2021-03-27T00:01:27.329+0000", + "modifyTimestamp": "2021-10-15T23:19:12.223+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "284759", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/assetrelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "AssetRelease", + "plural": "assetreleases", + "references": { + "toResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "Stage", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetMappingTemplate", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "Category", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "Environment", + "group": "management", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetMapping", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "Asset", + "group": "catalog", + "types": ["hard"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "APIService", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "ReleaseTag", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + } + ], + "fromResources": [ + { + "kind": "ProductRelease", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "Product", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "AssetRequest", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes", + "spec.version", + "spec.type", + "spec.state" + ], + "subResources": { + "names": ["references", "icon"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "assetresource-asset", + "title": "assetresource-asset", + "metadata": { + "id": "8a2e89737870faf8017870fc51c30057", + "audit": { + "createTimestamp": "2021-03-27T00:01:27.491+0000", + "modifyTimestamp": "2021-10-15T23:19:15.039+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "284764", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/assetresource-asset" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "AssetResource", + "scope": { + "kind": "Asset" + }, + "plural": "assetresources", + "references": { + "toResources": [ + { + "kind": "Stage", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "AssetRequestDefinition", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "APIServiceRevision", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "APIServiceInstance", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + } + ], + "fromResources": [ + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetMapping", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["references"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "assetmapping-asset", + "title": "assetmapping-asset", + "metadata": { + "id": "8a2e89737870faf8017870fc27120001", + "audit": { + "createTimestamp": "2021-03-27T00:01:16.562+0000", + "modifyTimestamp": "2021-10-15T23:18:50.313+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "284721", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/assetmapping-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "AssetMapping", + "scope": { + "kind": "Asset" + }, + "plural": "assetmappings", + "references": { + "toResources": [ + { + "from": { + "subResourceName": "status" + }, + "kind": "APIServiceInstance", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "AssetMappingTemplate", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AccessRequestDefinition", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetRequestDefinition", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + }, + { + "kind": "AccessRequestDefinition", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "kind": "Category", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "APIServiceRevision", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "APIService", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "Stage", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "APIServiceInstance", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "Asset", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetMappingTemplate", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "APIService", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetResource", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + }, + { + "kind": "APIServiceRevision", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + } + ], + "fromResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "Asset", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetRelease", + "group": "catalog", + "types": ["soft"] + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "asset", + "title": "asset", + "metadata": { + "id": "8a2e89737870faf8017870fc51f60058", + "audit": { + "createTimestamp": "2021-03-27T00:01:27.542+0000", + "modifyTimestamp": "2021-10-15T23:19:15.721+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "284763", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/asset" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Asset", + "plural": "assets", + "references": { + "toResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetMappingTemplate", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "Stage", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "Environment", + "group": "management", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetMapping", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "APIService", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "Category", + "group": "catalog", + "types": ["soft"] + } + ], + "fromResources": [ + { + "kind": "Product", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetMapping", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "ProductRelease", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "AssetRelease", + "group": "catalog", + "types": ["hard"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "Product", + "group": "catalog", + "types": ["soft"] + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes", + "spec.type", + "state" + ], + "subResources": { + "names": ["references", "icon", "state"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "assetrequestdefinition-asset", + "title": "assetrequestdefinition-asset", + "metadata": { + "id": "8a2e89737870faf8017870fc52160059", + "audit": { + "createTimestamp": "2021-03-27T00:01:27.574+0000", + "modifyTimestamp": "2021-10-15T23:19:13.251+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "284750", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/assetrequestdefinition-asset" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "AssetRequestDefinition", + "scope": { + "kind": "Asset" + }, + "plural": "assetrequestdefinitions", + "references": { + "toResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "AccessRequestDefinition", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + } + ], + "fromResources": [ + { + "kind": "AssetResource", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetMapping", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["references"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "assetrequest-asset", + "title": "assetrequest-asset", + "metadata": { + "id": "8a2e89737870faf8017870fc5258005b", + "audit": { + "createTimestamp": "2021-03-27T00:01:27.640+0000", + "modifyTimestamp": "2021-10-15T23:19:13.500+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "284755", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/assetrequest-asset" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "AssetRequest", + "scope": { + "kind": "Asset" + }, + "plural": "assetrequests", + "references": { + "toResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "AccessRequest", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "AssetRelease", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "AssetResource", + "group": "catalog", + "types": ["hard"], + "scopeKind": "AssetRelease" + }, + { + "kind": "AssetRequestDefinition", + "group": "catalog", + "types": ["soft"], + "scopeKind": "AssetRelease" + } + ], + "fromResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "AccessRequest", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes", + "state.name", + "status.level" + ], + "subResources": { + "names": ["references", "state", "status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "marketplace", + "title": "marketplace", + "metadata": { + "id": "8a2e8e477b5fc617017b5fc77402004d", + "audit": { + "createTimestamp": "2021-08-19T18:58:32.066+0000", + "modifyTimestamp": "2021-10-15T23:19:12.381+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "2", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/marketplace" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Marketplace", + "plural": "marketplaces", + "references": { + "toResources": [], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "publishedproduct-marketplace", + "title": "publishedproduct-marketplace", + "metadata": { + "id": "8a2e93457c5c29b0017c5c2c2578004b", + "audit": { + "createTimestamp": "2021-10-07T19:12:49.528+0000", + "modifyTimestamp": "2021-10-15T23:19:12.777+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "2", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/publishedproduct-marketplace" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "PublishedProduct", + "scope": { + "kind": "Marketplace" + }, + "plural": "publishedproducts", + "references": { + "toResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "ProductRelease", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "Product", + "group": "catalog", + "types": ["hard"] + } + ], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["references"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "category", + "title": "category", + "metadata": { + "id": "8a2e93bb7a3a4d96017a3a5009cd005c", + "audit": { + "createTimestamp": "2021-06-23T19:19:21.804+0000", + "modifyTimestamp": "2021-10-15T23:19:15.873+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "24", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/category" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Category", + "plural": "categories", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "Product", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "AssetMappingTemplate", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "AssetRelease", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "APIService", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "Asset", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "ConsumerInstance", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "AssetMapping", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "ProductRelease", + "group": "catalog", + "types": ["soft"] + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "documentation-product", + "title": "documentation-product", + "metadata": { + "id": "8a2e94e27b418d1e017b418f8cf1004c", + "audit": { + "createTimestamp": "2021-08-13T22:08:51.953+0000", + "modifyTimestamp": "2021-10-15T23:19:12.962+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "16", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/documentation-product" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Documentation", + "scope": { + "kind": "Product" + }, + "plural": "documentation", + "references": { + "toResources": [ + { + "kind": "Terms", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Product" + }, + { + "kind": "AssetResource", + "group": "catalog", + "types": ["soft"], + "scopeKind": "AssetRelease" + }, + { + "kind": "ReleaseNotes", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Product" + } + ], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes", + "status.level" + ], + "subResources": { + "names": ["status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "releasenote-product", + "title": "releasenote-product", + "metadata": { + "id": "8a2e94e27b418d1e017b418f8d05004d", + "audit": { + "createTimestamp": "2021-08-13T22:08:51.973+0000", + "modifyTimestamp": "2021-10-15T23:19:15.342+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "2", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/releasenote-product" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "ReleaseNotes", + "scope": { + "kind": "Product" + }, + "plural": "releasenotes", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "Documentation", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Product" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "terms-product", + "title": "terms-product", + "metadata": { + "id": "8a2e94e27b418d1e017b418f8d17004e", + "audit": { + "createTimestamp": "2021-08-13T22:08:51.991+0000", + "modifyTimestamp": "2021-10-15T23:19:15.587+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "2", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/terms-product" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Terms", + "scope": { + "kind": "Product" + }, + "plural": "terms", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "Documentation", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Product" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "terms-productrelease", + "title": "terms-productrelease", + "metadata": { + "id": "8a2e95697b84d780017b84d98c940012", + "audit": { + "createTimestamp": "2021-08-26T23:44:14.996+0000", + "modifyTimestamp": "2021-10-15T23:19:11.897+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "1", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/terms-productrelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Terms", + "scope": { + "kind": "ProductRelease" + }, + "plural": "terms", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "Documentation", + "group": "catalog", + "types": ["soft"], + "scopeKind": "ProductRelease" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "releasetag-product", + "title": "releasetag-product", + "metadata": { + "id": "8a2e95697b84d780017b84d98cb20013", + "audit": { + "createTimestamp": "2021-08-26T23:44:15.026+0000", + "modifyTimestamp": "2021-10-15T23:19:12.614+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "2", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/releasetag-product" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "ReleaseTag", + "scope": { + "kind": "Product" + }, + "plural": "releasetags", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "ProductRelease", + "group": "catalog", + "types": ["hard"] + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes", + "status.level" + ], + "subResources": { + "names": ["state", "status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "releasenote-productrelease", + "title": "releasenote-productrelease", + "metadata": { + "id": "8a2e95697b84d780017b84d98cd80014", + "audit": { + "createTimestamp": "2021-08-26T23:44:15.064+0000", + "modifyTimestamp": "2021-10-15T23:19:13.082+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "1", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/releasenote-productrelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "ReleaseNotes", + "scope": { + "kind": "ProductRelease" + }, + "plural": "releasenotes", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "Documentation", + "group": "catalog", + "types": ["soft"], + "scopeKind": "ProductRelease" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "documentation-productrelease", + "title": "documentation-productrelease", + "metadata": { + "id": "8a2e95697b84d780017b84d98d030015", + "audit": { + "createTimestamp": "2021-08-26T23:44:15.107+0000", + "modifyTimestamp": "2021-10-15T23:19:14.411+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "13", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/documentation-productrelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Documentation", + "scope": { + "kind": "ProductRelease" + }, + "plural": "documentation", + "references": { + "toResources": [ + { + "kind": "ReleaseNotes", + "group": "catalog", + "types": ["soft"], + "scopeKind": "ProductRelease" + }, + { + "kind": "AssetResource", + "group": "catalog", + "types": ["soft"], + "scopeKind": "AssetRelease" + }, + { + "kind": "Terms", + "group": "catalog", + "types": ["soft"], + "scopeKind": "ProductRelease" + } + ], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes", + "status.level" + ], + "subResources": { + "names": ["status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "productrelease", + "title": "productrelease", + "metadata": { + "id": "8a2e95697b84d780017b84d98d2b0016", + "audit": { + "createTimestamp": "2021-08-26T23:44:15.147+0000", + "modifyTimestamp": "2021-10-15T23:19:14.785+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "13", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/productrelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "ProductRelease", + "plural": "productreleases", + "references": { + "toResources": [ + { + "kind": "Asset", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "Category", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "AssetRelease", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "ReleaseTag", + "group": "catalog", + "types": ["hard"], + "scopeKind": "Product" + }, + { + "kind": "Product", + "group": "catalog", + "types": ["hard"] + } + ], + "fromResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "PublishedProduct", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Marketplace" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes", + "spec.version", + "status.level", + "spec.state" + ], + "subResources": { + "names": ["icon", "status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "assetrequestdefinition-assetrelease", + "title": "assetrequestdefinition-assetrelease", + "metadata": { + "id": "8a2e97c1798bedb401798beeda24002e", + "audit": { + "createTimestamp": "2021-05-20T22:39:17.028+0000", + "modifyTimestamp": "2021-10-15T23:19:15.160+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "17", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/assetrequestdefinition-assetrelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "AssetRequestDefinition", + "scope": { + "kind": "AssetRelease" + }, + "plural": "assetrequestdefinitions", + "references": { + "toResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "AccessRequestDefinition", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + } + ], + "fromResources": [ + { + "kind": "AssetRequest", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + }, + { + "kind": "AssetResource", + "group": "catalog", + "types": ["soft"], + "scopeKind": "AssetRelease" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["references"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "releasetag-asset", + "title": "releasetag-asset", + "metadata": { + "id": "8a2e97c1798bedb401798beeda4a002f", + "audit": { + "createTimestamp": "2021-05-20T22:39:17.066+0000", + "modifyTimestamp": "2021-10-15T23:19:13.752+0000" + }, + "scope": { + "id": "8a2e89737870faf8017870fc23cb0000", + "kind": "ResourceGroup", + "name": "catalog", + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "resourceVersion": "4", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog/resources/releasetag-asset" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "ReleaseTag", + "scope": { + "kind": "Asset" + }, + "plural": "releasetags", + "references": { + "toResources": [], + "fromResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetRelease", + "group": "catalog", + "types": ["soft"] + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes", + "status.level" + ], + "subResources": { + "names": ["state", "status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "identityprovider", + "title": "identityprovider", + "metadata": { + "id": "8ac9842c90c13dc90190c14326df0007", + "audit": { + "createTimestamp": "2024-07-17T15:15:21.951+0000", + "modifyTimestamp": "2024-07-17T15:15:21.951+0000" + }, + "scope": { + "id": "8ac9842c90c13dc90190c14325410000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/identityprovider" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "IdentityProvider", + "plural": "identityproviders", + "references": { + "toResources": [], + "fromResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "Environment", + "group": "management", + "types": [ + "soft" + ] + }, + { + "kind": "CredentialRequestDefinition", + "group": "catalog", + "types": [ + "hard" + ], + "scopeKind": "AuthorizationProfile" + }, + { + "kind": "CredentialRequestDefinition", + "group": "management", + "types": [ + "hard" + ], + "scopeKind": "Environment" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.state", + "languages.additional.de-de.status", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.deletedReferences.kind", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "metadata.deletedReferences.id", + "title", + "metadata.audit.createTimestamp", + "languages.additional.en-us.status", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "languages.resource.code", + "name", + "languages.additional.fr-fr.status", + "attributes", + "languages.additional.pt-br.status", + "metadata.deletedReferences.name" + ], + "subResources": { + "names": [ + "security" + ] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "idpsecret-identityprovider", + "title": "idpsecret-identityprovider", + "metadata": { + "id": "8ac9842c90c13dc90190c1432a36001c", + "audit": { + "createTimestamp": "2024-07-17T15:15:22.806+0000", + "modifyTimestamp": "2024-07-17T15:15:22.806+0000" + }, + "scope": { + "id": "8ac9842c90c13dc90190c14325410000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/idpsecret-identityprovider" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "IdentityProviderSecret", + "scope": { + "kind": "IdentityProvider" + }, + "plural": "idpsecrets", + "references": { + "toResources": [], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.state", + "languages.additional.de-de.status", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.deletedReferences.kind", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "metadata.deletedReferences.id", + "title", + "metadata.audit.createTimestamp", + "languages.additional.en-us.status", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "languages.resource.code", + "name", + "languages.additional.fr-fr.status", + "attributes", + "languages.additional.pt-br.status", + "metadata.deletedReferences.name" + ], + "subResources": { + "names": [] + } + } + } +] diff --git a/test/resources/testData/apiResponses/environments.json b/test/resources/testData/apiResponses/environments.json new file mode 100644 index 00000000..9b7770bc --- /dev/null +++ b/test/resources/testData/apiResponses/environments.json @@ -0,0 +1,111 @@ +[ + { + "group": "management", + "apiVersion": "v1alpha1", + "kind": "Environment", + "name": "testenv1", + "title": "testenv1 title", + "metadata": { + "id": "e4e0892e71a3f7b00171c2d28eb0052f", + "audit": { + "createTimestamp": "2020-04-28T22:05:11.218+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-04-28T22:05:11.218+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "resourceVersion": "5126", + "references": [] + }, + "attributes": { + "createdBy": "yaml", + "randomNum": "1" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "description": "testenv1 description" + }, + "owner": { + "id": "9c1925de-f60a-4a68-af51-33b4b6e8a19c", + "type": "team", + "teamName": "9c1925de-f60a-4a68-af51-33b4b6e8a19c" + } + }, + { + "group": "management", + "apiVersion": "v1alpha1", + "kind": "Environment", + "name": "testenv2", + "title": "testenv2 title", + "metadata": { + "id": "e4e0892e71a3f7b00171c2d2acbf0547", + "audit": { + "createTimestamp": "2020-04-28T22:05:18.911+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-04-28T22:05:18.911+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "resourceVersion": "5134", + "references": [] + }, + "attributes": { + "createdBy": "yaml", + "randomNum": "1" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "description": "testenv2 description" + } + }, + { + "group": "management", + "apiVersion": "v1alpha1", + "kind": "Environment", + "name": "autogeneratedname1", + "title": "autogeneratedname1 title", + "metadata": { + "id": "e4e0892e71a3f7b00171c2d2acbf0547", + "audit": { + "createTimestamp": "2020-04-28T22:05:18.911+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-04-28T22:05:18.911+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "resourceVersion": "5134", + "references": [] + }, + "attributes": { + "createdBy": "yaml", + "randomNum": "1" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "description": "autogeneratedname1 description" + } + }, + { + "group": "management", + "apiVersion": "v1alpha1", + "kind": "Environment", + "name": "autogeneratedname2", + "title": "autogeneratedname2 title", + "metadata": { + "id": "e4e0892e71a3f7b00171c2d2acbf0547", + "audit": { + "createTimestamp": "2020-04-28T22:05:18.911+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-04-28T22:05:18.911+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "resourceVersion": "5134", + "references": [] + }, + "attributes": { + "createdBy": "yaml", + "randomNum": "1" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "description": "autogeneratedname2 description" + } + } +] diff --git a/test/resources/testData/apiResponses/environmentsV1.json b/test/resources/testData/apiResponses/environmentsV1.json new file mode 100644 index 00000000..b614c5ba --- /dev/null +++ b/test/resources/testData/apiResponses/environmentsV1.json @@ -0,0 +1,110 @@ +[ + { + "group": "management", + "apiVersion": "v1", + "kind": "Environment", + "name": "testenv1", + "title": "testenv1 title", + "metadata": { + "id": "e4e0892e71a3f7b00171c2d28eb0052f", + "audit": { + "createTimestamp": "2020-04-28T22:05:11.218+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-04-28T22:05:11.218+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "resourceVersion": "5126", + "references": [] + }, + "attributes": { + "createdBy": "yaml", + "randomNum": "1" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "description": "testenv1 description" + }, + "owner": { + "id": "9c1925de-f60a-4a68-af51-33b4b6e8a19c", + "type": "team", + "teamName": "9c1925de-f60a-4a68-af51-33b4b6e8a19c" + } + }, + { + "group": "management", + "apiVersion": "v1", + "kind": "Environment", + "name": "testenv2", + "title": "testenv2 title", + "metadata": { + "id": "e4e0892e71a3f7b00171c2d2acbf0547", + "audit": { + "createTimestamp": "2020-04-28T22:05:18.911+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-04-28T22:05:18.911+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "resourceVersion": "5134", + "references": [] + }, + "attributes": { + "createdBy": "yaml", + "randomNum": "1" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "description": "testenv2 description" + } + }, + { + "group": "management", + "apiVersion": "v1", + "kind": "Environment", + "name": "autogeneratedname1", + "title": "autogeneratedname1 title", + "metadata": { + "id": "e4e0892e71a3f7b00171c2d2acbf0547", + "audit": { + "createTimestamp": "2020-04-28T22:05:18.911+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-04-28T22:05:18.911+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "resourceVersion": "5134", + "references": [] + }, + "attributes": { + "createdBy": "yaml", + "randomNum": "1" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "description": "autogeneratedname1 description" + } + }, + { + "group": "management", + "kind": "Environment", + "name": "autogeneratedname2", + "title": "autogeneratedname2 title", + "metadata": { + "id": "e4e0892e71a3f7b00171c2d2acbf0547", + "audit": { + "createTimestamp": "2020-04-28T22:05:18.911+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-04-28T22:05:18.911+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "resourceVersion": "5134", + "references": [] + }, + "attributes": { + "createdBy": "yaml", + "randomNum": "1" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "description": "autogeneratedname2 description" + } + } +] diff --git a/test/resources/testData/apiResponses/frenchLanguageDefinition.json b/test/resources/testData/apiResponses/frenchLanguageDefinition.json new file mode 100644 index 00000000..05707854 --- /dev/null +++ b/test/resources/testData/apiResponses/frenchLanguageDefinition.json @@ -0,0 +1,35 @@ +{ + "group": "catalog", + "apiVersion": "v1alpha1", + "kind": "Product", + "name": "test-product-1", + "metadata": { + "id": "8a2e8e7c91fb25fa0191fcb760c551dd", + "resourceVersion": "15", + "selfLink": "/catalog/v1alpha1/products/test-product-1" + }, + "languages": { + "resource": { + "code": "en-us" + } + }, + "languages-fr-fr": { + "values": [ + { + "path": "/title", + "value": "La sous updated demande un identifiant de département", + "status": "complete", + "_embedded": { + "resource": { + "schema": { + "type": "string", + "maxLength": 350, + "description": "The resource title." + }, + "value": "test-product-1" + } + } + } + ] + } +} \ No newline at end of file diff --git a/test/resources/testData/apiResponses/groups.json b/test/resources/testData/apiResponses/groups.json new file mode 100644 index 00000000..49d70acf --- /dev/null +++ b/test/resources/testData/apiResponses/groups.json @@ -0,0 +1,65 @@ +[ + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "metadata": { + "id": "e4e08f487156b7c8017156b9ed930000", + "audit": { + "createTimestamp": "2020-04-07T22:19:17.851+0000", + "modifyTimestamp": "2020-04-07T22:19:17.851+0000" + }, + "resourceVersion": "1607", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": {} + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceGroup", + "name": "definitions", + "title": "definitions", + "metadata": { + "id": "e4e08f487156b7c8017156b9ee220001", + "audit": { + "createTimestamp": "2020-04-07T22:19:17.936+0000", + "modifyTimestamp": "2020-04-07T22:19:17.936+0000" + }, + "resourceVersion": "1608", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/definitions" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": {} + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceGroup", + "name": "catalog", + "title": "catalog", + "metadata": { + "id": "8a2e89737870faf8017870fc23cb0000", + "audit": { + "createTimestamp": "2021-03-27T00:01:15.722+0000", + "modifyTimestamp": "2021-03-27T00:01:15.722+0000" + }, + "resourceVersion": "284679", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/catalog" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": {} + } +] diff --git a/test/resources/testData/apiResponses/managementCommandLines.json b/test/resources/testData/apiResponses/managementCommandLines.json new file mode 100644 index 00000000..3f02bc42 --- /dev/null +++ b/test/resources/testData/apiResponses/managementCommandLines.json @@ -0,0 +1,6834 @@ +[ + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "k8scluster", + "title": "k8scluster", + "metadata": { + "id": "8ac985a3850c194c01850c707972013f", + "audit": { + "createTimestamp": "2022-12-13T17:02:55.858+0000", + "modifyTimestamp": "2022-12-13T17:02:55.858+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7062c5000d", + "kind": "ResourceDefinition", + "name": "k8scluster", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/k8scluster", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/k8scluster" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "k8sclusters", + "singular": "k8scluster", + "shortNames": [ + "k8", + "k8s" + ], + "shortNamesAlias": [ + "k8sc" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the cluster." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the cluster was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the cluster." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "k8scluster" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "virtualapirelease", + "title": "virtualapirelease", + "metadata": { + "id": "8ac985a3850c194c01850c7079bd0145", + "audit": { + "createTimestamp": "2022-12-13T17:02:55.933+0000", + "modifyTimestamp": "2022-12-13T17:02:55.933+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c70691e0033", + "kind": "ResourceDefinition", + "name": "virtualapirelease", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/virtualapirelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "virtualapireleases", + "singular": "virtualapirelease", + "shortNames": [ + "vapirelease", + "vapireleases", + "virtualapirelease", + "virtualapireleases" + ], + "shortNamesAlias": [ + "virtualapirelease" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the virtual API." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the virtual API release was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the virtual API." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + } + ], + "resourceDefinition": "virtualapirelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "virtualapi", + "title": "virtualapi", + "metadata": { + "id": "8ac985a3850c194c01850c7079da0147", + "audit": { + "createTimestamp": "2022-12-13T17:02:55.962+0000", + "modifyTimestamp": "2022-12-13T17:02:55.962+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c70637f0011", + "kind": "ResourceDefinition", + "name": "virtualapi", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/virtualapi", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "virtualapis", + "singular": "virtualapi", + "shortNames": [ + "vapi" + ], + "shortNamesAlias": [ + "vapi" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the virtual API." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the virtual API was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the virtual API." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.level", + "description": "The resource status." + } + ], + "resourceDefinition": "virtualapi" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "mesh", + "title": "mesh", + "metadata": { + "id": "8ac985a3850c194c01850c707a27014b", + "audit": { + "createTimestamp": "2022-12-13T17:02:56.038+0000", + "modifyTimestamp": "2022-12-13T17:02:56.038+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c70696f0036", + "kind": "ResourceDefinition", + "name": "mesh", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/mesh", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/mesh" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "meshes", + "singular": "mesh", + "shortNames": [ + "mesh", + "meshes" + ], + "shortNamesAlias": [ + "mesh" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Mesh." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Mesh was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Mesh." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "mesh" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "watchtopic", + "title": "watchtopic", + "metadata": { + "id": "8ac985a3850c194c01850c707c30016b", + "audit": { + "createTimestamp": "2022-12-13T17:02:56.560+0000", + "modifyTimestamp": "2022-12-13T17:02:56.560+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706a75003e", + "kind": "ResourceDefinition", + "name": "watchtopic", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/watchtopic", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/watchtopic" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "watchtopics", + "singular": "watchtopic", + "shortNames": [ + "wt", + "wts" + ], + "shortNamesAlias": [ + "wt" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the WatchTopic." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the WatchTopic was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the WatchTopic." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "watchtopic" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "environment", + "title": "environment", + "metadata": { + "id": "8ac985a3850c194c01850c707c65016f", + "audit": { + "createTimestamp": "2022-12-13T17:02:56.613+0000", + "modifyTimestamp": "2022-12-13T17:02:56.613+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706a9f003f", + "kind": "ResourceDefinition", + "name": "environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "environments", + "singular": "environment", + "shortNames": [ + "env", + "envs" + ], + "shortNamesAlias": [ + "env" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the environment." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the environment was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the environment." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + }, + { + "name": "Owner", + "type": "teamGuid", + "jsonPath": ".owner.id", + "description": "The owner name." + } + ], + "resourceDefinition": "environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "integration", + "title": "integration", + "metadata": { + "id": "8ac985a3850c194c01850c707d020179", + "audit": { + "createTimestamp": "2022-12-13T17:02:56.770+0000", + "modifyTimestamp": "2022-12-13T17:02:56.770+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7065e4001f", + "kind": "ResourceDefinition", + "name": "integration", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/integration", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/integration" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "integrations", + "singular": "integration", + "shortNames": [ + "integr" + ], + "shortNamesAlias": [ + "integ" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the integration." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the integration group was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the integration." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "integration" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "accesscontrollist-virtualapirelease", + "title": "accesscontrollist-virtualapirelease", + "metadata": { + "id": "8ac996b686a3f2420186a3f37b870001", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.519+0000", + "modifyTimestamp": "2023-03-02T20:11:28.519+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706211000a", + "kind": "ResourceDefinition", + "name": "accesscontrollist-virtualapirelease", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/accesscontrollist-virtualapirelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/accesscontrollist-virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "accesscontrollists", + "singular": "accesscontrollist", + "shortNames": [ + "acl", + "acls" + ], + "shortNamesAlias": [ + "acl" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the AccessControlList." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the AccessControlList was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the AccessControlList." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the AccessControlList." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the AccessControlList." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "accesscontrollist-virtualapirelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "accesscontrollist-integration", + "title": "accesscontrollist-integration", + "metadata": { + "id": "8ac996b686a3f2420186a3f37ba80003", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.552+0000", + "modifyTimestamp": "2023-03-02T20:11:28.552+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706838002d", + "kind": "ResourceDefinition", + "name": "accesscontrollist-integration", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/accesscontrollist-integration", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/accesscontrollist-integration" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "accesscontrollists", + "singular": "accesscontrollist", + "shortNames": [ + "acl", + "acls" + ], + "shortNamesAlias": [ + "acl" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the AccessControlList." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the AccessControlList was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the AccessControlList." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the AccessControlList." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the AccessControlList." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "accesscontrollist-integration" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "accesscontrollist-k8scluster", + "title": "accesscontrollist-k8scluster", + "metadata": { + "id": "8ac996b686a3f2420186a3f37bc30005", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.579+0000", + "modifyTimestamp": "2023-03-02T20:11:28.579+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7066cb0023", + "kind": "ResourceDefinition", + "name": "accesscontrollist-k8scluster", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/accesscontrollist-k8scluster", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/accesscontrollist-k8scluster" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "accesscontrollists", + "singular": "accesscontrollist", + "shortNames": [ + "acl", + "acls" + ], + "shortNamesAlias": [ + "acl" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the AccessControlList." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the AccessControlList was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the AccessControlList." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the AccessControlList." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the AccessControlList." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "accesscontrollist-k8scluster" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "accesscontrollist-watchtopic", + "title": "accesscontrollist-watchtopic", + "metadata": { + "id": "8ac996b686a3f2420186a3f37bdf0007", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.607+0000", + "modifyTimestamp": "2023-03-02T20:11:28.607+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706a36003c", + "kind": "ResourceDefinition", + "name": "accesscontrollist-watchtopic", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/accesscontrollist-watchtopic", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/accesscontrollist-watchtopic" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "accesscontrollists", + "singular": "accesscontrollist", + "shortNames": [ + "acl", + "acls" + ], + "shortNamesAlias": [ + "acl" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the AccessControlList." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the AccessControlList was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the AccessControlList." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the AccessControlList." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the AccessControlList." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "accesscontrollist-watchtopic" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "accesscontrollist-environment", + "title": "accesscontrollist-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37bf60009", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.630+0000", + "modifyTimestamp": "2023-03-02T20:11:28.630+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7069a80038", + "kind": "ResourceDefinition", + "name": "accesscontrollist-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/accesscontrollist-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/accesscontrollist-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "accesscontrollists", + "singular": "accesscontrollist", + "shortNames": [ + "acl", + "acls" + ], + "shortNamesAlias": [ + "acl" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the AccessControlList." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the AccessControlList was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the AccessControlList." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the AccessControlList." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the AccessControlList." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "accesscontrollist-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "accesscontrollist-mesh", + "title": "accesscontrollist-mesh", + "metadata": { + "id": "8ac996b686a3f2420186a3f37c11000b", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.657+0000", + "modifyTimestamp": "2023-03-02T20:11:28.657+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c70686a002e", + "kind": "ResourceDefinition", + "name": "accesscontrollist-mesh", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/accesscontrollist-mesh", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/accesscontrollist-mesh" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "accesscontrollists", + "singular": "accesscontrollist", + "shortNames": [ + "acl", + "acls" + ], + "shortNamesAlias": [ + "acl" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the AccessControlList." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the AccessControlList was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the AccessControlList." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the AccessControlList." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the AccessControlList." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "accesscontrollist-mesh" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "accesscontrollist-virtualapi", + "title": "accesscontrollist-virtualapi", + "metadata": { + "id": "8ac996b686a3f2420186a3f37c29000d", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.681+0000", + "modifyTimestamp": "2023-03-02T20:11:28.681+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706afc0042", + "kind": "ResourceDefinition", + "name": "accesscontrollist-virtualapi", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/accesscontrollist-virtualapi", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/accesscontrollist-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "accesscontrollists", + "singular": "accesscontrollist", + "shortNames": [ + "acl", + "acls" + ], + "shortNamesAlias": [ + "acl" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the AccessControlList." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the AccessControlList was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the AccessControlList." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the AccessControlList." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the AccessControlList." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "accesscontrollist-virtualapi" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "webhook-environment", + "title": "webhook-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37c42000f", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.706+0000", + "modifyTimestamp": "2023-03-02T20:11:28.706+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7061940008", + "kind": "ResourceDefinition", + "name": "webhook-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/webhook-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/webhook-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "webhooks", + "singular": "webhook", + "shortNames": [ + "wh", + "whs" + ], + "shortNamesAlias": [ + "webh" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Webhook." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Webhook was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Webhook." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Webhook." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Webhook." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The group of the Webhook." + } + ], + "resourceDefinition": "webhook-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "webhook-integration", + "title": "webhook-integration", + "metadata": { + "id": "8ac996b686a3f2420186a3f37c590011", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.729+0000", + "modifyTimestamp": "2023-03-02T20:11:28.729+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7066040020", + "kind": "ResourceDefinition", + "name": "webhook-integration", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/webhook-integration", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/webhook-integration" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "webhooks", + "singular": "webhook", + "shortNames": [ + "wh", + "whs" + ], + "shortNamesAlias": [ + "webh" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Webhook." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Webhook was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Webhook." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Webhook." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Webhook." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The group of the Webhook." + } + ], + "resourceDefinition": "webhook-integration" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "ampruntimeconfig-environment", + "title": "ampruntimeconfig-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37c760013", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.758+0000", + "modifyTimestamp": "2023-03-02T20:11:28.758+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7069520035", + "kind": "ResourceDefinition", + "name": "ampruntimeconfig-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/ampruntimeconfig-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/ampruntimeconfig-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "ampruntimeconfigs", + "singular": "ampruntimeconfig", + "shortNames": [ + "amprtcfg" + ], + "shortNamesAlias": [ + "amprtcfg" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.name", + "description": "The resource status." + } + ], + "resourceDefinition": "ampruntimeconfig-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "credentialrequestdefinition-environment", + "title": "credentialrequestdefinition-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37c8c0015", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.780+0000", + "modifyTimestamp": "2023-03-02T20:11:28.780+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7069330034", + "kind": "ResourceDefinition", + "name": "credentialrequestdefinition-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/credentialrequestdefinition-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/credentialrequestdefinition-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "credentialrequestdefinitions", + "singular": "credentialrequestdefinition", + "shortNames": [ + "credreqdef", + "credreqdefs" + ], + "shortNamesAlias": [ + "credreqdef" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the CredentialRequestDefinition." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the CredentialRequestDefinition was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the CredentialRequestDefinition." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the CredentialRequestDefinition." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the CredentialRequestDefinition." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "credentialrequestdefinition-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "apispec-k8scluster", + "title": "apispec-k8scluster", + "metadata": { + "id": "8ac996b686a3f2420186a3f37ca10017", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.801+0000", + "modifyTimestamp": "2023-03-02T20:11:28.801+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7064660015", + "kind": "ResourceDefinition", + "name": "apispec-k8scluster", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/apispec-k8scluster", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/apispec-k8scluster" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "apispecs", + "singular": "apispec", + "shortNames": [ + "apispec", + "apispecs" + ], + "shortNamesAlias": [ + "apisp" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "apispec-k8scluster" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "governanceagent-environment", + "title": "governanceagent-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37cb60019", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.822+0000", + "modifyTimestamp": "2023-03-02T20:11:28.822+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706550001b", + "kind": "ResourceDefinition", + "name": "governanceagent-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/governanceagent-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/governanceagent-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "governanceagents", + "singular": "governanceagent", + "shortNames": [ + "ga", + "gas" + ], + "shortNamesAlias": [ + "ga" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the governance agent." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the governance agent was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the governance agent." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the governance agent." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the governance agent." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The group of the governance agent." + }, + { + "name": "Dataplane Type", + "type": "string", + "jsonPath": ".spec.dataplaneType", + "description": "The dataplane type the agent connects to." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.state", + "description": "The current status of the governance agent." + } + ], + "resourceDefinition": "governanceagent-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "managedapplication-environment", + "title": "managedapplication-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37ccb001b", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.843+0000", + "modifyTimestamp": "2023-03-02T20:11:28.843+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7064b40017", + "kind": "ResourceDefinition", + "name": "managedapplication-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/managedapplication-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/managedapplication-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "managedapplications", + "singular": "managedapplication", + "shortNames": [ + "managedapp", + "managedapps" + ], + "shortNamesAlias": [ + "managedapp" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the ManagedApplication." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the ManagedApplication was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the ManagedApplication." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The ManagedApplication kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the ManagedApplication." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the ManagedApplication." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.level", + "description": "The resource status." + } + ], + "resourceDefinition": "managedapplication-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "resourcehook-integration", + "title": "resourcehook-integration", + "metadata": { + "id": "8ac996b686a3f2420186a3f37ce0001d", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.864+0000", + "modifyTimestamp": "2023-03-02T20:11:28.864+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7069850037", + "kind": "ResourceDefinition", + "name": "resourcehook-integration", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/resourcehook-integration", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/resourcehook-integration" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "resourcehooks", + "singular": "resourcehook", + "shortNames": [ + "reshook" + ], + "shortNamesAlias": [ + "resourceh" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource Hook." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource Hook was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource Hook." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource Hook." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource Hook." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "resourcehook-integration" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "externalsecret-environment", + "title": "externalsecret-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37cf7001f", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.887+0000", + "modifyTimestamp": "2023-03-02T20:11:28.887+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7067710028", + "kind": "ResourceDefinition", + "name": "externalsecret-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/externalsecret-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/externalsecret-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "externalsecrets", + "singular": "externalsecret", + "shortNames": [ + "externalsecret", + "xsecret" + ], + "shortNamesAlias": [ + "externalsecret" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "externalsecret-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "externalsecret-virtualapi", + "title": "externalsecret-virtualapi", + "metadata": { + "id": "8ac996b686a3f2420186a3f37d0e0021", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.910+0000", + "modifyTimestamp": "2023-03-02T20:11:28.910+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7068c60030", + "kind": "ResourceDefinition", + "name": "externalsecret-virtualapi", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/externalsecret-virtualapi", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/externalsecret-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "externalsecrets", + "singular": "externalsecret", + "shortNames": [ + "externalsecret", + "xsecret" + ], + "shortNamesAlias": [ + "externalsecret" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "externalsecret-virtualapi" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "externalsecret-virtualapirelease", + "title": "externalsecret-virtualapirelease", + "metadata": { + "id": "8ac996b686a3f2420186a3f37d240023", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.932+0000", + "modifyTimestamp": "2023-03-02T20:11:28.932+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7062ef000e", + "kind": "ResourceDefinition", + "name": "externalsecret-virtualapirelease", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/externalsecret-virtualapirelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/externalsecret-virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "externalsecrets", + "singular": "externalsecret", + "shortNames": [ + "externalsecret", + "xsecret" + ], + "shortNamesAlias": [ + "externalsecret" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "externalsecret-virtualapirelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "credential-environment", + "title": "credential-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37d390025", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.953+0000", + "modifyTimestamp": "2023-03-02T20:11:28.953+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706888002f", + "kind": "ResourceDefinition", + "name": "credential-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/credential-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/credential-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "credentials", + "singular": "credential", + "shortNames": [ + "cred", + "creds" + ], + "shortNamesAlias": [ + "cred" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Credential." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Credential was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Credential." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Credential kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Credential." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Credential." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.level", + "description": "The Credential status." + } + ], + "resourceDefinition": "credential-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "accessrequestdefinition-environment", + "title": "accessrequestdefinition-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37d4f0027", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.975+0000", + "modifyTimestamp": "2023-03-02T20:11:28.975+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7068e80031", + "kind": "ResourceDefinition", + "name": "accessrequestdefinition-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/accessrequestdefinition-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/accessrequestdefinition-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "accessrequestdefinitions", + "singular": "accessrequestdefinition", + "shortNames": [ + "accreqdef" + ], + "shortNamesAlias": [ + "accreqdef" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "accessrequestdefinition-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "ratelimitrule-virtualapi", + "title": "ratelimitrule-virtualapi", + "metadata": { + "id": "8ac996b686a3f2420186a3f37d650029", + "audit": { + "createTimestamp": "2023-03-02T20:11:28.997+0000", + "modifyTimestamp": "2023-03-02T20:11:28.997+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706576001c", + "kind": "ResourceDefinition", + "name": "ratelimitrule-virtualapi", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/ratelimitrule-virtualapi", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/ratelimitrule-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "ratelimitrules", + "singular": "ratelimitrule", + "shortNames": [ + "ratelimit" + ], + "shortNamesAlias": [ + "ratelimit" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "ratelimitrule-virtualapi" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "ratelimitrule-virtualapirelease", + "title": "ratelimitrule-virtualapirelease", + "metadata": { + "id": "8ac996b686a3f2420186a3f37d7b002b", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.019+0000", + "modifyTimestamp": "2023-03-02T20:11:29.019+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c70611b0005", + "kind": "ResourceDefinition", + "name": "ratelimitrule-virtualapirelease", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/ratelimitrule-virtualapirelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/ratelimitrule-virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "ratelimitrules", + "singular": "ratelimitrule", + "shortNames": [ + "ratelimit" + ], + "shortNamesAlias": [ + "ratelimit" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "ratelimitrule-virtualapirelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "releasetag-virtualapi", + "title": "releasetag-virtualapi", + "metadata": { + "id": "8ac996b686a3f2420186a3f37d90002d", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.040+0000", + "modifyTimestamp": "2023-03-02T20:11:29.040+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7069e6003a", + "kind": "ResourceDefinition", + "name": "releasetag-virtualapi", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/releasetag-virtualapi", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/releasetag-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "releasetags", + "singular": "releasetag", + "shortNames": [ + "releasetag", + "releasetags" + ], + "shortNamesAlias": [ + "releasetag" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Release Tag." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Release Tag was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Release Tag." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The kind of the scope of the resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The name of the resource this Release Tag belongs to." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.level", + "description": "The status of the Resource." + } + ], + "resourceDefinition": "releasetag-virtualapi" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "assetmapping-environment", + "title": "assetmapping-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37da6002f", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.062+0000", + "modifyTimestamp": "2023-03-02T20:11:29.062+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c70627c000c", + "kind": "ResourceDefinition", + "name": "assetmapping-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/assetmapping-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/assetmapping-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "assetmappings", + "singular": "assetmapping", + "shortNames": [ + "assetmapping", + "assetmappings" + ], + "shortNamesAlias": [ + "assetmpng" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "assetmapping-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "meshworkload-mesh", + "title": "meshworkload-mesh", + "metadata": { + "id": "8ac996b686a3f2420186a3f37dc40031", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.092+0000", + "modifyTimestamp": "2023-03-02T20:11:29.092+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c70643f0014", + "kind": "ResourceDefinition", + "name": "meshworkload-mesh", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/meshworkload-mesh", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/meshworkload-mesh" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "meshworkloads", + "singular": "meshworkload", + "shortNames": [ + "meshworkload", + "meshworkloads" + ], + "shortNamesAlias": [ + "meshwrk" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the mesh workload." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the mesh was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the mesh workload." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "meshworkload-mesh" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "oas3document-virtualapi", + "title": "oas3document-virtualapi", + "metadata": { + "id": "8ac996b686a3f2420186a3f37ddd0033", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.116+0000", + "modifyTimestamp": "2023-03-02T20:11:29.116+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7060ef0004", + "kind": "ResourceDefinition", + "name": "oas3document-virtualapi", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/oas3document-virtualapi", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/oas3document-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "oas3documents", + "singular": "oas3document", + "shortNames": [ + "oas3" + ], + "shortNamesAlias": [] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "oas3document-virtualapi" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "oas3document-virtualapirelease", + "title": "oas3document-virtualapirelease", + "metadata": { + "id": "8ac996b686a3f2420186a3f37df10035", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.137+0000", + "modifyTimestamp": "2023-03-02T20:11:29.137+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7063fb0013", + "kind": "ResourceDefinition", + "name": "oas3document-virtualapirelease", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/oas3document-virtualapirelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/oas3document-virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "oas3documents", + "singular": "oas3document", + "shortNames": [ + "oas3" + ], + "shortNamesAlias": [] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "oas3document-virtualapirelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "apikeyauthrule-virtualapi", + "title": "apikeyauthrule-virtualapi", + "metadata": { + "id": "8ac996b686a3f2420186a3f37e040037", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.156+0000", + "modifyTimestamp": "2023-03-02T20:11:29.156+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7061de0009", + "kind": "ResourceDefinition", + "name": "apikeyauthrule-virtualapi", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/apikeyauthrule-virtualapi", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/apikeyauthrule-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "apikeyauthrules", + "singular": "apikeyauthrule", + "shortNames": [ + "akar", + "apikeyauth", + "apikeyauthrule" + ], + "shortNamesAlias": [ + "apikeyauthrules" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "apikeyauthrule-virtualapi" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "apikeyauthrule-virtualapirelease", + "title": "apikeyauthrule-virtualapirelease", + "metadata": { + "id": "8ac996b686a3f2420186a3f37e160039", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.174+0000", + "modifyTimestamp": "2023-03-02T20:11:29.174+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706abe0040", + "kind": "ResourceDefinition", + "name": "apikeyauthrule-virtualapirelease", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/apikeyauthrule-virtualapirelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/apikeyauthrule-virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "apikeyauthrules", + "singular": "apikeyauthrule", + "shortNames": [ + "akar", + "apikeyauth", + "apikeyauthrule" + ], + "shortNamesAlias": [ + "apikeyauthrules" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "apikeyauthrule-virtualapirelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "specdiscovery-k8scluster", + "title": "specdiscovery-k8scluster", + "metadata": { + "id": "8ac996b686a3f2420186a3f37e29003b", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.193+0000", + "modifyTimestamp": "2023-03-02T20:11:29.193+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706316000f", + "kind": "ResourceDefinition", + "name": "specdiscovery-k8scluster", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/specdiscovery-k8scluster", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/specdiscovery-k8scluster" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "specdiscoveries", + "singular": "specdiscovery", + "shortNames": [ + "specdisc", + "specdiscs" + ], + "shortNamesAlias": [ + "specd" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "specdiscovery-k8scluster" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "dataplane-environment", + "title": "dataplane-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37e3c003d", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.212+0000", + "modifyTimestamp": "2023-03-02T20:11:29.212+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac996f785cb7a200185cb7b4d760000", + "kind": "ResourceDefinition", + "name": "dataplane-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/dataplane-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/dataplane-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "dataplanes", + "singular": "dataplane", + "shortNames": [ + "dp" + ], + "shortNamesAlias": [ + "dp" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the dataplane." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the data plane was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the data plane." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the dataplane." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the dataplane." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The group of the dataplane." + }, + { + "name": "Dataplane Type", + "type": "string", + "jsonPath": ".spec.dataplaneType", + "description": "The dataplane type the agent connects to." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.level", + "description": "The resource status." + } + ], + "resourceDefinition": "dataplane-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "discoveryagent-environment", + "title": "discoveryagent-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37e51003f", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.233+0000", + "modifyTimestamp": "2023-03-02T20:11:29.233+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7066f20024", + "kind": "ResourceDefinition", + "name": "discoveryagent-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/discoveryagent-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/discoveryagent-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "discoveryagents", + "singular": "discoveryagent", + "shortNames": [ + "da", + "das" + ], + "shortNamesAlias": [ + "da" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the discovery agent." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the discovery agent was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the discovery agent." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the discovery agent." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the discovery agent." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The group of the discovery agent." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.level", + "description": "The resource status." + } + ], + "resourceDefinition": "discoveryagent-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "apiservice-environment", + "title": "apiservice-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37e660041", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.254+0000", + "modifyTimestamp": "2023-03-02T20:11:29.254+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7063cf0012", + "kind": "ResourceDefinition", + "name": "apiservice-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/apiservice-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/apiservice-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "apiservices", + "singular": "apiservice", + "shortNames": [ + "apisvc", + "apisvcs" + ], + "shortNamesAlias": [ + "apis" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the API Service." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the API Service was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the API Service." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the API Service." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the API Service." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + }, + { + "name": "Owner", + "type": "teamGuid", + "jsonPath": ".owner.id", + "description": "The owner name." + } + ], + "resourceDefinition": "apiservice-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "virtualservice-virtualapi", + "title": "virtualservice-virtualapi", + "metadata": { + "id": "8ac996b686a3f2420186a3f37e800043", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.280+0000", + "modifyTimestamp": "2023-03-02T20:11:29.280+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706a06003b", + "kind": "ResourceDefinition", + "name": "virtualservice-virtualapi", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/virtualservice-virtualapi", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/virtualservice-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "virtualservices", + "singular": "virtualservice", + "shortNames": [ + "virtualservice", + "virtualservices" + ], + "shortNamesAlias": [ + "virtualservices" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "virtualservice-virtualapi" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "virtualservice-virtualapirelease", + "title": "virtualservice-virtualapirelease", + "metadata": { + "id": "8ac996b686a3f2420186a3f37e960045", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.302+0000", + "modifyTimestamp": "2023-03-02T20:11:29.302+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c70650c0019", + "kind": "ResourceDefinition", + "name": "virtualservice-virtualapirelease", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/virtualservice-virtualapirelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/virtualservice-virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "virtualservices", + "singular": "virtualservice", + "shortNames": [ + "virtualservice", + "virtualservices" + ], + "shortNamesAlias": [ + "virtualservices" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "virtualservice-virtualapirelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "ampconfig-environment", + "title": "ampconfig-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37eb00047", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.327+0000", + "modifyTimestamp": "2023-03-02T20:11:29.327+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706241000b", + "kind": "ResourceDefinition", + "name": "ampconfig-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/ampconfig-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/ampconfig-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "ampconfigs", + "singular": "ampconfig", + "shortNames": [ + "ampcfg" + ], + "shortNamesAlias": [ + "ampcfg" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "ampconfig-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "consumerinstance-environment", + "title": "consumerinstance-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37ecb0049", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.355+0000", + "modifyTimestamp": "2023-03-02T20:11:29.355+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7064e70018", + "kind": "ResourceDefinition", + "name": "consumerinstance-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/consumerinstance-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/consumerinstance-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "consumerinstances", + "singular": "consumerinstance", + "shortNames": [ + "consinst", + "consinsts" + ], + "shortNamesAlias": [ + "consumeri" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Consumer Instance." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Consumer Instance was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Consumer Instance." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Consumer Instance." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Consumer Instance." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "consumerinstance-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "amplifyoauth2authrule-virtualapi", + "title": "amplifyoauth2authrule-virtualapi", + "metadata": { + "id": "8ac996b686a3f2420186a3f37ee4004b", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.380+0000", + "modifyTimestamp": "2023-03-02T20:11:29.380+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706ae40041", + "kind": "ResourceDefinition", + "name": "amplifyoauth2authrule-virtualapi", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/amplifyoauth2authrule-virtualapi", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/amplifyoauth2authrule-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "amplifyoauth2authrules", + "singular": "amplifyoauth2authrule", + "shortNames": [ + "amplifyoauth2", + "ampoauth2" + ], + "shortNamesAlias": [ + "amplifyoauth2authrules" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "amplifyoauth2authrule-virtualapi" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "amplifyoauth2authrule-virtualapirelease", + "title": "amplifyoauth2authrule-virtualapirelease", + "metadata": { + "id": "8ac996b686a3f2420186a3f37efa004d", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.402+0000", + "modifyTimestamp": "2023-03-02T20:11:29.402+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706594001d", + "kind": "ResourceDefinition", + "name": "amplifyoauth2authrule-virtualapirelease", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/amplifyoauth2authrule-virtualapirelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/amplifyoauth2authrule-virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "amplifyoauth2authrules", + "singular": "amplifyoauth2authrule", + "shortNames": [ + "amplifyoauth2", + "ampoauth2" + ], + "shortNamesAlias": [ + "amplifyoauth2authrules" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "amplifyoauth2authrule-virtualapirelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "secret-environment", + "title": "secret-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37f0f004f", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.423+0000", + "modifyTimestamp": "2023-03-02T20:11:29.423+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c70681a002c", + "kind": "ResourceDefinition", + "name": "secret-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/secret-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/secret-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "secrets", + "singular": "secret", + "shortNames": [], + "shortNamesAlias": [ + "secret" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Secret." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Secret was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Secret." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Secret." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Secret." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The group of the Secret." + } + ], + "resourceDefinition": "secret-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "secret-integration", + "title": "secret-integration", + "metadata": { + "id": "8ac996b686a3f2420186a3f37f2f0051", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.455+0000", + "modifyTimestamp": "2023-03-02T20:11:29.455+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7061390006", + "kind": "ResourceDefinition", + "name": "secret-integration", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/secret-integration", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/secret-integration" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "secrets", + "singular": "secret", + "shortNames": [], + "shortNamesAlias": [ + "secret" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Secret." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Secret was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Secret." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Secret." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Secret." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The group of the Secret." + } + ], + "resourceDefinition": "secret-integration" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "corsrule-virtualapi", + "title": "corsrule-virtualapi", + "metadata": { + "id": "8ac996b686a3f2420186a3f37f430053", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.475+0000", + "modifyTimestamp": "2023-03-02T20:11:29.475+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706b280043", + "kind": "ResourceDefinition", + "name": "corsrule-virtualapi", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/corsrule-virtualapi", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/corsrule-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "corsrules", + "singular": "corsrule", + "shortNames": [ + "cors" + ], + "shortNamesAlias": [] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Rule." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Rule was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Rule." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The VirtualAPI." + } + ], + "resourceDefinition": "corsrule-virtualapi" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "corsrule-virtualapirelease", + "title": "corsrule-virtualapirelease", + "metadata": { + "id": "8ac996b686a3f2420186a3f37f570055", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.495+0000", + "modifyTimestamp": "2023-03-02T20:11:29.495+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7069cf0039", + "kind": "ResourceDefinition", + "name": "corsrule-virtualapirelease", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/corsrule-virtualapirelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/corsrule-virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "corsrules", + "singular": "corsrule", + "shortNames": [ + "cors" + ], + "shortNamesAlias": [] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Rule." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Rule was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Rule." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The VirtualAPI." + } + ], + "resourceDefinition": "corsrule-virtualapirelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "dataplanesecret-environment", + "title": "dataplanesecret-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37f6a0057", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.514+0000", + "modifyTimestamp": "2023-03-02T20:11:29.514+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac98a80867f15ae01867f16e8950000", + "kind": "ResourceDefinition", + "name": "dataplanesecret-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/dataplanesecret-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/dataplanesecret-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "dataplanesecrets", + "singular": "dataplanesecret", + "shortNames": [ + "dps" + ], + "shortNamesAlias": [ + "dps" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the dataplane secret." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the dataplane secret was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the dataplane secret." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the dataplane secret." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the dataplane secret." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The group of the dataplane secret." + } + ], + "resourceDefinition": "dataplanesecret-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "apiservicerevision-environment", + "title": "apiservicerevision-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37f7d0059", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.533+0000", + "modifyTimestamp": "2023-03-02T20:11:29.533+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7067fb002b", + "kind": "ResourceDefinition", + "name": "apiservicerevision-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/apiservicerevision-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/apiservicerevision-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "apiservicerevisions", + "singular": "apiservicerevision", + "shortNames": [ + "apisvcrev", + "apisvcrevs" + ], + "shortNamesAlias": [ + "apisr" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the API Service Revision." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the API Service Revision was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the API Service Revision." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the API Service Revision." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the API Service Revision." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "apiservicerevision-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "meshdiscovery-mesh", + "title": "meshdiscovery-mesh", + "metadata": { + "id": "8ac996b686a3f2420186a3f37f90005b", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.552+0000", + "modifyTimestamp": "2023-03-02T20:11:29.552+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7067140025", + "kind": "ResourceDefinition", + "name": "meshdiscovery-mesh", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/meshdiscovery-mesh", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/meshdiscovery-mesh" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "meshdiscoveries", + "singular": "meshdiscovery", + "shortNames": [ + "meshdisc", + "meshdiscs" + ], + "shortNamesAlias": [ + "meshd" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "meshdiscovery-mesh" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "accessrequest-environment", + "title": "accessrequest-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37fa6005d", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.574+0000", + "modifyTimestamp": "2023-03-02T20:11:29.574+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c706a52003d", + "kind": "ResourceDefinition", + "name": "accessrequest-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/accessrequest-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/accessrequest-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "accessrequests", + "singular": "accessrequest", + "shortNames": [ + "accessrequest" + ], + "shortNamesAlias": [ + "accreq" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.level", + "description": "The resource status." + } + ], + "resourceDefinition": "accessrequest-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "virtualhost-environment", + "title": "virtualhost-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37fb9005f", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.593+0000", + "modifyTimestamp": "2023-03-02T20:11:29.593+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7060b90003", + "kind": "ResourceDefinition", + "name": "virtualhost-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/virtualhost-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/virtualhost-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "virtualhosts", + "singular": "virtualhost", + "shortNames": [ + "vhost" + ], + "shortNamesAlias": [ + "vhost" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "virtualhost-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "deployment-environment", + "title": "deployment-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37fce0061", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.613+0000", + "modifyTimestamp": "2023-03-02T20:11:29.613+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c70675a0027", + "kind": "ResourceDefinition", + "name": "deployment-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/deployment-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/deployment-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "deployments", + "singular": "deployment", + "shortNames": [ + "deploy" + ], + "shortNamesAlias": [ + "deploy" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.level", + "description": "The resource status." + }, + { + "name": "State", + "type": "string", + "jsonPath": ".metadata.state", + "description": "The resource state." + } + ], + "resourceDefinition": "deployment-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "consumersubscriptiondef-environment", + "title": "consumersubscriptiondef-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f37fe30063", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.635+0000", + "modifyTimestamp": "2023-03-02T20:11:29.635+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7067870029", + "kind": "ResourceDefinition", + "name": "consumersubscriptiondef-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/consumersubscriptiondef-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/consumersubscriptiondef-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "consumersubscriptiondefs", + "singular": "consumersubscriptiondef", + "shortNames": [ + "conssubcsrdef", + "conssubcsrdefs" + ], + "shortNamesAlias": [ + "consumersd" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Consumer Instance Subscription." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Consumer Instance Subscription was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Consumer Instance Subscription." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Consumer Instance Subscription." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Consumer Instance Subscription." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "consumersubscriptiondef-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "k8resource-k8scluster", + "title": "k8resource-k8scluster", + "metadata": { + "id": "8ac996b686a3f2420186a3f37ff70065", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.655+0000", + "modifyTimestamp": "2023-03-02T20:11:29.655+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7065bb001e", + "kind": "ResourceDefinition", + "name": "k8resource-k8scluster", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/k8resource-k8scluster", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/k8resource-k8scluster" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "k8sresources", + "singular": "k8resource", + "shortNames": [ + "k8sres" + ], + "shortNamesAlias": [ + "k8sr" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "k8resource-k8scluster" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "traceabilityagent-environment", + "title": "traceabilityagent-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f380250067", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.701+0000", + "modifyTimestamp": "2023-03-02T20:11:29.701+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7069090032", + "kind": "ResourceDefinition", + "name": "traceabilityagent-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/traceabilityagent-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/traceabilityagent-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "traceabilityagents", + "singular": "traceabilityagent", + "shortNames": [ + "ta", + "tas" + ], + "shortNamesAlias": [ + "ta" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the traceability agent." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the traceability agent was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the traceability agent." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the traceability agent." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the traceability agent." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The group of the traceability agent." + }, + { + "name": "Dataplane Type", + "type": "string", + "jsonPath": ".spec.dataplaneType", + "description": "The dataplane type the agent connects to." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.state", + "description": "The current status of the traceability agent." + } + ], + "resourceDefinition": "traceabilityagent-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "complianceagent-environment", + "title": "complianceagent-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f380250067", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.701+0000", + "modifyTimestamp": "2023-03-02T20:11:29.701+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7069090032", + "kind": "ResourceDefinition", + "name": "complianceagent-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/complianceagent-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/complianceagent-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "complianceagents", + "singular": "complianceagent", + "shortNames": [ + "ca", + "cas" + ], + "shortNamesAlias": [ + "ca" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the compliance agent." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the compliance agent was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the compliance agent." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the compliance agent." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the compliance agent." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The group of the compliance agent." + }, + { + "name": "Dataplane Type", + "type": "string", + "jsonPath": ".spec.dataplaneType", + "description": "The dataplane type the agent connects to." + }, + { + "name": "Status", + "type": "string", + "jsonPath": ".status.state", + "description": "The current status of the compliance agent." + } + ], + "resourceDefinition": "complianceagent-environment" + } +}, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "apiserviceinstance-environment", + "title": "apiserviceinstance-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f380410069", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.729+0000", + "modifyTimestamp": "2023-03-02T20:11:29.729+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c70648b0016", + "kind": "ResourceDefinition", + "name": "apiserviceinstance-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/apiserviceinstance-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/apiserviceinstance-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "apiserviceinstances", + "singular": "apiserviceinstance", + "shortNames": [ + "apisvcinst", + "apisvcinsts" + ], + "shortNamesAlias": [ + "apisi" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the API Service Instance." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the API Service Instance was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the API Service Instance." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the API Service Instance." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the API Service Instance." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "apiserviceinstance-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "amplifyjwtauthrule-virtualapi", + "title": "amplifyjwtauthrule-virtualapi", + "metadata": { + "id": "8ac996b686a3f2420186a3f38062006b", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.762+0000", + "modifyTimestamp": "2023-03-02T20:11:29.762+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7067ba002a", + "kind": "ResourceDefinition", + "name": "amplifyjwtauthrule-virtualapi", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/amplifyjwtauthrule-virtualapi", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/amplifyjwtauthrule-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "amplifyjwtauthrules", + "singular": "amplifyjwtauthrule", + "shortNames": [ + "ampjwt", + "amplifyjwt" + ], + "shortNamesAlias": [ + "amplifyjwtauthrules" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "amplifyjwtauthrule-virtualapi" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "amplifyjwtauthrule-virtualapirelease", + "title": "amplifyjwtauthrule-virtualapirelease", + "metadata": { + "id": "8ac996b686a3f2420186a3f38088006d", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.800+0000", + "modifyTimestamp": "2023-03-02T20:11:29.800+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7061640007", + "kind": "ResourceDefinition", + "name": "amplifyjwtauthrule-virtualapirelease", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/amplifyjwtauthrule-virtualapirelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/amplifyjwtauthrule-virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "amplifyjwtauthrules", + "singular": "amplifyjwtauthrule", + "shortNames": [ + "ampjwt", + "amplifyjwt" + ], + "shortNamesAlias": [ + "amplifyjwtauthrules" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "amplifyjwtauthrule-virtualapirelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "resourcediscovery-k8scluster", + "title": "resourcediscovery-k8scluster", + "metadata": { + "id": "8ac996b686a3f2420186a3f3809e006f", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.822+0000", + "modifyTimestamp": "2023-03-02T20:11:29.822+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7063420010", + "kind": "ResourceDefinition", + "name": "resourcediscovery-k8scluster", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/resourcediscovery-k8scluster", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/resourcediscovery-k8scluster" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "resourcediscoveries", + "singular": "resourcediscovery", + "shortNames": [ + "resdisc", + "resdiscs" + ], + "shortNamesAlias": [ + "resourced" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "resourcediscovery-k8scluster" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "assetmappingtemplate-environment", + "title": "assetmappingtemplate-environment", + "metadata": { + "id": "8ac996b686a3f2420186a3f380b10071", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.841+0000", + "modifyTimestamp": "2023-03-02T20:11:29.841+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7066210021", + "kind": "ResourceDefinition", + "name": "assetmappingtemplate-environment", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/assetmappingtemplate-environment", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/assetmappingtemplate-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "assetmappingtemplates", + "singular": "assetmappingtemplate", + "shortNames": [ + "assetmappingtemplate", + "assetmappingtemplates" + ], + "shortNamesAlias": [ + "assetmpngtmpl" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "assetmappingtemplate-environment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "graphqldocument-virtualapi", + "title": "graphqldocument-virtualapi", + "metadata": { + "id": "8ac996b686a3f2420186a3f380c50073", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.861+0000", + "modifyTimestamp": "2023-03-02T20:11:29.861+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7066990022", + "kind": "ResourceDefinition", + "name": "graphqldocument-virtualapi", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/graphqldocument-virtualapi", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/graphqldocument-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "graphqldocuments", + "singular": "graphqldocument", + "shortNames": [ + "graphql" + ], + "shortNamesAlias": [] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "graphqldocument-virtualapi" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "graphqldocument-virtualapirelease", + "title": "graphqldocument-virtualapirelease", + "metadata": { + "id": "8ac996b686a3f2420186a3f380d70075", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.879+0000", + "modifyTimestamp": "2023-03-02T20:11:29.879+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c70652d001a", + "kind": "ResourceDefinition", + "name": "graphqldocument-virtualapirelease", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/graphqldocument-virtualapirelease", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/graphqldocument-virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "graphqldocuments", + "singular": "graphqldocument", + "shortNames": [ + "graphql" + ], + "shortNamesAlias": [] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The Resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + } + ], + "resourceDefinition": "graphqldocument-virtualapirelease" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "CommandLineInterface", + "name": "meshservice-mesh", + "title": "meshservice-mesh", + "metadata": { + "id": "8ac996b686a3f2420186a3f380e90077", + "audit": { + "createTimestamp": "2023-03-02T20:11:29.897+0000", + "modifyTimestamp": "2023-03-02T20:11:29.897+0000" + }, + "scope": { + "id": "8ac985a3850c194c01850c705f980000", + "kind": "ResourceGroup", + "name": "management", + "title": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "acl": [], + "accessRights": { + "canChangeOwner": false, + "canDelete": false, + "canWrite": false, + "canRead": true + }, + "resourceVersion": "0", + "references": [ + { + "id": "8ac985a3850c194c01850c7067320026", + "kind": "ResourceDefinition", + "name": "meshservice-mesh", + "scopeKind": "ResourceGroup", + "scopeName": "management", + "selfLink": "/definitions/v1alpha1/groups/management/resources/meshservice-mesh", + "type": "hard", + "group": "definitions" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/commandlines/meshservice-mesh" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "names": { + "plural": "meshservices", + "singular": "meshservice", + "shortNames": [ + "meshservice", + "meshservices" + ], + "shortNamesAlias": [ + "meshsvc" + ] + }, + "columns": [ + { + "name": "Name", + "type": "string", + "jsonPath": ".name", + "description": "The name of the Resource." + }, + { + "name": "Age", + "type": "date", + "jsonPath": ".metadata.audit.createTimestamp", + "description": "The date when the Resource was created." + }, + { + "name": "Title", + "type": "string", + "jsonPath": ".title", + "description": "The title of the Resource." + }, + { + "name": "Resource Kind", + "type": "string", + "jsonPath": ".kind", + "description": "The resource kind." + }, + { + "name": "Scope Kind", + "type": "string", + "jsonPath": ".metadata.scope.kind", + "description": "The scope kind of the Resource." + }, + { + "name": "Scope Name", + "type": "string", + "jsonPath": ".metadata.scope.name", + "description": "The scope name of the Resource." + }, + { + "name": "Resource Group", + "type": "string", + "jsonPath": ".group", + "description": "The resource group." + } + ], + "resourceDefinition": "meshservice-mesh" + } + } +] \ No newline at end of file diff --git a/test/resources/testData/apiResponses/managementResources.json b/test/resources/testData/apiResponses/managementResources.json new file mode 100644 index 00000000..20899c0c --- /dev/null +++ b/test/resources/testData/apiResponses/managementResources.json @@ -0,0 +1,3238 @@ +[ + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "meshdiscovery-mesh", + "title": "meshdiscovery-mesh", + "metadata": { + "id": "8a2e839b76fe2efa0176fe3026750000", + "audit": { + "createTimestamp": "2021-01-13T23:58:57.141+0000", + "modifyTimestamp": "2021-10-15T23:18:48.804+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "63823", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/meshdiscovery-mesh" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "MeshDiscovery", + "scope": { + "kind": "Mesh" + }, + "plural": "meshdiscoveries", + "references": { + "toResources": [ + { + "kind": "Environment", + "group": "management", + "types": ["hard"] + } + ], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "meshservice-mesh", + "title": "meshservice-mesh", + "metadata": { + "id": "8a2e839b76fe2efa0176fe3027160001", + "audit": { + "createTimestamp": "2021-01-13T23:58:57.302+0000", + "modifyTimestamp": "2021-10-15T23:18:49.205+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "63853", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/meshservice-mesh" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "MeshService", + "scope": { + "kind": "Mesh" + }, + "plural": "meshservices", + "references": { + "toResources": [ + { + "kind": "K8SResource", + "group": "management", + "types": ["soft"], + "scopeKind": "K8SCluster" + }, + { + "kind": "APISpec", + "group": "management", + "types": ["soft"], + "scopeKind": "K8SCluster" + }, + { + "kind": "MeshWorkload", + "group": "management", + "types": ["soft"], + "scopeKind": "Mesh" + } + ], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "assetmapping-environment", + "title": "assetmapping-environment", + "metadata": { + "id": "8a2e89737870faf8017870fc27120001", + "audit": { + "createTimestamp": "2021-03-27T00:01:16.562+0000", + "modifyTimestamp": "2021-10-15T23:18:50.313+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "284721", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/assetmapping-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "AssetMapping", + "scope": { + "kind": "Environment" + }, + "plural": "assetmappings", + "references": { + "toResources": [ + { + "from": { + "subResourceName": "status" + }, + "kind": "APIServiceInstance", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "AssetMappingTemplate", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AccessRequestDefinition", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetRequestDefinition", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + }, + { + "kind": "AccessRequestDefinition", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "kind": "Category", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "APIServiceRevision", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "APIService", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "Stage", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "APIServiceInstance", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "Asset", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetMappingTemplate", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "APIService", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetResource", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + }, + { + "kind": "APIServiceRevision", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + } + ], + "fromResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "Asset", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetRelease", + "group": "catalog", + "types": ["soft"] + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "accessrequestdefinition-environment", + "title": "accessrequestdefinition-environment", + "metadata": { + "id": "8a2e89737870faf8017870fc27740002", + "audit": { + "createTimestamp": "2021-03-27T00:01:16.660+0000", + "modifyTimestamp": "2021-10-15T23:18:50.629+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "284710", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/accessrequestdefinition-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "AccessRequestDefinition", + "scope": { + "kind": "Environment" + }, + "plural": "accessrequestdefinitions", + "references": { + "toResources": [], + "fromResources": [ + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetMapping", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "AssetMapping", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetRequestDefinition", + "group": "catalog", + "types": ["soft"], + "scopeKind": "AssetRelease" + }, + { + "kind": "APIServiceInstance", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetRequestDefinition", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "accessrequest-environment", + "title": "accessrequest-environment", + "metadata": { + "id": "8a2e89737870faf8017870fc27c00003", + "audit": { + "createTimestamp": "2021-03-27T00:01:16.736+0000", + "modifyTimestamp": "2021-10-15T23:18:53.229+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "284688", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/accessrequest-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "AccessRequest", + "scope": { + "kind": "Environment" + }, + "plural": "accessrequests", + "references": { + "toResources": [ + { + "kind": "APIServiceInstance", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetRequest", + "group": "catalog", + "types": ["hard"], + "scopeKind": "Asset" + } + ], + "fromResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetRequest", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes", + "state.name" + ], + "subResources": { + "names": ["references", "state"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "assetmappingtemplate-environment", + "title": "assetmappingtemplate-environment", + "metadata": { + "id": "8a2e89737870faf8017870fc27fa0004", + "audit": { + "createTimestamp": "2021-03-27T00:01:16.794+0000", + "modifyTimestamp": "2021-10-15T23:18:54.233+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "284710", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/assetmappingtemplate-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "AssetMappingTemplate", + "scope": { + "kind": "Environment" + }, + "plural": "assetmappingtemplates", + "references": { + "toResources": [ + { + "kind": "Category", + "group": "catalog", + "types": ["soft"] + } + ], + "fromResources": [ + { + "kind": "AssetMapping", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetMapping", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetRelease", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "Asset", + "group": "catalog", + "types": ["soft"] + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "apiservice-environment", + "title": "apiservice-environment", + "metadata": { + "id": "8a2e8aed75d8459b0175d8480fe20000", + "audit": { + "createTimestamp": "2020-11-17T22:16:42.722+0000", + "modifyTimestamp": "2021-10-15T23:18:51.850+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50680", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/apiservice-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "APIService", + "scope": { + "kind": "Environment" + }, + "plural": "apiservices", + "references": { + "toResources": [ + { + "kind": "Category", + "group": "catalog", + "types": ["soft"] + } + ], + "fromResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetRelease", + "group": "catalog", + "types": ["soft"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "ConsumerInstance", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetMapping", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "Asset", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "APIServiceRevision", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "Deployment", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "AssetMapping", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "apispec-k8scluster", + "title": "apispec-k8scluster", + "metadata": { + "id": "8a2e8aed75d8459b0175d84810500002", + "audit": { + "createTimestamp": "2020-11-17T22:16:42.832+0000", + "modifyTimestamp": "2021-10-15T23:18:52.219+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50672", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/apispec-k8scluster" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "APISpec", + "scope": { + "kind": "K8SCluster" + }, + "plural": "apispecs", + "references": { + "toResources": [ + { + "kind": "SpecDiscovery", + "group": "management", + "types": ["hard"], + "scopeKind": "K8SCluster" + } + ], + "fromResources": [ + { + "kind": "MeshService", + "group": "management", + "types": ["soft"], + "scopeKind": "Mesh" + }, + { + "kind": "MeshWorkload", + "group": "management", + "types": ["soft"], + "scopeKind": "Mesh" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "secret-integration", + "title": "secret-integration", + "metadata": { + "id": "8a2e8aed75d8459b0175d848107a0003", + "audit": { + "createTimestamp": "2020-11-17T22:16:42.874+0000", + "modifyTimestamp": "2021-10-15T23:18:49.007+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50656", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/secret-integration" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Secret", + "scope": { + "kind": "Integration" + }, + "plural": "secrets", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "Webhook", + "group": "management", + "types": ["soft"], + "scopeKind": "Integration" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "apiserviceinstance-environment", + "title": "apiserviceinstance-environment", + "metadata": { + "id": "8a2e8aed75d8459b0175d848114d0006", + "audit": { + "createTimestamp": "2020-11-17T22:16:43.085+0000", + "modifyTimestamp": "2021-10-15T23:18:52.439+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50688", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/apiserviceinstance-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "APIServiceInstance", + "scope": { + "kind": "Environment" + }, + "plural": "apiserviceinstances", + "references": { + "toResources": [ + { + "kind": "AccessRequestDefinition", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "APIServiceRevision", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + } + ], + "fromResources": [ + { + "kind": "AssetMapping", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetResource", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + }, + { + "kind": "ConsumerInstance", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "kind": "AccessRequest", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetMapping", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetResource", + "group": "catalog", + "types": ["soft"], + "scopeKind": "AssetRelease" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "consumerinstance-environment", + "title": "consumerinstance-environment", + "metadata": { + "id": "8a2e8aed75d8459b0175d848118a0007", + "audit": { + "createTimestamp": "2020-11-17T22:16:43.146+0000", + "modifyTimestamp": "2021-10-15T23:18:52.782+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50692", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/consumerinstance-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "ConsumerInstance", + "scope": { + "kind": "Environment" + }, + "plural": "consumerinstances", + "references": { + "toResources": [ + { + "kind": "APIServiceInstance", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "APIServiceRevision", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "APIService", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "Category", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "ConsumerSubscriptionDefinition", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + } + ], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes", + "status.phase.level", + "spec.owningTeam" + ], + "subResources": { + "names": ["references", "status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "webhook-environment", + "title": "webhook-environment", + "metadata": { + "id": "8a2e8aed75d8459b0175d84811bc0008", + "audit": { + "createTimestamp": "2020-11-17T22:16:43.196+0000", + "modifyTimestamp": "2021-10-15T23:18:49.543+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50661", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/webhook-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Webhook", + "scope": { + "kind": "Environment" + }, + "plural": "webhooks", + "references": { + "toResources": [ + { + "kind": "Secret", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + } + ], + "fromResources": [ + { + "kind": "ConsumerSubscriptionDefinition", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "consumersubscriptiondef-environment", + "title": "consumersubscriptiondef-environment", + "metadata": { + "id": "8a2e8aed75d8459b0175d84811e70009", + "audit": { + "createTimestamp": "2020-11-17T22:16:43.239+0000", + "modifyTimestamp": "2021-10-15T23:18:49.693+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50662", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/consumersubscriptiondef-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "ConsumerSubscriptionDefinition", + "scope": { + "kind": "Environment" + }, + "plural": "consumersubscriptiondefs", + "references": { + "toResources": [ + { + "kind": "Webhook", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + } + ], + "fromResources": [ + { + "kind": "ConsumerInstance", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "apiservicerevision-environment", + "title": "apiservicerevision-environment", + "metadata": { + "id": "8a2e8aed75d8459b0175d8481210000a", + "audit": { + "createTimestamp": "2020-11-17T22:16:43.280+0000", + "modifyTimestamp": "2021-10-15T23:18:49.858+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50690", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/apiservicerevision-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "APIServiceRevision", + "scope": { + "kind": "Environment" + }, + "plural": "apiservicerevisions", + "references": { + "toResources": [ + { + "kind": "APIService", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + } + ], + "fromResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "ConsumerInstance", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "status" + }, + "kind": "AssetMapping", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "AssetMapping", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetResource", + "group": "catalog", + "types": ["soft"], + "scopeKind": "AssetRelease" + }, + { + "kind": "APIServiceInstance", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetResource", + "group": "catalog", + "types": ["soft"], + "scopeKind": "Asset" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes", + "spec.definition.type" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "secret-environment", + "title": "secret-environment", + "metadata": { + "id": "8a2e8aed75d8459b0175d8481236000b", + "audit": { + "createTimestamp": "2020-11-17T22:16:43.318+0000", + "modifyTimestamp": "2021-10-15T23:18:50.089+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50664", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/secret-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Secret", + "scope": { + "kind": "Environment" + }, + "plural": "secrets", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "Webhook", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "k8resource-k8scluster", + "title": "k8resource-k8scluster", + "metadata": { + "id": "8a2e8aed75d8459b0175d84812a8000e", + "audit": { + "createTimestamp": "2020-11-17T22:16:43.432+0000", + "modifyTimestamp": "2021-10-15T23:18:53.895+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50681", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/k8resource-k8scluster" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "K8SResource", + "scope": { + "kind": "K8SCluster" + }, + "plural": "k8sresources", + "references": { + "toResources": [ + { + "kind": "ResourceDiscovery", + "group": "management", + "types": ["hard"], + "scopeKind": "K8SCluster" + } + ], + "fromResources": [ + { + "kind": "MeshWorkload", + "group": "management", + "types": ["soft"], + "scopeKind": "Mesh" + }, + { + "kind": "MeshService", + "group": "management", + "types": ["soft"], + "scopeKind": "Mesh" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "specdiscovery-k8scluster", + "title": "specdiscovery-k8scluster", + "metadata": { + "id": "8a2e8aed75d8459b0175d84812e9000f", + "audit": { + "createTimestamp": "2020-11-17T22:16:43.496+0000", + "modifyTimestamp": "2021-10-15T23:18:50.725+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50668", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/specdiscovery-k8scluster" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "SpecDiscovery", + "scope": { + "kind": "K8SCluster" + }, + "plural": "specdiscoveries", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "APISpec", + "group": "management", + "types": ["hard"], + "scopeKind": "K8SCluster" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "resourcediscovery-k8scluster", + "title": "resourcediscovery-k8scluster", + "metadata": { + "id": "8a2e8aed75d8459b0175d84813050010", + "audit": { + "createTimestamp": "2020-11-17T22:16:43.525+0000", + "modifyTimestamp": "2021-10-15T23:18:50.900+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50669", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/resourcediscovery-k8scluster" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "ResourceDiscovery", + "scope": { + "kind": "K8SCluster" + }, + "plural": "resourcediscoveries", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "K8SResource", + "group": "management", + "types": ["hard"], + "scopeKind": "K8SCluster" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "webhook-integration", + "title": "webhook-integration", + "metadata": { + "id": "8a2e8aed75d8459b0175d848133a0011", + "audit": { + "createTimestamp": "2020-11-17T22:16:43.578+0000", + "modifyTimestamp": "2021-10-15T23:18:54.129+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50670", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/webhook-integration" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Webhook", + "scope": { + "kind": "Integration" + }, + "plural": "webhooks", + "references": { + "toResources": [ + { + "kind": "Secret", + "group": "management", + "types": ["soft"], + "scopeKind": "Integration" + } + ], + "fromResources": [ + { + "kind": "ResourceHook", + "group": "management", + "types": ["soft"], + "scopeKind": "Integration" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "resourcehook-integration", + "title": "resourcehook-integration", + "metadata": { + "id": "8a2e8aed75d8459b0175d848139c0013", + "audit": { + "createTimestamp": "2020-11-17T22:16:43.676+0000", + "modifyTimestamp": "2021-10-15T23:18:51.765+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "50672", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/resourcehook-integration" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "ResourceHook", + "scope": { + "kind": "Integration" + }, + "plural": "resourcehooks", + "references": { + "toResources": [ + { + "kind": "Webhook", + "group": "management", + "types": ["soft"], + "scopeKind": "Integration" + } + ], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "traceabilityagent-environment", + "title": "traceabilityagent-environment", + "metadata": { + "id": "8a2e8c85779860f90177986340710000", + "audit": { + "createTimestamp": "2021-02-12T22:36:17.393+0000", + "modifyTimestamp": "2021-10-15T23:18:51.100+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "93306", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/traceabilityagent-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "TraceabilityAgent", + "scope": { + "kind": "Environment" + }, + "plural": "traceabilityagents", + "references": { + "toResources": [], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "complianceagent-environment", + "title": "complianceagent-environment", + "metadata": { + "id": "8a2e8c85779860f90177986340710000", + "audit": { + "createTimestamp": "2021-02-12T22:36:17.393+0000", + "modifyTimestamp": "2021-10-15T23:18:51.100+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "93306", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/complianceagent-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "ComplianceAgent", + "scope": { + "kind": "Environment" + }, + "plural": "complianceagents", + "references": { + "toResources": [], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "discoveryagent-environment", + "title": "discoveryagent-environment", + "metadata": { + "id": "8a2e8c85779860f90177986341440001", + "audit": { + "createTimestamp": "2021-02-12T22:36:17.604+0000", + "modifyTimestamp": "2021-10-15T23:18:54.445+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "93307", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/discoveryagent-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "DiscoveryAgent", + "scope": { + "kind": "Environment" + }, + "plural": "discoveryagents", + "references": { + "toResources": [], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "dataplane-environment", + "title": "dataplane-environment", + "metadata": { + "id": "8a2e8c85779860f90177986341440001", + "audit": { + "createTimestamp": "2021-02-12T22:36:17.604+0000", + "modifyTimestamp": "2021-10-15T23:18:54.445+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "93307", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/dataplane-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Dataplane", + "scope": { + "kind": "Environment" + }, + "plural": "dataplanes", + "references": { + "toResources": [], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "dataplanesecret-environment", + "title": "dataplanesecret-environment", + "metadata": { + "id": "8a2e8c85779860f90177986341440001", + "audit": { + "createTimestamp": "2021-02-12T22:36:17.604+0000", + "modifyTimestamp": "2021-10-15T23:18:54.445+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "93307", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/dataplanesecret-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "DataplaneSecret", + "scope": { + "kind": "Environment" + }, + "plural": "dataplanesecrets", + "references": { + "toResources": [], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "meshworkload-mesh", + "title": "meshworkload-mesh", + "metadata": { + "id": "8a2e8cc976ab509e0176ab52e9590000", + "audit": { + "createTimestamp": "2020-12-28T21:48:26.328+0000", + "modifyTimestamp": "2021-10-15T23:18:52.118+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "59318", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/meshworkload-mesh" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "MeshWorkload", + "scope": { + "kind": "Mesh" + }, + "plural": "meshworkloads", + "references": { + "toResources": [ + { + "kind": "K8SResource", + "group": "management", + "types": ["soft"], + "scopeKind": "K8SCluster" + }, + { + "kind": "APISpec", + "group": "management", + "types": ["soft"], + "scopeKind": "K8SCluster" + } + ], + "fromResources": [ + { + "kind": "MeshService", + "group": "management", + "types": ["soft"], + "scopeKind": "Mesh" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "ampconfig-environment", + "title": "ampconfig-environment", + "metadata": { + "id": "8a2e8fd77b84d790017b84d91c070012", + "audit": { + "createTimestamp": "2021-08-26T23:43:46.183+0000", + "modifyTimestamp": "2021-10-15T23:18:50.186+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "1", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/ampconfig-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "AmplifyConfig", + "scope": { + "kind": "Environment" + }, + "plural": "ampconfigs", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "AmplifyRuntimeConfig", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "ampruntimeconfig-environment", + "title": "ampruntimeconfig-environment", + "metadata": { + "id": "8a2e8fd77b84d790017b84d91c9e0013", + "audit": { + "createTimestamp": "2021-08-26T23:43:46.334+0000", + "modifyTimestamp": "2021-10-15T23:18:51.481+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "8", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/ampruntimeconfig-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "AmplifyRuntimeConfig", + "scope": { + "kind": "Environment" + }, + "plural": "ampruntimeconfigs", + "references": { + "toResources": [ + { + "kind": "Deployment", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "AmplifyConfig", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + } + ], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "oas3document-virtualapi", + "title": "oas3document-virtualapi", + "metadata": { + "id": "8a2e967b7a5dfeea017a5e0111c00011", + "audit": { + "createTimestamp": "2021-06-30T17:39:26.272+0000", + "modifyTimestamp": "2021-10-15T23:18:48.598+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "2", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/oas3document-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "OAS3Document", + "scope": { + "kind": "VirtualAPI" + }, + "plural": "oas3documents", + "references": { + "toResources": [ + { + "kind": "VirtualService", + "group": "management", + "types": ["hard"], + "scopeKind": "VirtualAPI" + } + ], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "deployment-environment", + "title": "deployment-environment", + "metadata": { + "id": "8a2e967b7a5dfeea017a5e0112040012", + "audit": { + "createTimestamp": "2021-06-30T17:39:26.340+0000", + "modifyTimestamp": "2021-10-15T23:18:49.401+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "11", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/deployment-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Deployment", + "scope": { + "kind": "Environment" + }, + "plural": "deployments", + "references": { + "toResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "APIService", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "VirtualAPIRelease", + "group": "management", + "types": ["hard"] + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "VirtualAPI", + "group": "management", + "types": ["soft"] + } + ], + "fromResources": [ + { + "kind": "AmplifyRuntimeConfig", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes", + "references.virtualAPI" + ], + "subResources": { + "names": ["references", "state"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "virtualapirelease", + "title": "virtualapirelease", + "metadata": { + "id": "8a2e967b7a5dfeea017a5e01122c0013", + "audit": { + "createTimestamp": "2021-06-30T17:39:26.380+0000", + "modifyTimestamp": "2021-10-15T23:18:51.202+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "13", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "VirtualAPIRelease", + "plural": "virtualapireleases", + "references": { + "toResources": [ + { + "kind": "VirtualAPI", + "group": "management", + "types": ["hard"] + } + ], + "fromResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "ReleaseTag", + "group": "management", + "types": ["soft"], + "scopeKind": "VirtualAPI" + }, + { + "kind": "Deployment", + "group": "management", + "types": ["hard"], + "scopeKind": "Environment" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["icon", "state"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "virtualapi", + "title": "virtualapi", + "metadata": { + "id": "8a2e967b7a5dfeea017a5e0112600014", + "audit": { + "createTimestamp": "2021-06-30T17:39:26.432+0000", + "modifyTimestamp": "2021-10-15T23:18:51.314+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "5", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "VirtualAPI", + "plural": "virtualapis", + "references": { + "toResources": [], + "fromResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "Deployment", + "group": "management", + "types": ["soft"], + "scopeKind": "Environment" + }, + { + "kind": "VirtualAPIRelease", + "group": "management", + "types": ["hard"] + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["icon", "state"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "oas3document-virtualapirelease", + "title": "oas3document-virtualapirelease", + "metadata": { + "id": "8a2e967b7a5dfeea017a5e0112870015", + "audit": { + "createTimestamp": "2021-06-30T17:39:26.471+0000", + "modifyTimestamp": "2021-10-15T23:18:51.966+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "2", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/oas3document-virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "OAS3Document", + "scope": { + "kind": "VirtualAPIRelease" + }, + "plural": "oas3documents", + "references": { + "toResources": [ + { + "kind": "VirtualService", + "group": "management", + "types": ["hard"], + "scopeKind": "VirtualAPIRelease" + } + ], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "corsrule-virtualapirelease", + "title": "corsrule-virtualapirelease", + "metadata": { + "id": "8a2e967b7a5dfeea017a5e0112b90016", + "audit": { + "createTimestamp": "2021-06-30T17:39:26.521+0000", + "modifyTimestamp": "2021-10-15T23:18:52.329+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "2", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/corsrule-virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "CorsRule", + "scope": { + "kind": "VirtualAPIRelease" + }, + "plural": "corsrules", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "VirtualService", + "group": "management", + "types": ["soft"], + "scopeKind": "VirtualAPIRelease" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "releasetag-virtualapi", + "title": "releasetag-virtualapi", + "metadata": { + "id": "8a2e967b7a5dfeea017a5e0112ed0017", + "audit": { + "createTimestamp": "2021-06-30T17:39:26.573+0000", + "modifyTimestamp": "2021-10-15T23:18:52.586+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "2", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/releasetag-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "ReleaseTag", + "scope": { + "kind": "VirtualAPI" + }, + "plural": "releasetags", + "references": { + "toResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "VirtualAPIRelease", + "group": "management", + "types": ["soft"] + } + ], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["references", "status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "virtualservice-virtualapi", + "title": "virtualservice-virtualapi", + "metadata": { + "id": "8a2e967b7a5dfeea017a5e01133c0018", + "audit": { + "createTimestamp": "2021-06-30T17:39:26.652+0000", + "modifyTimestamp": "2021-10-15T23:18:52.938+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "2", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/virtualservice-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "VirtualService", + "scope": { + "kind": "VirtualAPI" + }, + "plural": "virtualservices", + "references": { + "toResources": [ + { + "kind": "CorsRule", + "group": "management", + "types": ["soft"], + "scopeKind": "VirtualAPI" + } + ], + "fromResources": [ + { + "kind": "OAS3Document", + "group": "management", + "types": ["hard"], + "scopeKind": "VirtualAPI" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "virtualservice-virtualapirelease", + "title": "virtualservice-virtualapirelease", + "metadata": { + "id": "8a2e967b7a5dfeea017a5e01136c0019", + "audit": { + "createTimestamp": "2021-06-30T17:39:26.700+0000", + "modifyTimestamp": "2021-10-15T23:18:53.080+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "2", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/virtualservice-virtualapirelease" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "VirtualService", + "scope": { + "kind": "VirtualAPIRelease" + }, + "plural": "virtualservices", + "references": { + "toResources": [ + { + "kind": "CorsRule", + "group": "management", + "types": ["soft"], + "scopeKind": "VirtualAPIRelease" + } + ], + "fromResources": [ + { + "kind": "OAS3Document", + "group": "management", + "types": ["hard"], + "scopeKind": "VirtualAPIRelease" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "governanceagent-environment", + "title": "governanceagent-environment", + "metadata": { + "id": "8a2e967b7a5dfeea017a5e0113a8001a", + "audit": { + "createTimestamp": "2021-06-30T17:39:26.760+0000", + "modifyTimestamp": "2021-10-15T23:18:53.528+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "2", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/governanceagent-environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "GovernanceAgent", + "scope": { + "kind": "Environment" + }, + "plural": "governanceagents", + "references": { + "toResources": [], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["agentconfigstatus", "status"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "corsrule-virtualapi", + "title": "corsrule-virtualapi", + "metadata": { + "id": "8a2e967b7a5dfeea017a5e0113e0001b", + "audit": { + "createTimestamp": "2021-06-30T17:39:26.816+0000", + "modifyTimestamp": "2021-10-15T23:18:54.031+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "2", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/corsrule-virtualapi" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "CorsRule", + "scope": { + "kind": "VirtualAPI" + }, + "plural": "corsrules", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "VirtualService", + "group": "management", + "types": ["soft"], + "scopeKind": "VirtualAPI" + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "environment", + "title": "environment", + "metadata": { + "id": "e4e08f487156b7c8017156b9eef60002", + "audit": { + "createTimestamp": "2020-04-07T22:19:18.141+0000", + "modifyTimestamp": "2021-10-15T23:18:53.389+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "1624", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/environment" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Environment", + "plural": "environments", + "references": { + "toResources": [], + "fromResources": [ + { + "from": { + "subResourceName": "references" + }, + "kind": "Asset", + "group": "catalog", + "types": ["soft"] + }, + { + "kind": "MeshDiscovery", + "group": "management", + "types": ["hard"], + "scopeKind": "Mesh" + }, + { + "from": { + "subResourceName": "references" + }, + "kind": "AssetRelease", + "group": "catalog", + "types": ["soft"] + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": ["references"] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "k8scluster", + "title": "k8scluster", + "metadata": { + "id": "e4e540a975678cb90175678ed2dc0003", + "audit": { + "createTimestamp": "2020-10-27T00:56:54.748+0000", + "modifyTimestamp": "2021-10-15T23:18:50.451+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "41372", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/k8scluster" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "K8SCluster", + "plural": "k8sclusters", + "references": { + "toResources": [ + { + "kind": "Mesh", + "group": "management", + "types": ["hard"] + } + ], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "mesh", + "title": "mesh", + "metadata": { + "id": "e4e540a975678cb90175678ed32f0005", + "audit": { + "createTimestamp": "2020-10-27T00:56:54.830+0000", + "modifyTimestamp": "2021-10-15T23:18:51.660+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "41374", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/mesh" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Mesh", + "plural": "meshes", + "references": { + "toResources": [], + "fromResources": [ + { + "kind": "K8SCluster", + "group": "management", + "types": ["hard"] + } + ] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinition", + "name": "integration", + "title": "integration", + "metadata": { + "id": "e4e91d0f744bc01401744bc27d8e0002", + "audit": { + "createTimestamp": "2020-09-01T22:21:11.438+0000", + "modifyTimestamp": "2021-10-15T23:18:53.740+0000" + }, + "scope": { + "id": "e4e08f487156b7c8017156b9ed930000", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "21299", + "references": [], + "selfLink": "/definitions/v1alpha1/groups/management/resources/integration" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "kind": "Integration", + "plural": "integrations", + "references": { + "toResources": [], + "fromResources": [] + }, + "queryParams": [ + "metadata.resourceVersion", + "metadata.references.kind", + "metadata.scope.owner.id", + "metadata.scope.name", + "metadata.id", + "metadata.audit.modifyTimestamp", + "metadata.references.name", + "title", + "metadata.audit.createTimestamp", + "tags", + "metadata.scope.kind", + "metadata.references.id", + "owner.id", + "name", + "attributes" + ], + "subResources": { + "names": [] + } + } + } +] diff --git a/test/resources/testData/apiResponses/productWithLanguages.json b/test/resources/testData/apiResponses/productWithLanguages.json new file mode 100644 index 00000000..1146a935 --- /dev/null +++ b/test/resources/testData/apiResponses/productWithLanguages.json @@ -0,0 +1,138 @@ +[ + { + "group": "catalog", + "apiVersion": "v1alpha1", + "kind": "Product", + "name": "test-product-1", + "title": "test-product-1", + "metadata": { + "id": "8a2e8aaa9126f35801912dd66cbe4517", + "audit": { + "createTimestamp": "2024-08-07T17:15:12.958+0000", + "createUserId": "bef7b97d-9637-4dc5-87e1-38c7ba63dca9", + "modifyTimestamp": "2024-08-07T17:15:14.536+0000", + "modifyUserId": "bef7b97d-9637-4dc5-87e1-38c7ba63dca9" + }, + "acl": [], + "accessRights": { + "canChangeOwner": true, + "canDelete": true, + "canWrite": true, + "canRead": true + }, + "resourceVersion": "3", + "references": [ + { + "id": "8a2e83f29127009001912dd66f1a63b7", + "kind": "ReleaseTag", + "name": "test-product-1", + "scopeKind": "Product", + "scopeName": "test-product-1", + "selfLink": "/catalog/v1alpha1/products/test-product-1/releasetags/test-product-1", + "type": "soft", + "group": "catalog" + }, + { + "id": "8a2e89ce9126f24c01912dd66fe746c6", + "kind": "ProductRelease", + "name": "test-product-1-1.0.0", + "selfLink": "/catalog/v1/productreleases/test-product-1-1.0.0", + "type": "soft", + "group": "catalog" + } + ], + "selfLink": "/catalog/v1alpha1/products/test-product-1" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "assets": [], + "categories": [] + }, + "latestrelease": { + "name": "test-product-1-1.0.0", + "tagName": "test-product-1/test-product-1", + "version": "1.0.0" + }, + "references": { + "assets": [], + "marketplaces": [] + }, + "state": "active", + "languages": { + "resource": { + "code": "fr-fr" + }, + "metadata": { + "additional": [ + { + "code": "de-de", + "status": "complete" + } + ], + "audit": { + "createTimestamp": "2024-08-07T17:15:14.536+0000", + "createUserId": "bef7b97d-9637-4dc5-87e1-38c7ba63dca9", + "modifyTimestamp": "2024-08-07T17:15:14.536+0000", + "modifyUserId": "bef7b97d-9637-4dc5-87e1-38c7ba63dca9" + } + } + }, + "languages-de-de": { + "metadata": { + "code": "de-de", + "status": "complete", + "audit": { + "createTimestamp": "2024-08-07T17:30:16.042+0000", + "createUserId": "bef7b97d-9637-4dc5-87e1-38c7ba63dca9", + "modifyTimestamp": "2024-08-07T17:30:40.905+0000", + "modifyUserId": "bef7b97d-9637-4dc5-87e1-38c7ba63dca9" + } + }, + "values": [ + { + "path": "/title", + "value": "La souscription demande un identifiant de département", + "status": "complete" + } + ] + }, + "languages-en-us": { + "metadata": { + "code": "en-us", + "status": "undefined" + }, + "values": [ + { + "path": "/title", + "status": "undefined" + } + ] + }, + "languages-pt-br": { + "metadata": { + "code": "pt-br", + "status": "undefined" + }, + "values": [ + { + "path": "/title", + "status": "undefined" + } + ] + }, + "languages-fr-fr": { + "metadata": { + "code": "fr-fr", + "status": "undefined" + }, + "values": [ + { + "path": "/title", + "status": "undefined" + } + ] + } + } +] \ No newline at end of file diff --git a/test/resources/testData/apiResponses/products.json b/test/resources/testData/apiResponses/products.json new file mode 100644 index 00000000..0be35884 --- /dev/null +++ b/test/resources/testData/apiResponses/products.json @@ -0,0 +1,130 @@ +{ + "group": "catalog", + "apiVersion": "v1alpha1", + "kind": "Product", + "name": "test-product-1", + "title": "test-product-1", + "metadata": { + "id": "8a2e8e7c91fb25fa0191fcb760c551dd", + "audit": { + "createTimestamp": "2024-09-16T21:22:41.989+0000", + "createUserId": "bef7b97d-9637-4dc5-87e1-38c7ba63dca9", + "modifyTimestamp": "2024-09-18T17:39:56.357+0000", + "modifyUserId": "bef7b97d-9637-4dc5-87e1-38c7ba63dca9" + }, + "acl": [], + "accessRights": { + "canChangeOwner": true, + "canDelete": true, + "canWrite": true, + "canRead": true + }, + "resourceVersion": "15", + "references": [ + { + "id": "8a2e8e7c91fb25fa0191fcb7632c51ed", + "kind": "ReleaseTag", + "name": "test-product-1", + "scopeKind": "Product", + "scopeName": "test-product-1", + "selfLink": "/catalog/v1alpha1/products/test-product-1/releasetags/test-product-1", + "type": "soft", + "group": "catalog" + }, + { + "id": "8a2e85478c1cc7b5018c1dda7a970aef", + "kind": "Asset", + "name": "jlq-state-asset", + "selfLink": "/catalog/v1alpha1/assets/jlq-state-asset", + "type": "soft", + "group": "catalog" + }, + { + "id": "8a2e90ac91fb26b70191fcb763d23e47", + "kind": "ProductRelease", + "name": "test-product-1-0.0.1", + "selfLink": "/catalog/v1/productreleases/test-product-1-0.0.1", + "type": "soft", + "group": "catalog" + }, + { + "id": "8a2e914e8c1cc6bf018c1dd88d7e0b33", + "kind": "APIService", + "name": "jlq-service-1", + "scopeKind": "Environment", + "scopeName": "jlq-state-env", + "selfLink": "/management/v1alpha1/environments/jlq-state-env/apiservices/jlq-service-1", + "type": "soft", + "group": "management" + }, + { + "id": "8a2e8c6590c1696b0190cd48f1527c6c", + "kind": "AssetRelease", + "name": "jlq-state-asset-2.5.0", + "selfLink": "/catalog/v1alpha1/assetreleases/jlq-state-asset-2.5.0", + "type": "soft", + "group": "catalog" + } + ], + "selfLink": "/catalog/v1alpha1/products/test-product-1" + }, + "attributes": {}, + "finalizers": [], + "tags": [], + "spec": { + "assets": [ + { + "name": "jlq-state-asset" + } + ], + "autoRelease": { + "releaseType": "patch" + } + }, + "latestrelease": { + "name": "test-product-1-0.0.1", + "tagName": "test-product-1/test-product-1", + "version": "0.0.1" + }, + "references": { + "assets": [ + { + "name": "jlq-state-asset", + "release": { + "name": "jlq-state-asset-2.5.0", + "state": "active", + "version": "2.5.0", + "apiServices": [ + "management/jlq-state-env/jlq-service-1" + ] + } + } + ], + "marketplaces": [] + }, + "state": "active", + "languages": { + "resource": { + "code": "en-us" + } + }, + "languages-fr-fr": { + "values": [ + { + "path": "/title", + "value": "La sous updated demande un identifiant de département", + "status": "complete", + "_embedded": { + "resource": { + "schema": { + "type": "string", + "maxLength": 350, + "description": "The resource title." + }, + "value": "test-product-1" + } + } + } + ] + } +} \ No newline at end of file diff --git a/test/resources/testData/apiResponses/secrets.json b/test/resources/testData/apiResponses/secrets.json new file mode 100644 index 00000000..a1c99f95 --- /dev/null +++ b/test/resources/testData/apiResponses/secrets.json @@ -0,0 +1,134 @@ +[ + { + "group": "management", + "apiVersion": "v1alpha1", + "kind": "Secret", + "name": "secret1", + "title": "secret1 title", + "metadata": { + "id": "e4e8e508720d140801722f2318dd4f6c", + "audit": { + "createTimestamp": "2020-05-19T22:52:08.799+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-05-19T22:52:08.799+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "scope": { + "id": "e4e56388720d64e301722f2315c64f6d", + "kind": "Environment", + "name": "testenv1" + }, + "resourceVersion": "13092", + "references": [] + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "data": { + "key1": "val1", + "key2": "val2" + } + } + }, + { + "group": "management", + "apiVersion": "v1alpha1", + "kind": "Secret", + "name": "secret2", + "title": "secret2 title", + "metadata": { + "id": "e4e0888f71a3f8b10171c2d2b34e0558", + "audit": { + "createTimestamp": "2020-04-28T22:05:20.590+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-05-19T23:17:15.053+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "scope": { + "id": "e4e0892e71a3f7b00171c2d2acbf0547", + "kind": "Environment", + "name": "testenv2" + }, + "resourceVersion": "5138", + "references": [] + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "data": { + "key1": "val1", + "key2": "val2" + } + } + }, + { + "group": "management", + "apiVersion": "v1alpha1", + "kind": "Secret", + "name": "secret3", + "title": "secret3 title", + "metadata": { + "id": "e4e0888f71a3f8b10171c2d2b34e0558", + "audit": { + "createTimestamp": "2020-04-28T22:05:20.590+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-05-19T23:17:15.053+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "scope": { + "id": "e4e0892e71a3f7b00171c2d2acbf0547", + "kind": "Environment", + "name": "testenv2" + }, + "resourceVersion": "5138", + "references": [] + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "data": { + "key1": "val1", + "key2": "val2" + } + } + }, + { + "group": "management", + "apiVersion": "v1alpha1", + "kind": "Secret", + "name": "secret4", + "title": "secret4 title", + "metadata": { + "id": "e4e0888f71a3f8b10171c2d2b34e0558", + "audit": { + "createTimestamp": "2020-04-28T22:05:20.590+0000", + "createUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6", + "modifyTimestamp": "2020-05-19T23:17:15.053+0000", + "modifyUserId": "DOSA_0832cea74d4a4c409a788688a0dc0bb6" + }, + "scope": { + "id": "e39408skdlkas098234kslsfa", + "kind": "Environment", + "name": "testenv3" + }, + "resourceVersion": "5138", + "references": [] + }, + "attributes": { + "createdBy": "yaml" + }, + "tags": ["cli", "axway", "testing"], + "spec": { + "data": { + "key1": "val1", + "key2": "val2" + } + } + } +] diff --git a/test/resources/testData/apiResponses/stages.json b/test/resources/testData/apiResponses/stages.json new file mode 100644 index 00000000..c5433f24 --- /dev/null +++ b/test/resources/testData/apiResponses/stages.json @@ -0,0 +1,58 @@ +[ + { + "group": "catalog", + "apiVersion": "v1alpha1", + "kind": "Stage", + "name": "test", + "title": "Postman APIS Sample for promoting to Amplify Marketplace", + "metadata": { + "id": "8ac9890179baae620179bac6865900a5", + "audit": { + "createTimestamp": "2021-05-30T00:57:23.289+0000", + "createUserId": "197a23b1-a64b-46d7-a351-89425ef6079a", + "modifyTimestamp": "2021-05-30T00:57:23.289+0000", + "modifyUserId": "197a23b1-a64b-46d7-a351-89425ef6079a" + }, + "resourceVersion": "0", + "references": [], + "selfLink": "/catalog/v1alpha1/stages/test" + }, + "attributes": { + "release": "1.0.0", + "location": "USA" + }, + "finalizers": [], + "tags": ["axway", "saas"], + "spec": { + "description": "Group of APIs to get promoted to Amplify Marketplace" + } + }, + { + "group": "catalog", + "apiVersion": "v1alpha1", + "kind": "Stage", + "name": "prod", + "title": "Postman APIS Sample for promoting to Amplify Marketplace", + "metadata": { + "id": "8ac9890179baae620179bac6865900a5", + "audit": { + "createTimestamp": "2021-05-30T00:57:23.289+0000", + "createUserId": "197a23b1-a64b-46d7-a351-89425ef6079a", + "modifyTimestamp": "2021-05-30T00:57:23.289+0000", + "modifyUserId": "197a23b1-a64b-46d7-a351-89425ef6079a" + }, + "resourceVersion": "0", + "references": [], + "selfLink": "/catalog/v1alpha1/stages/test" + }, + "attributes": { + "release": "1.0.0", + "location": "USA" + }, + "finalizers": [], + "tags": ["axway", "saas"], + "spec": { + "description": "Group of APIs to get promoted to Amplify Marketplace" + } + } +] diff --git a/test/resources/testData/apiResponses/versions.json b/test/resources/testData/apiResponses/versions.json new file mode 100644 index 00000000..4e936fbb --- /dev/null +++ b/test/resources/testData/apiResponses/versions.json @@ -0,0 +1,1756 @@ +[ + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinitionVersion", + "name": "resourcehookintegration-v1alpha1", + "title": "resourcehookintegration-v1alpha1", + "metadata": { + "id": "4028805e756b05b501756b384e7d0523", + "audit": { + "createTimestamp": "2020-10-27T18:00:53.629+0000", + "modifyTimestamp": "2020-10-27T18:00:53.629+0000" + }, + "scope": { + "id": "4028805e756b05b501756b384c82050f", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "21", + "references": [ + { + "id": "4028805e756b05b501756b384d62051a", + "kind": "ResourceDefinition", + "name": "resourcehookintegration", + "type": "hard", + "selfLink": "/definitions/v1alpha1/groups/management/resources/resourcehookintegration" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/resourceversions/resourcehookintegration-v1alpha1" + }, + "attributes": { + "storage": "true" + }, + "finalizers": [], + "tags": [], + "spec": { + "name": "v1alpha1", + "spec": { + "type": "object", + "required": [ + "triggers", + "webhooks" + ], + "properties": { + "triggers": { + "type": "array", + "items": { + "type": "object", + "required": [ + "kind", + "name", + "type", + "group" + ], + "properties": { + "kind": { + "type": "string", + "maxLength": 30, + "description": "Value for the Kind of the resource. Use \"*\" for any." + }, + "name": { + "type": "string", + "pattern": "^([\\*])|([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)", + "maxLength": 100, + "description": "Name of the resource. Use \"*\" for any." + }, + "type": { + "type": "array", + "items": { + "enum": [ + "created", + "updated", + "deleted" + ], + "type": "string", + "description": "Type of the operation for the tracked resources." + } + }, + "group": { + "type": "string", + "maxLength": 30, + "description": "Value for the group of the resource. Use \"*\" for any." + }, + "scope": { + "type": "object", + "required": [ + "kind", + "name" + ], + "properties": { + "kind": { + "type": "string", + "maxLength": 30, + "description": "Value for the Kind of the scope of the resource. Use \"*\" for any." + }, + "name": { + "type": "string", + "pattern": "^([\\*])|([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)", + "maxLength": 100, + "description": "Name of the scope of the resource. Use \"*\" for any." + } + }, + "description": "Resource scope trigger details.", + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "webhooks": { + "type": "array", + "items": { + "type": "string", + "description": "Reference to webhooks to be invoked when a trigger matches.", + "x-amplify-kind-ref": "Webhook", + "x-amplify-kind-ref-type": "soft" + }, + "description": "List of Webhook kind resource names that dictates what webhooks will be invoked on matching triggers." + } + }, + "additionalProperties": false + }, + "served": true, + "storage": true, + "resourceDefinition": "resourcehookintegration" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinitionVersion", + "name": "apiserviceenvironment-v1alpha1", + "title": "apiserviceenvironment-v1alpha1", + "metadata": { + "id": "4028805e756b05b501756b384e970526", + "audit": { + "createTimestamp": "2020-10-27T18:00:53.655+0000", + "modifyTimestamp": "2020-10-27T18:00:53.655+0000" + }, + "scope": { + "id": "4028805e756b05b501756b384c82050f", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "22", + "references": [ + { + "id": "4028805e756b05b501756b384e350520", + "kind": "ResourceDefinition", + "name": "apiserviceenvironment", + "type": "hard", + "selfLink": "/definitions/v1alpha1/groups/management/resources/apiserviceenvironment" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/resourceversions/apiserviceenvironment-v1alpha1" + }, + "attributes": { + "storage": "true" + }, + "finalizers": [], + "tags": [], + "spec": { + "name": "v1alpha1", + "spec": { + "type": "object", + "properties": { + "icon": { + "type": "object", + "properties": { + "data": { + "type": "string", + "format": "base64", + "description": "Base64 encoded image." + }, + "contentType": { + "enum": [ + "image/jpeg", + "image/png" + ], + "type": "string", + "description": "Content-Type of the image." + } + }, + "description": "Image for the APIService.", + "additionalProperties": false + }, + "description": { + "type": "string", + "description": "The description of the api service." + } + }, + "additionalProperties": false + }, + "served": true, + "storage": true, + "resourceDefinition": "apiserviceenvironment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinitionVersion", + "name": "edgedataplaneenvironment-v1alpha1", + "title": "edgedataplaneenvironment-v1alpha1", + "metadata": { + "id": "4028805e756b05b501756b384eae0529", + "audit": { + "createTimestamp": "2020-10-27T18:00:53.678+0000", + "modifyTimestamp": "2020-10-27T18:00:53.678+0000" + }, + "scope": { + "id": "4028805e756b05b501756b384c82050f", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "23", + "references": [ + { + "id": "4028805e756b05b501756b384cdc0513", + "kind": "ResourceDefinition", + "name": "edgedataplaneenvironment", + "type": "hard", + "selfLink": "/definitions/v1alpha1/groups/management/resources/edgedataplaneenvironment" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/resourceversions/edgedataplaneenvironment-v1alpha1" + }, + "attributes": { + "storage": "true" + }, + "finalizers": [], + "tags": [], + "spec": { + "name": "v1alpha1", + "spec": { + "type": "object", + "properties": { + "apiManager": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The host name where Axway API Manager is deployed" + }, + "port": { + "type": "integer", + "description": "The Axway API Manager admin port. Defaults to 8075" + }, + "pollInterval": { + "type": "string", + "pattern": "^(?:\\d+h)?(\\d+m?)?(\\d+s)?$", + "description": "Interval the agent will poll the Axway API Manager. Defaults to '30s' indicating 30 seconds The value is a sequence of number with unit suffix. Example '30s', '1m30s'. Valid units:\n * 'h' - Hours\n * 'm' - Minutes\n * 's' - Seconds\n" + } + }, + "description": "Axway API Manager configuration." + }, + "apiGatewayManager": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The host name where Axway API Gateway Manager is deployed" + }, + "port": { + "type": "integer", + "description": "The Axway API Gateway Manager admin port. Defaults to 8090" + }, + "pollInterval": { + "type": "string", + "pattern": "^(?:\\d+h)?(\\d+m?)?(\\d+s)?$", + "description": "Interval the agent will poll API Gateway. Defaults to '1m' indicating 1 minute. The value is a sequence of number with unit suffix. Example '30s', '1m30s'. Valid units:\n * 'h' - Hours\n * 'm' - Minutes\n * 's' - Seconds\n" + } + }, + "description": "Axway API Gateway Manager configuration." + } + }, + "additionalProperties": false + }, + "served": true, + "storage": true, + "resourceDefinition": "edgedataplaneenvironment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinitionVersion", + "name": "webhookenvironment-v1alpha1", + "title": "webhookenvironment-v1alpha1", + "metadata": { + "id": "4028805e756b05b501756b384ec8052c", + "audit": { + "createTimestamp": "2020-10-27T18:00:53.704+0000", + "modifyTimestamp": "2020-10-27T18:00:53.704+0000" + }, + "scope": { + "id": "4028805e756b05b501756b384c82050f", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "24", + "references": [ + { + "id": "4028805e756b05b501756b384cb90511", + "kind": "ResourceDefinition", + "name": "webhookenvironment", + "type": "hard", + "selfLink": "/definitions/v1alpha1/groups/management/resources/webhookenvironment" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/resourceversions/webhookenvironment-v1alpha1" + }, + "attributes": { + "storage": "true" + }, + "finalizers": [], + "tags": [], + "spec": { + "name": "v1alpha1", + "spec": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string", + "pattern": "^https?://[\\w-_\\:\\.\\?\/=%&#~\\[\\]\\(\\)@!$&',;]*$" + }, + "auth": { + "type": "object", + "properties": { + "secret": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Key to be used from the referenced secret." + }, + "name": { + "type": "string", + "description": "Secret name to be used as a reference. If the secret is removed, the reference gets removed and the webhook invocation will be done with no Authorization header.\n", + "x-amplify-kind-ref": "Secret", + "x-amplify-kind-ref-type": "soft" + } + }, + "additionalProperties": false + } + }, + "description": "Defines the authentication used to invoke the webhook. When invoking the webhook, the value from the secret/key will be sent in the Authorization header.\n", + "additionalProperties": false + }, + "enabled": { + "type": "boolean" + }, + "headers": { + "type": "object", + "description": "A list of headers that will be sent as par of the http call to the webhook endpoint.", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "served": true, + "storage": true, + "resourceDefinition": "webhookenvironment" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinitionVersion", + "name": "secretintegration-v1alpha1", + "title": "secretintegration-v1alpha1", + "metadata": { + "id": "4028805e756b05b501756b384ee2052f", + "audit": { + "createTimestamp": "2020-10-27T18:00:53.730+0000", + "modifyTimestamp": "2020-10-27T18:00:53.730+0000" + }, + "scope": { + "id": "4028805e756b05b501756b384c82050f", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "25", + "references": [ + { + "id": "4028805e756b05b501756b384d340518", + "kind": "ResourceDefinition", + "name": "secretintegration", + "type": "hard", + "selfLink": "/definitions/v1alpha1/groups/management/resources/secretintegration" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/resourceversions/secretintegration-v1alpha1" + }, + "attributes": { + "storage": "true" + }, + "finalizers": [], + "tags": [], + "spec": { + "name": "v1alpha1", + "spec": { + "type": "object", + "properties": { + "data": { + "type": "object", + "description": "Key value pairs. The value will be stored encrypted.", + "x-amplify-encrypted": true, + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "served": true, + "storage": true, + "resourceDefinition": "secretintegration" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinitionVersion", + "name": "integration-v1alpha1", + "title": "integration-v1alpha1", + "metadata": { + "id": "4028805e756b05b501756b384ef90532", + "audit": { + "createTimestamp": "2020-10-27T18:00:53.753+0000", + "modifyTimestamp": "2020-10-27T18:00:53.753+0000" + }, + "scope": { + "id": "4028805e756b05b501756b384c82050f", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "26", + "references": [ + { + "id": "4028805e756b05b501756b384e20051f", + "kind": "ResourceDefinition", + "name": "integration", + "type": "hard", + "selfLink": "/definitions/v1alpha1/groups/management/resources/integration" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/resourceversions/integration-v1alpha1" + }, + "attributes": { + "storage": "true" + }, + "finalizers": [], + "tags": [], + "spec": { + "name": "v1alpha1", + "spec": { + "type": "object", + "properties": { + "description": { + "type": "string" + } + }, + "additionalProperties": false + }, + "served": true, + "storage": true, + "resourceDefinition": "integration" + } + }, + { + "group": "definitions", + "apiVersion": "v1alpha1", + "kind": "ResourceDefinitionVersion", + "name": "awstraceabilityagentenvironment-v1alpha1", + "title": "awstraceabilityagentenvironment-v1alpha1", + "metadata": { + "id": "4028805e756b05b501756b384f0f0535", + "audit": { + "createTimestamp": "2020-10-27T18:00:53.775+0000", + "modifyTimestamp": "2020-10-27T18:00:53.775+0000" + }, + "scope": { + "id": "4028805e756b05b501756b384c82050f", + "kind": "ResourceGroup", + "name": "management", + "selfLink": "/definitions/v1alpha1/groups/management" + }, + "resourceVersion": "27", + "references": [ + { + "id": "4028805e756b05b501756b384e490521", + "kind": "ResourceDefinition", + "name": "awstraceabilityagentenvironment", + "type": "hard", + "selfLink": "/definitions/v1alpha1/groups/management/resources/awstraceabilityagentenvironment" + } + ], + "selfLink": "/definitions/v1alpha1/groups/management/resourceversions/awstraceabilityagentenvironment-v1alpha1" + }, + "attributes": { + "storage": "true" + }, + "finalizers": [], + "tags": [], + "spec": { + "name": "v1alpha1", + "spec": { + "type": "object", + "required": [ + "dataplane", + "config" + ], + "properties": { + "config": { + "type": "object", + "properties": { + "excludeHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of header names that will not be included in the captured traffic by the agent" + }, + "processHeaders": { + "type": "boolean", + "description": "Configures the agent to include request and response headers in captured traffic. Defaults to true" + } + }, + "description": "Represents the AWS traceability agent config" + }, + "logging": { + "type": "object", + "properties": { + "level": { + "enum": [ + "error", + "warn", + "info", + "debug" + ], + "type": "string", + "description": "Defines the log level." + } + }, + "description": "Logging config" + }, + "dataplane": { + "type": "string", + "description": "The name of the AWS dataplane associated to this agent", + "x-amplify-kind-ref": "AWSDataplane" + } + }, + "additionalProperties": false + }, + "served": true, + "status": { + "type": "object", + "properties": { + "state": { + "enum": [ + "running", + "stopped", + "failed" + ], + "type": "string", + "description": "Agent status:\n * running - Passed all health checks. Up and running\n * stopped - Agent is not running\n * failed - Failed health checks\n" + }, + "message": { + "type": "string", + "description": "A way to communicate details about the current status by the agent" + }, + "version": { + "type": "string", + "pattern": "^(?!\\s).*(? Date: Tue, 26 May 2026 16:30:29 -0700 Subject: [PATCH 05/19] Update build.yml --- .github/workflows/build.yml | 59 +++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 09534d77..8d82f698 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -74,3 +74,62 @@ jobs: run: npm install - name: Run tests run: npm run test + + EngageMacOsTest: + needs: Lint + name: ${{ matrix.os }} ${{ matrix.nodeVersion }} Tests + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + nodeVersion: ["22.13.1", "24.14.1"] + os: [macos-latest] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.nodeVersion }} + - name: Install dependencies + run: npm install + - name: Run tests + run: npm run engagetest + + EngageWindowsTest: + needs: Lint + name: ${{ matrix.os }} ${{ matrix.nodeVersion }} Tests + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + nodeVersion: ["22.13.1", "24.14.1"] + os: [windows-latest] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.nodeVersion }} + - name: Install dependencies + run: npm install + - name: Run tests + run: npm run engagetest + + EngageUbuntuTest: + needs: Lint + name: ${{ matrix.os }} ${{ matrix.nodeVersion }} Tests + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + nodeVersion: ["22.13.1", "24.14.1"] + os: [ubuntu-latest] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.nodeVersion }} + - name: Install libsecret + run: sudo apt-get install -y libsecret-1-0 + - name: Install dependencies + run: npm install + - name: Run tests + run: npm run engagetest From 742591e480700e757390476ec98d5689add31e03 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Tue, 26 May 2026 16:36:32 -0700 Subject: [PATCH 06/19] Update build.yml --- .github/workflows/build.yml | 65 ++++--------------------------------- 1 file changed, 6 insertions(+), 59 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d82f698..bcf5b56e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,65 +16,6 @@ jobs: - name: Lint run: npm run lint - MacOsTest: - needs: Lint - name: ${{ matrix.os }} ${{ matrix.nodeVersion }} Tests - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - nodeVersion: ["22.13.1", "24.14.1"] - os: [macos-latest] - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.nodeVersion }} - - name: Install dependencies - run: npm install - - name: Run tests - run: npm run test - - WindowsTest: - needs: Lint - name: ${{ matrix.os }} ${{ matrix.nodeVersion }} Tests - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - nodeVersion: ["22.13.1", "24.14.1"] - os: [windows-latest] - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.nodeVersion }} - - name: Install dependencies - run: npm install - - name: Run tests - run: npm run test - - UbuntuTest: - needs: Lint - name: ${{ matrix.os }} ${{ matrix.nodeVersion }} Tests - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - nodeVersion: ["22.13.1", "24.14.1"] - os: [ubuntu-latest] - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.nodeVersion }} - - name: Install libsecret - run: sudo apt-get install -y libsecret-1-0 - - name: Install dependencies - run: npm install - - name: Run tests - run: npm run test - EngageMacOsTest: needs: Lint name: ${{ matrix.os }} ${{ matrix.nodeVersion }} Tests @@ -91,6 +32,8 @@ jobs: node-version: ${{ matrix.nodeVersion }} - name: Install dependencies run: npm install + - name: Build CLI + run: npm run build - name: Run tests run: npm run engagetest @@ -110,6 +53,8 @@ jobs: node-version: ${{ matrix.nodeVersion }} - name: Install dependencies run: npm install + - name: Build CLI + run: npm run build - name: Run tests run: npm run engagetest @@ -131,5 +76,7 @@ jobs: run: sudo apt-get install -y libsecret-1-0 - name: Install dependencies run: npm install + - name: Build CLI + run: npm run build - name: Run tests run: npm run engagetest From 2b19c52655ddc6acb576a7c083fceba3bbc600a7 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Tue, 26 May 2026 16:43:53 -0700 Subject: [PATCH 07/19] Update build.yml --- .github/workflows/build.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bcf5b56e..ed67de45 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: with: node-version: "20.x" - name: Install dependencies - run: npm install + run: npm ci - name: Lint run: npm run lint @@ -31,9 +31,13 @@ jobs: with: node-version: ${{ matrix.nodeVersion }} - name: Install dependencies - run: npm install + run: npm ci - name: Build CLI run: npm run build + - name: Verify built CLI entrypoint + run: | + test -f dist/index.js + node dist/index.js --version - name: Run tests run: npm run engagetest @@ -52,9 +56,13 @@ jobs: with: node-version: ${{ matrix.nodeVersion }} - name: Install dependencies - run: npm install + run: npm ci - name: Build CLI run: npm run build + - name: Verify built CLI entrypoint + run: | + if not exist dist\index.js exit /b 1 + node dist/index.js --version - name: Run tests run: npm run engagetest @@ -75,8 +83,12 @@ jobs: - name: Install libsecret run: sudo apt-get install -y libsecret-1-0 - name: Install dependencies - run: npm install + run: npm ci - name: Build CLI run: npm run build + - name: Verify built CLI entrypoint + run: | + test -f dist/index.js + node dist/index.js --version - name: Run tests run: npm run engagetest From a119d570ad9feb57c3f1b8379c2eb4e70faa2140 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Tue, 26 May 2026 17:01:05 -0700 Subject: [PATCH 08/19] Updates --- .github/workflows/build.yml | 12 ------------ package.json | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ed67de45..7ec24192 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,10 +34,6 @@ jobs: run: npm ci - name: Build CLI run: npm run build - - name: Verify built CLI entrypoint - run: | - test -f dist/index.js - node dist/index.js --version - name: Run tests run: npm run engagetest @@ -59,10 +55,6 @@ jobs: run: npm ci - name: Build CLI run: npm run build - - name: Verify built CLI entrypoint - run: | - if not exist dist\index.js exit /b 1 - node dist/index.js --version - name: Run tests run: npm run engagetest @@ -86,9 +78,5 @@ jobs: run: npm ci - name: Build CLI run: npm run build - - name: Verify built CLI entrypoint - run: | - test -f dist/index.js - node dist/index.js --version - name: Run tests run: npm run engagetest diff --git a/package.json b/package.json index 24291407..5f3722b5 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "bugs": "https://github.com/appcelerator/amplify-tooling/issues", "repository": "https://github.com/appcelerator/amplify-tooling", "bin": { - "axway": "./bin/run.js" + "axway": "./bin/run" }, "scripts": { "debug": "node --inspect=9254 --enable-source-maps dist/index.js", From ba363d0816d8887611357fe05ffbe4b91eed8299 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Wed, 27 May 2026 11:00:52 -0700 Subject: [PATCH 09/19] Updates --- .github/workflows/build.yml | 6 ++++++ test/helpers/engage-test-helper.js | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7ec24192..5ee8a96a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,6 +32,8 @@ jobs: node-version: ${{ matrix.nodeVersion }} - name: Install dependencies run: npm ci + - name: Rebuild native modules + run: npm rebuild keytar - name: Build CLI run: npm run build - name: Run tests @@ -53,6 +55,8 @@ jobs: node-version: ${{ matrix.nodeVersion }} - name: Install dependencies run: npm ci + - name: Rebuild native modules + run: npm rebuild keytar - name: Build CLI run: npm run build - name: Run tests @@ -76,6 +80,8 @@ jobs: run: sudo apt-get install -y libsecret-1-0 - name: Install dependencies run: npm ci + - name: Rebuild native modules + run: npm rebuild keytar - name: Build CLI run: npm run build - name: Run tests diff --git a/test/helpers/engage-test-helper.js b/test/helpers/engage-test-helper.js index 5b0e7ec1..0cf54ada 100644 --- a/test/helpers/engage-test-helper.js +++ b/test/helpers/engage-test-helper.js @@ -43,7 +43,7 @@ export { e400, e500 }; export async function setupEngageAuth() { initHomeDir('home-local'); await loginCLISync(); - const engageServer = globalThis.__axwayEngageServer || this.servers?.[2] || globalThis.__axwayTestServers?.[2]; + const engageServer = this.servers?.[2]; engageServer?.resetEngageState(); return engageServer; } From 4c33fdae7d9b7db703f76c12ad54d0f2cc7f889c Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Wed, 27 May 2026 11:28:12 -0700 Subject: [PATCH 10/19] Add logging for the failed test --- test/commands/engage/apply/test-apply.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/commands/engage/apply/test-apply.js b/test/commands/engage/apply/test-apply.js index c42eef6e..14c05af1 100644 --- a/test/commands/engage/apply/test-apply.js +++ b/test/commands/engage/apply/test-apply.js @@ -25,7 +25,9 @@ describe('axway engage apply', () => { it('should error if not authenticated', async () => { initHomeDir('home-local'); // home-local has valid config but no login tokens - const { status, stderr } = await runAxwaySync([ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstances1short.yaml') ], { env: engageEnv }); + const { status, stderr, stdout } = await runAxwaySync([ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstances1short.yaml') ], { env: engageEnv }); + console.log(stderr); + console.log(stdout); expect(status).to.equal(1); expect(stderr).to.match(renderRegexFromFile('templates/not-authenticated')); }); From 6683a9e7ed969a5c8a83f59eee3b9074628fb0e6 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Wed, 27 May 2026 11:35:07 -0700 Subject: [PATCH 11/19] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f3722b5..48b85fae 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "engagetest": "mocha --exit --recursive -r test/helpers/setup.js --slow 15000 --timeout 40000 test/**/engage/**", "lint": "eslint . --ignore-pattern 'dist/**'", "watch": "tsc --watch", - "postinstall": "node dist/scripts/postinstall.js || node -e true" + "postinstall": "node -e \"const fs=require('fs'); if(fs.existsSync('dist/scripts/postinstall.js')) require('./dist/scripts/postinstall.js')\"" }, "dependencies": { "@inquirer/prompts": "^7.9.0", From b82983fbe6cb05bc1307ec12c3745ce6dccfaf0d Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Wed, 27 May 2026 11:48:36 -0700 Subject: [PATCH 12/19] Update tsconfig.json --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index 5d77cf41..7c4fa1e7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "noImplicitReturns": true, "noUnusedLocals": true, "noUnusedParameters": true, + "rootDir": "./src", "outDir": "./dist", "resolveJsonModule": true, "sourceMap": true, From 66d3f47411717f8b9b98472dc3f94f90e7fe2593 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Wed, 27 May 2026 12:16:09 -0700 Subject: [PATCH 13/19] Updates --- bin/run.js | 1 + test/_capture-output.mjs | 47 ---------------------------------------- test/helpers/index.js | 6 ++--- 3 files changed, 3 insertions(+), 51 deletions(-) delete mode 100644 test/_capture-output.mjs diff --git a/bin/run.js b/bin/run.js index b469ba4b..f0a7a73c 100755 --- a/bin/run.js +++ b/bin/run.js @@ -4,4 +4,5 @@ enableCompileCache(); import { execute, settings } from '@oclif/core'; settings.performanceEnabled = true; +settings.enableAutoTranspile = false; await execute({ dir: import.meta.url }); diff --git a/test/_capture-output.mjs b/test/_capture-output.mjs deleted file mode 100644 index 5065358c..00000000 --- a/test/_capture-output.mjs +++ /dev/null @@ -1,47 +0,0 @@ -// Temporary script to capture raw CLI output format -import { spawn } from 'child_process'; -import path from 'path'; -import os from 'os'; -import fs from 'fs'; -import { fileURLToPath } from 'url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const root = path.resolve(__dirname, '..'); - -const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'axway-fmt-test-')); -const homeLocal = path.resolve(root, 'test/helpers/home-local'); -fs.cpSync(homeLocal, path.join(tmpHome, '.axway', 'axway-cli'), { recursive: true }); - -const env = { - ...process.env, - HOME: tmpHome, - AXWAY_CENTRAL_BASE_URL: 'http://127.0.0.1:8777', - FORCE_COLOR: '1', - AXWAY_TEST: '1', -}; - -const axwayBin = path.resolve(root, 'dist/index.js'); - -async function run(args) { - return new Promise((resolve) => { - const p = spawn(process.execPath, [axwayBin, ...args], { env }); - let stdout = ''; - let stderr = ''; - p.stdout.on('data', d => stdout += d.toString()); - p.stderr.on('data', d => stderr += d.toString()); - p.on('close', (status) => resolve({ status, stdout, stderr })); - }); -} - -// Login -const login = await run(['auth', 'login', '--client-id', 'test_client', '--client-secret', 'secret', '--no-banner']); -console.log('Login status:', login.status); - -// Apply (no --output, uses spinner → stderr) -const result = await run(['engage', 'apply', '--file', path.resolve(root, 'test/resources/testData/testInstances1short.yaml'), '--no-banner']); -console.log('=== STDERR (raw JSON) ==='); -console.log(JSON.stringify(result.stderr)); -console.log('=== STDERR (visible) ==='); -process.stdout.write(result.stderr); - -fs.rmSync(tmpHome, { recursive: true }); diff --git a/test/helpers/index.js b/test/helpers/index.js index e0f802d3..dc012cc6 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -13,7 +13,7 @@ const { log } = logger('test'); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const axwayBin = path.resolve(__dirname, `../../${process.env.AXWAY_COVERAGE ? 'src' : 'dist'}/index.js`); +const axwayBin = path.resolve(__dirname, '../../bin/run.js'); export function initHomeDir(templateDir) { if (!fs.existsSync(templateDir) && !path.isAbsolute(templateDir)) { @@ -97,9 +97,7 @@ export function resetHomeDir() { function _runAxway(fn, args = [], opts = {}, cfg) { const env = Object.assign({}, process.env, opts.env); if (env.AXWAY_TEST) { - if (args.includes('--no-color') || args.includes('--no-colors')) { - delete env.FORCE_COLOR; - } + delete env.FORCE_COLOR; } if (cfg) { From b2501ddd2f688f203e27e5a0f337b224ba9010c7 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Wed, 27 May 2026 12:21:44 -0700 Subject: [PATCH 14/19] Fix --- package.json | 2 +- test/commands/engage/apply/test-apply.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 48b85fae..d4afbc2e 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "build": "tsc", "pretest": "tsc", "test": "mocha --exit --recursive -r test/helpers/setup.js --slow 15000 --timeout 40000 test", - "engagetest": "mocha --exit --recursive -r test/helpers/setup.js --slow 15000 --timeout 40000 test/**/engage/**", + "engagetest": "mocha --exit --recursive -r test/helpers/setup.js --slow 15000 --timeout 40000 \"test/**/engage/**/*.js\"", "lint": "eslint . --ignore-pattern 'dist/**'", "watch": "tsc --watch", "postinstall": "node -e \"const fs=require('fs'); if(fs.existsSync('dist/scripts/postinstall.js')) require('./dist/scripts/postinstall.js')\"" diff --git a/test/commands/engage/apply/test-apply.js b/test/commands/engage/apply/test-apply.js index 14c05af1..c42eef6e 100644 --- a/test/commands/engage/apply/test-apply.js +++ b/test/commands/engage/apply/test-apply.js @@ -25,9 +25,7 @@ describe('axway engage apply', () => { it('should error if not authenticated', async () => { initHomeDir('home-local'); // home-local has valid config but no login tokens - const { status, stderr, stdout } = await runAxwaySync([ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstances1short.yaml') ], { env: engageEnv }); - console.log(stderr); - console.log(stdout); + const { status, stderr } = await runAxwaySync([ 'engage', 'apply', '--file', path.join(testDataDir, 'testInstances1short.yaml') ], { env: engageEnv }); expect(status).to.equal(1); expect(stderr).to.match(renderRegexFromFile('templates/not-authenticated')); }); From 0afc2bc42341f198190c1cbc486586c7749a76e7 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Wed, 27 May 2026 12:30:48 -0700 Subject: [PATCH 15/19] Fix Windows Tests --- package-lock.json | 4 +--- package.json | 2 -- test/helpers/index.js | 11 +++++------ .../install-service/on-prem/test-akamai.onprem.js | 4 ++-- .../install-service/on-prem/test-apigeex.onprem.js | 4 ++-- .../install-service/on-prem/test-aws.onprem.js | 4 ++-- .../on-prem/test-azure-gateway.onprem.js | 4 ++-- .../install-service/on-prem/test-backstage.onprem.js | 4 ++-- .../on-prem/test-edge-gateway.onprem.js | 4 ++-- .../install-service/on-prem/test-gitlab.onprem.js | 4 ++-- .../install-service/on-prem/test-graylog.onprem.js | 4 ++-- .../on-prem/test-ibm-api-connect.onprem.js | 4 ++-- .../install-service/on-prem/test-istio.onprem.js | 4 ++-- .../install-service/on-prem/test-kafka.onprem.js | 4 ++-- .../on-prem/test-sap-api-portal.onprem.js | 4 ++-- .../install-service/on-prem/test-sensedia.onprem.js | 4 ++-- .../on-prem/test-softwareag-webmethods.onprem.js | 4 ++-- .../install-service/on-prem/test-traceable.onprem.js | 4 ++-- .../install-service/on-prem/test-wso2.onprem.js | 4 ++-- .../services/install-service/saas/test-akamai.saas.js | 4 ++-- .../install-service/saas/test-apigeex.saas.js | 4 ++-- .../services/install-service/saas/test-aws.saas.js | 4 ++-- .../install-service/saas/test-azure-gateway.saas.js | 4 ++-- .../services/install-service/saas/test-github.saas.js | 4 ++-- .../install-service/saas/test-swaggerhub.saas.js | 4 ++-- .../install-service/saas/test-traceable.saas.js | 4 ++-- 26 files changed, 52 insertions(+), 57 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b623d34..b8970997 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "uuid": "^13.0.0" }, "bin": { - "axway": "bin/run.js" + "axway": "bin/run" }, "devDependencies": { "@koa/router": "^14.0.0", @@ -48,7 +48,6 @@ "@types/jest": "^30.0.0", "@types/lodash": "^4.17.20", "@types/node": "^22", - "@types/sinon": "^10.0.14", "@typescript-eslint/eslint-plugin": "^8.46.3", "@typescript-eslint/parser": "^8.59.3", "c8": "^11.0.0", @@ -66,7 +65,6 @@ "mock-stdin": "^1.0.0", "mustache": "^4.2.0", "nock": "^14.0.10", - "sinon": "^15.0.3", "testdouble": "^3.20.2", "tmp": "^0.2.5", "ts-node": "^10.9.2", diff --git a/package.json b/package.json index d4afbc2e..cd78ecdf 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "@types/jest": "^30.0.0", "@types/lodash": "^4.17.20", "@types/node": "^22", - "@types/sinon": "^10.0.14", "@typescript-eslint/eslint-plugin": "^8.46.3", "@typescript-eslint/parser": "^8.59.3", "c8": "^11.0.0", @@ -80,7 +79,6 @@ "mock-stdin": "^1.0.0", "mustache": "^4.2.0", "nock": "^14.0.10", - "sinon": "^15.0.3", "testdouble": "^3.20.2", "tmp": "^0.2.5", "ts-node": "^10.9.2", diff --git a/test/helpers/index.js b/test/helpers/index.js index dc012cc6..0e314ce2 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -1,6 +1,5 @@ import fs from 'fs'; import callerPath from 'caller-path'; -import chalk from 'chalk'; import Mustache from 'mustache'; import os from 'os'; import path from 'path'; @@ -26,7 +25,7 @@ export function initHomeDir(templateDir) { } const defaultVars = { - check: process.platform === 'win32' ? '√' : '✔', + check: '(?:√|✔)', delta: '\\d+(\\.\\d+)?\\w( \\d+(\\.\\d+)?\\w)*\\s*', localeDateTime: '[\\w\\d/,: ]+', nodeDeprecationWarning: '(?:\n*\u001b\\[33m ┃ ATTENTION! The Node\\.js version you are currently using \\(v\\d+\\.\\d+\\.\\d+\\) has been\u001b\\[39m\n\u001b\\[33m ┃ deprecated and is unsupported in Axway CLI v3 and newer\\. Please upgrade\u001b\\[39m\n\u001b\\[33m ┃ Node\\.js to the latest LTS release: https://nodejs\\.org/\u001b\\[39m)?', @@ -38,16 +37,16 @@ const defaultVars = { versionList: '(?:\u001b\\[36m(?:\\d+\\.\\d+\\.\\d+(?:-[^\\s]*)?\\s*)*\\s*\u001b\\[39m\n+)+', versionWithColor: '(?:(?:\u001b\\[\\d\\dm)?\\d+(?:\\.(?:\u001b\\[\\d\\dm)?\\d+){2}(?:-[^\\s]*)?(?:\u001b\\[39m)?\\s*)', whitespace: ' *', - x: process.platform === 'win32' ? 'x' : '✖', + x: '(?:x|✖)', uuid: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', year: (new Date()).getFullYear() }; for (const fn of [ 'bold', 'blue', 'cyan', 'gray', 'green', 'greenBright', 'magenta', 'red', 'yellow' ]) { defaultVars[fn] = () => { return (text, render) => { - return chalk[fn]('8675309') - .replace(/(? Date: Wed, 27 May 2026 14:05:16 -0700 Subject: [PATCH 16/19] Updates --- .github/workflows/build.yml | 6 ------ src/lib/engage/utils/bash-commands.ts | 7 +++++-- src/lib/engage/utils/tmp-file.ts | 5 +++-- test/commands/engage/edit/test-edit.js | 11 ++++++----- test/helpers/test-editor.js | 5 +++++ test/helpers/test-editor.sh | 3 --- 6 files changed, 19 insertions(+), 18 deletions(-) create mode 100644 test/helpers/test-editor.js delete mode 100755 test/helpers/test-editor.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5ee8a96a..7ec24192 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,8 +32,6 @@ jobs: node-version: ${{ matrix.nodeVersion }} - name: Install dependencies run: npm ci - - name: Rebuild native modules - run: npm rebuild keytar - name: Build CLI run: npm run build - name: Run tests @@ -55,8 +53,6 @@ jobs: node-version: ${{ matrix.nodeVersion }} - name: Install dependencies run: npm ci - - name: Rebuild native modules - run: npm rebuild keytar - name: Build CLI run: npm run build - name: Run tests @@ -80,8 +76,6 @@ jobs: run: sudo apt-get install -y libsecret-1-0 - name: Install dependencies run: npm ci - - name: Rebuild native modules - run: npm rebuild keytar - name: Build CLI run: npm run build - name: Run tests diff --git a/src/lib/engage/utils/bash-commands.ts b/src/lib/engage/utils/bash-commands.ts index 90b79d0d..c3f7698f 100644 --- a/src/lib/engage/utils/bash-commands.ts +++ b/src/lib/engage/utils/bash-commands.ts @@ -80,10 +80,13 @@ export const createTlsCert = async ( return { cert, privateKey }; }; -export const editor = (editor: string, filePath: string): Promise => { +export const editor = (editorCmd: string, filePath: string): Promise => { log.log(`editor ${filePath}`); + // editorCmd may be a multi-word string like "node /path/to/script.js"; split into + // executable + pre-defined args so that shell:true is not required. + const [ cmd, ...editorArgs ] = editorCmd.split(' '); return new Promise((resolve) => { - spawn(editor, [ filePath ], { stdio: 'inherit' }).on('exit', (code) => { + spawn(cmd, [ ...editorArgs, filePath ], { stdio: 'inherit' }).on('exit', (code) => { log.log(`editor exit code ${code}`); resolve(code); }); diff --git a/src/lib/engage/utils/tmp-file.ts b/src/lib/engage/utils/tmp-file.ts index be17fbe4..a1e24f2f 100644 --- a/src/lib/engage/utils/tmp-file.ts +++ b/src/lib/engage/utils/tmp-file.ts @@ -56,14 +56,15 @@ export default class TmpFile { /** * Open file in editor and return promise with flags indicating if edit was successful or not * (process killed, vim q! happened etc. ), and if file content has been changed or not. - * Using vim or "EDITOR" env on linux and only notepad on windows. + * Respects the EDITOR environment variable on all platforms; falls back to notepad on + * Windows or vi on Unix. * @returns {object} represent result of editing: * isComplete: editor process completed successfully * isUpdated: content of the file changed */ async edit(): Promise<{ isComplete: boolean; isUpdated: boolean }> { log.log(`editing: ${this.path}`); - const editorToUse = isWindows ? 'notepad' : process.env.EDITOR || 'vi'; + const editorToUse = process.env.EDITOR || (isWindows ? 'notepad' : 'vi'); const contentBeforeEdit = Buffer.from(this.read()); const editorExitCode = await editor(editorToUse, this.path); const isUpdated = !contentBeforeEdit.equals(Buffer.from(this.read())); diff --git a/test/commands/engage/edit/test-edit.js b/test/commands/engage/edit/test-edit.js index ee59ce1c..9e9e604c 100644 --- a/test/commands/engage/edit/test-edit.js +++ b/test/commands/engage/edit/test-edit.js @@ -11,9 +11,10 @@ import { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -// Path to the simple shell editor that appends a comment to the file so -// TmpFile detects a change (isUpdated = true). -const testEditorPath = path.resolve(__dirname, '../../../helpers/test-editor.sh'); +// Path to the cross-platform editor script that appends a comment so +// TmpFile detects a change (isUpdated = true). Invoked as: node