Skip to content

Commit da33fc6

Browse files
committed
feat: add support for krakend api gateway
1 parent 0ae9ce6 commit da33fc6

File tree

5 files changed

+292
-11
lines changed

5 files changed

+292
-11
lines changed

.suite-cli/cli/cli.js

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const { Command } = require('commander');
55
const { createPromptModule } = require('inquirer');
66
const { execSync } = require('node:child_process')
77
const actionHandlers = require('./scripts')
8-
const { logInfo, getExistingServices, getNextAvailablePort, scaffoldApp } = require('./scripts/scripts.module');
8+
const { logInfo, getExistingServices, getExistingApps, getNextAvailablePort, scaffoldApp, scaffoldGateways } = require('./scripts/scripts.module');
99
const { cwd } = require('node:process');
1010
const program = new Command()
1111
const prompt = createPromptModule()
@@ -239,10 +239,83 @@ program
239239
checked: true, // Default all services to be selected
240240
})),
241241
},
242+
{
243+
type: 'input',
244+
name: 'gateway_port',
245+
message: 'Enter port (optional):',
246+
default: 8080,
247+
validate: input => !isNaN(input) ? true : 'Port must be a number.'
248+
},
249+
{
250+
type: 'input',
251+
name: 'api_version',
252+
message: 'Whats the api version? (optional):',
253+
default: 'v1',
254+
},
255+
{
256+
type: 'input',
257+
name: 'gateway_cache_period',
258+
message: 'How long do you want the gateway to cache data (optional):',
259+
default: 3600,
260+
validate: input => !isNaN(input) ? true : 'Caching period must be a number.'
261+
},
262+
{
263+
type: 'input',
264+
name: 'gateway_timeout',
265+
message: 'How long should a request take before timing out (optional):',
266+
default: 300,
267+
validate: input => input === '' || !isNaN(input) ? true : 'Timeout must be a number.'
268+
}
242269
]).then(answers => {
243270
scaffoldApp({ answers })
244271
})
245272
break;
273+
case 'gateway':
274+
const all_apps = getExistingApps({ currentDir: cwd() });
275+
console.log({all_apps})
276+
const formatAppsName = (app) => app.name;
277+
prompt([
278+
{
279+
type: 'checkbox',
280+
name: 'apps',
281+
message: 'Select apps',
282+
choices: all_apps.map(app => ({
283+
name: formatAppsName(app),
284+
value: app,
285+
checked: true, // Default all services to be selected
286+
})),
287+
},
288+
{
289+
type: 'input',
290+
name: 'gateway_port',
291+
message: 'Enter port (optional):',
292+
default: 8080,
293+
validate: input => !isNaN(input) ? true : 'Port must be a number.'
294+
},
295+
{
296+
type: 'input',
297+
name: 'api_version',
298+
message: 'Whats the api version? (optional):',
299+
default: 'v1',
300+
},
301+
{
302+
type: 'input',
303+
name: 'gateway_cache_period',
304+
message: 'How long do you want the gateway to cache data (optional):',
305+
default: 3600,
306+
validate: input => !isNaN(input) ? true : 'Caching period must be a number.'
307+
},
308+
{
309+
type: 'input',
310+
name: 'gateway_timeout',
311+
message: 'How long should a request take before timing out (optional):',
312+
default: 300,
313+
validate: input => input === '' || !isNaN(input) ? true : 'Timeout must be a number.'
314+
}
315+
]).then(answers => {
316+
scaffoldGateways({ answers })
317+
})
318+
break;
246319
default:
247320
console.log('Handling other resources, not yet implemented.');
248321
// Handle other cases or provide feedback that other options are not yet implemented

.suite-cli/cli/scripts/assets/dockerComposeContent.asset.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module.exports = ({ services, webserver }) => {
1+
module.exports = ({ services, webserver, krakend_port, env }) => {
22
const servicesConfig = services.map(service => `
33
${service.name.toLowerCase().replace(/\s+/g, '-')}:
44
build:
@@ -54,5 +54,13 @@ ${serviceNames.map(service => ` - ${service}`).join('\n')}
5454
volumes:
5555
- type: bind
5656
source: ./data
57-
target: /data/db`;
57+
target: /data/db
58+
krakend:
59+
image: devopsfaith/krakend:2.7${env === 'dev' ? '-watch' : ''}
60+
ports:
61+
- '${krakend_port}:${krakend_port}'
62+
- '8090:8090'
63+
volumes:
64+
- ./krakend/:/etc/krakend/
65+
command: ['run','-d','-c','/etc/krakend/krakend.json']`;
5866
};

.suite-cli/cli/scripts/assets/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,6 @@ module.exports.brokerSubscriberContent = require('./brokerSubscriberContent.asse
5858
module.exports.brokerWorkerQueueContent = require('./brokerWorkerQueueContent.asset');
5959
module.exports.subscriberContent = require('./subscriberContent.asset');
6060
module.exports.subscriberIndexContent = require('./subscriberIndexContent.asset');
61+
module.exports.krakendConfigContent = require('./krakendConfigContent.asset');
6162

6263

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
module.exports = ({
2+
services,
3+
projectName,
4+
api_version,
5+
gateway_cache_period,
6+
gateway_timeout,
7+
gateway_port
8+
}) => {
9+
// Health check for all services
10+
const suiteStatusCheckBackends = services.map((service) => `
11+
{
12+
"host": ["http://${service.name.toLowerCase()}:${service.port}"],
13+
"url_pattern": "/",
14+
"mapping": {
15+
"message": "${service.name.toLowerCase()}"
16+
}
17+
}`).join(',\n');
18+
19+
// List all services
20+
const allServicesListBackends = services.map((service) => `
21+
{
22+
"host": ["http://${service.name.toLowerCase()}:${service.port}"],
23+
"url_pattern": "/api/${api_version}/${service.name.toLowerCase()}s",
24+
"mapping": {
25+
"data": "${service.name.toLowerCase()}s"
26+
}
27+
}`).join(',\n');
28+
29+
// Each service backend
30+
const eachServiceListBackends = [services[0]].map((service) => `
31+
{
32+
"host": ["http://${service.name.toLowerCase()}:${service.port}"],
33+
"url_pattern": "/api/${api_version}/${service.name.toLowerCase()}s",
34+
"mapping": {
35+
"data": "${service.name.toLowerCase()}s"
36+
}
37+
}`).join(',\n');
38+
39+
// The JSON result string
40+
return `
41+
{
42+
"$schema": "https://www.krakend.io/schema/v2.5/krakend.json",
43+
"version": 3,
44+
"name": "${projectName.charAt(0).toUpperCase() + projectName.slice(1)} API Gateway",
45+
"port": ${gateway_port},
46+
"cache_ttl": "${gateway_cache_period}s",
47+
"timeout": "${gateway_timeout}s",
48+
"endpoints": [
49+
{
50+
"@comment": "Check the health of all your APIs",
51+
"endpoint": "/__suite_status",
52+
"method": "GET",
53+
"backend": [${suiteStatusCheckBackends}]
54+
},
55+
{
56+
"@comment": "Hit all API list endpoints simultaneously",
57+
"endpoint": "/api/${api_version}/",
58+
"method": "GET",
59+
"backend": [${allServicesListBackends}]
60+
},
61+
{
62+
"@comment": "Fetch all data from the ${services[0].name.toLowerCase()} service",
63+
"endpoint": "/api/${api_version}/${services[0].name.toLowerCase()}s",
64+
"method": "GET",
65+
"backend": [${eachServiceListBackends}]
66+
}
67+
]
68+
}`;
69+
};

.suite-cli/cli/scripts/scripts.module.js

Lines changed: 138 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1351,6 +1351,13 @@ const getExistingServices = ({ currentDir }) => {
13511351
const { services } = readFileContent({ currentDir })
13521352
return services
13531353
}
1354+
1355+
const getExistingApps = ({ currentDir }) => {
1356+
const { apps } = readFileContent({ currentDir });
1357+
1358+
return apps
1359+
}
1360+
13541361
const registerServiceWithSuiteJson = ({ root_dir, name, port }) => {
13551362
// Read the project configuration file
13561363
const configPath = resolve(root_dir, 'suite.json');
@@ -1365,6 +1372,20 @@ const registerServiceWithSuiteJson = ({ root_dir, name, port }) => {
13651372
writeFile(configPath, JSON.stringify(config, null, 2), 'utf8');
13661373
}
13671374

1375+
const registerAppWithSuiteJson = ({ root_dir, name, services }) => {
1376+
// Read the project configuration file
1377+
const configPath = resolve(root_dir, 'suite.json');
1378+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
1379+
const services_names = services.map((s) => s.name);
1380+
if (!config.apps) {
1381+
config.apps = [];
1382+
}
1383+
config.apps.push({ name, services: services_names });
1384+
1385+
// keep the apps ordered by names
1386+
config.apps.sort((a, b) => a.name - b.name);
1387+
writeFile(configPath, JSON.stringify(config, null, 2), 'utf8');
1388+
}
13681389
/**
13691390
* Releases a package or generates a release for the workspace.
13701391
* @async
@@ -1395,20 +1416,35 @@ const test = async ({ package }) => {
13951416

13961417
const scaffoldApp = ({ answers }) => {
13971418

1398-
const { webserver } = readFileContent({ currentDir: cwd() })
1399-
const project_root = generatRootPath({ currentDir: cwd() })
1400-
const app_directory = join(project_root, 'gateways/apps', answers.app_name)
1401-
const webserver_dir = join(app_directory, webserver)
1402-
const data_dir = join(app_directory, 'data')
1419+
const { webserver } = readFileContent({ currentDir: cwd() });
1420+
const { projectName } = readFileContent({ currentDir: cwd() });
1421+
const project_root = generatRootPath({ currentDir: cwd() });
1422+
const app_directory = join(project_root, 'gateways/apps', answers.app_name);
1423+
const webserver_dir = join(app_directory, webserver);
1424+
const krakend_dir = join(app_directory, 'krakend');
1425+
const data_dir = join(app_directory, 'data');
14031426

14041427
// Remove the directory if it already exists
14051428
if (existsSync(app_directory)) {
14061429
rmSync(app_directory, { recursive: true });
14071430
}
14081431
mkdirSync(webserver_dir, { recursive: true });
1432+
mkdirSync(krakend_dir, { recursive: true });
14091433
mkdirSync(data_dir, { recursive: true });
1410-
writeFileSync(join(app_directory, 'docker-compose.dev.yml'), assets.dockerComposeContent({ services: answers.services, app_name: answers.app_name, webserver }));
1411-
writeFileSync(join(app_directory, 'docker-compose.yml'), assets.dockerComposeContent({ services: answers.services, app_name: answers.app_name, webserver }));
1434+
writeFileSync(join(app_directory, 'docker-compose.dev.yml'), assets.dockerComposeContent({
1435+
services: answers.services,
1436+
app_name: answers.app_name,
1437+
webserver,
1438+
krakend_port: answers.gateway_port,
1439+
env: 'dev'
1440+
}));
1441+
writeFileSync(join(app_directory, 'docker-compose.yml'), assets.dockerComposeContent({
1442+
services: answers.services,
1443+
app_name: answers.app_name,
1444+
webserver,
1445+
krakend_port: answers.gateway_port,
1446+
env: 'prod'
1447+
}));
14121448
ora().succeed(`Generated docker-compose configs at: ${app_directory}`)
14131449
switch (webserver) {
14141450
case 'nginx':
@@ -1418,6 +1454,17 @@ const scaffoldApp = ({ answers }) => {
14181454

14191455
ora().info('Handling other webservers');
14201456
}
1457+
generateKrakendConfiguration({
1458+
services: answers.services,
1459+
krakend_dir,
1460+
projectName,
1461+
gateway_port: answers.gateway_port,
1462+
api_version: answers.api_version,
1463+
gateway_cache_period: answers.gateway_cache_period,
1464+
gateway_timeout: answers.gateway_timeout
1465+
});
1466+
registerAppWithSuiteJson({ root_dir: project_root, name: answers.app_name, services: answers.services })
1467+
14211468
}
14221469
const readFileContent = ({ currentDir }) => {
14231470
const root_dir = generatRootPath({ currentDir, height: 5 })
@@ -1433,6 +1480,27 @@ const generateNginxConfiguration = ({ services, webserver_dir }) => {
14331480
writeFile(join(webserver_dir, 'Dockerfile.dev'), assets.nginxDockerfileContent());
14341481
ora().succeed(`Generated webserver configs at: ${webserver_dir}`)
14351482
}
1483+
const generateKrakendConfiguration = ({
1484+
services,
1485+
krakend_dir,
1486+
projectName,
1487+
gateway_port,
1488+
api_version,
1489+
gateway_cache_period,
1490+
gateway_timeout
1491+
}) => {
1492+
writeFile(join(krakend_dir,
1493+
'krakend.json'),
1494+
assets.krakendConfigContent({
1495+
services,
1496+
projectName,
1497+
gateway_port,
1498+
api_version,
1499+
gateway_cache_period,
1500+
gateway_timeout
1501+
}));
1502+
ora().succeed(`Generated krakend gateway configs at: ${krakend_dir}`)
1503+
}
14361504
const installDependencies = async ({ project_base, workspace, spinner, deps, flags }) => {
14371505

14381506

@@ -1549,6 +1617,66 @@ const getDependencies = ({ type, workspace }) => {
15491617
break
15501618
}
15511619
}
1620+
1621+
1622+
const scaffoldGateways = async ({ answers }) => {
1623+
console.log({answers})
1624+
const { webserver } = readFileContent({ currentDir: cwd() });
1625+
const { projectName } = readFileContent({ currentDir: cwd() });
1626+
const { apps } = answers;
1627+
const project_root = generatRootPath({ currentDir: cwd() });
1628+
1629+
await Promise.all(apps.map(async (app) => {
1630+
return scaffoldGateway({ project_root, app, webserver, projectName })
1631+
}))
1632+
}
1633+
1634+
const scaffoldGateway = ({ project_root, app, webserver, projectName }) => {
1635+
const app_directory = join(project_root, 'gateways/apps', app.name);
1636+
const webserver_dir = join(app_directory, webserver);
1637+
const krakend_dir = join(app_directory, 'krakend');
1638+
const services = app.services;
1639+
1640+
// Remove the directory if it already exists
1641+
if (existsSync(krakend_dir)) {
1642+
rmSync(krakend_dir, { recursive: true });
1643+
}
1644+
mkdirSync(webserver_dir, { recursive: true });
1645+
mkdirSync(krakend_dir, { recursive: true });
1646+
writeFileSync(join(app_directory, 'docker-compose.dev.yml'), assets.dockerComposeContent({
1647+
services,
1648+
app_name: app.name,
1649+
webserver,
1650+
krakend_port: answers.gateway_port,
1651+
env: 'dev'
1652+
}));
1653+
writeFileSync(join(app_directory, 'docker-compose.yml'), assets.dockerComposeContent({
1654+
services,
1655+
app_name: app.name,
1656+
webserver,
1657+
krakend_port: answers.gateway_port,
1658+
env: 'prod'
1659+
}));
1660+
ora().succeed(`Generated docker-compose configs at: ${app_directory}`)
1661+
switch (webserver) {
1662+
case 'nginx':
1663+
generateNginxConfiguration({ services, webserver_dir });
1664+
break
1665+
default:
1666+
ora().info('Handling other webservers');
1667+
}
1668+
generateKrakendConfiguration({
1669+
services,
1670+
krakend_dir,
1671+
projectName,
1672+
gateway_port: answers.gateway_port,
1673+
api_version: answers.api_version,
1674+
gateway_cache_period: answers.gateway_cache_period,
1675+
gateway_timeout: answers.gateway_timeout
1676+
});
1677+
1678+
}
1679+
15521680
module.exports = {
15531681
generateDirectoryPath,
15541682
changeDirectory,
@@ -1578,5 +1706,7 @@ module.exports = {
15781706
getNextAvailablePort,
15791707
getExistingServices,
15801708
test,
1581-
scaffoldApp
1709+
scaffoldApp,
1710+
scaffoldGateways,
1711+
getExistingApps
15821712
}

0 commit comments

Comments
 (0)