Skip to content

Commit b08078a

Browse files
authored
fix: use node-fetch for data downloads to avoid Axios proxy issues (#353)
We've seen users hit various issues with Axios and their HTTP proxies. I've been able to reproduce the first issue below running a squid proxy locally. Switching to node-fetch for the data download seems to fix this. Axios issues: * axios/axios#2452 * axios/axios#5256
1 parent acab0d6 commit b08078a

3 files changed

Lines changed: 125 additions & 58 deletions

File tree

package-lock.json

Lines changed: 61 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"lodash": "^4.17.21",
5454
"mingo": "^6.1.2",
5555
"node-cache": "^5.1.2",
56+
"node-fetch": "^2.6.2",
5657
"pg": "^8.8.0",
5758
"pg-copy-streams": "^6.0.4",
5859
"pg-format": "^1.0.4",
@@ -70,6 +71,7 @@
7071
"@types/glob": "^8.0.0",
7172
"@types/lodash": "^4.14.191",
7273
"@types/node": "^18.11.8",
74+
"@types/node-fetch": "^2.6.2",
7375
"@types/pg": "^8.6.5",
7476
"@types/pg-copy-streams": "^1.2.1",
7577
"@types/pg-format": "^1.0.2",

src/cmd/dataDownload.ts

Lines changed: 62 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import axios, { AxiosResponse } from 'axios';
1+
import fetch, { Response } from 'node-fetch';
22
import ProgressBar from 'progress';
33
import fs from 'fs';
44
import yargs from 'yargs';
@@ -13,7 +13,7 @@ async function run() {
1313
out: { type: 'string', default: './data/products/products.csv.gz' },
1414
}).argv;
1515

16-
let latestResp: AxiosResponse<{ downloadUrl: string }>;
16+
let latestResp: Response;
1717

1818
if (!config.infracostAPIKey) {
1919
config.logger.error('Please set INFRACOST_API_KEY.');
@@ -24,7 +24,7 @@ async function run() {
2424
}
2525

2626
try {
27-
latestResp = await axios.get(
27+
latestResp = await fetch(
2828
`${config.infracostPricingApiEndpoint}/data-download/latest`,
2929
{
3030
headers: {
@@ -33,62 +33,78 @@ async function run() {
3333
},
3434
}
3535
);
36-
} catch (e) {
37-
if (e.response?.status === 403) {
38-
config.logger.error(
39-
'You do not have permission to download data. Please set a valid INFRACOST_API_KEY.'
40-
);
36+
if (!latestResp.ok) {
37+
const body = latestResp.body.read().toString();
38+
4139
config.logger.error(
42-
'A new key can be obtained by installing the infracost CLI and running "infracost auth login". The key is usually saved in ~/.config/infracost/credentials.yml'
40+
`There was an error downloading data: HTTP ${latestResp.status}: ${body}`
4341
);
44-
} else {
45-
config.logger.error(`There was an error downloading data: ${e.message}`);
42+
43+
try {
44+
const errorData = (await JSON.parse(body)) as { error: string };
45+
if (
46+
latestResp.status === 403 &&
47+
errorData.error === 'Invalid API key'
48+
) {
49+
config.logger.error(
50+
'You do not have permission to download data. Please set a valid INFRACOST_API_KEY.'
51+
);
52+
config.logger.error(
53+
'A new key can be obtained by installing the infracost CLI and running "infracost auth login". The key is usually saved in ~/.config/infracost/credentials.yml'
54+
);
55+
}
56+
} catch (e) {
57+
// eslint-disable no-empty
58+
// We don't care if the body is not valid JSON since we log it above anyway
59+
}
60+
61+
process.exit(1);
4662
}
63+
} catch (e) {
64+
config.logger.error(`There was an error downloading data: ${e}`);
4765
process.exit(1);
4866
}
4967

50-
const { downloadUrl } = latestResp.data;
68+
const { downloadUrl } = (await latestResp.json()) as { downloadUrl: string };
5169
config.logger.debug(`Downloading dump from ${downloadUrl}`);
5270

5371
const writer = fs.createWriteStream(argv.out);
54-
await axios({
55-
method: 'get',
56-
url: downloadUrl,
57-
responseType: 'stream',
58-
}).then(
59-
(resp) =>
60-
new Promise((resolve, reject) => {
61-
const progressBar = new ProgressBar(
62-
`-> downloading ${argv.out} [:bar] :percent (:etas remaining)`,
63-
{
64-
width: 40,
65-
complete: '=',
66-
incomplete: ' ',
67-
renderThrottle: 500,
68-
total: parseInt(resp.headers['content-length'] || '0', 10),
69-
}
70-
);
7172

72-
resp.data.on('data', (chunk: { length: number }) =>
73-
progressBar.tick(chunk.length)
74-
);
73+
await new Promise((resolve, reject) => {
74+
fetch(downloadUrl, {
75+
method: 'get',
76+
}).then((resp) => {
77+
const progressBar = new ProgressBar(
78+
`-> downloading ${argv.out} [:bar] :percent (:etas remaining)`,
79+
{
80+
width: 40,
81+
complete: '=',
82+
incomplete: ' ',
83+
renderThrottle: 500,
84+
total: parseInt(resp.headers.get('content-length') || '0', 10),
85+
}
86+
);
87+
88+
resp.body.on('data', (chunk: { length: number }) =>
89+
progressBar.tick(chunk.length)
90+
);
7591

76-
resp.data.pipe(writer);
92+
resp.body.pipe(writer);
7793

78-
let error: Error | null = null;
79-
writer.on('error', (err) => {
80-
error = err;
81-
writer.close();
82-
reject(err);
83-
});
94+
let error: Error | null = null;
95+
writer.on('error', (err) => {
96+
error = err;
97+
writer.close();
98+
reject(err);
99+
});
84100

85-
writer.on('close', () => {
86-
if (!error) {
87-
resolve(true);
88-
}
89-
});
90-
})
91-
);
101+
writer.on('close', () => {
102+
if (!error) {
103+
resolve(true);
104+
}
105+
});
106+
});
107+
});
92108
}
93109

94110
config.logger.info('Starting: downloading DB data');

0 commit comments

Comments
 (0)