Skip to content

Commit 70daed7

Browse files
committed
import api tool, make the whole thing configurable
1 parent c4d80c8 commit 70daed7

File tree

13 files changed

+625
-159
lines changed

13 files changed

+625
-159
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,10 @@ dist
104104

105105
# TernJS port file
106106
.tern-port
107+
108+
109+
*.txt
110+
*.log
111+
*.csv
112+
*.tsv
113+
config.hjson

api.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/sh
2+
3+
./node_modules/.bin/ts-node lib/api.ts $@

compare.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/sh
2+
3+
./node_modules/.bin/ts-node lib/compare.ts $@

config-example.hjson

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
# "param" or "header"
3+
authStyle: "header",
4+
5+
# only required if using authStyle="param"
6+
# authParam: "api_key",
7+
8+
# first one is default
9+
keyTypes: ['test', 'live'],
10+
11+
hosts: {
12+
prod: {
13+
host: 'prod.api.com',
14+
aliases: ['production'],
15+
}
16+
staging: {
17+
host: 'staging.api.com'
18+
},
19+
local: {
20+
host: 'localhost:8000',
21+
keyEnv: 'staging',
22+
protocol: 'http',
23+
},
24+
user: {
25+
takesArg: true,
26+
host: 'USER-staging.api.com'
27+
keyEnv: 'staging',
28+
},
29+
}
30+
}

lib/api.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/* eslint-disable no-console */
2+
import * as _ from "lodash";
3+
import * as queryString from 'query-string';
4+
import { globalCommandLineOptions } from "./cli-utils";
5+
import { argvToApiEnv, getApiEnvCommandLineOptions, ApiEnv, fixApiEnvKey } from "./apiEnv";
6+
import { runQuery } from "./run-query";
7+
import config from "./config";
8+
9+
const makeUsageString = (toolName: string) => `This tool has a lot of options, here are some examples:
10+
11+
${toolName} --prod /v1/endpoint param1=X param2=Y
12+
query /v1/endpoint in prod, with the params specified, and look for PROD_API_KEY in the environemnt or .env
13+
14+
${toolName} --prod /v1/endpoint --key YYYY param1=X param2=Y
15+
same thing, but will use the key specified on the commandline
16+
17+
${toolName} --local /v1/endpoint param1=X param2=Y
18+
would run against localhost, looking for LOCAL_API_KEY or reaching into mongo to try to find one
19+
20+
${toolName} --local /v1/endpoint --env=staging param1=X param2=Y
21+
would run against localhost, but look for STAGING_API_KEY
22+
`
23+
24+
function parseArgv() {
25+
let yargs = require("yargs");
26+
27+
yargs.usage(makeUsageString(process.argv[0]));
28+
29+
_.forEach(globalCommandLineOptions, (val, key) => {
30+
yargs.option(key, val);
31+
});
32+
33+
_.forEach(getApiEnvCommandLineOptions(), (val, key) => {
34+
yargs.option(key, val);
35+
});
36+
37+
return yargs.argv;
38+
}
39+
40+
const argv = parseArgv();
41+
42+
let endpoint = argv._[0];
43+
44+
if (!endpoint) {
45+
console.error("This tool requires an api endpoint to be specified");
46+
process.exit(1);
47+
}
48+
49+
function printResponse(response: any) {
50+
console.log(response);
51+
52+
if (typeof response === "object") {
53+
if (process.stdout.isTTY) {
54+
console.dir(response, { depth: null, colors: true });
55+
} else {
56+
console.log(JSON.stringify(response, null, 4));
57+
}
58+
} else {
59+
console.log(response);
60+
}
61+
}
62+
63+
let apiEnv: ApiEnv;
64+
65+
let params: any = {};
66+
67+
if (argv._.length === 1 && argv._[0].startsWith('http')) {
68+
// The user just specified a radar url, they probably just want to run it with the
69+
// right api key. So infer all the params from the passed url
70+
const url = new URL(argv._[0]);
71+
apiEnv = {
72+
...apiEnv,
73+
protocol: url.protocol.replace(':', ''),
74+
host: url.host,
75+
};
76+
77+
if (!apiEnv.keyEnv) {
78+
_.forEach(config.hosts, (hostEntry) => {
79+
if (url.host === hostEntry.host) {
80+
apiEnv.keyEnv = hostEntry.keyEnv
81+
}
82+
})
83+
}
84+
85+
fixApiEnvKey(apiEnv);
86+
87+
endpoint = url.pathname;
88+
params = queryString.parse(url.search);
89+
} else {
90+
apiEnv = argvToApiEnv(argv);
91+
92+
argv._.slice(1).forEach((datum: string) => {
93+
if (!datum.includes("=")) {
94+
console.error(`data argument ${datum} did not have =, exiting`);
95+
process.exit(1);
96+
}
97+
const key = datum.split("=")[0];
98+
const value = datum.split("=")[1];
99+
params[key] = value;
100+
});
101+
}
102+
103+
runQuery(apiEnv, { params, method: argv.method, endpoint, verbose: true }).then(({ data }) => printResponse(data));

lib/apiEnv.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import * as _ from "lodash";
2+
import logger from "./logger";
3+
import { ConfigHostEntry } from "./config";
4+
import config from "./config";
5+
import { failedExit } from "./cli-utils";
6+
7+
const apiEnvCommandLineOptions: Record<string, any> = {
8+
host: {
9+
type: "string",
10+
description: "Host/port - will override --env",
11+
},
12+
protocol: {
13+
choices: ["http", "https"],
14+
description:
15+
"What protocol to use (if not specified in url), defaults to http for local, https otherwise",
16+
},
17+
key: {
18+
type: "string",
19+
description: `Authorization key, if not specified will try to find one in the env, in .env or, in local mode, directly in mongo`,
20+
},
21+
};
22+
23+
export function getApiEnvCommandLineOptions(): Record<string, any> {
24+
if (config.keyTypes) {
25+
apiEnvCommandLineOptions.key_type = {
26+
choices: config.keyTypes,
27+
default: config.keyTypes[0],
28+
description: "authorization key type to use",
29+
};
30+
}
31+
32+
if (config.hosts) {
33+
apiEnvCommandLineOptions.env = {
34+
choices: [..._.keys(config.hosts), ..._.flatMap(config.hosts, (v) => v.aliases || [])],
35+
description: `api host to talk to`,
36+
};
37+
38+
_.forEach(config.hosts, (hostEntry: ConfigHostEntry, key: string) => {
39+
apiEnvCommandLineOptions[key] = {
40+
alias: hostEntry.aliases,
41+
description: `set host to ${hostEntry.host}`,
42+
};
43+
});
44+
}
45+
return apiEnvCommandLineOptions;
46+
}
47+
48+
export interface ApiEnv {
49+
key: string;
50+
protocol: string;
51+
host: string;
52+
keyEnv: string;
53+
keyType?: string;
54+
}
55+
56+
type KeyParams = Pick<ApiEnv, "keyEnv" | "keyType">;
57+
58+
function findKey({ keyEnv, keyType }: KeyParams): string {
59+
require("dotenv").config();
60+
61+
const env_variable_name = [keyEnv, keyType, "API_KEY"]
62+
.filter((s) => !_.isEmpty(s))
63+
.join("_")
64+
.toUpperCase();
65+
logger.info(`Looking for key in env ${env_variable_name}`);
66+
const key = process.env[env_variable_name];
67+
if (!key) {
68+
failedExit(`No key found for ${env_variable_name} in .env and --key not specified`);
69+
}
70+
return key;
71+
}
72+
73+
export function fixApiEnvKey(apiEnv: Partial<ApiEnv>) {
74+
apiEnv.key =
75+
apiEnv.key ||
76+
findKey({ keyEnv: apiEnv.keyEnv || "", keyType: apiEnv.keyType || config.keyTypes?.[0] });
77+
}
78+
79+
export function argvToApiEnv(argv: any): ApiEnv {
80+
let apiEnv: Partial<ApiEnv> = _.clone(argv);
81+
82+
let aliasedHostEntry: ConfigHostEntry;
83+
_.forEach(config.hosts, (hostEntry: ConfigHostEntry, key: string) => {
84+
if (argv[key]) {
85+
if (aliasedHostEntry) {
86+
throw new Error(`Can only specify one of ${_.keys(config.hosts).join(",")}`);
87+
}
88+
89+
if (hostEntry.takesArg) {
90+
const toReplace = key.toUpperCase();
91+
hostEntry.host = hostEntry.host.replace(toReplace, argv[key]);
92+
}
93+
94+
hostEntry.keyEnv = hostEntry.keyEnv || key;
95+
96+
aliasedHostEntry = hostEntry;
97+
}
98+
});
99+
100+
if (aliasedHostEntry) {
101+
apiEnv = {
102+
...aliasedHostEntry,
103+
...apiEnv,
104+
};
105+
}
106+
107+
if (apiEnv.host.startsWith("http")) {
108+
const url = new URL(apiEnv.host);
109+
apiEnv.host = url.host;
110+
apiEnv.protocol = url.protocol.replace(":", "");
111+
}
112+
113+
fixApiEnvKey(apiEnv);
114+
115+
return apiEnv as ApiEnv;
116+
}

lib/cli-utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export function failedExit(msg: string) {
2+
console.error(msg);
3+
process.exit(1);
4+
}
5+
6+
export const globalCommandLineOptions = {
7+
method: {
8+
choices: ["GET", "POST", "PUT"],
9+
default: "GET",
10+
description: "what http method to use",
11+
},
12+
color: {
13+
type: "boolean",
14+
description: "turns on/off colorized output, defaults to true for stdin, false for redirected output"
15+
},
16+
};

0 commit comments

Comments
 (0)