Skip to content

Commit

Permalink
feat: add dependency graph generation to APT
Browse files Browse the repository at this point in the history
  • Loading branch information
remie committed Dec 14, 2024
1 parent 9b6260a commit b19e004
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 3 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@types/js-yaml": "4",
"@types/node": "18.16.0",
"@types/pg": "8",
"@types/unzipper": "^0",
"@typescript-eslint/eslint-plugin": "7.6.0",
"@typescript-eslint/parser": "7.6.0",
"@vitest/coverage-v8": "2.1.5",
Expand Down Expand Up @@ -95,6 +96,7 @@
"sequelize": "6.37.2",
"simple-git": "3.24.0",
"tedious": "18.1.0",
"unzipper": "0.12.3",
"xpath": "0.0.34",
"yaml": "2.6.0",
"zod": "3.23.6"
Expand Down
133 changes: 133 additions & 0 deletions src/apt/helpers/generateDependencyTree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { spawn } from 'child_process';
import { existsSync, rmSync } from 'fs';
import { glob } from 'glob';
import { homedir } from 'os';
import { basename, join } from 'path';
import { Open } from 'unzipper';

import { TAPTDependencyTreeOptions } from '../../types/DCAPT';
import { downloadApp } from './downloadApp';

export const generateDependencyTree = async (options: TAPTDependencyTreeOptions) => {

// Placeholder for temporary directory
const tmpDir = join(homedir(), '.dcdx', 'tmp');

// Download the file from MPAC
console.log('Downloading archive from the Atlassian Marketplace');
const file = await downloadApp(options.appKey);

try {
// Placeholder for archive directory
const archiveDir = join(tmpDir, `.${options.appKey}`);

try {
// Extract the file to a temporary directory
console.log(`Extracting archive to a temporary location (${archiveDir})`);
const archive = await Open.file(file);
await archive.extract({ path: archiveDir })

// Check if we are dealing with an OBR file
if (existsSync(join(archiveDir, 'obr.xml'))) {

console.log('The archive is an OSGi Bundle Repository (OBR)');
console.log('Searching for the main artifact');

// Get the main JAR file (which is located in the root directory)
const [ relativePathToJar ] = await glob(`*.jar`, { cwd: archiveDir });

// Make sure we actually found the main jar
if (!relativePathToJar) {
console.log('Failed to locate the main JAR file in the archive');
return;
} else {
console.log(`Found the main artifact (${relativePathToJar})`);

// Get the full path to the JAR file
const jarFile = join(archiveDir, relativePathToJar);

// Placeholder directroy in which we will be extracting the main JAR
const jarDir = join(archiveDir, basename(jarFile, '.jar'));

// Extract the main JAR file into the placeholder directory
console.log(`Extracting the main artifact to a temporary location (${jarDir})`);
const archive = await Open.file(jarFile);
await archive.extract({ path: jarDir });

// Now try to find the POM file for the main JAR
console.log(`Searching for POM file in the main artifact`);
const [ relativePathToPOM ] = await glob(`META-INF/maven/**/pom.xml`, { cwd: jarDir });

// Make sure we have found the POM file
if (!relativePathToPOM) {
console.log('Failed to locate the POM file in the main artifact');
return;
} else {
console.log(`Found the POM file`);

// Get the full path to the POM file
const pomFile = join(jarDir, relativePathToPOM);

// Ask Maven to generate the depdency tree graph!
console.log('Asking Apache Maven to generate the dependency graph');
await new Promise<void>((resolve, reject) => {
const maven = spawn(
'mvn',
[
'dependency:tree',
'-f', pomFile,
'-DoutputType=dot',
`-DoutputFile=${options.outputFile}`,
],
{ stdio: 'inherit' }
);
maven.on('exit', (code) => (code === 0) ? resolve() : reject(new Error(`Apache Maven exited with code ${code}`)));
});

console.log('Finished generating the dependency graph');
}
}

// This is actually already the main JAR file
} else {

// Now try to find the POM file for the main JAR
console.log(`Searching for POM file in the artifact`);
const [ relativePathToPOM ] = await glob(`META-INF/maven/**/pom.xml`, { cwd: archiveDir });

// Make sure we have found the POM file
if (!relativePathToPOM) {
console.log('Failed to locate the POM file in the artifact');
return;
} else {
console.log(`Found the POM file`);

// Get the full path to the POM file
const pomFile = join(archiveDir, relativePathToPOM);

// Ask Maven to generate the depdency tree graph!
console.log('Asking Apache Maven to generate the dependency graph');
await new Promise<void>((resolve, reject) => {
const maven = spawn(
'mvn',
[
'dependency:tree',
'-f', pomFile,
'-DoutputType=dot',
`-DoutputFile=${options.outputFile}`,
],
{ stdio: 'inherit' }
);
maven.on('exit', (code) => (code === 0) ? resolve() : reject(new Error(`Apache Maven exited with code ${code}`)));
});

console.log('Finished generating the dependency graph');
}
}
} finally {
rmSync(archiveDir, { force: true, recursive: true });
}
} finally {
rmSync(file, { force: true });
}
}
24 changes: 23 additions & 1 deletion src/commands/apt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import { Command as Commander, Option } from 'commander';
import { gracefulExit } from 'exit-hook';
import { join } from 'path';
import { cwd } from 'process';

import { generateDependencyTree } from '../apt/helpers/generateDependencyTree';
import { generatePerformanceReport } from '../apt/helpers/generatePerformanceReport';
import { generateScalabilityReport } from '../apt/helpers/generateScalabilityReport';
import { getHostLicense } from '../apt/helpers/getHostLicense';
Expand All @@ -21,7 +23,7 @@ import { Performance } from '../apt/performance';
import { Scalability } from '../apt/scalability';
import { ActionHandler } from '../helpers/ActionHandler';
import { SupportedApplications } from '../types/Application';
import { PerformanceTestTypes, ReportTypes, TAPTArgs, TAPTPerformanceReportArgs, TAPTPerformanceTestArgs, TAPTProvisionArgs, TAPTRestartArgs, TAPTScalabilityReportArgs, TAPTScalabilityTestArgs, TAPTTeardownArgs } from '../types/DCAPT';
import { PerformanceTestTypes, ReportTypes, TAPTArgs, TAPTDependencyTreeArgs, TAPTPerformanceReportArgs, TAPTPerformanceTestArgs, TAPTProvisionArgs, TAPTRestartArgs, TAPTScalabilityReportArgs, TAPTScalabilityTestArgs, TAPTTeardownArgs } from '../types/DCAPT';

const program = new Commander();

Expand Down Expand Up @@ -296,6 +298,18 @@ const TeardownCommand = () => ({
}
})

const DependencyTreeCommand = () => ({
action: async (options: TAPTDependencyTreeArgs) => {
await generateDependencyTree({
appKey: options.appKey,
outputFile: options.outputFile || join(cwd(), 'maven_dependency_tree.gv')
});
},
errorHandler: async () => {

}
})

program
.name('dcdx apt')
.showHelpAfterError(true);
Expand Down Expand Up @@ -466,6 +480,14 @@ program
.addOption(new Option('-y, --force', 'Use default values for input questions when available').default(false))
.action(options => ActionHandler(program, TeardownCommand(), options));

program
.command('dependencies')
.description('Generate the Data Center App Performance Testing dependency tree')
.addOption(new Option('--appKey <appKey>', 'The key of the app (for automated installation)'))
.addOption(new Option('-O, --outputFile <path>', 'Specify the output file where to store the generated dependency tree (defaults to `./maven_dependency_tree.gv`)'))
.action(options => ActionHandler(program, DependencyTreeCommand(), options));


program.parseAsync(process.argv).catch(() => gracefulExit(1));

process.on('SIGINT', () => {
Expand Down
14 changes: 14 additions & 0 deletions src/types/DCAPT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,18 @@ export const APTTeardownOptions = z.object({
export const APTRestartArgs = APTTeardownArgs.extend({});
export const APTRestartOptions = APTTeardownOptions.extend({});

export const APTDependencyTreeArgs = z.object({
appKey: z.string(),
outputFile: z.string()
}).partial({
outputFile: true
});

export const APTDependencyTreeOptions = z.object({
appKey: z.string(),
outputFile: z.string()
});

export const APTArgs = z.intersection(
APTProvisionOptions,
APTPerformanceTestArgs,
Expand Down Expand Up @@ -235,3 +247,5 @@ export type TAPTTeardownOptions = z.infer<typeof APTTeardownOptions>;
export type TTestResults = z.infer<typeof TestResults>;
export type TReportTypes = z.infer<typeof ReportTypes>;

export type TAPTDependencyTreeArgs = z.infer<typeof APTDependencyTreeArgs>;
export type TAPTDependencyTreeOptions = z.infer<typeof APTDependencyTreeOptions>;
42 changes: 40 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1946,6 +1946,15 @@ __metadata:
languageName: node
linkType: hard

"@types/unzipper@npm:^0":
version: 0.10.10
resolution: "@types/unzipper@npm:0.10.10"
dependencies:
"@types/node": "npm:*"
checksum: 10c0/10e9da33791be1087adb25adc2fe4d5ab267dae51fbcf7b1f10d0aca3130a13ef5fed994d7be45af8c465ff3946bc360a53eff6e5aab4eb9ac9489477535342f
languageName: node
linkType: hard

"@types/validator@npm:^13.7.17":
version: 13.11.9
resolution: "@types/validator@npm:13.11.9"
Expand Down Expand Up @@ -2637,6 +2646,13 @@ __metadata:
languageName: node
linkType: hard

"bluebird@npm:~3.7.2":
version: 3.7.2
resolution: "bluebird@npm:3.7.2"
checksum: 10c0/680de03adc54ff925eaa6c7bb9a47a0690e8b5de60f4792604aae8ed618c65e6b63a7893b57ca924beaf53eee69c5af4f8314148c08124c550fe1df1add897d2
languageName: node
linkType: hard

"bottleneck@npm:^2.15.3":
version: 2.19.5
resolution: "bottleneck@npm:2.19.5"
Expand Down Expand Up @@ -3348,6 +3364,7 @@ __metadata:
"@types/js-yaml": "npm:4"
"@types/node": "npm:18.16.0"
"@types/pg": "npm:8"
"@types/unzipper": "npm:^0"
"@typescript-eslint/eslint-plugin": "npm:7.6.0"
"@typescript-eslint/parser": "npm:7.6.0"
"@vitest/coverage-v8": "npm:2.1.5"
Expand Down Expand Up @@ -3383,6 +3400,7 @@ __metadata:
tedious: "npm:18.1.0"
typescript: "npm:5.4.4"
typescript-eslint: "npm:7.6.0"
unzipper: "npm:0.12.3"
vitest: "npm:2.1.5"
vitest-mock-process: "npm:1.0.4"
xpath: "npm:0.0.34"
Expand Down Expand Up @@ -3614,7 +3632,7 @@ __metadata:
languageName: node
linkType: hard

"duplexer2@npm:~0.1.0":
"duplexer2@npm:~0.1.0, duplexer2@npm:~0.1.4":
version: 0.1.4
resolution: "duplexer2@npm:0.1.4"
dependencies:
Expand Down Expand Up @@ -4836,7 +4854,7 @@ __metadata:
languageName: node
linkType: hard

"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.6":
"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.6":
version: 4.2.11
resolution: "graceful-fs@npm:4.2.11"
checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2
Expand Down Expand Up @@ -6773,6 +6791,13 @@ __metadata:
languageName: node
linkType: hard

"node-int64@npm:^0.4.0":
version: 0.4.0
resolution: "node-int64@npm:0.4.0"
checksum: 10c0/a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a
languageName: node
linkType: hard

"nodemon@npm:3.1.0":
version: 3.1.0
resolution: "nodemon@npm:3.1.0"
Expand Down Expand Up @@ -9804,6 +9829,19 @@ __metadata:
languageName: node
linkType: hard

"unzipper@npm:0.12.3":
version: 0.12.3
resolution: "unzipper@npm:0.12.3"
dependencies:
bluebird: "npm:~3.7.2"
duplexer2: "npm:~0.1.4"
fs-extra: "npm:^11.2.0"
graceful-fs: "npm:^4.2.2"
node-int64: "npm:^0.4.0"
checksum: 10c0/4cae2ad23bfd47011d5f8a6d61fb1dc0e4b5008bc3896e6f3d5ab946a64e9482714992a988128bce541440aa646e16e5e5c9bf35e49097edbaf833e7f814d36d
languageName: node
linkType: hard

"uri-js@npm:^4.2.2":
version: 4.4.1
resolution: "uri-js@npm:4.4.1"
Expand Down

0 comments on commit b19e004

Please sign in to comment.