mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
[ACS-11927] init acs aps scripts update terraform (#11976)
* [ACS-11927] updated init-acs and aps scripts * [ACS-11927] copilot review fixes 1 * [ACS-11927] handled error catches * [ACS-11927] last error handled * [ACS-11927] caught errors stringyfied * [ACS-11927] scripts made more robust * [ACS-11927] copilot review fixes 2 * [ACS-11927] eslint fixes 1 * [ACS-11927] eslint fixes 2 * ci:force * ci:force * [ACS-11927] copilot review fixes 2
This commit is contained in:
+262
-133
@@ -28,12 +28,16 @@ interface InitAcsEnvArgs {
|
|||||||
username?: string;
|
username?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_RETRY = 10;
|
const MAX_RETRY = 10;
|
||||||
let counter = 0;
|
const RETRY_DELAY_MS = 6000;
|
||||||
const TIMEOUT = 6000;
|
|
||||||
const ACS_DEFAULT = require('./resources').ACS_DEFAULT;
|
const ACS_DEFAULT = require('./resources').ACS_DEFAULT;
|
||||||
|
|
||||||
let alfrescoJsApi: AlfrescoApi;
|
let alfrescoJsApi: AlfrescoApi;
|
||||||
|
let nodesApi: NodesApi;
|
||||||
|
let uploadApi: UploadApi;
|
||||||
|
let sharedlinksApi: SharedlinksApi;
|
||||||
|
let favoritesApi: FavoritesApi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init ACS environment command
|
* Init ACS environment command
|
||||||
@@ -46,7 +50,6 @@ Usage: init-acs-env [options]
|
|||||||
Initialize ACS environment
|
Initialize ACS environment
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-v, --version Output the version number
|
|
||||||
--host <host> Remote environment host
|
--host <host> Remote environment host
|
||||||
--clientId <id> SSO client (default: "alfresco")
|
--clientId <id> SSO client (default: "alfresco")
|
||||||
-p, --password <pass> Password
|
-p, --password <pass> Password
|
||||||
@@ -56,11 +59,6 @@ Options:
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv.includes('-v') || argv.includes('--version')) {
|
|
||||||
console.log('0.1.0');
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { values } = parseArgs({
|
const { values } = parseArgs({
|
||||||
args: argv.slice(2),
|
args: argv.slice(2),
|
||||||
options: {
|
options: {
|
||||||
@@ -92,160 +90,293 @@ Options:
|
|||||||
|
|
||||||
await checkEnv(opts);
|
await checkEnv(opts);
|
||||||
|
|
||||||
logger.info(`***** Step initialize ACS *****`);
|
logger.info('***** Step initialize ACS *****');
|
||||||
await initializeDefaultFiles();
|
await initializeDefaultFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup default files
|
* Initialize default files. Creates the e2e folder and ensures each file
|
||||||
|
* exists with its required state (locked, shared, favorite).
|
||||||
|
* Idempotent: only creates/modifies what is missing.
|
||||||
*/
|
*/
|
||||||
async function initializeDefaultFiles() {
|
async function initializeDefaultFiles() {
|
||||||
const e2eFolder = ACS_DEFAULT.e2eFolder;
|
const e2eFolderName: string = ACS_DEFAULT.e2eFolder.name;
|
||||||
const parentFolder = await createFolder(e2eFolder.name, '-my-');
|
|
||||||
const parentFolderId = parentFolder.entry.id;
|
|
||||||
|
|
||||||
for (let j = 0; j < ACS_DEFAULT.files.length; j++) {
|
let parentFolder: NodeEntry;
|
||||||
const fileInfo = ACS_DEFAULT.files[j];
|
|
||||||
switch (fileInfo.action) {
|
|
||||||
case 'UPLOAD': {
|
|
||||||
await uploadFile(fileInfo.name, parentFolderId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'LOCK': {
|
|
||||||
const fileToLock = await uploadFile(fileInfo.name, parentFolderId);
|
|
||||||
await lockFile(fileToLock.entry.id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'SHARE': {
|
|
||||||
const fileToShare = await uploadFile(fileInfo.name, parentFolderId);
|
|
||||||
await shareFile(fileToShare.entry.id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'FAVORITE': {
|
|
||||||
const fileToFav = await uploadFile(fileInfo.name, parentFolderId);
|
|
||||||
await favoriteFile(fileToFav.entry.id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
logger.error('No action found for file ', fileInfo.name, parentFolderId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create folder
|
|
||||||
*
|
|
||||||
* @param folderName folder name
|
|
||||||
* @param parentId parent folder id
|
|
||||||
*/
|
|
||||||
async function createFolder(folderName: string, parentId: string) {
|
|
||||||
let createdFolder: NodeEntry;
|
|
||||||
const body = {
|
|
||||||
name: folderName,
|
|
||||||
nodeType: 'cm:folder'
|
|
||||||
};
|
|
||||||
try {
|
try {
|
||||||
createdFolder = await new NodesApi(alfrescoJsApi).createNode(parentId, body, { overwrite: true });
|
parentFolder = await withRetry(() => ensureFolder(e2eFolderName, '-my-'), `ensure folder ${e2eFolderName}`);
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.warn(`Skipping file initialization: test-data folder could not be created: ${formatError(error)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(`Folder ${folderName} was created`);
|
const parentFolderId = getEntryId(parentFolder, `folder ${e2eFolderName}`);
|
||||||
} catch (err) {
|
|
||||||
if (err.status === 409) {
|
for (const fileInfo of ACS_DEFAULT.files) {
|
||||||
const relativePath = `/${folderName}`;
|
await withRetry(() => processFile(fileInfo, parentFolderId), `initialize ${fileInfo.name}`);
|
||||||
createdFolder = await new NodesApi(alfrescoJsApi).getNode('-my-', { relativePath });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return createdFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload file
|
* Process a single file: upload if missing, then apply its action (lock/share/favorite).
|
||||||
*
|
* @param fileInfo file descriptor from ACS_DEFAULT
|
||||||
* @param fileName file name
|
* @param fileInfo.name file name
|
||||||
* @param fileDestination destination path
|
* @param fileInfo.action action to apply (LOCK, SHARE, FAVORITE)
|
||||||
|
* @param parentFolderId parent folder node id
|
||||||
*/
|
*/
|
||||||
async function uploadFile(fileName: string, fileDestination: string): Promise<NodeEntry> {
|
async function processFile(fileInfo: { name: string; action: string }, parentFolderId: string) {
|
||||||
|
const existingNode = await findNodeByRelativePath(parentFolderId, fileInfo.name);
|
||||||
|
|
||||||
|
let nodeId: string;
|
||||||
|
if (existingNode?.entry?.id) {
|
||||||
|
logger.info(`File ${fileInfo.name} already exists, verifying required state.`);
|
||||||
|
nodeId = existingNode.entry.id;
|
||||||
|
} else {
|
||||||
|
const createdNode = await uploadFile(fileInfo.name, parentFolderId);
|
||||||
|
nodeId = getEntryId(createdNode, `file ${fileInfo.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (fileInfo.action) {
|
||||||
|
case 'LOCK':
|
||||||
|
await ensureLocked(nodeId, fileInfo.name, existingNode?.entry?.isLocked);
|
||||||
|
break;
|
||||||
|
case 'SHARE':
|
||||||
|
await ensureShared(nodeId, fileInfo.name);
|
||||||
|
break;
|
||||||
|
case 'FAVORITE':
|
||||||
|
await ensureFavorite(nodeId, fileInfo.name);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure a folder exists under the given parent. Creates it if missing.
|
||||||
|
* Handles 409 conflict (race condition) by fetching the existing folder.
|
||||||
|
* @param folderName folder name
|
||||||
|
* @param parentId parent node id
|
||||||
|
* @returns the folder node entry
|
||||||
|
*/
|
||||||
|
async function ensureFolder(folderName: string, parentId: string): Promise<NodeEntry> {
|
||||||
|
const existingFolder = await findNodeByRelativePath(parentId, folderName);
|
||||||
|
|
||||||
|
if (existingFolder?.entry?.id) {
|
||||||
|
if (!existingFolder.entry.isFolder) {
|
||||||
|
throw new Error(
|
||||||
|
`Cannot use ${folderName} as test-data folder: a non-folder node with that name already exists (nodeType: ${existingFolder.entry.nodeType}, id: ${existingFolder.entry.id}).`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
logger.info(`Folder ${folderName} already exists.`);
|
||||||
|
return existingFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const createdFolder = await nodesApi.createNode(parentId, { name: folderName, nodeType: 'cm:folder' }, { overwrite: true });
|
||||||
|
logger.info(`Folder ${folderName} was created`);
|
||||||
|
return createdFolder;
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error?.status === 409) {
|
||||||
|
const conflictingFolder = await findNodeByRelativePath(parentId, folderName);
|
||||||
|
|
||||||
|
if (conflictingFolder?.entry?.id) {
|
||||||
|
if (!conflictingFolder.entry.isFolder) {
|
||||||
|
throw new Error(
|
||||||
|
`Cannot use ${folderName} as test-data folder: a non-folder node with that name already exists (nodeType: ${conflictingFolder.entry.nodeType}, id: ${conflictingFolder.entry.id}).`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
logger.info(`Folder ${folderName} already exists.`);
|
||||||
|
return conflictingFolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to ensure folder ${folderName}: ${formatError(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a node by relative path under a parent. Returns null if not found (404).
|
||||||
|
* @param parentId parent node id
|
||||||
|
* @param fileName relative path / file name
|
||||||
|
* @returns the node entry or null if not found
|
||||||
|
*/
|
||||||
|
async function findNodeByRelativePath(parentId: string, fileName: string): Promise<NodeEntry | null> {
|
||||||
|
try {
|
||||||
|
return await nodesApi.getNode(parentId, { relativePath: `/${fileName}`, include: ['isLocked'] });
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error?.status === 404) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to fetch ${fileName}: ${formatError(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload a file to the given destination folder.
|
||||||
|
* @param fileName file name
|
||||||
|
* @param destinationId destination folder node id
|
||||||
|
* @returns the uploaded node entry
|
||||||
|
*/
|
||||||
|
async function uploadFile(fileName: string, destinationId: string): Promise<NodeEntry> {
|
||||||
const filePath = `../resources/content/${fileName}`;
|
const filePath = `../resources/content/${fileName}`;
|
||||||
const file = createReadStream(path.join(__dirname, filePath));
|
const file = createReadStream(path.join(__dirname, filePath));
|
||||||
let uploadedFile: NodeEntry;
|
|
||||||
try {
|
try {
|
||||||
uploadedFile = await new UploadApi(alfrescoJsApi).uploadFile(file, '', fileDestination, null, {
|
const uploadedFile = await uploadApi.uploadFile(file, '', destinationId, null, {
|
||||||
name: fileName,
|
name: fileName,
|
||||||
nodeType: 'cm:content',
|
nodeType: 'cm:content',
|
||||||
renditions: 'doclib',
|
renditions: 'doclib',
|
||||||
overwrite: true
|
overwrite: true
|
||||||
});
|
});
|
||||||
logger.info(`File ${fileName} was uploaded`);
|
logger.info(`File ${fileName} was uploaded`);
|
||||||
} catch (err) {
|
|
||||||
logger.error(`Failed to upload file with error: `, err);
|
|
||||||
}
|
|
||||||
return uploadedFile;
|
return uploadedFile;
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(`Failed to upload ${fileName}: ${formatError(error)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lock file node
|
* Ensure a file is locked. Skips if already locked.
|
||||||
*
|
|
||||||
* @param nodeId node id
|
* @param nodeId node id
|
||||||
|
* @param fileName file name (for logging)
|
||||||
|
* @param isAlreadyLocked whether the node is already locked
|
||||||
*/
|
*/
|
||||||
async function lockFile(nodeId: string): Promise<NodeEntry> {
|
async function ensureLocked(nodeId: string, fileName: string, isAlreadyLocked = false) {
|
||||||
const data = {
|
if (isAlreadyLocked) {
|
||||||
type: 'ALLOW_OWNER_CHANGES'
|
logger.info(`File ${fileName} is already locked.`);
|
||||||
};
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await new NodesApi(alfrescoJsApi).lockNode(nodeId, data);
|
await nodesApi.lockNode(nodeId, { type: 'ALLOW_OWNER_CHANGES' });
|
||||||
logger.info('File was locked');
|
logger.info(`File ${fileName} was locked`);
|
||||||
return result;
|
} catch (error: any) {
|
||||||
} catch (error) {
|
throw new Error(`Failed to lock ${fileName}: ${formatError(error)}`);
|
||||||
logger.error('Failed to lock file with error: ', error);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share file node
|
* Ensure a file is shared. Handles 409 (already shared) gracefully.
|
||||||
*
|
|
||||||
* @param nodeId node id
|
* @param nodeId node id
|
||||||
|
* @param fileName file name (for logging)
|
||||||
*/
|
*/
|
||||||
async function shareFile(nodeId: string) {
|
async function ensureShared(nodeId: string, fileName: string) {
|
||||||
const data = {
|
|
||||||
nodeId
|
|
||||||
};
|
|
||||||
try {
|
try {
|
||||||
await new SharedlinksApi(alfrescoJsApi).createSharedLink(data);
|
await sharedlinksApi.createSharedLink({ nodeId });
|
||||||
logger.info('File was shared');
|
logger.info(`File ${fileName} was shared`);
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error('Failed to share file with error: ', error);
|
if (error?.status === 409) {
|
||||||
|
logger.info(`File ${fileName} is already shared.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to share ${fileName}: ${formatError(error)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Favorite file node
|
* Ensure a file is favorite. Handles 409 (already favorite) gracefully.
|
||||||
*
|
|
||||||
* @param nodeId node id
|
* @param nodeId node id
|
||||||
|
* @param fileName file name (for logging)
|
||||||
*/
|
*/
|
||||||
async function favoriteFile(nodeId: string) {
|
async function ensureFavorite(nodeId: string, fileName: string) {
|
||||||
const data = {
|
try {
|
||||||
|
await favoritesApi.createFavorite('-me-', {
|
||||||
target: {
|
target: {
|
||||||
['file']: {
|
file: {
|
||||||
guid: nodeId
|
guid: nodeId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
try {
|
logger.info(`File ${fileName} was added to favorites`);
|
||||||
await new FavoritesApi(alfrescoJsApi).createFavorite('-me-', data);
|
} catch (error: any) {
|
||||||
logger.info('File was add to favorites');
|
if (error?.status === 409) {
|
||||||
} catch (error) {
|
logger.info(`File ${fileName} is already a favorite.`);
|
||||||
logger.error('Failed to add the file to favorites with error: ', error);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to favorite ${fileName}: ${formatError(error)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check environment state
|
* Extract entry id from a node response. Throws if missing.
|
||||||
*
|
* @param nodeEntry node entry response
|
||||||
* @param opts command options
|
* @param label label for error message
|
||||||
|
* @returns the node id
|
||||||
*/
|
*/
|
||||||
async function checkEnv(opts: InitAcsEnvArgs) {
|
function getEntryId(nodeEntry: NodeEntry, label: string): string {
|
||||||
|
const nodeId = nodeEntry?.entry?.id;
|
||||||
|
|
||||||
|
if (nodeId) {
|
||||||
|
return nodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Missing ACS response entry for ${label}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format an error for logging.
|
||||||
|
* @param error error object
|
||||||
|
* @returns formatted error string
|
||||||
|
*/
|
||||||
|
function formatError(error: any): string {
|
||||||
|
if (!error) {
|
||||||
|
return 'Unknown error';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return error?.message || error?.stack || JSON.stringify(error);
|
||||||
|
} catch {
|
||||||
|
return 'Unknown error (unable to serialize)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retry wrapper for transient failures.
|
||||||
|
* @param fn async function to execute
|
||||||
|
* @param label label for logging
|
||||||
|
* @param maxAttempts maximum retry attempts
|
||||||
|
* @returns the result of the function
|
||||||
|
*/
|
||||||
|
async function withRetry<T>(fn: () => Promise<T>, label: string, maxAttempts = 3): Promise<T> {
|
||||||
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||||
|
try {
|
||||||
|
return await fn();
|
||||||
|
} catch (error: any) {
|
||||||
|
if (attempt === maxAttempts) {
|
||||||
|
logger.error(`${label}: failed after ${maxAttempts} attempts: ${formatError(error)}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn(`${label}: attempt ${attempt} failed, retrying in ${RETRY_DELAY_MS / 1000}s: ${formatError(error)}`);
|
||||||
|
await wait(RETRY_DELAY_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`${label}: exhausted all ${maxAttempts} attempts`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async delay.
|
||||||
|
* @param ms milliseconds to wait
|
||||||
|
* @returns a promise that resolves after the delay
|
||||||
|
*/
|
||||||
|
function wait(ms: number): Promise<void> {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check environment state and authenticate. Retries on transient failures.
|
||||||
|
* @param opts command options
|
||||||
|
* @param attempt current attempt number
|
||||||
|
*/
|
||||||
|
async function checkEnv(opts: InitAcsEnvArgs, attempt = 1) {
|
||||||
try {
|
try {
|
||||||
alfrescoJsApi = new AlfrescoApi({
|
alfrescoJsApi = new AlfrescoApi({
|
||||||
provider: 'ALL',
|
provider: 'ALL',
|
||||||
@@ -260,31 +391,29 @@ async function checkEnv(opts: InitAcsEnvArgs) {
|
|||||||
},
|
},
|
||||||
contextRoot: 'alfresco'
|
contextRoot: 'alfresco'
|
||||||
});
|
});
|
||||||
|
|
||||||
await alfrescoJsApi.login(opts.username, opts.password);
|
await alfrescoJsApi.login(opts.username, opts.password);
|
||||||
} catch (e) {
|
|
||||||
if (e.error.code === 'ETIMEDOUT') {
|
nodesApi = new NodesApi(alfrescoJsApi);
|
||||||
logger.error('The env is not reachable. Terminating');
|
uploadApi = new UploadApi(alfrescoJsApi);
|
||||||
exit(1);
|
sharedlinksApi = new SharedlinksApi(alfrescoJsApi);
|
||||||
}
|
favoritesApi = new FavoritesApi(alfrescoJsApi);
|
||||||
logger.error('Login error environment down or inaccessible');
|
} catch (error: any) {
|
||||||
counter++;
|
const errorCode = error?.error?.code;
|
||||||
if (MAX_RETRY === counter) {
|
|
||||||
logger.error('Give up');
|
if (errorCode === 'ETIMEDOUT' || error?.status === 504) {
|
||||||
exit(1);
|
logger.warn('Login attempt timed out or received a gateway error, environment may still be starting up.');
|
||||||
} else {
|
} else {
|
||||||
logger.error(`Retry in 1 minute attempt N ${counter}`);
|
logger.error('Login error, environment down or inaccessible.');
|
||||||
sleep(TIMEOUT);
|
|
||||||
await checkEnv(opts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (attempt >= MAX_RETRY) {
|
||||||
* Perform a delay
|
logger.error('Give up');
|
||||||
*
|
exit(1);
|
||||||
* @param delay timeout in milliseconds
|
}
|
||||||
*/
|
|
||||||
function sleep(delay: number) {
|
logger.warn(`Retry in ${RETRY_DELAY_MS / 1000} seconds, attempt ${attempt}`);
|
||||||
const start = new Date().getTime();
|
await wait(RETRY_DELAY_MS);
|
||||||
while (new Date().getTime() < start + delay) {}
|
await checkEnv(opts, attempt + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+143
-84
@@ -40,8 +40,7 @@ interface InitApsEnvArgs {
|
|||||||
license?: string;
|
license?: string;
|
||||||
}
|
}
|
||||||
const MAX_RETRY = 10;
|
const MAX_RETRY = 10;
|
||||||
let counter = 0;
|
const RETRY_DELAY_MS = 6000;
|
||||||
const TIMEOUT = 6000;
|
|
||||||
const TENANT_DEFAULT_ID = 1;
|
const TENANT_DEFAULT_ID = 1;
|
||||||
const TENANT_DEFAULT_NAME = 'default';
|
const TENANT_DEFAULT_NAME = 'default';
|
||||||
const CONTENT_DEFAULT_NAME = 'adw-content';
|
const CONTENT_DEFAULT_NAME = 'adw-content';
|
||||||
@@ -60,7 +59,6 @@ Usage: init-aps-env [options]
|
|||||||
Initialize APS environment
|
Initialize APS environment
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-v, --version Output the version number
|
|
||||||
--host <host> Remote environment host
|
--host <host> Remote environment host
|
||||||
--clientId <id> SSO client (default: "alfresco")
|
--clientId <id> SSO client (default: "alfresco")
|
||||||
-p, --password <pass> Password
|
-p, --password <pass> Password
|
||||||
@@ -71,11 +69,6 @@ Options:
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv.includes('-v') || argv.includes('--version')) {
|
|
||||||
console.log('0.1.0');
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { values } = parseArgs({
|
const { values } = parseArgs({
|
||||||
args: argv.slice(2),
|
args: argv.slice(2),
|
||||||
options: {
|
options: {
|
||||||
@@ -111,6 +104,14 @@ Options:
|
|||||||
|
|
||||||
await checkEnv(opts);
|
await checkEnv(opts);
|
||||||
|
|
||||||
|
const e2eAppReady = await ensureE2eApplicationDeployed();
|
||||||
|
if (e2eAppReady) {
|
||||||
|
logger.info(`APS environment already initialized (terraform). Skipping.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await alfrescoJsApi.login(opts.username, opts.password);
|
||||||
|
|
||||||
logger.info(`***** Step 1 - Check License *****`);
|
logger.info(`***** Step 1 - Check License *****`);
|
||||||
|
|
||||||
let licenceUploaded = false;
|
let licenceUploaded = false;
|
||||||
@@ -146,25 +147,24 @@ Options:
|
|||||||
logger.info(`***** Step 4 - Create users *****`);
|
logger.info(`***** Step 4 - Create users *****`);
|
||||||
const users = await getDefaultApsUsersFromRealm(opts);
|
const users = await getDefaultApsUsersFromRealm(opts);
|
||||||
if (tenantId && users && users.length > 0) {
|
if (tenantId && users && users.length > 0) {
|
||||||
for (let i = 0; i < users.length; i++) {
|
for (const user of users) {
|
||||||
await createUsers(tenantId, users[i]);
|
await createUsers(tenantId, user);
|
||||||
}
|
}
|
||||||
for (let i = 0; i < users.length; i++) {
|
for (const user of users) {
|
||||||
logger.info('Impersonate user: ' + users[i].username);
|
logger.info('Impersonate user: ' + user.username);
|
||||||
await alfrescoJsApi.login(users[i].username, 'password');
|
await alfrescoJsApi.login(user.username, 'password');
|
||||||
await authorizeUserToContentRepo(opts, users[i]);
|
await authorizeUserToContentRepo(opts, user);
|
||||||
|
|
||||||
const defaultUser = 'hruser';
|
if (user.username.includes('hruser')) {
|
||||||
if (users[i].username.includes(defaultUser)) {
|
logger.info(`***** Step initialize APS apps for user hruser *****`);
|
||||||
logger.info(`***** Step initialize APS apps for user ${defaultUser} *****`);
|
|
||||||
await initializeDefaultApps();
|
await initializeDefaultApps();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.info('Something went wrong. Was not able to create the users');
|
logger.info('Something went wrong. Was not able to create the users');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error(`Aps something went wrong. Tenant id ${tenantId}`, error);
|
logger.error(`Aps something went wrong. Tenant id ${tenantId}: ${formatError(error)}`);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -173,15 +173,56 @@ Options:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure e2e-Application is deployed for hruser.
|
||||||
|
* If the app is already present, returns true (skip full init).
|
||||||
|
* If hruser can log in but the app is missing, imports, publishes and deploys it.
|
||||||
|
* Returns false only if hruser cannot log in or deployment fails.
|
||||||
|
* @returns `true` if app is deployed, otherwise `false`
|
||||||
|
*/
|
||||||
|
async function ensureE2eApplicationDeployed(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await alfrescoJsApi.login('hruser', 'password');
|
||||||
|
const runtimeAppDefinitionsApi = new RuntimeAppDefinitionsApi(alfrescoJsApi);
|
||||||
|
const availableApps = await runtimeAppDefinitionsApi.getAppDefinitions();
|
||||||
|
const e2eApp = availableApps.data?.filter((app) => app.name?.includes('e2e-Application'));
|
||||||
|
if (e2eApp && e2eApp.length > 0) {
|
||||||
|
logger.info(`e2e-Application is already deployed for hruser`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logger.info(`e2e-Application not found for hruser - uploading and deploying it now`);
|
||||||
|
const appDefinition = await importPublishApp('e2e-Application');
|
||||||
|
if (appDefinition?.appDefinition?.id) {
|
||||||
|
await deployApp(appDefinition.appDefinition.id);
|
||||||
|
const verifyApps = await runtimeAppDefinitionsApi.getAppDefinitions();
|
||||||
|
const deployed = verifyApps.data?.some((app) => app.name?.includes('e2e-Application'));
|
||||||
|
if (deployed) {
|
||||||
|
logger.info(`e2e-Application successfully deployed for hruser`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logger.info(`e2e-Application deployment could not be verified - proceeding with full initialization`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logger.info(`Failed to import/deploy e2e-Application for hruser - proceeding with full initialization`);
|
||||||
|
return false;
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.info(`Unable to verify APS state for hruser - proceeding with initialization: ${formatError(error)}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialise default applications
|
* Initialise default applications
|
||||||
*/
|
*/
|
||||||
async function initializeDefaultApps() {
|
async function initializeDefaultApps() {
|
||||||
for (let x = 0; x < ACTIVITI_APPS.apps.length; x++) {
|
for (const appInfo of ACTIVITI_APPS.apps) {
|
||||||
const appInfo = ACTIVITI_APPS.apps[x];
|
|
||||||
const isDeployed = await isDefaultAppDeployed(appInfo.name);
|
const isDeployed = await isDefaultAppDeployed(appInfo.name);
|
||||||
if (isDeployed !== undefined && !isDeployed) {
|
if (!isDeployed) {
|
||||||
const appDefinition = await importPublishApp(`${appInfo.name}`);
|
const appDefinition = await importPublishApp(appInfo.name);
|
||||||
|
if (!appDefinition?.appDefinition?.id) {
|
||||||
|
logger.error(`Failed to import app ${appInfo.name}, skipping deployment.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
await deployApp(appDefinition.appDefinition.id);
|
await deployApp(appDefinition.appDefinition.id);
|
||||||
} else {
|
} else {
|
||||||
logger.info(`***** App ${appInfo.name} already deployed *****`);
|
logger.info(`***** App ${appInfo.name} already deployed *****`);
|
||||||
@@ -190,11 +231,11 @@ async function initializeDefaultApps() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check environment
|
* Check environment state and authenticate. Retries on transient failures.
|
||||||
*
|
|
||||||
* @param opts command options
|
* @param opts command options
|
||||||
|
* @param attempt current attempt number
|
||||||
*/
|
*/
|
||||||
async function checkEnv(opts: InitApsEnvArgs) {
|
async function checkEnv(opts: InitApsEnvArgs, attempt = 1) {
|
||||||
try {
|
try {
|
||||||
alfrescoJsApi = new AlfrescoApi({
|
alfrescoJsApi = new AlfrescoApi({
|
||||||
provider: 'ALL',
|
provider: 'ALL',
|
||||||
@@ -210,27 +251,28 @@ async function checkEnv(opts: InitApsEnvArgs) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
await alfrescoJsApi.login(opts.username, opts.password);
|
await alfrescoJsApi.login(opts.username, opts.password);
|
||||||
} catch (e) {
|
} catch (error: any) {
|
||||||
if (e.error.code === 'ETIMEDOUT') {
|
const errorCode = error?.error?.code;
|
||||||
logger.error('The env is not reachable. Terminating');
|
|
||||||
exit(1);
|
if (errorCode === 'ETIMEDOUT' || error?.status === 504) {
|
||||||
|
logger.warn('Login attempt timed out or received a gateway error, environment may still be starting up.');
|
||||||
|
} else {
|
||||||
|
logger.error('Login error, environment down or inaccessible.');
|
||||||
}
|
}
|
||||||
logger.info('Login error environment down or inaccessible');
|
|
||||||
counter++;
|
if (attempt >= MAX_RETRY) {
|
||||||
if (MAX_RETRY === counter) {
|
|
||||||
logger.error('Give up');
|
logger.error('Give up');
|
||||||
exit(1);
|
exit(1);
|
||||||
} else {
|
|
||||||
logger.error(`Retry in 1 minute attempt N ${counter}`);
|
|
||||||
sleep(TIMEOUT);
|
|
||||||
await checkEnv(opts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.warn(`Retry in ${RETRY_DELAY_MS / 1000} seconds, attempt ${attempt}`);
|
||||||
|
await wait(RETRY_DELAY_MS);
|
||||||
|
await checkEnv(opts, attempt + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the default tenant is present
|
* Check if the default tenant is present
|
||||||
*
|
|
||||||
* @param tenantId tenant id
|
* @param tenantId tenant id
|
||||||
* @param tenantName tenant name
|
* @param tenantName tenant name
|
||||||
* @returns `true` if tenant is found, otherwise `false`
|
* @returns `true` if tenant is found, otherwise `false`
|
||||||
@@ -241,7 +283,7 @@ async function hasDefaultTenant(tenantId: number, tenantName: string): Promise<b
|
|||||||
try {
|
try {
|
||||||
const adminTenantsApi = new AdminTenantsApi(alfrescoJsApi);
|
const adminTenantsApi = new AdminTenantsApi(alfrescoJsApi);
|
||||||
tenant = await adminTenantsApi.getTenant(tenantId);
|
tenant = await adminTenantsApi.getTenant(tenantId);
|
||||||
} catch (error) {
|
} catch {
|
||||||
logger.info(`Aps: does not have tenant with id: ${tenantId}`);
|
logger.info(`Aps: does not have tenant with id: ${tenantId}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -258,8 +300,8 @@ async function hasDefaultTenant(tenantId: number, tenantName: string): Promise<b
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create default tenant
|
* Create default tenant
|
||||||
*
|
|
||||||
* @param tenantName tenant name
|
* @param tenantName tenant name
|
||||||
|
* @returns the tenant id or null
|
||||||
*/
|
*/
|
||||||
async function createDefaultTenant(tenantName: string) {
|
async function createDefaultTenant(tenantName: string) {
|
||||||
const tenantPost = {
|
const tenantPost = {
|
||||||
@@ -273,17 +315,17 @@ async function createDefaultTenant(tenantName: string) {
|
|||||||
const tenant = await adminTenantsApi.createTenant(tenantPost);
|
const tenant = await adminTenantsApi.createTenant(tenantPost);
|
||||||
logger.info(`APS: Tenant ${tenantName} created with id: ${tenant.id}`);
|
logger.info(`APS: Tenant ${tenantName} created with id: ${tenant.id}`);
|
||||||
return tenant.id;
|
return tenant.id;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.info(`APS: not able to create the default tenant: ${JSON.parse(error.message)}`);
|
logger.info(`APS: not able to create the default tenant: ${formatError(error)}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create users
|
* Create users
|
||||||
*
|
|
||||||
* @param tenantId tenant id
|
* @param tenantId tenant id
|
||||||
* @param user user object
|
* @param user user object
|
||||||
|
* @returns the created user
|
||||||
*/
|
*/
|
||||||
async function createUsers(tenantId: number, user: any) {
|
async function createUsers(tenantId: number, user: any) {
|
||||||
logger.info(`Create user ${user.email} on tenant: ${tenantId}`);
|
logger.info(`Create user ${user.email} on tenant: ${tenantId}`);
|
||||||
@@ -303,15 +345,15 @@ async function createUsers(tenantId: number, user: any) {
|
|||||||
const userInfo = await adminUsersApi.createNewUser(userJson);
|
const userInfo = await adminUsersApi.createNewUser(userJson);
|
||||||
logger.info(`APS: User ${userInfo.email} created with id: ${userInfo.id}`);
|
logger.info(`APS: User ${userInfo.email} created with id: ${userInfo.id}`);
|
||||||
return user;
|
return user;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.info(`APS: not able to create the default user: ${error.message}`);
|
logger.info(`APS: not able to create the default user: ${formatError(error)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update Activiti license
|
* Update Activiti license
|
||||||
*
|
|
||||||
* @param opts command options
|
* @param opts command options
|
||||||
|
* @returns `true` if license uploaded successfully, otherwise `false`
|
||||||
*/
|
*/
|
||||||
async function updateLicense(opts: InitApsEnvArgs) {
|
async function updateLicense(opts: InitApsEnvArgs) {
|
||||||
const fileContent = createReadStream(path.join(__dirname, '/activiti.lic'));
|
const fileContent = createReadStream(path.join(__dirname, '/activiti.lic'));
|
||||||
@@ -330,15 +372,14 @@ async function updateLicense(opts: InitApsEnvArgs) {
|
|||||||
);
|
);
|
||||||
logger.info(`Aps License uploaded!`);
|
logger.info(`Aps License uploaded!`);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error(`Aps License failed!`, error.message);
|
logger.error(`Aps License failed! ${formatError(error)}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if default application is deployed
|
* Check if default application is deployed
|
||||||
*
|
|
||||||
* @param appName application name
|
* @param appName application name
|
||||||
* @returns `true` if application is deployed, otherwise `false`
|
* @returns `true` if application is deployed, otherwise `false`
|
||||||
*/
|
*/
|
||||||
@@ -349,18 +390,18 @@ async function isDefaultAppDeployed(appName: string): Promise<boolean> {
|
|||||||
const availableApps = await runtimeAppDefinitionsApi.getAppDefinitions();
|
const availableApps = await runtimeAppDefinitionsApi.getAppDefinitions();
|
||||||
const defaultApp = availableApps.data?.filter((app) => app.name?.includes(appName));
|
const defaultApp = availableApps.data?.filter((app) => app.name?.includes(appName));
|
||||||
return defaultApp && defaultApp.length > 0;
|
return defaultApp && defaultApp.length > 0;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error(`Aps app failed to import/Publish!`);
|
logger.error(`Failed to check if ${appName} is deployed: ${formatError(error)}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import and publish the application
|
* Import and publish the application
|
||||||
*
|
|
||||||
* @param appName application name
|
* @param appName application name
|
||||||
|
* @returns the app definition result
|
||||||
*/
|
*/
|
||||||
async function importPublishApp(appName: string): Promise<AppDefinitionUpdateResultRepresentation> {
|
async function importPublishApp(appName: string): Promise<AppDefinitionUpdateResultRepresentation | null> {
|
||||||
const appNameExtension = `../resources/${appName}.zip`;
|
const appNameExtension = `../resources/${appName}.zip`;
|
||||||
logger.info(`Import app ${appNameExtension}`);
|
logger.info(`Import app ${appNameExtension}`);
|
||||||
const pathFile = path.join(__dirname, appNameExtension);
|
const pathFile = path.join(__dirname, appNameExtension);
|
||||||
@@ -371,15 +412,14 @@ async function importPublishApp(appName: string): Promise<AppDefinitionUpdateRes
|
|||||||
const result = await appDefinitionsApi.importAndPublishApp(fileContent, { renewIdmEntries: true });
|
const result = await appDefinitionsApi.importAndPublishApp(fileContent, { renewIdmEntries: true });
|
||||||
logger.info(`Aps app imported and published!`);
|
logger.info(`Aps app imported and published!`);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error(`Aps app failed to import/Publish!`, error.message);
|
logger.error(`Aps app failed to import/Publish! ${formatError(error)}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deploy application
|
* Deploy application
|
||||||
*
|
|
||||||
* @param appDefinitionId app definition id
|
* @param appDefinitionId app definition id
|
||||||
*/
|
*/
|
||||||
async function deployApp(appDefinitionId: number) {
|
async function deployApp(appDefinitionId: number) {
|
||||||
@@ -392,15 +432,15 @@ async function deployApp(appDefinitionId: number) {
|
|||||||
const runtimeAppDefinitionsApi = new RuntimeAppDefinitionsApi(alfrescoJsApi);
|
const runtimeAppDefinitionsApi = new RuntimeAppDefinitionsApi(alfrescoJsApi);
|
||||||
await runtimeAppDefinitionsApi.deployAppDefinitions(body);
|
await runtimeAppDefinitionsApi.deployAppDefinitions(body);
|
||||||
logger.info(`Aps app deployed`);
|
logger.info(`Aps app deployed`);
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error(`Aps app failed to deploy!`);
|
logger.error(`Aps app failed to deploy: ${formatError(error)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if Activiti app has license
|
* Checks if Activiti app has license
|
||||||
*
|
|
||||||
* @param opts command options
|
* @param opts command options
|
||||||
|
* @returns `true` if license is valid, otherwise `false`
|
||||||
*/
|
*/
|
||||||
async function hasLicense(opts: InitApsEnvArgs): Promise<boolean> {
|
async function hasLicense(opts: InitApsEnvArgs): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
@@ -415,13 +455,13 @@ async function hasLicense(opts: InitApsEnvArgs): Promise<boolean> {
|
|||||||
['application/json'],
|
['application/json'],
|
||||||
['application/json']
|
['application/json']
|
||||||
);
|
);
|
||||||
if (license && license.status === 'valid') {
|
if (license?.status === 'valid') {
|
||||||
logger.info(`Aps has a valid License!`);
|
logger.info(`Aps has a valid License!`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
logger.info(`Aps does NOT have a valid License!`);
|
logger.info(`Aps does NOT have a valid License!`);
|
||||||
return false;
|
return false;
|
||||||
} catch (error) {
|
} catch {
|
||||||
logger.error(`Aps not able to check the license`);
|
logger.error(`Aps not able to check the license`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -429,8 +469,8 @@ async function hasLicense(opts: InitApsEnvArgs): Promise<boolean> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get default users from the realm
|
* Get default users from the realm
|
||||||
*
|
|
||||||
* @param opts command options
|
* @param opts command options
|
||||||
|
* @returns array of default APS users or null
|
||||||
*/
|
*/
|
||||||
async function getDefaultApsUsersFromRealm(opts: InitApsEnvArgs) {
|
async function getDefaultApsUsersFromRealm(opts: InitApsEnvArgs) {
|
||||||
try {
|
try {
|
||||||
@@ -449,18 +489,18 @@ async function getDefaultApsUsersFromRealm(opts: InitApsEnvArgs) {
|
|||||||
const apsDefaultUsers = users.filter((user) => usernamesOfApsDefaultUsers.includes(user.username));
|
const apsDefaultUsers = users.filter((user) => usernamesOfApsDefaultUsers.includes(user.username));
|
||||||
logger.info(`Keycloak found ${apsDefaultUsers.length} users`);
|
logger.info(`Keycloak found ${apsDefaultUsers.length} users`);
|
||||||
return apsDefaultUsers;
|
return apsDefaultUsers;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error(`APS: not able to fetch user: ${error.message}`);
|
logger.error(`APS: not able to fetch user: ${formatError(error)}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that ACS repo for Activiti is present
|
* Validate that ACS repo for Activiti is present
|
||||||
*
|
|
||||||
* @param opts command options
|
* @param opts command options
|
||||||
* @param tenantId tenant id
|
* @param tenantId tenant id
|
||||||
* @param contentName content service name
|
* @param contentName content service name
|
||||||
|
* @returns `true` if content repo is present, otherwise `false`
|
||||||
*/
|
*/
|
||||||
async function isContentRepoPresent(opts: InitApsEnvArgs, tenantId: number, contentName: string): Promise<boolean> {
|
async function isContentRepoPresent(opts: InitApsEnvArgs, tenantId: number, contentName: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
@@ -476,18 +516,18 @@ async function isContentRepoPresent(opts: InitApsEnvArgs, tenantId: number, cont
|
|||||||
['application/json']
|
['application/json']
|
||||||
);
|
);
|
||||||
return !!contentRepos.data.find((repo) => repo.name === contentName);
|
return !!contentRepos.data.find((repo) => repo.name === contentName);
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error(`APS: not able to create content: ${error.message}`);
|
logger.error(`APS: not able to check content repo: ${formatError(error)}`);
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add content service with basic auth
|
* Add content service with basic auth
|
||||||
*
|
|
||||||
* @param opts command options
|
* @param opts command options
|
||||||
* @param tenantId tenant id
|
* @param tenantId tenant id
|
||||||
* @param name content name
|
* @param name content name
|
||||||
|
* @returns the created content repo
|
||||||
*/
|
*/
|
||||||
async function addContentRepoWithBasic(opts: InitApsEnvArgs, tenantId: number, name: string) {
|
async function addContentRepoWithBasic(opts: InitApsEnvArgs, tenantId: number, name: string) {
|
||||||
logger.info(`Create Content with name ${name} and basic auth`);
|
logger.info(`Create Content with name ${name} and basic auth`);
|
||||||
@@ -517,14 +557,13 @@ async function addContentRepoWithBasic(opts: InitApsEnvArgs, tenantId: number, n
|
|||||||
);
|
);
|
||||||
logger.info(`Content created!`);
|
logger.info(`Content created!`);
|
||||||
return content;
|
return content;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error(`APS: not able to create content: ${error.message}`);
|
logger.error(`APS: not able to create content: ${formatError(error)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authorize activiti user to ACS repo
|
* Authorize activiti user to ACS repo
|
||||||
*
|
|
||||||
* @param opts command options
|
* @param opts command options
|
||||||
* @param user user object
|
* @param user user object
|
||||||
*/
|
*/
|
||||||
@@ -551,17 +590,17 @@ async function authorizeUserToContentRepo(opts: InitApsEnvArgs, user: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error(`APS: not able to authorize content: ${error.message}`);
|
logger.error(`APS: not able to authorize content: ${formatError(error)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authorize user with content using basic auth
|
* Authorize user with content using basic auth
|
||||||
*
|
|
||||||
* @param opts command options
|
* @param opts command options
|
||||||
* @param username username
|
* @param username username
|
||||||
* @param contentId content id
|
* @param contentId content id
|
||||||
|
* @returns the authorized content
|
||||||
*/
|
*/
|
||||||
async function authorizeUserToContentWithBasic(opts: InitApsEnvArgs, username: string, contentId: string) {
|
async function authorizeUserToContentWithBasic(opts: InitApsEnvArgs, username: string, contentId: string) {
|
||||||
logger.info(`Authorize ${username} on contentId: ${contentId} in basic auth`);
|
logger.info(`Authorize ${username} on contentId: ${contentId} in basic auth`);
|
||||||
@@ -580,15 +619,15 @@ async function authorizeUserToContentWithBasic(opts: InitApsEnvArgs, username: s
|
|||||||
);
|
);
|
||||||
logger.info(`User authorized!`);
|
logger.info(`User authorized!`);
|
||||||
return content;
|
return content;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error(`APS: not able to authorize content: ${error.message}`);
|
logger.error(`APS: not able to authorize content: ${formatError(error)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download APS license file
|
* Download APS license file
|
||||||
*
|
|
||||||
* @param apsLicensePath path to license file
|
* @param apsLicensePath path to license file
|
||||||
|
* @returns `true` if download succeeded, otherwise `false`
|
||||||
*/
|
*/
|
||||||
async function downloadLicenseFile(apsLicensePath: string) {
|
async function downloadLicenseFile(apsLicensePath: string) {
|
||||||
const args = [`s3`, `cp`, apsLicensePath, `./`];
|
const args = [`s3`, `cp`, apsLicensePath, `./`];
|
||||||
@@ -606,11 +645,31 @@ async function downloadLicenseFile(apsLicensePath: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a delay
|
* Format an error for logging.
|
||||||
*
|
* @param error error object
|
||||||
* @param delay timeout in milliseconds
|
* @returns formatted error string
|
||||||
*/
|
*/
|
||||||
function sleep(delay: number) {
|
function formatError(error: any): string {
|
||||||
const start = new Date().getTime();
|
if (!error) {
|
||||||
while (new Date().getTime() < start + delay) {}
|
return 'Unknown error';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return error?.message || error?.stack || JSON.stringify(error);
|
||||||
|
} catch {
|
||||||
|
return 'Unknown error (unable to serialize)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async delay.
|
||||||
|
* @param ms milliseconds to wait
|
||||||
|
* @returns a promise that resolves after the delay
|
||||||
|
*/
|
||||||
|
function wait(ms: number): Promise<void> {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user