From 9b1d7051de7b161ad2f848dbd7d85f9992af86e4 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Tue, 7 Jul 2026 16:27:20 -0700 Subject: [PATCH 1/3] APIGOV-33064 Query Engage for latest version of agent for creating output of install command --- .../clients-external/apiserverclient.ts | 16 ++++++ src/lib/engage/services/install-service.ts | 10 ++-- src/lib/engage/types.ts | 48 +++++++++++++++-- src/lib/engage/utils/agents/getters.ts | 52 ++++--------------- 4 files changed, 75 insertions(+), 51 deletions(-) diff --git a/src/lib/engage/clients-external/apiserverclient.ts b/src/lib/engage/clients-external/apiserverclient.ts index 5b382fc3..62d873cb 100644 --- a/src/lib/engage/clients-external/apiserverclient.ts +++ b/src/lib/engage/clients-external/apiserverclient.ts @@ -10,6 +10,7 @@ import { ApiServerVersions, BasePaths, CommandLineInterface, + Component, GenericResource, GenericResourceWithoutName, LanguageTypes, @@ -894,6 +895,21 @@ export class ApiServerClient { } } + async getComponentDefinitionsByName(componentName: string, version = ApiServerVersions.v1alpha1): Promise { + const log = logger('ApiServerClient.getComponentDefinitionsByName'); + log.info('get component definitions'); + try { + const service = await this.initializeDataService(); + const component: Component = await service.get(`definitions/${version}/components/${componentName}`); + + return component; + + } catch (e: any) { + log.error('get specs, error: ', e); + throw e; + } + } + async bulkCreate( resources: Array, sortedDefsMap: Map, diff --git a/src/lib/engage/services/install-service.ts b/src/lib/engage/services/install-service.ts index a3c8ab61..62a0c0a5 100644 --- a/src/lib/engage/services/install-service.ts +++ b/src/lib/engage/services/install-service.ts @@ -89,17 +89,17 @@ const determineRegion = async (region: string | undefined): Promise => { return configurationRegion ? configurationRegion : Regions.US; }; -async function getAgentVersions(agentInstallFlow: InstallationFlowMethods, installConfig: AgentInstallConfig, account: Account): Promise { +async function getAgentVersions(agentInstallFlow: InstallationFlowMethods, installConfig: AgentInstallConfig, apiServerClient: ApiServerClient): Promise { if (agentInstallFlow.AgentNameMap && !installConfig.switches.isHostedInstall && installConfig.switches.isDaEnabled) { installConfig.daVersion = await helpers.getLatestAgentVersion( + apiServerClient, agentInstallFlow.AgentNameMap[AgentTypes.da] as string, - account ); } if (agentInstallFlow.AgentNameMap && !installConfig.switches.isHostedInstall && installConfig.switches.isTaEnabled) { installConfig.taVersion = await helpers.getLatestAgentVersion( + apiServerClient, agentInstallFlow.AgentNameMap[AgentTypes.ta] as string, - account ); } } @@ -249,8 +249,8 @@ export async function installAgents(params: InstallAgentsCommandParams): Promise installConfig.switches.isDockerInstall = installConfig.deploymentType === AgentConfigTypes.DOCKERIZED; installConfig.switches.isBinaryInstall = installConfig.deploymentType === AgentConfigTypes.BINARIES; - // Get the version of the agents from jfrog, not needed in hosted install - await getAgentVersions(agentInstallFlow, installConfig, params.account); + // Get the version of the agents from Engage, not needed in hosted install + await getAgentVersions(agentInstallFlow, installConfig, apiServerClient); // if EDGE_GATEWAY or EDGE_GATEWAY_ONLY and isDaEnabled, ask if the organization structure should replicate if ( diff --git a/src/lib/engage/types.ts b/src/lib/engage/types.ts index 7d1b888d..3cb57426 100644 --- a/src/lib/engage/types.ts +++ b/src/lib/engage/types.ts @@ -153,6 +153,49 @@ export interface CommandLineInterface { }; } +export interface Component { + group: 'definitions', + apiVersion: ApiServerVersions, + kind: 'Component', + name: string, + title: string, + metadata: { + id: string, + audit: { + createTimestamp: string, + modifyTimestamp: string + }, + accessRights: { + canChangeOwner: boolean, + canDelete: boolean, + canWrite: boolean, + canRead: boolean + }, + resourceVersion: string, + selfLink: string + }, + spec: { + type: ComponentType, + latest: ComponentSpecVersionInfo, + retracted: string[], + supported: ComponentSpecVersionInfo[] + } + +} + +export enum ComponentType { + Agent = 'agent', + DA = 'DiscoveryAgent', + TA = 'TraceabilityAgent', + CA = 'ComplianceAgent' +} + +export interface ComponentSpecVersionInfo { + version: string; + releaseDate: string; + endOfSupportDate: string; +} + export interface AuditMetadata { createTimestamp: string; // '2020-08-04T21:05:32.106Z'; createUserId: string; // '07e6b449-3a31-4a96-8920-e87dd504cb87'; @@ -349,7 +392,6 @@ export enum BasePaths { V7Agents = '/artifactory/ampc-public-generic-release/v7-agents', AWSAgents = '/artifactory/ampc-public-generic-release/aws-agents', DockerAgentPublicRepo = '/agent', - DockerAgentAPIRepoPath = '/artifactory/api/docker/ampc-public-docker-release/v2/agent', } export interface ValidatedDocs { @@ -836,8 +878,8 @@ export const GatewayTypeToDataPlane = { [GatewayTypes.WSO2]: DataPlaneNames.WSO2, }; -export const PublicRepoUrl = 'https://axway.jfrog.io'; -export const PublicDockerRepoBaseUrl = 'axway.jfrog.io/ampc-public-docker-release'; +export const PublicRepoUrl = 'https://repository.axway.com'; +export const PublicDockerRepoBaseUrl = 'repository.axway.com/ampc-public-docker-release'; export class DOSAConfigInfo { clientId: string | null; diff --git a/src/lib/engage/utils/agents/getters.ts b/src/lib/engage/utils/agents/getters.ts index d1e77c05..257da74f 100644 --- a/src/lib/engage/utils/agents/getters.ts +++ b/src/lib/engage/utils/agents/getters.ts @@ -1,9 +1,7 @@ import { ApiServerClient } from '../../clients-external/apiserverclient.js'; -import { ApiServerClientListResult, BasePaths, PublicRepoUrl } from '../../types.js'; -import { dataService } from '../../../request.js'; +import { ApiServerClientListResult } from '../../types.js'; import { DefinitionsManager } from '../../results/DefinitionsManager.js'; import logger from '../../../logger.js'; -import { Account } from '../../../../types.js'; const { log, error } = logger('lib: engage: utils: agents: getters'); @@ -56,49 +54,17 @@ export const getEnvironmentId = async ( return resource.data?.metadata ? resource.data.metadata.id || '' : ''; }; -export const getLatestAgentVersion = async (agent: string, account: Account): Promise => { +export const getLatestAgentVersion = async (client: ApiServerClient, agentName: string): Promise => { try { - const service = await dataService({ account, baseUrl: PublicRepoUrl + BasePaths.DockerAgentAPIRepoPath }); - - const response = await service.get( - `/${agent}/tags/list`, - { - // docker api requires auth, even if its just anonymous - Authorization: `Basic ${Buffer.from('anonymous:').toString('base64')}`, - }, - true - ); - - if (response.tags.length === 0) { - return 'latest'; + const componentDef = await client.getComponentDefinitionsByName(agentName); + const version = componentDef.spec?.latest?.version; + if (version) { + log(`Latest Version (${agentName}): ${version}`); + return version; } - const latestVersion = response.tags.reduce((prev: string, current: string) => { - // skip any tags that are latest - if (prev === 'latest') { - return current; - } else if (current === 'latest') { - return prev; - } - - // find the largest tag - const [ pMajor, pMinor, pPatch ] = prev.split('.').map(Number); - const [ cMajor, cMinor, cPatch ] = current.split('.').map(Number); - - if (cMajor > pMajor) { - return current; - } - if (cMajor === pMajor && cMinor > pMinor) { - return current; - } - if (cMajor === pMajor && cMinor === pMinor && cPatch > pPatch) { - return current; - } - return prev; - }); - log(`Latest Version (${agent}): ${latestVersion}`); - return latestVersion; + return 'latest'; } catch (e: any) { - error('Error hit retrieving latest version of agent, setting tag to latest'); + error('Error retrieving latest version, setting tag to latest'); error(e); return 'latest'; } From 8a7522e997c089ea402cf1167f9cbb024ae0c64d Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Wed, 8 Jul 2026 12:23:08 -0700 Subject: [PATCH 2/3] APIGOV-33064 Updates --- src/lib/engage/clients-external/apiserverclient.ts | 2 +- src/lib/engage/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/engage/clients-external/apiserverclient.ts b/src/lib/engage/clients-external/apiserverclient.ts index 62d873cb..9b30c971 100644 --- a/src/lib/engage/clients-external/apiserverclient.ts +++ b/src/lib/engage/clients-external/apiserverclient.ts @@ -900,7 +900,7 @@ export class ApiServerClient { log.info('get component definitions'); try { const service = await this.initializeDataService(); - const component: Component = await service.get(`definitions/${version}/components/${componentName}`); + const component: Component = await service.get(`/definitions/${version}/components/${componentName}`); return component; diff --git a/src/lib/engage/types.ts b/src/lib/engage/types.ts index 3cb57426..cd060ba2 100644 --- a/src/lib/engage/types.ts +++ b/src/lib/engage/types.ts @@ -878,7 +878,7 @@ export const GatewayTypeToDataPlane = { [GatewayTypes.WSO2]: DataPlaneNames.WSO2, }; -export const PublicRepoUrl = 'https://repository.axway.com'; +export const PublicRepoUrl = 'https://axway.jfrog.io'; export const PublicDockerRepoBaseUrl = 'repository.axway.com/ampc-public-docker-release'; export class DOSAConfigInfo { From 77d44780d2ab8aa38853c0bb7e4e7c59e2d23ed6 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Thu, 9 Jul 2026 16:16:10 -0700 Subject: [PATCH 3/3] APIGOV-33064 Update to remove download of zip files for aws and edge agents --- src/lib/engage/types.ts | 1 - .../engage/utils/agents/flows/awsAgents.ts | 83 ++++--------------- .../engage/utils/agents/flows/edgeAgents.ts | 70 ++++------------ 3 files changed, 32 insertions(+), 122 deletions(-) diff --git a/src/lib/engage/types.ts b/src/lib/engage/types.ts index cd060ba2..9701b2f9 100644 --- a/src/lib/engage/types.ts +++ b/src/lib/engage/types.ts @@ -878,7 +878,6 @@ export const GatewayTypeToDataPlane = { [GatewayTypes.WSO2]: DataPlaneNames.WSO2, }; -export const PublicRepoUrl = 'https://axway.jfrog.io'; export const PublicDockerRepoBaseUrl = 'repository.axway.com/ampc-public-docker-release'; export class DOSAConfigInfo { diff --git a/src/lib/engage/utils/agents/flows/awsAgents.ts b/src/lib/engage/utils/agents/flows/awsAgents.ts index e97ea2fa..f37d8b0a 100644 --- a/src/lib/engage/utils/agents/flows/awsAgents.ts +++ b/src/lib/engage/utils/agents/flows/awsAgents.ts @@ -1,13 +1,10 @@ import chalk from 'chalk'; -import fs from 'fs'; import logger from '../../../../logger.js'; -import { dataService } from '../../../../request.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, AWSRegions, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, PublicDockerRepoBaseUrl, PublicRepoUrl, TrueFalse, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, AWSRegions, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, PublicDockerRepoBaseUrl, 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'; import * as helpers from '../index.js'; -import { Account } from '../../../../../types.js'; const debugLog = logger('lib: engage: utils: agents: flows: awsAgents'); const daImage = `${PublicDockerRepoBaseUrl}${BasePaths.DockerAgentPublicRepo}/${AgentNames.AWS_DA}`; @@ -361,8 +358,6 @@ ${chalk.cyan( ${chalk.cyan( ` aws ssm put-parameter --type SecureString --name ${awsAgentValues.cloudFormationConfig.SSMPublicKeyParameter} --value "file://public_key.pem"` )}`; - // Cleanup EC2 file - fs.unlinkSync(ConfigFiles.EC2DeployYAML); break; } case DeploymentTypes.OTHER: { @@ -373,10 +368,6 @@ ${chalk.cyan( (value) => (s3ResourcesIncludes += `--include "${value}" `) ); - // Cleanup EC2 file - fs.unlinkSync(ConfigFiles.EC2DeployYAML); - // Cleanup ECS Fargate file - fs.unlinkSync(ConfigFiles.FargateDeployYAML); const info = `To utilize the agents, pull the latest Docker images and run them using the appropriate supplied environment files, (${helpers.configFiles.DA_ENV_VARS} & ${helpers.configFiles.TA_ENV_VARS}):`; dockerEnvConfig = `Wait for the CloudFormation Stack to complete. @@ -451,8 +442,6 @@ ${chalk.cyan( ${chalk.cyan( ` aws ssm put-parameter --type SecureString --name ${awsAgentValues.cloudFormationConfig.SSMPublicKeyParameter} --value "file://public_key.pem"` )}`; - // Cleanup Fargate file - fs.unlinkSync(ConfigFiles.FargateDeployYAML); break; } } @@ -463,7 +452,13 @@ ${chalk.cyan( const s3Region = awsAgentValues.region === AWSRegions.US_EAST_1 ? 's3' : `s3.${awsAgentValues.region}`; return ` -To complete the install, run the following AWS CLI command: +To complete the install, first download and extract the CloudFormation templates: + - Download the CloudFormation package: +${chalk.cyan(` curl -O https://repository.axway.com/artifactory/ampc-public-generic-release/aws-agents/aws_apigw_agent_config/latest/${ConfigFiles.AgentConfigZip}`)} + - Extract the package: +${chalk.cyan(` unzip ${ConfigFiles.AgentConfigZip}`)} + +Then run the following AWS CLI commands: - Create, if necessary, and upload all files to your S3 bucket ${chalk.cyan( ` aws s3api create-bucket --bucket ${awsAgentValues.cloudFormationConfig.AgentResourcesBucket} --create-bucket-configuration LocationConstraint=${awsAgentValues.region}` @@ -487,55 +482,6 @@ ${chalk.gray(`Additional information about agent features can be found here:\n${ `; }; -// Download latest aws apigw config zip -const downloadAPIGWAgentConfigZip = async (account: Account): Promise => { - const url = `${BasePaths.AWSAgents}/aws_apigw_agent_config/latest/${ConfigFiles.AgentConfigZip}`; - - const service = await dataService({ - account, - baseUrl: PublicRepoUrl, - }); - try { - const token = account.auth?.tokens?.access_token; - if (!token) { - throw new Error('Invalid/expired account'); - } - const { stream } = await service.download(url); - await helpers.streamPipeline(stream, fs.createWriteStream(ConfigFiles.AgentConfigZip)); - return ConfigFiles.AgentConfigZip; - } catch (err: any) { - throw new Error(`Failed to download the agent: ${err.message}`); - } -}; - -// Unzip latest aws apigw config zip -const unzipAPIGWAgentConfigZip = async (zipFile: string, log: (text: string) => void = () => {}): Promise => { - await helpers.unzip(zipFile); - fs.unlinkSync(zipFile); - - const isCloudFormation = fs.existsSync(ConfigFiles.DeployAllYAML); - if (!isCloudFormation) { - log(`${ConfigFiles.DeployAllYAML} was not extracted from ${ConfigFiles.AgentConfigZip}`); - return false; - } - return true; -}; - -export const installPreprocess = async (installConfig: AgentInstallConfig): Promise => { - // attempt to download the cloud formation files - installConfig.log(chalk.gray('Downloading the latest Cloud formation template...')); - const account = installConfig.centralConfig.apiServerClient?.account; - if (!account) { - throw new Error('Unable to resolve account for DataService call during AWS agent install preprocess'); - } - const apigwAgentConfigZipFile = await downloadAPIGWAgentConfigZip(account); - if (apigwAgentConfigZipFile !== '') { - installConfig.log(chalk.gray('\nSuccess')); - } - (installConfig.gatewayConfig as helpers.AWSAgentValues).apigwAgentConfigZipFile = apigwAgentConfigZipFile; - return installConfig; -}; - export const completeInstall = async (installConfig: AgentInstallConfig): Promise => { /** * Create agent resources @@ -546,11 +492,7 @@ export const completeInstall = async (installConfig: AgentInstallConfig): Promis awsAgentValues.centralConfig = installConfig.centralConfig; awsAgentValues.traceabilityConfig = installConfig.traceabilityConfig; - const unpackZip = await unzipAPIGWAgentConfigZip(awsAgentValues.apigwAgentConfigZipFile, installConfig.log); - if (unpackZip) { - installConfig.log('\nCreating the agent environment files for AWS...'); - } - + installConfig.log('\nCreating the agent environment files for AWS...'); installConfig.log('Generating the configuration file(s)...'); installConfig.log('Generating the cloud formation parameters file...'); @@ -586,9 +528,12 @@ export const AWSInstallMethods: InstallationFlowMethods = { GetBundleType: askBundleType, GetDeploymentType: askConfigType, AskGatewayQuestions: gatewayConnectivity, - InstallPreprocess: installPreprocess, FinalizeGatewayInstall: completeInstall, - ConfigFiles: Object.values(ConfigFiles), + ConfigFiles: [ + ConfigFiles.DAEnvVars, + ConfigFiles.TAEnvVars, + ConfigFiles.CFProperties, + ], AgentNameMap: { [AgentTypes.da]: AgentNames.AWS_DA, [AgentTypes.ta]: AgentNames.AWS_TA, diff --git a/src/lib/engage/utils/agents/flows/edgeAgents.ts b/src/lib/engage/utils/agents/flows/edgeAgents.ts index 661040ad..5b36bb43 100644 --- a/src/lib/engage/utils/agents/flows/edgeAgents.ts +++ b/src/lib/engage/utils/agents/flows/edgeAgents.ts @@ -1,14 +1,11 @@ import chalk from 'chalk'; -import fs from 'fs'; -import { dataService } from '../../../../request.js'; -import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, localhost, LoggingSource, PublicDockerRepoBaseUrl, PublicRepoUrl, svcAccMsg, YesNo, YesNoChoices } from '../../../types.js'; +import { AgentConfigTypes, AgentInstallConfig, AgentNames, AgentTypes, BasePaths, BundleType, GatewayTypes, InstallationFlowMethods, localhost, LoggingSource, PublicDockerRepoBaseUrl, 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'; import * as helpers from '../index.js'; import { kubectl } from '../kubectl.js'; import { amplifyAgentsNs } from './akamaiAgent.js'; -import { Account } from '../../../../../types.js'; const defaultLogFiles = '/group-*_instance-*.log'; const defaultOTLogFiles = '/group-*_instance-*_traffic*.log'; @@ -45,47 +42,6 @@ export const prompts = { 'Do you want to replicate your original organization structure for your newly discovered APIs? If yes, make sure the organization names match the team names that are created in Amplify platform', }; -const downloadV7AgentBundle = async (account: Account, type: BundleType, version: string): Promise => { - const fileName - = type === BundleType.DISCOVERY ? `discovery_agent-${version}.zip` : `traceability_agent-${version}.zip`; - const url - = type === BundleType.DISCOVERY - ? `${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 { - const { stream } = await service.download(url); - await helpers.streamPipeline(stream, fs.createWriteStream(fileName)); - return fileName; - } catch (err: any) { - throw new Error(`Failed to download the agent: ${err.message}`); - } -}; - -const downloadBinary = async (account: Account, bundleType: BundleType, version: string) => { - const fileName = await downloadV7AgentBundle(account, bundleType, version); - await helpers.unzip(fileName); - fs.unlinkSync(fileName); -}; - -const downloadBinaries = async (installConfig: AgentInstallConfig) => { - const account = installConfig.centralConfig.apiServerClient?.account; - if (!account) { - throw new Error('Unable to resolve account for DataService call during AWS agent install preprocess'); - } - installConfig.log('Downloading and unpacking binary files...'); - if (installConfig.switches.isDaEnabled) { - await downloadBinary(account, BundleType.DISCOVERY, installConfig.daVersion); - } - if (installConfig.switches.isTaEnabled) { - await downloadBinary(account, BundleType.TRACEABILITY, installConfig.taVersion); - } - installConfig.log('Downloading and unpacking is complete.'); -}; - export const askIsGatewayOnlyMode = async (): Promise => { const mode = await askList({ msg: prompts.enterGatewayManagerMode, @@ -273,6 +229,8 @@ const generateSuccessHelpMsg = (installConfig: AgentInstallConfig) => { installConfig.centralConfig.ampcDosaInfo.isNew, installConfig.switches.isDaEnabled, installConfig.switches.isTaEnabled, + installConfig.daVersion, + installConfig.taVersion, installConfig.log ); } else if (configType === AgentConfigTypes.DOCKERIZED) { @@ -298,11 +256,6 @@ export const installPreprocess = async (installConfig: AgentInstallConfig): Prom = await helpers.askPublicAndPrivateKeysPath(); } - // attempt to download the binaries prior to creating resources - if (installConfig.switches.isBinaryInstall) { - await downloadBinaries(installConfig); - } - return installConfig; }; @@ -418,7 +371,20 @@ const dockerSuccessMsg = (installConfig: AgentInstallConfig, eventLogPath: strin } }; -const binarySuccessMsg = (isNewDosa: boolean, isDaEnabled: boolean, isTaEnabled: boolean, log: (text: string) => void = () => {}) => { +const binarySuccessMsg = (isNewDosa: boolean, isDaEnabled: boolean, isTaEnabled: boolean, daVersion: string, taVersion: string, log: (text: string) => void = () => {}) => { + const baseUrl = 'https://repository.axway.com/artifactory/ampc-public-generic-release/v7-agents'; + + if (isDaEnabled) { + log(chalk.whiteBright('\nDownload the Discovery Agent binary:')); + log(chalk.cyan(`curl -O ${baseUrl}/v7_discovery_agent/${daVersion}/discovery_agent-${daVersion}.zip`)); + log(chalk.cyan(`unzip discovery_agent-${daVersion}.zip`)); + } + if (isTaEnabled) { + log(chalk.whiteBright('\nDownload the Traceability Agent binary:')); + log(chalk.cyan(`curl -O ${baseUrl}/v7_traceability_agent/${taVersion}/traceability_agent-${taVersion}.zip`)); + log(chalk.cyan(`unzip traceability_agent-${taVersion}.zip`)); + } + const daFiles = [ ConfigFiles.DAEnvVars, ConfigFiles.EdgeDABinaryFile, ConfigFiles.EdgeDAYaml ]; const taFiles = [ ConfigFiles.TAEnvVars, ConfigFiles.EdgeTABinaryFile, ConfigFiles.EdgeTAYaml ]; const keys = [ 'private_key.pem', 'public_key.pem' ]; @@ -435,7 +401,7 @@ const binarySuccessMsg = (isNewDosa: boolean, isDaEnabled: boolean, isTaEnabled: } const agents = isDaEnabled && isTaEnabled ? 'agents' : 'agent'; - log(chalk.whiteBright('Please copy following files from current folder to API Gateway machine:')); + log(chalk.whiteBright('\nPlease copy following files from current folder to API Gateway machine:')); log(chalk.cyan(files.join('\n'))); log(chalk.whiteBright('for example') + ' ' + chalk.cyan(`scp ${files.join(' ')} root@host:~/some_folder/`));