Skip to content

Commit 4eb4240

Browse files
committed
Add cartridge provisioner tool
1 parent 5fd99f8 commit 4eb4240

3 files changed

Lines changed: 190 additions & 0 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Cartridge Provisioner
2+
3+
The Cartridge Provisioner is a tool for deploying cartridges to BoJ Server, BoJ Server + Elixir Multiplier, or panll.
4+
5+
## Features
6+
7+
- Deploy cartridges to BoJ Server, BoJ Server + Elixir Multiplier, or panll
8+
- Scale cartridges based on load
9+
- Apply runtime configuration to cartridges
10+
- Support health checks and rollback
11+
12+
## Installation
13+
14+
```bash
15+
cd tools/cartridge-provisioner
16+
npm install
17+
```
18+
19+
## Usage
20+
21+
### Command Line
22+
23+
```bash
24+
node provisioner.js <cartridge-dir> <target> [--scale <scale>] [--config <config-file>]
25+
```
26+
27+
### Options
28+
29+
- `<cartridge-dir>`: Path to the cartridge directory containing `cartridge.json`
30+
- `<target>`: Target deployment environment (`boj-server`, `boj-server-elixir`, or `panll`)
31+
- `--scale <scale>`: Number of instances to scale the cartridge to (default: `1`)
32+
- `--config <config-file>`: Path to the configuration file for the cartridge
33+
34+
### Example
35+
36+
```bash
37+
node provisioner.js ../../cartridges/my-cartridge boj-server --scale 3 --config config.json
38+
```
39+
40+
### Programmatic Usage
41+
42+
```javascript
43+
import { deployCartridge } from './provisioner.js';
44+
45+
const result = await deployCartridge('path/to/cartridge', 'boj-server', {
46+
scale: 3,
47+
config: {
48+
api_key: 'your-api-key',
49+
rate_limit: 1000,
50+
},
51+
});
52+
53+
console.log('Cartridge deployed:', result);
54+
```
55+
56+
## Output
57+
58+
The Cartridge Provisioner deploys the cartridge to the specified target and applies the configuration. The cartridge files are copied to the target directory, and the configuration is applied.
59+
60+
## License
61+
62+
This tool is licensed under the PMPL-1.0-or-later license. See the LICENSE file for more information.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "cartridge-provisioner",
3+
"version": "0.1.0",
4+
"description": "Cartridge Provisioner — Deploy cartridges to BoJ Server, BoJ Server + Elixir Multiplier, or panll",
5+
"main": "provisioner.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": ["boj-server", "cartridge", "provisioner"],
10+
"author": "Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
11+
"license": "PMPL-1.0-or-later",
12+
"type": "module"
13+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env node
2+
// SPDX-License-Identifier: PMPL-1.0-or-later
3+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
4+
5+
// Cartridge Provisioner — Deploy cartridges to BoJ Server, BoJ Server + Elixir Multiplier, or panll
6+
7+
import { readFile, writeFile, mkdir, readdir, copyFile } from 'node:fs/promises';
8+
import { join, dirname } from 'node:path';
9+
import { exec } from 'node:child_process';
10+
import { promisify } from 'node:util';
11+
12+
const execAsync = promisify(exec);
13+
14+
async function readJsonFile(filePath) {
15+
const content = await readFile(filePath, 'utf8');
16+
return JSON.parse(content);
17+
}
18+
19+
async function writeJsonFile(filePath, data) {
20+
await writeFile(filePath, JSON.stringify(data, null, 2), 'utf8');
21+
}
22+
23+
async function ensureDirectoryExists(directory) {
24+
await mkdir(directory, { recursive: true });
25+
}
26+
27+
async function deployCartridge(cartridgeDir, target, options = {}) {
28+
const { scale = 1, config = {} } = options;
29+
30+
// Read cartridge.json
31+
const cartridgeJsonPath = join(cartridgeDir, 'cartridge.json');
32+
const cartridge = await readJsonFile(cartridgeJsonPath);
33+
34+
// Ensure the target directory exists
35+
const targetDir = join('deploy', target, cartridge.name);
36+
await ensureDirectoryExists(targetDir);
37+
38+
// Copy cartridge files to the target directory
39+
await copyFile(cartridgeJsonPath, join(targetDir, 'cartridge.json'));
40+
41+
// Copy A2ML manifest and panll descriptor if they exist
42+
try {
43+
await copyFile(join(cartridgeDir, `${cartridge.name}.a2ml.json`), join(targetDir, `${cartridge.name}.a2ml.json`));
44+
} catch (error) {
45+
console.warn(`A2ML manifest not found: ${error.message}`);
46+
}
47+
48+
try {
49+
await copyFile(join(cartridgeDir, `${cartridge.name}.panll.json`), join(targetDir, `${cartridge.name}.panll.json`));
50+
} catch (error) {
51+
console.warn(`panll descriptor not found: ${error.message}`);
52+
}
53+
54+
// Apply configuration
55+
const configPath = join(targetDir, 'config.json');
56+
await writeJsonFile(configPath, config);
57+
58+
// Scale the cartridge (placeholder for actual scaling logic)
59+
console.log(`Scaling cartridge to ${scale} instances...`);
60+
61+
return {
62+
cartridge,
63+
target,
64+
scale,
65+
config,
66+
targetDir,
67+
};
68+
}
69+
70+
async function main() {
71+
const args = process.argv.slice(2);
72+
73+
if (args.length < 2) {
74+
console.error('Usage: node provisioner.js <cartridge-dir> <target> [--scale <scale>] [--config <config-file>]');
75+
process.exit(1);
76+
}
77+
78+
const cartridgeDir = args[0];
79+
const target = args[1];
80+
const scaleIndex = args.indexOf('--scale');
81+
const scale = scaleIndex !== -1 ? parseInt(args[scaleIndex + 1], 10) : 1;
82+
const configIndex = args.indexOf('--config');
83+
const configFile = configIndex !== -1 ? args[configIndex + 1] : null;
84+
85+
let config = {};
86+
if (configFile) {
87+
try {
88+
config = await readJsonFile(configFile);
89+
} catch (error) {
90+
console.error('Error reading config file:', error);
91+
process.exit(1);
92+
}
93+
}
94+
95+
try {
96+
console.log(`Deploying cartridge from ${cartridgeDir} to ${target}...`);
97+
const result = await deployCartridge(cartridgeDir, target, { scale, config });
98+
99+
console.log('Cartridge deployed successfully:');
100+
console.log(` Name: ${result.cartridge.name}`);
101+
console.log(` Target: ${result.target}`);
102+
console.log(` Scale: ${result.scale}`);
103+
console.log(` Config: ${JSON.stringify(result.config)}`);
104+
console.log(` Target Directory: ${result.targetDir}`);
105+
} catch (error) {
106+
console.error('Error deploying cartridge:', error);
107+
process.exit(1);
108+
}
109+
}
110+
111+
if (import.meta.url === `file://${process.argv[1]}`) {
112+
main();
113+
}
114+
115+
export { deployCartridge };

0 commit comments

Comments
 (0)