From 26677c6789758757593fc10633a3570e7dcd97b9 Mon Sep 17 00:00:00 2001 From: Maurizio Vitale Date: Fri, 10 Jan 2020 17:02:16 +0000 Subject: [PATCH] adf-cli command init-aae use the user based on the role (#5364) * Reuse the common deployment and modeling api * Add files to testing * Pass parameters * Travis initaae with correct parameters * Travis initaae with correct parameters * Add cli to the smart build --- angular.json | 14 + lib/cli/README.md | 2 +- lib/cli/scripts/init-aae-env.ts | 269 ++++++------------ lib/cli/scripts/kubectl-clean-app.ts | 19 +- lib/testing/src/lib/core/public-api.ts | 1 + lib/testing/src/lib/core/structure/api.ts | 42 +++ .../src/lib/core/structure/application.ts | 58 ++++ .../src/lib/core/structure/deployment-api.ts | 69 +++++ .../core/structure/e2e-request-api.helper.ts | 112 ++++++++ .../src/lib/core/structure/modeling-api.ts | 78 +++++ lib/testing/src/lib/core/structure/project.ts | 132 +++++++++ .../src/lib/core/structure/public-api.ts | 21 ++ lib/testing/src/lib/core/structure/utilapi.ts | 44 +++ nx.json | 3 + scripts/affected-libs.sh | 11 +- scripts/smart-build.sh | 8 + .../travis/e2e/process-services-cloud-e2e.sh | 2 +- 17 files changed, 698 insertions(+), 187 deletions(-) create mode 100644 lib/testing/src/lib/core/structure/api.ts create mode 100644 lib/testing/src/lib/core/structure/application.ts create mode 100644 lib/testing/src/lib/core/structure/deployment-api.ts create mode 100644 lib/testing/src/lib/core/structure/e2e-request-api.helper.ts create mode 100644 lib/testing/src/lib/core/structure/modeling-api.ts create mode 100644 lib/testing/src/lib/core/structure/project.ts create mode 100644 lib/testing/src/lib/core/structure/public-api.ts create mode 100644 lib/testing/src/lib/core/structure/utilapi.ts diff --git a/angular.json b/angular.json index b9e86ff2d7..291b46ac66 100644 --- a/angular.json +++ b/angular.json @@ -838,6 +838,20 @@ } } } + }, + "cli": { + "root": "lib/cli", + "sourceRoot": "lib/cli", + "projectType": "library", + "prefix": "adf", + "architect": { + "build": { + "builder": "@angular-devkit/build-ng-packagr:build", + "options": { + "tsConfig": "lib/cli/tsconfig.json" + } + } + } } }, "defaultProject": "dist" diff --git a/lib/cli/README.md b/lib/cli/README.md index a94a23cf2d..bc228e089e 100644 --- a/lib/cli/README.md +++ b/lib/cli/README.md @@ -141,7 +141,7 @@ adf-cli update-commit-sha --pathProject "$(pwd)" --skipGnu The following command is in charge of Initializing the activiti cloud env with the default apps: ```bash -adf-cli init-aae-env --host "gateway_env" --oauth "identity_env" --identityHost "identity_env" --username "username" --password "password" +adf-cli init-aae-env --host "gateway_env" --oauth "identity_env" --identityHost "identity_env" --modelerUsername "modelerusername" --modelerPassword "modelerpassword" --devopsUsername "devopsusername" --devopsPassword "devopspassword" ``` If you want to add a new app the schema needs to be: diff --git a/lib/cli/scripts/init-aae-env.ts b/lib/cli/scripts/init-aae-env.ts index b07187d2ac..2ebf5acab5 100644 --- a/lib/cli/scripts/init-aae-env.ts +++ b/lib/cli/scripts/init-aae-env.ts @@ -17,7 +17,7 @@ * limitations under the License. */ -import { ACTIVITI_CLOUD_APPS } from '@alfresco/adf-testing'; +import { ACTIVITI_CLOUD_APPS, DeploymentAPI, ModelingAPI } from '@alfresco/adf-testing'; import * as program from 'commander'; /* tslint:disable */ @@ -28,39 +28,26 @@ import * as fs from 'fs'; import { logger } from './logger'; export interface ConfigArgs { - username: string; - password: string; + modelerUsername: string; + modelerPassword: string; + devopsUsername: string; + devopsPassword: string; clientId: string; host: string; oauth: string; identityHost: boolean; } +let browser: any; +let deploymentAPI: DeploymentAPI; +let modelingAPI: ModelingAPI; + export const AAE_MICROSERVICES = [ 'deployment-service', 'modeling-service', 'dmn-service' ]; -async function getDeployedApplicationsByStatus(args: ConfigArgs, apiService: any, status: string) { - const url = `${args.host}/deployment-service/v1/applications`; - - const pathParams = {}, queryParams = {status: status}, - headerParams = {}, formParams = {}, bodyParam = {}, - contentTypes = ['application/json'], accepts = ['application/json']; - - let data; - try { - data = await apiService.oauth2Auth.callCustomApi(url, 'GET', pathParams, queryParams, headerParams, formParams, bodyParam, - contentTypes, accepts); - return data.list.entries; - } catch (error) { - logger.error(`Not possible get the applications from deployment-service ${JSON.stringify(error)} `); - process.exit(1); - } - -} - async function healthCheck(args: ConfigArgs, apiService: any, nameService: string, result: any) { const url = `${args.host}/${nameService}/actuator/health`; @@ -103,72 +90,50 @@ function getAlfrescoJsApiInstance(args: ConfigArgs) { async function login(args: ConfigArgs, alfrescoJsApi: any) { logger.info(`Perform login...`); try { - await alfrescoJsApi.login(args.username, args.password); + await alfrescoJsApi.login(args.modelerUsername, args.modelerPassword); } catch (error) { - logger.error(`Not able to login. Credentials ${args.username}:${args.password} are not valid`); + logger.error(`Not able to login. Credentials ${args.modelerUsername}:${args.modelerPassword} are not valid`); process.exit(1); } return alfrescoJsApi; } -async function deployMissingApps(args: ConfigArgs, alfrescoJsApi: any) { - const deployedApps = await getDeployedApplicationsByStatus(args, alfrescoJsApi, ''); - const absentApps = findMissingApps(deployedApps); +async function deployMissingApps() { + const deployedApps = await deploymentAPI.getApplicationByStatus(''); + const absentApps = findMissingApps(deployedApps.list.entries); if (absentApps.length > 0) { logger.warn(`Missing apps: ${JSON.stringify(absentApps)}`); - await checkIfAppIsReleased(args, alfrescoJsApi, absentApps); + await checkIfAppIsReleased(absentApps); } else { logger.warn(`All the apps are correctly deployed`); } } -async function getAppProjects(args: ConfigArgs, apiService: any) { - const url = `${args.host}/modeling-service/v1/projects?maxItems=200&skipCount=0`; - - const pathParams = {}, queryParams = {}, - headerParams = {}, formParams = {}, bodyParam = {}, - contentTypes = ['application/json'], accepts = ['application/json']; - - let data; - try { - data = await apiService.oauth2Auth.callCustomApi(url, 'GET', pathParams, queryParams, headerParams, formParams, bodyParam, - contentTypes, accepts); - return data.list.entries; - } catch (error) { - logger.error(`Not possible get the application from modeling-service ` + error); - process.exit(1); - } -} - -async function checkIfAppIsReleased(args: ConfigArgs, apiService: any, absentApps: any []) { - const projectList = await getAppProjects(args, apiService); +async function checkIfAppIsReleased(absentApps: any []) { + const projectList = await modelingAPI.getProjects(); let TIME = 5000; let noError = true; for (let i = 0; i < absentApps.length; i++) { noError = true; const currentAbsentApp = absentApps[i]; - const app = projectList.find((currentApp: any) => { + const project = projectList.list.entries.find((currentApp: any) => { return currentAbsentApp.name === currentApp.entry.name; }); let projectRelease: any; - if (app === undefined) { + if (project === undefined) { logger.warn('Missing project: Create the project for ' + currentAbsentApp.name); try { - const uploadedApp = await importProjectApp(args, apiService, currentAbsentApp); - logger.warn('Project imported ' + currentAbsentApp.name); - if (uploadedApp) { - projectRelease = await releaseProject(args, apiService, uploadedApp); - } + projectRelease = await importProjectAndRelease(currentAbsentApp); } catch (error) { logger.info(`error status ${error.status}`); if (error.status !== 409) { - logger.info(`Not possible to upload the project ${app.name} status : ${JSON.stringify(error.status)} ${JSON.stringify(error.response.text)}`); + logger.info(`Not possible to upload the project ${project.entry.name} status : ${JSON.stringify(error.status)} ${JSON.stringify(error.response.text)}`); process.exit(1); } else { logger.error(`Not possible to upload the project because inconsistency CS - Modelling try to delete manually the node`); @@ -180,13 +145,13 @@ async function checkIfAppIsReleased(args: ConfigArgs, apiService: any, absentApp TIME += 5000; - logger.info('Project ' + app.entry.name + ' found'); + logger.info('Project ' + project.entry.name + ' found'); - const projectReleaseList = await getReleaseAppProjectId(args, apiService, app.entry.id); + const projectReleaseList = await modelingAPI.getProjectRelease(project.entry.id); if (projectReleaseList.list.entries.length === 0) { logger.warn('Project needs release'); - projectRelease = await releaseProject(args, apiService, app); + projectRelease = await modelingAPI.releaseProject(project.entry); logger.warn(`Project released: ${projectRelease.id}`); } else { logger.info('Project already has release'); @@ -203,139 +168,37 @@ async function checkIfAppIsReleased(args: ConfigArgs, apiService: any, absentApp } if (noError) { - await checkDescriptorExist(args, apiService, currentAbsentApp.name); + await checkDescriptorExist(currentAbsentApp.name); await sleep(TIME); - await deployApp(args, apiService, currentAbsentApp, projectRelease.entry.id); + const deployPayload = { + 'name': currentAbsentApp.name, + 'releaseId': projectRelease.entry.id, + 'security': currentAbsentApp.security, + 'infrastructure': currentAbsentApp.infrastructure, + 'variables': currentAbsentApp.variables + }; + await deploymentAPI.deploy(deployPayload); } } } -async function deployApp(args: ConfigArgs, apiService: any, appInfo: any, projectReleaseId: string) { - logger.warn(`Deploy app ${appInfo.name} with projectReleaseId ${projectReleaseId}`); - - const url = `${args.host}/deployment-service/v1/applications`; - - const pathParams = {}; - const bodyParam = { - 'name': appInfo.name, - 'releaseId': projectReleaseId, - 'security': appInfo.security, - 'infrastructure': appInfo.infrastructure, - 'variables': appInfo.variables - }; - - logger.debug(`Deploy with body: ${JSON.stringify(bodyParam)}`); - - const headerParams = {}, formParams = {}, queryParams = {}, - contentTypes = ['application/json'], accepts = ['application/json']; - - try { - return await apiService.oauth2Auth.callCustomApi(url, 'POST', pathParams, queryParams, headerParams, formParams, bodyParam, - contentTypes, accepts); - } catch (error) { - logger.error(`Not possible to deploy the project ${appInfo.name} status : ${JSON.stringify(error.status)} ${JSON.stringify(error.response.text)}`); - // await deleteSiteByName(name); - process.exit(1); - } -} - -async function checkDescriptorExist(args: ConfigArgs, apiService: any, name: string) { +async function checkDescriptorExist(name: string) { logger.info(`Check descriptor ${name} exist in the list `); - const descriptorList: [] = await getDescriptorList(args, apiService); - descriptorList.forEach( async(descriptor: any) => { + const descriptorList = await deploymentAPI.getDescriptors(); + descriptorList.list.entries.forEach( async(descriptor: any) => { if (descriptor.entry.name === name) { if (descriptor.entry.deployed === false) { - await deleteDescriptor(args, apiService, descriptor.entry.name); + await deploymentAPI.deleteDescriptor(descriptor.entry.name); } } }); return false; } -async function getDescriptorList(args: ConfigArgs, apiService: any) { - const url = `${args.host}/deployment-service/v1/descriptors?page=0&size=50&sort=lastModifiedAt,desc`; - - const pathParams = {}, queryParams = {}, - headerParams = {}, formParams = {}, bodyParam = {}, - contentTypes = ['application/json'], accepts = ['application/json']; - - let data; - try { - data = await apiService.oauth2Auth.callCustomApi(url, 'GET', pathParams, queryParams, headerParams, formParams, bodyParam, - contentTypes, accepts); - return data.list.entries; - } catch (error) { - logger.error(`Not possible get the descriptors from deployment-service ${JSON.stringify(error)} `); - } - -} - -async function deleteDescriptor(args: ConfigArgs, apiService: any, name: string) { - logger.warn(`Delete the descriptor ${name}`); - - const url = `${args.host}/deployment-service/v1/descriptors/${name}`; - - const pathParams = {}; - const bodyParam = {}; - - const headerParams = {}, formParams = {}, queryParams = {}, - contentTypes = ['application/json'], accepts = ['application/json']; - - try { - return await apiService.oauth2Auth.callCustomApi(url, 'DELETE', pathParams, queryParams, headerParams, formParams, bodyParam, contentTypes, accepts); - } catch (error) { - logger.error(`Not possible to delete the descriptor ${name} status : ${JSON.stringify(error.status)} ${JSON.stringify(error.response.text)}`); - } -} - -async function releaseProject(args: ConfigArgs, apiService: any, app: any) { - const url = `${args.host}/modeling-service/v1/projects/${app.entry.id}/releases`; - - logger.info(`Release ID ${app.entry.id}`); - const pathParams = {}, queryParams = {}, - headerParams = {}, formParams = {}, bodyParam = {}, - contentTypes = ['application/json'], accepts = ['application/json']; - - try { - return await apiService.oauth2Auth.callCustomApi(url, 'POST', pathParams, queryParams, headerParams, formParams, bodyParam, - contentTypes, accepts); - } catch (error) { - logger.info(`Not possible to release the project ${app.entry.name} status : $ \n ${JSON.stringify(error.status)} \n ${JSON.stringify(error.response.text)}`); - process.exit(1); - } -} - -async function getReleaseAppProjectId(args: ConfigArgs, apiService: any, projectId: string) { - const url = `${args.host}/modeling-service/v1/projects/${projectId}/releases`; - - const pathParams = {}, queryParams = {}, - headerParams = {}, formParams = {}, bodyParam = {}, - contentTypes = ['application/json'], accepts = ['application/json']; - - try { - return await apiService.oauth2Auth.callCustomApi(url, 'GET', pathParams, queryParams, headerParams, formParams, bodyParam, - contentTypes, accepts); - } catch (error) { - logger.error(`Not possible to get the release of the project ${projectId} ` + JSON.stringify(error)); - process.exit(1); - } - -} - -async function importProjectApp(args: ConfigArgs, apiService: any, app: any) { +async function importProjectAndRelease(app: any) { await getFileFromRemote(app.file_location, app.name); - - const file = fs.createReadStream(`${app.name}.zip`).on('error', () => {logger.error(`${app.name}.zip does not exist`); }); - - const url = `${args.host}/modeling-service/v1/projects/import`; - - const pathParams = {}, queryParams = {}, - headerParams = {}, formParams = {'file': file}, bodyParam = {}, - contentTypes = ['multipart/form-data'], accepts = ['application/json']; - - logger.warn(`import app ${app.file_location}`); - const result = await apiService.oauth2Auth.callCustomApi(url, 'POST', pathParams, queryParams, headerParams, formParams, bodyParam, - contentTypes, accepts); + logger.warn('Project imported ' + app.name); + const result = await modelingAPI.importAndReleaseProject(`${app.name}.zip`); deleteLocalFile(`${app.name}`); return result; } @@ -380,27 +243,69 @@ async function sleep(time: number) { return; } +async function initConfiguration(args: ConfigArgs) { + browser = { + params: { + config: { + log: true + }, + adminapp: { + apiConfig: { + authType: 'OAUTH', + identityHost: args.identityHost, + oauth2: { + host: args.oauth, + authPath: '/protocol/openid-connect/token/', + clientId: args.clientId, + scope: 'openid', + implicitFlow: false, + redirectUri: '' + }, + bpmHost: args.host, + providers: 'BPM' + }, + modeler: args.modelerUsername, + modeler_password: args.modelerPassword, + devops: args.devopsUsername, + devops_password: args.devopsPassword + } + } + }; + + global['protractor'] = {browser: browser}; + + deploymentAPI = new DeploymentAPI(); + modelingAPI = new ModelingAPI(); + + await deploymentAPI.setUp(); + await modelingAPI.setUp(); +} + export default async function (args: ConfigArgs) { await main(args); } -async function main(args) { +async function main(args: ConfigArgs) { program .version('0.1.0') .description('The following command is in charge of Initializing the activiti cloud env with the default apps' + - 'adf-cli init-aae-env --host "gateway_env" --oauth "identity_env" --identityHost "identity_env" --username "username" --password "password"') + 'adf-cli init-aae-env --host "gateway_env" --oauth "identity_env" --identityHost "identity_env" --modelerUsername "modelerusername" --modelerPassword "modelerpassword" --devopsUsername "devevopsusername" --devopsPassword "devopspassword"') .option('-h, --host [type]', 'Host gateway') .option('-o, --oauth [type]', 'Host sso server') .option('--clientId[type]', 'sso client') - .option('--username [type]', 'username') - .option('--password [type]', 'password') + .option('--modelerUsername [type]', 'username of a user with role ACTIVIT_MODELER') + .option('--modelerPassword [type]', 'modeler password') + .option('--devopsUsername [type]', 'username of a user with role ACTIVIT_DEVOPS') + .option('--devopsPassword [type]', 'devops password') .parse(process.argv); if (process.argv.includes('-h') || process.argv.includes('--help')) { program.outputHelp(); } + await initConfiguration(args); + const alfrescoJsApi = getAlfrescoJsApiInstance(args); await login(args, alfrescoJsApi); @@ -412,7 +317,7 @@ async function main(args) { if (result.isValid) { logger.error('The envirorment is up and running'); - await deployMissingApps(args, alfrescoJsApi); + await deployMissingApps(); } else { logger.error('The envirorment is not up'); process.exit(1); diff --git a/lib/cli/scripts/kubectl-clean-app.ts b/lib/cli/scripts/kubectl-clean-app.ts index ca9c4546e5..27dcb6734c 100644 --- a/lib/cli/scripts/kubectl-clean-app.ts +++ b/lib/cli/scripts/kubectl-clean-app.ts @@ -181,7 +181,7 @@ function createTmpTable(podName: string, appName: string) { function dropTmpTable(podName: string) { logger.info('Perform drop tmp table...'); - const query = `drop table query_table`; + const query = `drop table IF EXISTS query_table`; const response = exec('kubectl', [`exec`, `${podName}`, `--`, `psql`, `-U`, `alfresco`, `-d`, `postgres`, `-c`, `${query}` ], {}); logger.info(response); } @@ -200,10 +200,27 @@ function deleteRelease(podName: string) { logger.info(response); } +function deleteInvolvedGroupsRelease(podName: string) { + logger.info('Perform delete release involved groups...'); + const query = `delete from release_involved_groups where release_entity_id in (select id from release where release.project_id in (select query_table.project_id from query_table))`; + const response = exec('kubectl', [`exec`, `${podName}`, `--`, `psql`, `-U`, `alfresco`, `-d`, `postgres`, `-c`, `${query}` ], {}); + logger.info(response); +} + +function deleteInvolvedUsersRelease(podName: string) { + logger.info('Perform delete release involved users...'); + const query = `delete from release_involved_users where release_entity_id in (select id from release where release.project_id in (select query_table.project_id from query_table))`; + const response = exec('kubectl', [`exec`, `${podName}`, `--`, `psql`, `-U`, `alfresco`, `-d`, `postgres`, `-c`, `${query}` ], {}); + logger.info(response); +} + function deleteAllReleases(podName: string, appName: string) { logger.info(podName, appName); + dropTmpTable(podName); createTmpTable(podName, appName); deleteLatestRelease(podName); + deleteInvolvedGroupsRelease(podName); + deleteInvolvedUsersRelease(podName); deleteRelease(podName); dropTmpTable(podName); } diff --git a/lib/testing/src/lib/core/public-api.ts b/lib/testing/src/lib/core/public-api.ts index c4ebe673dc..635aa7f36a 100644 --- a/lib/testing/src/lib/core/public-api.ts +++ b/lib/testing/src/lib/core/public-api.ts @@ -20,3 +20,4 @@ export * from './pages/public-api'; export * from './models/public-api'; export * from './dialog/public-api'; export * from './utils/public-api'; +export * from './structure/public-api'; diff --git a/lib/testing/src/lib/core/structure/api.ts b/lib/testing/src/lib/core/structure/api.ts new file mode 100644 index 0000000000..8cf77906ca --- /dev/null +++ b/lib/testing/src/lib/core/structure/api.ts @@ -0,0 +1,42 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AlfrescoApi } from '@alfresco/js-api'; +import { browser } from 'protractor'; + +export abstract class Api { + public api: AlfrescoApi; + testConfig = browser.params; + + constructor(root: string) { + this.api = this.configureApi(root); + } + + private configureApi(root: string): AlfrescoApi { + const config = browser.params.adminapp.apiConfig; + return new AlfrescoApi({ + provider: 'BPM', + authType: config.authType, + oauth2: config.oauth2, + hostBpm: config.bpmHost + '/' + root + }); + } + + abstract setUp(): Promise; + + abstract tearDown(); +} diff --git a/lib/testing/src/lib/core/structure/application.ts b/lib/testing/src/lib/core/structure/application.ts new file mode 100644 index 0000000000..bef69bba92 --- /dev/null +++ b/lib/testing/src/lib/core/structure/application.ts @@ -0,0 +1,58 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { E2eRequestApiHelper } from './e2e-request-api.helper'; +import { Api } from './api'; +import { Logger } from '../utils/logger'; +import { ResultSetPaging } from '@alfresco/js-api'; + +export class Application { + + requestApiHelper: E2eRequestApiHelper; + endPoint = `/v1/applications/`; + + constructor(api: Api) { + this.requestApiHelper = new E2eRequestApiHelper(api); + } + + async deploy(model: any): Promise { + await this.requestApiHelper.post(`${this.endPoint}`, { bodyParam: model}); + Logger.info(`[Application] Application '${model.name}' was deployed successfully.`); + } + + async delete(applicationId: string): Promise { + await this.requestApiHelper.delete(`${this.endPoint}${applicationId}`); + Logger.info(`[Application] Application: '${applicationId}' was deleted successfully.`); + } + + async deleteDescriptor(name: string): Promise { + await this.requestApiHelper.delete(`v1/descriptors/${name}`); + Logger.info(`[Descriptor] Descriptor: '${name}' was deleted successfully.`); + } + + async getDescriptors(): Promise { + Logger.info(`[Descriptor] Return descriptors`); + return this.requestApiHelper.get(`v1/descriptors`, {}); + } + + async getApplicationsByStatus(status: string): Promise { + Logger.info(`[Application] Return application by status: ${status}`); + return this.requestApiHelper.get(this.endPoint, { + queryParams: { status: status } + }); + } +} diff --git a/lib/testing/src/lib/core/structure/deployment-api.ts b/lib/testing/src/lib/core/structure/deployment-api.ts new file mode 100644 index 0000000000..e821192680 --- /dev/null +++ b/lib/testing/src/lib/core/structure/deployment-api.ts @@ -0,0 +1,69 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Api } from './api'; +import { Application } from './application'; +import { Logger } from '../utils/logger'; +import { browser } from 'protractor'; +import { ResultSetPaging } from '@alfresco/js-api'; + +export class DeploymentAPI extends Api { + public application: Application; + + constructor(ROOT: string = 'deployment-service') { + super(ROOT); + } + + async setUp(): Promise { + await this.login(); + this.application = new Application(this); + return this; + } + + async tearDown(): Promise { + await this.api.logout(); + } + + private async login(): Promise { + try { + await this.api.login( + browser.params.adminapp.devops, + browser.params.adminapp.devops_password + ); + } catch (error) { + Logger.error(error); + } + } + + async deploy(releasedProject: any): Promise { + await this.application.deploy(releasedProject); + } + + async deleteDescriptor(name: string): Promise { + await this.application.deleteDescriptor(name); + } + + async getDescriptors(): Promise { + const descriptors = await this.application.getDescriptors(); + return descriptors; + } + + async getApplicationByStatus(status: string): Promise { + const applications = this.application.getApplicationsByStatus(status); + return applications; + } +} diff --git a/lib/testing/src/lib/core/structure/e2e-request-api.helper.ts b/lib/testing/src/lib/core/structure/e2e-request-api.helper.ts new file mode 100644 index 0000000000..a5be6d502e --- /dev/null +++ b/lib/testing/src/lib/core/structure/e2e-request-api.helper.ts @@ -0,0 +1,112 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Oauth2Auth } from '@alfresco/js-api/src/authentication/oauth2Auth'; + +export interface E2eRequestApiHelperOptions { + pathParams?: { [key: string]: any }; + queryParams?: { [key: string]: any }; + headerParams?: { [key: string]: any }; + formParams?: { [key: string]: any }; + bodyParam?: { [key: string]: any }; + contentTypes?: string[]; + accepts?: string[]; + returnType?: any; + contextRoot?: string; + responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text'; +} + +function getDefaultOptions(): E2eRequestApiHelperOptions { + return { + pathParams: {}, + queryParams: {}, + headerParams: {}, + formParams: {}, + bodyParam: {}, + contentTypes: ['application/json'], + accepts: ['application/json'], + returnType: undefined + }; +} + +export class E2eRequestApiHelper { + + api: Oauth2Auth; + + constructor(private backend) { + this.api = backend.api.oauth2Auth; + } + + private buildUrl(endPoint: string): string { + const trimSlash = (str: string) => str.replace(/^\/|\/$/g, ''); + const host = this.backend.api.config.hostBpm; + const path = '/' + trimSlash(endPoint); + + return `${host}${path}`; + } + + public get(endPoint: string, overriddenOptions?: E2eRequestApiHelperOptions): PromiseLike { + return this.request('GET', endPoint, overriddenOptions); + } + + public post(endPoint: string, overriddenOptions?: E2eRequestApiHelperOptions): PromiseLike { + return this.request('POST', endPoint, overriddenOptions); + } + + public put(endPoint: string, overriddenOptions?: E2eRequestApiHelperOptions): PromiseLike { + return this.request('PUT', endPoint, overriddenOptions); + } + + public delete(endPoint: string, overriddenOptions?: E2eRequestApiHelperOptions): PromiseLike { + return this.request('DELETE', endPoint, overriddenOptions); + } + + private request(httpMethod: string, endPoint: string, overriddenOptions?: E2eRequestApiHelperOptions): PromiseLike { + const options = { + ...getDefaultOptions(), + ...overriddenOptions + }; + + const { + pathParams, + queryParams, + headerParams, + formParams, + bodyParam, + contentTypes, + accepts, + returnType, + contextRoot, + responseType + } = options; + + return this.api.callCustomApi( + this.buildUrl(endPoint), + httpMethod, + pathParams, + queryParams, + headerParams, + formParams, + bodyParam, + contentTypes, + accepts, + returnType, + contextRoot, + responseType + ); + } +} diff --git a/lib/testing/src/lib/core/structure/modeling-api.ts b/lib/testing/src/lib/core/structure/modeling-api.ts new file mode 100644 index 0000000000..f05608d45a --- /dev/null +++ b/lib/testing/src/lib/core/structure/modeling-api.ts @@ -0,0 +1,78 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Api } from './api'; +import { Project } from './project'; +import { Logger } from '../utils/logger'; +import { browser } from 'protractor'; +import { NodeEntry, ResultSetPaging } from '@alfresco/js-api'; + +export class ModelingAPI extends Api { + public project: Project; + + constructor(ROOT: string = 'modeling-service') { + super(ROOT); + } + + async setUp(): Promise { + await this.login(); + this.project = new Project(this); + return this; + } + + async tearDown(): Promise { + await this.api.logout(); + } + + private async login(): Promise { + try { + await this.api.login( + browser.params.adminapp.modeler, + browser.params.adminapp.modeler_password + ); + } catch (error) { + Logger.error(error); + } + } + + async createProject(): Promise { + const project = await this.project.create(); + return project; + } + + async releaseProject(project: any): Promise { + const releasedProject = await this.project.release(project.entry.id); + return releasedProject; + } + + async getProjectRelease(projectId: string): Promise { + const releasedProject = await this.project.getProjectRelease(projectId); + return releasedProject; + } + + async importAndReleaseProject(absoluteFilePath: string): Promise { + const project = await this.project.import(absoluteFilePath); + const releasedProject = await this.project.release(project.entry.id); + return releasedProject; + } + + async getProjects(): Promise { + const projects = await this.project.searchProjects(); + return projects; + } + +} diff --git a/lib/testing/src/lib/core/structure/project.ts b/lib/testing/src/lib/core/structure/project.ts new file mode 100644 index 0000000000..4c93cae7ac --- /dev/null +++ b/lib/testing/src/lib/core/structure/project.ts @@ -0,0 +1,132 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { browser } from 'protractor'; +import { ModelingAPI } from './modeling-api'; +import { NodeEntry, ResultSetPaging } from '@alfresco/js-api'; +import { UtilApi } from './utilapi'; +import { E2eRequestApiHelper, E2eRequestApiHelperOptions } from './e2e-request-api.helper'; +import * as fs from 'fs'; +import { StringUtil } from '../utils/string.util'; +import { Logger } from '../utils/logger'; + +export class Project { + requestApiHelper: E2eRequestApiHelper; + endPoint = '/v1/projects/'; + namePrefix: string = browser.params.namePrefix; + + constructor(api: ModelingAPI) { + this.requestApiHelper = new E2eRequestApiHelper(api); + } + + async create(modelName: string = this.getRandomName()): Promise { + const project = await this.requestApiHelper + .post(this.endPoint, {bodyParam: { name: modelName }}); + + Logger.info( + `[Project] Project created with name: ${project.entry.name} and id: ${ + project.entry.id + }.` + ); + return project; + } + + async createAndWaitUntilAvailable(modelName: string = this.getRandomName()): Promise { + try { + const project = await this.create(modelName); + await this.retrySearchProject(project.entry.id); + return project; + } catch (error) { + Logger.error(`[Project] Create and wait for project to be available failed!`); + throw error; + } + } + + async get(projectId: string): Promise { + return this.requestApiHelper.get(`/v1/projects/${projectId}`); + } + + async delete(projectId: string): Promise { + await this.requestApiHelper.delete(`/v1/projects/${projectId}`); + Logger.info( + `[Project] Project '${projectId}' was deleted successfully.` + ); + } + + async release(projectId: string): Promise { + try { + const release = await this.requestApiHelper + .post(`/v1/projects/${projectId}/releases`); + Logger.info(`[Project] Project '${projectId}' was released.`); + return release; + } catch (error) { + Logger.error(`[Project] Release project failed!`); + throw error; + } + } + + async getProjectRelease(projectId: string): Promise { + try { + return await this.requestApiHelper + .get(`/v1/projects/${projectId}/releases`); + } catch (error) { + Logger.error(`[Project] Not able to fetch project release!`); + throw error; + } + } + + async import(projectFilePath: string): Promise { + const fileContent = await fs.createReadStream(projectFilePath); + const requestOptions: E2eRequestApiHelperOptions = { + formParams: { file: fileContent }, + contentTypes: ['multipart/form-data'] + }; + try { + const project = await this.requestApiHelper + .post(`/v1/projects/import`, requestOptions); + Logger.info(`[Project] Project imported with name '${project.entry.name}' and id '${project.entry.id}'.`); + return project; + } catch (error) { + Logger.error(`[Project] Import project failed!`); + throw error; + } + } + + async searchProjects(): Promise { + Logger.info(`[Project] Waiting created project to be ready for listing.`); + return this.requestApiHelper.get(this.endPoint, { + queryParams: { maxItems: 1000 } + }); + } + + private async retrySearchProject(modelId: string): Promise<{}> { + const predicate = (result: ResultSetPaging) => { + const foundModel = result.list.entries.find(model => { + return model.entry.id === modelId; + }); + + return !!foundModel; + }; + const apiCall = () => this.searchProjects(); + + return UtilApi.waitForApi(apiCall, predicate); + } + + private getRandomName(): string { + return this.namePrefix + StringUtil.generateRandomString(5).toLowerCase(); + } +} diff --git a/lib/testing/src/lib/core/structure/public-api.ts b/lib/testing/src/lib/core/structure/public-api.ts new file mode 100644 index 0000000000..2971e105b0 --- /dev/null +++ b/lib/testing/src/lib/core/structure/public-api.ts @@ -0,0 +1,21 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './api'; +export * from './utilapi'; +export * from './deployment-api'; +export * from './modeling-api'; diff --git a/lib/testing/src/lib/core/structure/utilapi.ts b/lib/testing/src/lib/core/structure/utilapi.ts new file mode 100644 index 0000000000..161451a525 --- /dev/null +++ b/lib/testing/src/lib/core/structure/utilapi.ts @@ -0,0 +1,44 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type ApiResultPredicate = (result: T) => boolean; +export type ApiCall = () => Promise; + +export class UtilApi { + static async waitForApi(apiCall: ApiCall, predicate: ApiResultPredicate) { + const apiCallWithPredicateChecking = async () => { + const apiCallResult = await apiCall(); + if (predicate(apiCallResult)) { + return Promise.resolve(apiCallResult); + } else { + return Promise.reject(apiCallResult); + } + }; + + return UtilApi.retryCall(apiCallWithPredicateChecking); + } + + static retryCall(fn: () => Promise, retry: number = 30, delay: number = 1000): Promise { + const pause = (duration: number) => new Promise((res) => setTimeout(res, duration)); + + const run = (retries: number) => { + return fn().catch((err) => (retries > 1 ? pause(delay).then(() => run(retries - 1)) : Promise.reject(err))); + }; + + return run(retry); + } +} diff --git a/nx.json b/nx.json index 1b037cd18a..008e3054e4 100644 --- a/nx.json +++ b/nx.json @@ -37,6 +37,9 @@ }, "testing": { "tags": [] + }, + "cli": { + "tags": [] } } } diff --git a/scripts/affected-libs.sh b/scripts/affected-libs.sh index da7a8fdb72..466c86d90b 100755 --- a/scripts/affected-libs.sh +++ b/scripts/affected-libs.sh @@ -84,7 +84,7 @@ done for i in "${libs[@]}" do if [ "$i" == "core" ] ; then - AFFECTED_LIBS="core$ content-services$ process-services$ process-services-cloud$ insights$ extensions$ testing$" + AFFECTED_LIBS="core$ content-services$ process-services$ process-services-cloud$ insights$ extensions$ testing$ cli$" echo "${AFFECTED_LIBS}" exit 0 fi @@ -122,7 +122,6 @@ do fi done - #process-services-cloud for i in "${libs[@]}" do @@ -131,4 +130,12 @@ do fi done +#cli +for i in "${libs[@]}" +do + if [ "$i" == "cli" ] ; then + AFFECTED_LIBS=$AFFECTED_LIBS" cli$" + fi +done + echo "${AFFECTED_LIBS}" diff --git a/scripts/smart-build.sh b/scripts/smart-build.sh index 3f5bd6e072..9e9dcca957 100755 --- a/scripts/smart-build.sh +++ b/scripts/smart-build.sh @@ -108,3 +108,11 @@ do ./scripts/build/build-testing.sh || exit 1; fi done + +#cli +for i in "${libs[@]}" +do + if [ "$i" == "cli$" ] ; then + ./scripts/build/build-cli.sh || exit 1; + fi +done diff --git a/scripts/travis/e2e/process-services-cloud-e2e.sh b/scripts/travis/e2e/process-services-cloud-e2e.sh index 63fad1a147..5c552e0062 100755 --- a/scripts/travis/e2e/process-services-cloud-e2e.sh +++ b/scripts/travis/e2e/process-services-cloud-e2e.sh @@ -15,7 +15,7 @@ AFFECTED_E2E="$(./scripts/git-util/affected-folder.sh -b $TRAVIS_BRANCH -f "e2e/ RUN_E2E=$(echo ./scripts/test-e2e-lib.sh -host http://localhost:4200 -proxy "$E2E_HOST_BPM" -u "$E2E_USERNAME" -p "$E2E_PASSWORD" -e "$E2E_EMAIL" -host_sso "$E2E_HOST_SSO" -host_bpm "$E2E_HOST_BPM" -host_identity "$E2E_HOST_IDENTITY" -identity_admin_email "$E2E_ADMIN_EMAIL_IDENTITY" -identity_admin_password "$E2E_ADMIN_PASSWORD_IDENTITY" -prefix $TRAVIS_BUILD_NUMBER -b --use-dist -m 3 ) -./node_modules/@alfresco/adf-cli/bin/adf-cli init-aae-env --host "$E2E_HOST_BPM" --oauth "$E2E_HOST_SSO" --username "$E2E_USERNAME" --password "$E2E_PASSWORD" --clientId 'activiti' || exit 1 +./node_modules/@alfresco/adf-cli/bin/adf-cli init-aae-env --host "$E2E_HOST_BPM" --oauth "$E2E_HOST_SSO" --modelerUsername "$E2E_MODELER_USERNAME" --modelerPassword "$E2E_MODELER_PASSWORD" --devopsUsername "$E2E_DEVOPS_USERNAME" --devopsPassword "$E2E_DEVOPS_PASSWORD" --clientId 'activiti' || exit 1 node ./scripts/check-env/check-cs-env.js --host "$E2E_HOST_BPM" -u "$E2E_USERNAME" -p "$E2E_PASSWORD" || exit 1 if [[ $AFFECTED_LIBS =~ "testing" || $AFFECTED_LIBS =~ "$CONTEXT_ENV" || $TRAVIS_PULL_REQUEST == "false" ]];