Skip to content

Commit eebe9dc

Browse files
committed
use network retries
1 parent c7d8561 commit eebe9dc

File tree

2 files changed

+120
-32
lines changed

2 files changed

+120
-32
lines changed

.github/workflows/integration-tests.yml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: integration tests
22
on:
33
pull_request:
44
schedule:
5-
- cron: "0 11 * * *" #once a day at 11 UTC
5+
- cron: '0 11 * * *' #once a day at 11 UTC
66
concurrency:
77
group: integration-tests-${{ github.ref }}
88
cancel-in-progress: true
@@ -52,11 +52,11 @@ jobs:
5252
pluginDir: ${{ fromJson(needs.setup-matrix.outputs.pluginDirs) }}
5353
fail-fast: false
5454
env:
55-
GF_AUTH_ANONYMOUS_ENABLED: "true"
56-
GF_AUTH_ANONYMOUS_ORG_ROLE: "Admin"
57-
GF_AUTH_BASIC_ENABLED: "false"
58-
GF_DEFAULT_APP_MODE: "development"
59-
GF_INSTALL_PLUGINS: "marcusolsson-static-datasource"
55+
GF_AUTH_ANONYMOUS_ENABLED: 'true'
56+
GF_AUTH_ANONYMOUS_ORG_ROLE: 'Admin'
57+
GF_AUTH_BASIC_ENABLED: 'false'
58+
GF_DEFAULT_APP_MODE: 'development'
59+
GF_INSTALL_PLUGINS: 'marcusolsson-static-datasource'
6060
steps:
6161
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
6262
with:
@@ -72,9 +72,9 @@ jobs:
7272
- name: Setup node version
7373
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
7474
with:
75-
node-version-file: "${{ matrix.pluginDir }}/.nvmrc"
76-
registry-url: "https://registry.npmjs.org"
77-
cache: "npm"
75+
node-version-file: '${{ matrix.pluginDir }}/.nvmrc'
76+
registry-url: 'https://registry.npmjs.org'
77+
cache: 'npm'
7878

7979
- name: Install dependencies
8080
run: |
@@ -99,7 +99,7 @@ jobs:
9999

100100
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
101101
with:
102-
go-version: "~1.24"
102+
go-version: '~1.24'
103103
check-latest: true
104104
cache-dependency-path: ${{ matrix.pluginDir }}/go.sum
105105
if: steps.backend-check.outputs.MAGEFILE_EXISTS == 'true'
@@ -184,7 +184,7 @@ jobs:
184184
if: steps.has-integration-tests.outputs.DIR == 'true' && steps.should-run-expected-latest-tests.outcome == 'success'
185185
uses: nev7n/wait_for_response@7fef3c1a6e8939d0b09062f14fec50d3c5d15fa1 # v1.0.1
186186
with:
187-
url: "http://localhost:3000/"
187+
url: 'http://localhost:3000/'
188188
responseCode: 200
189189
timeout: 60000
190190
interval: 500
@@ -227,7 +227,7 @@ jobs:
227227
if: steps.has-integration-tests.outputs.DIR == 'true' && steps.should-run-expected-latest-tests.outcome == 'success'
228228
uses: nev7n/wait_for_response@7fef3c1a6e8939d0b09062f14fec50d3c5d15fa1 # v1.0.1
229229
with:
230-
url: "http://localhost:3000/"
230+
url: 'http://localhost:3000/'
231231
responseCode: 200
232232
timeout: 60000
233233
interval: 500
@@ -295,7 +295,7 @@ jobs:
295295
if: steps.has-integration-tests.outputs.DIR == 'true'
296296
uses: nev7n/wait_for_response@7fef3c1a6e8939d0b09062f14fec50d3c5d15fa1 # v1.0.1
297297
with:
298-
url: "http://localhost:3000/"
298+
url: 'http://localhost:3000/'
299299
responseCode: 200
300300
timeout: 60000
301301
interval: 500
@@ -335,8 +335,8 @@ jobs:
335335
run: exit 1
336336

337337
publish-report:
338-
if: ${{ always() }}
339-
needs: [run-integration-tests]
338+
if: ${{ always() && needs.setup-matrix.result == 'success' }}
339+
needs: [setup-matrix, run-integration-tests]
340340
permissions:
341341
contents: write
342342
pull-requests: write

.github/workflows/scripts/npm-to-docker-image.js

Lines changed: 105 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
const https = require("https");
1+
const https = require('https');
2+
const util = require('util');
3+
const exec = util.promisify(require('child_process').exec);
24

3-
const npmTag = "npm-tag";
4-
const baseUrl =
5-
"https://registry.hub.docker.com/v2/repositories/grafana/grafana-dev/tags";
5+
const baseUrl = 'https://registry.hub.docker.com/v2/repositories/grafana/grafana-dev/tags';
66

77
module.exports = async ({ core }) => {
8-
const tag = core.getInput(npmTag);
9-
const exists = await checkIfTagExists(tag);
8+
const { stdout } = await exec('npm view @grafana/ui dist-tags.canary');
9+
const tag = stdout.trim();
1010

11+
const exists = await checkIfTagExists(tag);
1112
if (exists) {
1213
core.info(`Found grafana/grafana-dev:${tag}`);
1314
return tag;
@@ -75,8 +76,8 @@ async function findNextTag(tag) {
7576
}
7677

7778
function parseBuildInfo(tag) {
78-
const hypenIndex = tag.lastIndexOf("-");
79-
const preIndex = tag.lastIndexOf("pre");
79+
const hypenIndex = tag.lastIndexOf('-');
80+
const preIndex = tag.lastIndexOf('pre');
8081
const version = tag.slice(0, hypenIndex);
8182
const build = parseInt(tag.slice(hypenIndex + 1, preIndex), 10);
8283

@@ -93,23 +94,110 @@ function convertToNameSearchParam(build) {
9394
return target.slice(0, target.length - 1);
9495
}
9596

96-
function httpGet(url) {
97+
function httpGet(url, maxRetries = 3, retryDelay = 1000) {
9798
return new Promise((resolve, reject) => {
98-
https
99-
.get(url, (res) => {
99+
let attempts = 0;
100+
let timeoutId = null;
101+
102+
const makeRequest = () => {
103+
attempts++;
104+
105+
const req = https.get(url, { timeout: 10000 }, (res) => {
100106
let data = [];
101107

102-
res.on("data", (chunk) => {
108+
res.on('data', (chunk) => {
103109
data.push(chunk);
104110
});
105111

106-
res.on("end", () => {
112+
res.on('end', () => {
113+
if (timeoutId) {
114+
clearTimeout(timeoutId);
115+
timeoutId = null;
116+
}
117+
107118
const buffer = Buffer.concat(data).toString();
108-
resolve(JSON.parse(buffer));
119+
120+
// Check if response is successful
121+
if (res.statusCode >= 200 && res.statusCode < 300) {
122+
try {
123+
const jsonData = JSON.parse(buffer);
124+
resolve(jsonData);
125+
} catch (parseError) {
126+
const error = new Error(`Failed to parse JSON response from ${url}: ${parseError.message}`);
127+
error.responseBody = buffer.substring(0, 500); // Include first 500 chars for debugging
128+
129+
if (attempts < maxRetries) {
130+
console.warn(
131+
`JSON parse error on attempt ${attempts}/${maxRetries} for ${url}. Retrying in ${
132+
retryDelay * attempts
133+
}ms...`
134+
);
135+
timeoutId = setTimeout(makeRequest, retryDelay * attempts); // Exponential backoff
136+
} else {
137+
reject(error);
138+
}
139+
}
140+
} else if (res.statusCode >= 500 && attempts < maxRetries) {
141+
// Retry on server errors (5xx)
142+
console.warn(
143+
`Server error ${res.statusCode} on attempt ${attempts}/${maxRetries} for ${url}. Retrying in ${
144+
retryDelay * attempts
145+
}ms...`
146+
);
147+
timeoutId = setTimeout(makeRequest, retryDelay * attempts); // Exponential backoff
148+
} else {
149+
// Non-retryable error or max retries exceeded
150+
const error = new Error(`HTTP ${res.statusCode} error from ${url}`);
151+
error.statusCode = res.statusCode;
152+
error.responseBody = buffer.substring(0, 500); // Include first 500 chars for debugging
153+
reject(error);
154+
}
155+
});
156+
157+
res.on('error', (err) => {
158+
if (timeoutId) {
159+
clearTimeout(timeoutId);
160+
timeoutId = null;
161+
}
162+
handleRequestError(err);
109163
});
110-
})
111-
.on("error", (err) => {
112-
reject(err);
113164
});
165+
166+
req.on('timeout', () => {
167+
req.destroy();
168+
const err = new Error(`Request timeout for ${url}`);
169+
err.code = 'ETIMEDOUT';
170+
handleRequestError(err);
171+
});
172+
173+
req.on('error', (err) => {
174+
if (timeoutId) {
175+
clearTimeout(timeoutId);
176+
timeoutId = null;
177+
}
178+
handleRequestError(err);
179+
});
180+
181+
const handleRequestError = (err) => {
182+
if (attempts < maxRetries && isRetryableNetworkError(err)) {
183+
console.warn(
184+
`Network error on attempt ${attempts}/${maxRetries} for ${url}: ${err.message}. Retrying in ${
185+
retryDelay * attempts
186+
}ms...`
187+
);
188+
timeoutId = setTimeout(makeRequest, retryDelay * attempts); // Exponential backoff
189+
} else {
190+
reject(err);
191+
}
192+
};
193+
};
194+
195+
makeRequest();
114196
});
115197
}
198+
199+
function isRetryableNetworkError(error) {
200+
// Retry on common transient network errors
201+
const retryableCodes = ['ECONNRESET', 'ENOTFOUND', 'ECONNREFUSED', 'ETIMEDOUT'];
202+
return retryableCodes.includes(error.code);
203+
}

0 commit comments

Comments
 (0)