diff --git a/lib/cli/README.md b/lib/cli/README.md index aba09e0637..7bc4c640c6 100644 --- a/lib/cli/README.md +++ b/lib/cli/README.md @@ -114,3 +114,23 @@ Run command locally ```bash adf-cli update-commit-sha --pathProject "$(pwd)" --skipGnu ``` + +### Initialize activiti cloud env + +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" +``` + +If you want to add a new app the schema needs to be: +``` +TEST_APP: { +        name: 'testapp', +        file_location: 'https://github.com/Alfresco/alfresco-ng2-components/blob/branch/e2e/resources/testapp.zip?raw=true', +        security: [ +            {'role': 'APS_ADMIN', 'groups': ['myadmingroup'], 'users': ['myadminuser']}, +            {'role': 'APS_USER', 'groups': ['myusergroup'], 'users': ['myuser'] +        }] +    }, +``` diff --git a/lib/cli/scripts/init-aae-env.ts b/lib/cli/scripts/init-aae-env.ts new file mode 100644 index 0000000000..88ec3f0675 --- /dev/null +++ b/lib/cli/scripts/init-aae-env.ts @@ -0,0 +1,388 @@ +#!/usr/bin/env node + +/*! + * @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 { logging } from '@angular-devkit/core'; +/* tslint:disable */ +const alfrescoApi = require('@alfresco/js-api'); +/* tslint:enable */ +import request = require('request'); +import * as fs from 'fs'; + +export interface ConfigArgs { + username: string; + password: string; + host: string; + oauth: string; + identityHost: boolean; +} + +const ACTIVITI_CLOUD_APPS: any = { + CANDIDATE_BASE_APP: { + name: 'candidatebaseapp', + file_location: 'https://github.com/Alfresco/alfresco-ng2-components/blob/development/e2e/resources/activiti7/candidatebaseapp.zip?raw=true', + processes: { + candidateUserProcess: 'candidateUserProcess', + candidateGroupProcess: 'candidateGroupProcess', + anotherCandidateGroupProcess: 'anotherCandidateGroupProcess', + uploadFileProcess: 'uploadFileProcess' + }, + security: [ + {'role': 'APS_ADMIN', 'groups': [], 'users': ['superadminuser']}, + {'role': 'APS_USER', 'groups': ['hr', 'testgroup'], 'users': ['hruser'] + }] + }, + SIMPLE_APP: { + name: 'simpleapp', + file_location: 'https://github.com/Alfresco/alfresco-ng2-components/blob/development/e2e/resources/activiti7/simpleapp.zip?raw=true', + processes: { + processwithvariables: 'processwithvariables', + simpleProcess: 'simpleprocess', + dropdownrestprocess: 'dropdownrestprocess' + }, + forms: { + tabVisibilityFields: { + name: 'tabvisibilitywithfields', + id: 'form-26b01063-4fb0-455f-b3ba-90172e013678' + }, + tabVisibilityVars: { + name: 'tabvisibilitywithvars', + id: 'form-7bf363d2-83c9-4b00-853e-373d0d59963c' + } + }, + security: [ + {'role': 'APS_ADMIN', 'groups': [], 'users': ['superadminuser']}, + {'role': 'APS_USER', 'groups': ['hr', 'testgroup'], 'users': ['hruser'] + }] + }, + SUB_PROCESS_APP: { + name: 'subprocessapp', + file_location: 'https://github.com/Alfresco/alfresco-ng2-components/blob/development/e2e/resources/activiti7/subprocessapp.zip?raw=true', + security: [ + {'role': 'APS_ADMIN', 'groups': [], 'users': ['superadminuser']}, + {'role': 'APS_USER', 'groups': ['hr', 'testgroup'], 'users': ['hruser'] + }] + } +}; + +async function getDeployedApplicationsByStatus(args: ConfigArgs, apiService: any, status: string, logger: logging.Logger) { + 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); + } + +} + +function getAlfrescoJsApiInstance(args: ConfigArgs) { + const config = { + provider: 'BPM', + hostBpm: `${args.host}`, + authType: 'OAUTH', + oauth2: { + host: `${args.oauth}`, + clientId: 'activiti', + scope: 'openid', + secret: '', + implicitFlow: false, + silentLogin: false, + redirectUri: '/' + }, + identityHost: `${args.identityHost}` + }; + return new alfrescoApi.AlfrescoApiCompatibility(config); +} + +async function _login(args: ConfigArgs, alfrescoJsApi: any, logger: logging.Logger) { + logger.info(`Perform login...`); + await alfrescoJsApi.login(args.username, args.password); + return alfrescoJsApi; +} + +async function _deployMissingApps(args: ConfigArgs, logger: logging.Logger) { + const alfrescoJsApi = getAlfrescoJsApiInstance(args); + await _login(args, alfrescoJsApi, logger); + const deployedApps = await getDeployedApplicationsByStatus(args, alfrescoJsApi, '', logger); + const absentApps = findMissingApps(deployedApps); + + if (absentApps.length > 0) { + logger.warn(`Missing apps: ${JSON.stringify(absentApps)}`); + await checkIfAppIsReleased(args, alfrescoJsApi, absentApps, logger); + } else { + logger.warn(`All the apps are correctly deployed`); + } +} + +async function getAppProjects(args: ConfigArgs, apiService: any, logger: logging.Logger) { + 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 [], logger: logging.Logger) { + const projectList = await getAppProjects(args, apiService, logger); + 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) => { + return currentAbsentApp.name === currentApp.entry.name; + }); + let projectRelease: any; + if (app === undefined) { + logger.warn('Missing project: Create the project for ' + currentAbsentApp.name); + try { + const uploadedApp = await importProjectApp(args, apiService, currentAbsentApp, logger); + logger.warn('Project imported ' + currentAbsentApp.name); + if (uploadedApp) { + projectRelease = await releaseProject(args, apiService, uploadedApp, logger); + } + } catch (error) { + if (error.status !== 409) { + logger.info(`Not possible to upload the project ${app.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`); + noError = false; + } + } + } else { + TIME += 5000; + logger.info('Project ' + app.entry.name + ' found'); + + const projectReleaseList = await getReleaseAppProjectId(args, apiService, app.entry.id, logger); + + if (projectReleaseList.list.entries.length === 0) { + logger.warn('Project needs release'); + projectRelease = await releaseProject(args, apiService, app, logger); + logger.warn(`Project released: ${projectRelease.id}`); + } else { + logger.info('Project already has release'); + + // getting the latest project release + let currentReleaseVersion = -1; + projectReleaseList.list.entries.forEach((currentRelease: any) => { + if (currentRelease.entry.version > currentReleaseVersion) { + currentReleaseVersion = currentRelease.entry.version; + projectRelease = currentRelease; + } + }); + } + } + if (noError) { + await checkDescriptorExist(args, apiService, currentAbsentApp.name, logger); + await sleep(TIME, logger); + await deployApp(args, apiService, currentAbsentApp, projectRelease.entry.id, logger); + } + } +} + +async function deployApp(args: ConfigArgs, apiService: any, appInfo: any, projectReleaseId: string, logger: logging.Logger) { + 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 + }; + + 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, logger: logging.Logger) { + logger.info(`Check descriptor ${name} exist in the list `); + const descriptorList: [] = await getDescriptorList(args, apiService, logger); + descriptorList.forEach( async(descriptor: any) => { + if (descriptor.entry.name === name) { + if (descriptor.entry.deployed === false) { + await deleteDescriptor(args, apiService, descriptor.entry.name, logger); + } + } + }); + return false; +} + +async function getDescriptorList(args: ConfigArgs, apiService: any, logger: logging.Logger) { + 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: logging.Logger) { + 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, logger: logging.Logger) { + 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, logger: logging.Logger) { + 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, logger: logging.Logger) { + await getFileFromRemote(app.file_location, app.name, logger); + + 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); + deleteLocalFile(`${app.name}`, logger); + return result; +} + +function findMissingApps(deployedApps: any []) { + const absentApps: any [] = []; + Object.keys(ACTIVITI_CLOUD_APPS).forEach((key) => { + const isPresent = deployedApps.find((currentApp: any) => { + return ACTIVITI_CLOUD_APPS[key].name === currentApp.entry.name; + }); + + if (!isPresent) { + absentApps.push(ACTIVITI_CLOUD_APPS[key]); + } + }); + return absentApps; +} + +async function getFileFromRemote(url: string, name: string, logger: logging.Logger) { + return new Promise((resolve, reject) => { + request(url) + .pipe(fs.createWriteStream(`${name}.zip`)) + .on('finish', () => { + logger.info(`The file is finished downloading.`); + resolve(); + }) + .on('error', (error: any) => { + reject(error); + }); + }); +} + +async function deleteLocalFile(name: string, logger: logging.Logger) { + logger.info(`Deleting local file ${name}.zip`); + fs.unlinkSync(`${name}.zip`); +} + +async function sleep(time: number, logger: logging.Logger) { + logger.info(`Waiting for ${time} sec...`); + await new Promise(done => setTimeout(done, time)); + logger.info(`Done...`); + return; +} + +export default async function (args: ConfigArgs, logger: logging.Logger) { + await _deployMissingApps(args, logger); +} diff --git a/lib/cli/tsconfig.json b/lib/cli/tsconfig.json index 645ceb35d9..82588ee43a 100644 --- a/lib/cli/tsconfig.json +++ b/lib/cli/tsconfig.json @@ -30,6 +30,8 @@ "exclude": [ "dist/**/*", "./build.ts", - "node_modules/**/*" + "node_modules/**/*", + "**/node_modules/**/*" + ] } diff --git a/package.json b/package.json index f64d9dafbb..7648794bc0 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "@mat-datetimepicker/core": "^2.0.1", "@mat-datetimepicker/moment": "^2.0.1", "@ngx-translate/core": "^11.0.0", + "@types/request": "^2.48.3", "adf-monaco-extension": "0.0.8", "adf-tslint-rules": "0.0.6", "chart.js": "2.5.0",