Skip to content

Commit 35f728e

Browse files
ravikiranvmcezudas
andauthored
Resolve benchmark workflows for seed (#2006)
Fixes OPS-3751. ## Additional Notes This PR adds a helper method to return paths for benchmark workflows from the catalog for a given provider and user chosen workflow ids. The next step would be to import the flows using these paths. --------- Co-authored-by: Cezar <cezar@openops.com>
1 parent c618794 commit 35f728e

4 files changed

Lines changed: 169 additions & 1 deletion

File tree

packages/server/api/project.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@
2020
"outputPath": "dist/packages/server/api",
2121
"main": "packages/server/api/src/main.ts",
2222
"tsConfig": "packages/server/api/tsconfig.app.json",
23-
"assets": ["packages/server/api/src/assets"],
23+
"assets": [
24+
"packages/server/api/src/assets",
25+
{
26+
"input": "packages/server/api/src/app/benchmark",
27+
"glob": "workflows-catalog/**/*",
28+
"output": "."
29+
}
30+
],
2431
"webpackConfig": "packages/server/api/webpack.config.js",
2532
"generatePackageJson": false,
2633
"babelUpwardRootMode": true,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export type LifecycleWorkflow = {
2+
orchestratorWorkflowId: string;
3+
cleanupWorkflowId: string;
4+
};
5+
6+
export const PROVIDER_LIFECYCLE_WORKFLOWS: Record<string, LifecycleWorkflow> = {
7+
aws: {
8+
orchestratorWorkflowId: 'Run AWS Benchmark - orchestrator',
9+
cleanupWorkflowId: 'Clean-up AWS Benchmark data',
10+
},
11+
};
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
import {
4+
type LifecycleWorkflow,
5+
PROVIDER_LIFECYCLE_WORKFLOWS,
6+
} from './catalog-manifests';
7+
import { throwValidationError } from './errors';
8+
9+
const WORKFLOWS_CATALOG_DIR = 'workflows-catalog';
10+
11+
function getCatalogDir(provider: string): string {
12+
return path.join(__dirname, WORKFLOWS_CATALOG_DIR, provider.toLowerCase());
13+
}
14+
15+
function getLifecycleWorkflow(provider: string): LifecycleWorkflow {
16+
const normalized = provider.toLowerCase();
17+
const lifecycleWorkflow = PROVIDER_LIFECYCLE_WORKFLOWS[normalized];
18+
if (!lifecycleWorkflow) {
19+
throwValidationError(`Unsupported benchmark provider: ${provider}`);
20+
}
21+
return lifecycleWorkflow;
22+
}
23+
24+
function getOrchestratorId(provider: string): string {
25+
return getLifecycleWorkflow(provider).orchestratorWorkflowId;
26+
}
27+
28+
function getCleanupWorkflowId(provider: string): string {
29+
return getLifecycleWorkflow(provider).cleanupWorkflowId;
30+
}
31+
32+
export type ResolvedWorkflowPath = {
33+
id: string;
34+
filePath: string;
35+
};
36+
37+
function resolveWorkflowPaths(
38+
provider: string,
39+
workflowIds: string[],
40+
): ResolvedWorkflowPath[] {
41+
const catalogDir = getCatalogDir(provider);
42+
const result: ResolvedWorkflowPath[] = [];
43+
for (const id of workflowIds) {
44+
const filePath = path.join(catalogDir, `${id}.json`);
45+
if (!fs.existsSync(filePath)) {
46+
throwValidationError(`Workflow catalog file not found: ${id}`);
47+
}
48+
result.push({ id, filePath });
49+
}
50+
return result;
51+
}
52+
53+
export function resolveWorkflowPathsForSeed(
54+
provider: string,
55+
subWorkflowIds: string[],
56+
): ResolvedWorkflowPath[] {
57+
if (subWorkflowIds.length === 0) {
58+
getLifecycleWorkflow(provider);
59+
return [];
60+
}
61+
const orchestratorId = getOrchestratorId(provider);
62+
const cleanupId = getCleanupWorkflowId(provider);
63+
const allIds = [orchestratorId, cleanupId, ...subWorkflowIds];
64+
return resolveWorkflowPaths(provider, allIds);
65+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import fs from 'node:fs';
2+
import { resolveWorkflowPathsForSeed } from '../../../src/app/benchmark/catalog-resolver';
3+
4+
const ORCHESTRATOR_WORKFLOW_ID = 'Orchestrator Workflow';
5+
const CLEANUP_WORKFLOW_ID = 'Cleanup Workflow';
6+
const SUB_WORKFLOW_ID = 'Sub Workflow A';
7+
8+
const TEST_PROVIDER = 'provider_a';
9+
10+
jest.mock('../../../src/app/benchmark/catalog-manifests', () => ({
11+
PROVIDER_LIFECYCLE_WORKFLOWS: {
12+
provider_a: {
13+
orchestratorWorkflowId: 'Orchestrator Workflow',
14+
cleanupWorkflowId: 'Cleanup Workflow',
15+
},
16+
},
17+
}));
18+
19+
jest.mock('node:fs', () => ({
20+
...jest.requireActual('node:fs'),
21+
existsSync: jest.fn(),
22+
}));
23+
24+
const existsSyncMock = fs.existsSync as jest.MockedFunction<
25+
typeof fs.existsSync
26+
>;
27+
28+
function setCatalogExists(workflowFilesExist: (path: string) => boolean): void {
29+
existsSyncMock.mockImplementation((filePath: fs.PathLike) => {
30+
const str = String(filePath);
31+
return workflowFilesExist(str);
32+
});
33+
}
34+
35+
describe('catalog-resolver', () => {
36+
beforeEach(() => {
37+
jest.clearAllMocks();
38+
});
39+
40+
it('resolveWorkflowPathsForSeed throws when provider is not in catalog', () => {
41+
expect(() => resolveWorkflowPathsForSeed('gcp', [])).toThrow(
42+
'Unsupported benchmark provider: gcp',
43+
);
44+
});
45+
46+
it('resolveWorkflowPathsForSeed returns empty array when no sub-workflows', () => {
47+
const result = resolveWorkflowPathsForSeed(TEST_PROVIDER, []);
48+
expect(result).toEqual([]);
49+
});
50+
51+
it('resolveWorkflowPathsForSeed throws when workflow file does not exist', () => {
52+
setCatalogExists((filePath) => {
53+
return (
54+
filePath.endsWith(`${ORCHESTRATOR_WORKFLOW_ID}.json`) ||
55+
filePath.endsWith(`${CLEANUP_WORKFLOW_ID}.json`)
56+
);
57+
});
58+
59+
expect(() =>
60+
resolveWorkflowPathsForSeed(TEST_PROVIDER, [SUB_WORKFLOW_ID]),
61+
).toThrow(`Workflow catalog file not found: ${SUB_WORKFLOW_ID}`);
62+
});
63+
64+
it('resolveWorkflowPathsForSeed returns orchestrator, cleanup, then sub-workflows when all exist', () => {
65+
setCatalogExists((filePath) => {
66+
return (
67+
filePath.endsWith(`${ORCHESTRATOR_WORKFLOW_ID}.json`) ||
68+
filePath.endsWith(`${CLEANUP_WORKFLOW_ID}.json`) ||
69+
filePath.endsWith(`${SUB_WORKFLOW_ID}.json`)
70+
);
71+
});
72+
73+
const result = resolveWorkflowPathsForSeed(TEST_PROVIDER, [
74+
SUB_WORKFLOW_ID,
75+
]);
76+
77+
expect(result).toHaveLength(3);
78+
expect(result[0].id).toBe(ORCHESTRATOR_WORKFLOW_ID);
79+
expect(result[0].filePath).toContain(ORCHESTRATOR_WORKFLOW_ID);
80+
expect(result[1].id).toBe(CLEANUP_WORKFLOW_ID);
81+
expect(result[1].filePath).toContain(CLEANUP_WORKFLOW_ID);
82+
expect(result[2].id).toBe(SUB_WORKFLOW_ID);
83+
expect(result[2].filePath.endsWith(`${SUB_WORKFLOW_ID}.json`)).toBe(true);
84+
});
85+
});

0 commit comments

Comments
 (0)