Skip to content

Commit 3e962bf

Browse files
committed
refactor resolver script
1 parent eb05327 commit 3e962bf

File tree

2 files changed

+175
-203
lines changed

2 files changed

+175
-203
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
const https = require('https');
2+
3+
// Constants
4+
const DOCKERHUB_API_URL = 'https://registry.hub.docker.com/v2/repositories/grafana/grafana-dev/tags?page_size=25';
5+
const GRAFANA_DEV_TAG_REGEX = /^(\d+\.\d+\.\d+)-(\d+)pre$/;
6+
const HTTP_TIMEOUT_MS = 10000;
7+
const RETRYABLE_ERROR_CODES = ['ECONNRESET', 'ENOTFOUND', 'ECONNREFUSED', 'ETIMEDOUT'];
8+
9+
/**
10+
* Main entry point for the GitHub Action
11+
*/
12+
module.exports = async ({ core }) => {
13+
try {
14+
console.log('Getting latest Grafana dev tag from DockerHub...');
15+
16+
const latestTag = await getLatestGrafanaDevTag();
17+
18+
if (!latestTag) {
19+
core.setFailed('Could not find any Grafana dev tags on DockerHub');
20+
return;
21+
}
22+
23+
core.info(`Found grafana/grafana-dev:${latestTag}`);
24+
return latestTag;
25+
} catch (error) {
26+
core.setFailed(error.message);
27+
}
28+
};
29+
30+
/**
31+
* Fetches and returns the latest Grafana dev tag from DockerHub
32+
* @returns {Promise<string|null>} Latest tag name or null if not found
33+
*/
34+
async function getLatestGrafanaDevTag() {
35+
try {
36+
console.log('Fetching latest 25 tags from DockerHub...');
37+
const response = await httpGet(DOCKERHUB_API_URL);
38+
39+
if (!response?.results?.length) {
40+
console.log('No tags found');
41+
return null;
42+
}
43+
44+
console.log(`Found ${response.results.length} tags`);
45+
46+
const validTags = response.results
47+
.map((item) => item.name)
48+
.map(parseGrafanaDevTag)
49+
.filter(Boolean)
50+
.sort((a, b) => b.buildNumber - a.buildNumber);
51+
52+
if (validTags.length === 0) {
53+
console.log('No valid Grafana dev tags found');
54+
return null;
55+
}
56+
57+
const latestTag = validTags[0];
58+
console.log(`Latest tag: ${latestTag.tag} (build ${latestTag.buildNumber}, from ${validTags.length} valid tags)`);
59+
return latestTag.tag;
60+
} catch (error) {
61+
console.log(`Error getting latest tag: ${error.message}`);
62+
return null;
63+
}
64+
}
65+
66+
/**
67+
* Parses a Grafana dev tag string and extracts version and build information
68+
* @param {string} tagName - Tag name to parse (e.g., "11.4.0-175511pre")
69+
* @returns {Object|null} Parsed tag info or null if invalid
70+
*/
71+
function parseGrafanaDevTag(tagName) {
72+
const match = tagName.match(GRAFANA_DEV_TAG_REGEX);
73+
if (!match) {
74+
return null;
75+
}
76+
77+
return {
78+
tag: tagName,
79+
version: match[1],
80+
buildNumber: parseInt(match[2], 10),
81+
};
82+
}
83+
84+
/**
85+
* Makes an HTTP GET request with retry logic
86+
* @param {string} url - URL to fetch
87+
* @param {number} maxRetries - Maximum number of retry attempts
88+
* @param {number} retryDelay - Base delay between retries in milliseconds
89+
* @returns {Promise<Object>} Parsed JSON response
90+
*/
91+
function httpGet(url, maxRetries = 5, retryDelay = 2000) {
92+
return new Promise((resolve, reject) => {
93+
let attempts = 0;
94+
let timeoutId = null;
95+
96+
const clearRetryTimeout = () => {
97+
if (timeoutId) {
98+
clearTimeout(timeoutId);
99+
timeoutId = null;
100+
}
101+
};
102+
103+
const scheduleRetry = (error) => {
104+
if (attempts < maxRetries && isRetryableError(error)) {
105+
const delay = retryDelay * attempts;
106+
console.warn(`Retrying ${url} (attempt ${attempts}/${maxRetries}) in ${delay}ms: ${error.message}`);
107+
timeoutId = setTimeout(makeRequest, delay);
108+
} else {
109+
reject(error);
110+
}
111+
};
112+
113+
const makeRequest = () => {
114+
attempts++;
115+
116+
const req = https.get(url, { timeout: HTTP_TIMEOUT_MS }, (res) => {
117+
const chunks = [];
118+
119+
res.on('data', (chunk) => chunks.push(chunk));
120+
121+
res.on('end', () => {
122+
clearRetryTimeout();
123+
const responseBody = Buffer.concat(chunks).toString();
124+
125+
if (res.statusCode >= 200 && res.statusCode < 300) {
126+
try {
127+
resolve(JSON.parse(responseBody));
128+
} catch (parseError) {
129+
const error = new Error(`Failed to parse JSON from ${url}: ${parseError.message}`);
130+
error.responseBody = responseBody.substring(0, 500);
131+
scheduleRetry(error);
132+
}
133+
} else if (res.statusCode >= 500) {
134+
const error = new Error(`Server error ${res.statusCode} from ${url}`);
135+
error.statusCode = res.statusCode;
136+
scheduleRetry(error);
137+
} else {
138+
const error = new Error(`HTTP ${res.statusCode} error from ${url}`);
139+
error.statusCode = res.statusCode;
140+
error.responseBody = responseBody.substring(0, 500);
141+
reject(error);
142+
}
143+
});
144+
145+
res.on('error', (err) => {
146+
clearRetryTimeout();
147+
scheduleRetry(err);
148+
});
149+
});
150+
151+
req.on('timeout', () => {
152+
req.destroy();
153+
const error = new Error(`Request timeout for ${url}`);
154+
error.code = 'ETIMEDOUT';
155+
scheduleRetry(error);
156+
});
157+
158+
req.on('error', (err) => {
159+
clearRetryTimeout();
160+
scheduleRetry(err);
161+
});
162+
};
163+
164+
makeRequest();
165+
});
166+
}
167+
168+
/**
169+
* Determines if an error is retryable
170+
* @param {Error} error - Error to check
171+
* @returns {boolean} True if error is retryable
172+
*/
173+
function isRetryableError(error) {
174+
return RETRYABLE_ERROR_CODES.includes(error.code) || error.statusCode >= 500;
175+
}

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

Lines changed: 0 additions & 203 deletions
This file was deleted.

0 commit comments

Comments
 (0)