|
1 |
| -const Docker = require('dockerode'); |
2 |
| -const https = require('https'); |
3 |
| -import * as http from 'http'; |
| 1 | +const Docker = require("dockerode"); |
| 2 | +const https = require("https"); |
| 3 | +import * as http from "http"; |
4 | 4 |
|
5 |
| -interface HttpRequestOptions { |
6 |
| - method?: string; |
7 |
| - headers?: { [key: string]: string }; |
8 |
| -} |
| 5 | +export default class DockerSourceFinder { |
| 6 | + public static docker = new Docker(); |
9 | 7 |
|
10 |
| -interface HttpResponse { |
11 |
| - ok: boolean; |
12 |
| - status: number; |
13 |
| - statusText: string; |
14 |
| - json(): Promise<any>; |
15 |
| -} |
| 8 | + constructor() { |
| 9 | + DockerSourceFinder.docker = new Docker(); |
| 10 | + } |
16 | 11 |
|
17 |
| -export default class DockerSourceFinder { |
18 |
| - public static docker = new Docker(); |
| 12 | + async getImageLabels(imageName: string) { |
| 13 | + try { |
| 14 | + const image = await DockerSourceFinder.docker.getImage(imageName); |
| 15 | + const inspect = await image.inspect(); |
| 16 | + return inspect.Config.Labels; |
| 17 | + } catch (error: unknown) { |
| 18 | + console.error( |
| 19 | + `Error accessing image ${imageName}:`, |
| 20 | + error instanceof Error ? error.message : String(error) |
| 21 | + ); |
| 22 | + return null; |
| 23 | + } |
| 24 | + } |
19 | 25 |
|
20 |
| - constructor() { |
21 |
| - DockerSourceFinder.docker = new Docker(); |
| 26 | + parseGithubUrl(url: string) { |
| 27 | + const parsed = new URL(url); |
| 28 | + if (parsed.hostname === "github.com") { |
| 29 | + const pathParts = parsed.pathname.split("/").filter((p) => p); |
| 30 | + if (pathParts.length >= 2) { |
| 31 | + return `github.com/${pathParts[0]}/${pathParts[1]}`; |
| 32 | + } |
22 | 33 | }
|
| 34 | + return null; |
| 35 | + } |
23 | 36 |
|
24 |
| - async getImageLabels(imageName: string) { |
25 |
| - try { |
26 |
| - const image = await DockerSourceFinder.docker.getImage(imageName); |
27 |
| - const inspect = await image.inspect(); |
28 |
| - return inspect.Config.Labels; |
29 |
| - } catch (error: unknown) { |
30 |
| - console.error(`Error accessing image ${imageName}:`, |
31 |
| - error instanceof Error ? error.message : String(error)); |
32 |
| - return null; |
33 |
| - } |
| 37 | + async findSourceRepo(imageName: string) { |
| 38 | + // Try method 1: Check Docker labels |
| 39 | + const labels = await this.getImageLabels(imageName); |
| 40 | + if (labels && labels["org.opencontainers.image.source"]) { |
| 41 | + return labels["org.opencontainers.image.source"]; |
34 | 42 | }
|
35 | 43 |
|
36 |
| - parseGithubUrl(url: string) { |
37 |
| - const parsed = new URL(url); |
38 |
| - if (parsed.hostname === 'github.com') { |
39 |
| - const pathParts = parsed.pathname.split('/').filter(p => p); |
40 |
| - if (pathParts.length >= 2) { |
41 |
| - return `github.com/${pathParts[0]}/${pathParts[1]}`; |
42 |
| - } |
| 44 | + // Try method 2: Check Docker Hub API |
| 45 | + try { |
| 46 | + const dockerHubUrl = `https://hub.docker.com/v2/repositories/${imageName}`; |
| 47 | + const response = await this.makeHttpsRequest(dockerHubUrl); |
| 48 | + if (response.ok) { |
| 49 | + const data = await response.json(); |
| 50 | + const fullDescription = data.full_description || ""; |
| 51 | + if (fullDescription.toLowerCase().includes("[github]")) { |
| 52 | + const startIndex = fullDescription.indexOf("[github"); |
| 53 | + const endIndex = fullDescription.indexOf("]", startIndex); |
| 54 | + if (startIndex !== -1 && endIndex !== -1) { |
| 55 | + const githubUrl = fullDescription |
| 56 | + .slice(startIndex, endIndex) |
| 57 | + .replace("[github]", ""); |
| 58 | + return this.parseGithubUrl(githubUrl); |
| 59 | + } |
43 | 60 | }
|
44 |
| - return null; |
| 61 | + } |
| 62 | + } catch (error: unknown) { |
| 63 | + console.error( |
| 64 | + `Error checking Docker Hub API:`, |
| 65 | + error instanceof Error ? error.message : String(error) |
| 66 | + ); |
45 | 67 | }
|
46 | 68 |
|
47 |
| - async findSourceRepo(imageName: string) { |
48 |
| - // Try method 1: Check Docker labels |
49 |
| - const labels = await this.getImageLabels(imageName); |
50 |
| - if (labels && labels['org.opencontainers.image.source']) { |
51 |
| - return labels['org.opencontainers.image.source']; |
52 |
| - } |
| 69 | + // Try method 3: URL pattern matching |
| 70 | + const repoParts = imageName.split("/"); |
| 71 | + if (repoParts.length >= 2) { |
| 72 | + const username = repoParts[0]; |
| 73 | + const repoName = repoParts[repoParts.length - 1].split(":")[0]; |
| 74 | + const potentialRepo = `github.com/${username}/${repoName}`; |
53 | 75 |
|
54 |
| - // Try method 2: Check Docker Hub API |
55 |
| - try { |
56 |
| - const dockerHubUrl = `https://hub.docker.com/v2/repositories/${imageName}`; |
57 |
| - const response = await this.makeHttpsRequest(dockerHubUrl); |
58 |
| - if (response.ok) { |
59 |
| - const data = await response.json(); |
60 |
| - const fullDescription = data.full_description || ''; |
61 |
| - if (fullDescription.toLowerCase().includes('[github]')) { |
62 |
| - const startIndex = fullDescription.indexOf('[github'); |
63 |
| - const endIndex = fullDescription.indexOf(']', startIndex); |
64 |
| - if (startIndex !== -1 && endIndex !== -1) { |
65 |
| - const githubUrl = fullDescription.slice(startIndex, endIndex).replace('[github]', ''); |
66 |
| - return this.parseGithubUrl(githubUrl); |
67 |
| - } |
68 |
| - } |
69 |
| - } |
70 |
| - } catch (error: unknown) { |
71 |
| - console.error(`Error checking Docker Hub API:`, |
72 |
| - error instanceof Error ? error.message : String(error)); |
73 |
| - } |
| 76 | + const response = await this.makeHttpsRequest(`https://${potentialRepo}`, { |
| 77 | + method: "HEAD", |
| 78 | + }); |
| 79 | + if (response.ok) { |
| 80 | + return potentialRepo; |
| 81 | + } |
| 82 | + } |
74 | 83 |
|
75 |
| - // Try method 3: URL pattern matching |
76 |
| - const repoParts = imageName.split('/'); |
77 |
| - if (repoParts.length >= 2) { |
78 |
| - const username = repoParts[0]; |
79 |
| - const repoName = repoParts[repoParts.length - 1].split(':')[0]; |
80 |
| - const potentialRepo = `github.com/${username}/${repoName}`; |
| 84 | + return null; |
| 85 | + } |
81 | 86 |
|
82 |
| - const response = await this.makeHttpsRequest(`https://${potentialRepo}`, { method: 'HEAD' }); |
83 |
| - if (response.ok) { |
84 |
| - return potentialRepo; |
| 87 | + private makeHttpsRequest( |
| 88 | + url: string, |
| 89 | + options: { method?: string; headers?: { [key: string]: string } } = {} |
| 90 | + ): Promise<{ |
| 91 | + ok: boolean; |
| 92 | + status: number; |
| 93 | + statusText: string; |
| 94 | + json: () => Promise<any>; |
| 95 | + }> { |
| 96 | + return new Promise((resolve, reject) => { |
| 97 | + const req = https.request( |
| 98 | + url, |
| 99 | + { |
| 100 | + method: options.method || "GET", |
| 101 | + headers: options.headers || {}, |
| 102 | + }, |
| 103 | + (res: http.IncomingMessage) => { |
| 104 | + let body = ""; |
| 105 | + res.on("data", (chunk: Buffer) => (body += chunk)); |
| 106 | + res.on("end", () => { |
| 107 | + try { |
| 108 | + // Create response object with non-nullable properties |
| 109 | + const response = { |
| 110 | + ok: (res.statusCode || 0) >= 200 && (res.statusCode || 0) < 300, |
| 111 | + status: res.statusCode || 0, |
| 112 | + statusText: res.statusMessage || "Unknown Status", |
| 113 | + json: () => Promise.resolve(JSON.parse(body)), |
| 114 | + }; |
| 115 | + resolve(response); |
| 116 | + } catch (err: unknown) { |
| 117 | + reject( |
| 118 | + new Error( |
| 119 | + `Failed to parse response: ${ |
| 120 | + err instanceof Error ? err.message : String(err) |
| 121 | + }` |
| 122 | + ) |
| 123 | + ); |
85 | 124 | }
|
| 125 | + }); |
86 | 126 | }
|
| 127 | + ); |
87 | 128 |
|
88 |
| - return null; |
89 |
| - } |
90 |
| - |
91 |
| - private makeHttpsRequest(url: string, options: HttpRequestOptions = {}): Promise<HttpResponse> { |
92 |
| - return new Promise((resolve, reject) => { |
93 |
| - const req = https.request(url, { |
94 |
| - method: options.method || 'GET', |
95 |
| - headers: options.headers || {} |
96 |
| - }, (res: http.IncomingMessage) => { |
97 |
| - let body = ''; |
98 |
| - res.on('data', (chunk: Buffer) => body += chunk); |
99 |
| - res.on('end', () => { |
100 |
| - try { |
101 |
| - // Create response object with non-nullable properties |
102 |
| - const response: HttpResponse = { |
103 |
| - ok: (res.statusCode || 0) >= 200 && (res.statusCode || 0) < 300, |
104 |
| - status: res.statusCode || 0, |
105 |
| - statusText: res.statusMessage || 'Unknown Status', |
106 |
| - json: () => Promise.resolve(JSON.parse(body)) |
107 |
| - }; |
108 |
| - resolve(response); |
109 |
| - } catch (err: unknown) { |
110 |
| - reject(new Error(`Failed to parse response: ${err instanceof Error ? err.message : String(err)}`)); |
111 |
| - } |
112 |
| - }); |
113 |
| - }); |
| 129 | + req.on("error", (err: unknown) => { |
| 130 | + reject( |
| 131 | + new Error( |
| 132 | + `Request failed: ${ |
| 133 | + err instanceof Error ? err.message : String(err) |
| 134 | + }` |
| 135 | + ) |
| 136 | + ); |
| 137 | + }); |
114 | 138 |
|
115 |
| - req.on('error', (err: unknown) => { |
116 |
| - reject(new Error(`Request failed: ${err instanceof Error ? err.message : String(err)}`)); |
117 |
| - }); |
118 |
| - |
119 |
| - req.end(); |
120 |
| - }); |
121 |
| - } |
| 139 | + req.end(); |
| 140 | + }); |
| 141 | + } |
122 | 142 | }
|
0 commit comments