Skip to content

Commit 205a3de

Browse files
committed
Add dump config command
1 parent bd3e82f commit 205a3de

File tree

12 files changed

+257
-61
lines changed

12 files changed

+257
-61
lines changed

bin/kongfig-apply

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env node
2+
3+
require("babel-polyfill");
4+
require('../lib/cli-apply.js');

bin/kongfig-dump

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env node
2+
3+
require("babel-polyfill");
4+
require('../lib/cli-dump.js');

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"isomorphic-fetch": "^2.1.1",
2424
"js-yaml": "^3.4.3",
2525
"minimist": "^1.2.0",
26-
"object-assign": "^4.0.1"
26+
"object-assign": "^4.0.1",
27+
"prettyjson": "^1.1.3"
2728
},
2829
"devDependencies": {
2930
"babel-core": "^6.2.1",

src/adminApi.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import createRouter from './router';
22

33
require('isomorphic-fetch');
44

5+
let pluginSchemasCache;
6+
57
export default host => {
68
const router = createRouter(host);
79

@@ -11,10 +13,26 @@ export default host => {
1113
fetchPlugins: apiName => getJson(router({name: 'api-plugins', params: {apiName}})),
1214
fetchConsumers: () => getJson(router({name: 'consumers'})),
1315
fetchConsumerCredentials: (username, plugin) => getJson(router({name: 'consumer-credentials', params: {username, plugin}})),
16+
17+
// this is very chatty call and doesn't change so its cached
18+
fetchPluginSchemas: () => {
19+
if (pluginSchemasCache) {
20+
return Promise.resolve(pluginSchemasCache);
21+
}
22+
23+
return getJson(router({name: 'plugins-enabled'}))
24+
.then(json => Promise.all(json.enabled_plugins.map(plugin => getPluginScheme(plugin, plugin => router({name: 'plugins-scheme', params: {plugin}})))))
25+
.then(all => pluginSchemasCache = new Map(all));
26+
},
1427
requestEndpoint: (endpoint, params) => fetch(router(endpoint), prepareOptions(params))
1528
}
1629
}
1730

31+
function getPluginScheme(plugin, schemaRoute) {
32+
return getJson(schemaRoute(plugin))
33+
.then(({fields}) => [plugin, fields]);
34+
}
35+
1836
function getJson(uri) {
1937
return fetch(uri, {
2038
method: 'GET',
@@ -28,7 +46,7 @@ function getJson(uri) {
2846
throw new Error(`Content overflow on ${uri}, paggination not supported`);
2947
}
3048

31-
return json.data;
49+
return json.data ? json.data : json;
3250
});
3351
}
3452

src/cli-apply.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import execute from './core';
2+
import adminApi from './adminApi';
3+
import colors from 'colors';
4+
import configLoader from './configLoader';
5+
import program from 'commander';
6+
7+
program
8+
.version(require("../package.json").version)
9+
.option('--path <value>', 'Path to the configuration file')
10+
.option('--host <value>', 'Kong admin host (default: localhost:8001)', 'localhost:8001')
11+
.parse(process.argv);
12+
13+
if (!program.path) {
14+
console.log('--path to the config file is required'.red);
15+
process.exit(1);
16+
}
17+
18+
let config = configLoader(program.path);
19+
let host = program.host || config.host;
20+
21+
if (!host) {
22+
console.log('Kong admin host must be specified in config or --host'.red);
23+
process.exit(1);
24+
}
25+
26+
console.log(`Apply config to ${host}`.green);
27+
28+
execute(config, adminApi(host))
29+
.catch(error => {
30+
console.log(`${error}`.red);
31+
process.exit(1);
32+
});

src/cli-dump.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import readKongApi from './readKongApi';
2+
import {pretty} from './prettyConfig';
3+
import adminApi from './adminApi';
4+
import colors from 'colors';
5+
6+
import program from 'commander';
7+
8+
program
9+
.version(require("../package.json").version)
10+
.option('-f, --format <value>', 'Export format [screen, json, yaml] (default: yaml)', /^(screen|json|yaml|yml)$/, 'yaml')
11+
.option('--host <value>', 'Kong admin host (default: localhost:8001)', 'localhost:8001')
12+
.parse(process.argv);
13+
14+
if (!program.host) {
15+
console.log('--host to the kong admin is required e.g. localhost:8001'.red);
16+
process.exit(1);
17+
}
18+
19+
readKongApi(adminApi(program.host))
20+
.then(apis => {
21+
return {host: program.host, apis};
22+
})
23+
.then(pretty(program.format))
24+
.then(config => {
25+
process.stdout.write(config + '\n');
26+
})
27+
.catch(error => {
28+
console.log(`${error}`.red);
29+
process.exit(1);
30+
});

src/cli.js

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,9 @@
1-
import execute from './core';
2-
import adminApi from './adminApi';
3-
import colors from 'colors';
4-
import configLoader from './configLoader';
1+
import commander from 'commander';
52

6-
const argv = require('minimist')(process.argv.slice(2), { string: ['path', 'host'] });
3+
let pkg = require("../package.json");
74

8-
if (!argv.path) {
9-
console.log('--path to the config file is required'.red);
10-
process.exit(1);
11-
}
12-
13-
let config = configLoader(argv.path);
14-
15-
let host = argv.host || config.host;
16-
17-
if (!host) {
18-
console.log('Kong admin host must be specified in config or --host'.red);
19-
process.exit(1);
20-
}
21-
22-
execute(config, adminApi(host))
23-
.catch(error => {
24-
console.log(`${error}`.red);
25-
process.exit(1);
26-
});
5+
commander
6+
.version(pkg.version)
7+
.command('apply', 'Apply config to a kong server', {isDefault: true})
8+
.command('dump', 'Dump the configuration from a kong server')
9+
.parse(process.argv);

src/core.js

Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import colors from 'colors';
44
import createAdminApi from './adminApi';
55
import assign from 'object-assign';
6+
import kongState from './kongState';
67
import {
78
noop,
89
createApi,
@@ -89,58 +90,37 @@ function _executeActionOnApi(action, adminApi) {
8990

9091
function _bindWorldState(adminApi) {
9192
return f => async () => {
92-
const state = await _getKongState(adminApi);
93+
const state = await kongState(adminApi);
9394
return f(_createWorld(state));
9495
}
9596
}
9697

97-
async function _getKongState({fetchApis, fetchPlugins, fetchConsumers, fetchConsumerCredentials}) {
98-
const apis = await fetchApis();
99-
const apisWithPlugins = await Promise.all(apis.map(async item => {
100-
const plugins = await fetchPlugins(item.name);
101-
102-
return {...item, plugins};
103-
}));
104-
105-
const consumers = await fetchConsumers();
106-
const consumersWithCredentials = await Promise.all(consumers.map(async consumer => {
107-
if (consumer.custom_id && !consumer.username) {
108-
console.log(`Consumers with only custom_id not supported: ${consumer.custom_id}`);
109-
110-
return consumer;
111-
}
112-
113-
const oauth2 = await fetchConsumerCredentials(consumer.username, 'oauth2');
114-
const keyAuth = await fetchConsumerCredentials(consumer.username, 'key-auth');
115-
const jwt = await fetchConsumerCredentials(consumer.username, 'jwt');
116-
const basicAuth = await fetchConsumerCredentials(consumer.username, 'basic-auth');
117-
118-
return {...consumer, credentials: {oauth2, keyAuth, jwt, basicAuth}};
119-
}));
120-
121-
return {
122-
apis: apisWithPlugins,
123-
consumers: consumersWithCredentials
124-
};
125-
}
126-
12798
function _createWorld({apis, consumers}) {
128-
return {
99+
const world = {
129100
hasApi: apiName => apis.some(api => api.name === apiName),
130-
getPluginId: (apiName, pluginName) => {
101+
getApi: apiName => {
131102
const api = apis.find(api => api.name === apiName);
132103

133104
if (!api) {
134105
throw new Error(`Unable to find api ${apiName}`);
135106
}
136107

137-
const plugin = api.plugins.find(plugin => plugin.name == pluginName);
108+
return api;
109+
},
110+
getPlugin: (apiName, pluginName) => {
111+
const plugin = world.getApi(apiName).plugins.find(plugin => plugin.name == pluginName);
138112

139113
if (!plugin) {
140114
throw new Error(`Unable to find plugin ${pluginName}`);
141115
}
142116

143-
return plugin.id;
117+
return plugin;
118+
},
119+
getPluginId: (apiName, pluginName) => {
120+
return world.getPlugin(apiName, pluginName).id;
121+
},
122+
getPluginAttributes: (apiName, pluginName) => {
123+
return world.getPlugin(apiName, pluginName).config;
144124
},
145125
hasPlugin: (apiName, pluginName) => {
146126
return apis.some(api => api.name === apiName && api.plugins.some(plugin => plugin.name == pluginName));
@@ -191,6 +171,8 @@ function _createWorld({apis, consumers}) {
191171
return credential.id;
192172
}
193173
};
174+
175+
return world;
194176
}
195177

196178
function extractCredentialId(credentials, name, attributes) {

src/kongState.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export default async ({fetchApis, fetchPlugins, fetchConsumers, fetchConsumerCredentials}) => {
2+
const apis = await fetchApis();
3+
const apisWithPlugins = await Promise.all(apis.map(async item => {
4+
const plugins = await fetchPlugins(item.name);
5+
6+
return {...item, plugins};
7+
}));
8+
9+
const consumers = await fetchConsumers();
10+
const consumersWithCredentials = await Promise.all(consumers.map(async consumer => {
11+
if (consumer.custom_id && !consumer.username) {
12+
console.log(`Consumers with only custom_id not supported: ${consumer.custom_id}`);
13+
14+
return consumer;
15+
}
16+
17+
const oauth2 = await fetchConsumerCredentials(consumer.username, 'oauth2');
18+
const keyAuth = await fetchConsumerCredentials(consumer.username, 'key-auth');
19+
const jwt = await fetchConsumerCredentials(consumer.username, 'jwt');
20+
const basicAuth = await fetchConsumerCredentials(consumer.username, 'basic-auth');
21+
22+
return {...consumer, credentials: {oauth2, keyAuth, jwt, basicAuth}};
23+
}));
24+
25+
return {
26+
apis: apisWithPlugins,
27+
consumers: consumersWithCredentials
28+
};
29+
};

src/prettyConfig.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import prettyjson from 'prettyjson';
2+
import yaml from 'js-yaml';
3+
4+
export function pretty(format) {
5+
switch (format) {
6+
case 'json': return config => prettyJson(removeInfo(config));
7+
case 'yaml': return config => prettyYaml(removeInfo(config));
8+
case 'yml': return config => prettyYaml(removeInfo(config));
9+
case 'screen': return prettyScreen;
10+
default:
11+
throw new Error('Unknown --format ' + format);
12+
}
13+
}
14+
15+
export function prettyScreen(config) {
16+
return prettyjson.render(config, {});
17+
}
18+
19+
export function prettyJson(config) {
20+
return JSON.stringify(config, null, ' ');
21+
}
22+
23+
export function prettyYaml(config) {
24+
return yaml.safeDump(config);
25+
}
26+
27+
export function removeInfo(config) {
28+
return JSON.parse(JSON.stringify(config, (key, value) => {
29+
if (key == '_info') {
30+
return undefined;
31+
}
32+
33+
return value;
34+
}));
35+
}

src/readKongApi.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import kongState from './kongState';
2+
3+
export default async (adminApi) => {
4+
return Promise.all([kongState(adminApi), adminApi.fetchPluginSchemas()])
5+
.then(([state, schemas]) => {
6+
const prepareConfig = (plugin, config) => stripConfig(config, schemas.get(plugin));
7+
const parseApiPluginsForSchemes = plugins => parseApiPlugins(plugins, prepareConfig);
8+
9+
return parseApis(state.apis, parseApiPluginsForSchemes);
10+
})
11+
};
12+
13+
function parseApis(apis, parseApiPlugins) {
14+
return apis.map(({
15+
name, plugins,
16+
request_host, request_path, strip_request_path, preserve_host, upstream_url,
17+
id, created_at}) => {
18+
return {
19+
name,
20+
plugins: parseApiPlugins(plugins),
21+
attributes: {
22+
request_host,
23+
request_path,
24+
strip_request_path,
25+
preserve_host,
26+
upstream_url,
27+
},
28+
_info: {
29+
id,
30+
created_at
31+
}
32+
};
33+
});
34+
}
35+
36+
function parseApiPlugins(plugins, prepareConfig) {
37+
return plugins.map(({
38+
name,
39+
config,
40+
id, api_id, consumer_id, enabled, created_at
41+
}) => {
42+
return {
43+
name,
44+
attributes: {
45+
config: prepareConfig(name, config)
46+
},
47+
_info: {
48+
id,
49+
//api_id,
50+
consumer_id,
51+
enabled,
52+
created_at
53+
}
54+
};
55+
});
56+
}
57+
58+
function stripConfig(config, schema) {
59+
const mutableConfig = {...config};
60+
61+
if (false) {
62+
// strip default keys
63+
Object.keys(config).map(key => {
64+
if (schema[key].hasOwnProperty('default') && schema[key].default === config[key]) {
65+
delete mutableConfig[key];
66+
}
67+
});
68+
}
69+
70+
// remove some cache values
71+
delete mutableConfig['_key_der_cache'];
72+
delete mutableConfig['_cert_der_cache'];
73+
74+
return mutableConfig;
75+
}

0 commit comments

Comments
 (0)