@@ -5,6 +5,7 @@ import {ImageRegistryAdapterFactory} from "../registry-factory/ImageRegistryAdap
5
5
import logger from "./LoggerService" ;
6
6
import IgnoreService from "./IgnoreService" ;
7
7
import HomeassistantService from "./HomeassistantService" ;
8
+ import axios , { AxiosInstance } from 'axios' ;
8
9
import { mqttClient } from "../index" ;
9
10
10
11
/**
@@ -14,6 +15,7 @@ export default class DockerService {
14
15
public static docker = new Docker ( ) ;
15
16
public static events = new EventEmitter ( ) ;
16
17
public static updatingContainers : string [ ] = [ ] ;
18
+ public static SourceUrlCache = new Map < string , string > ( ) ;
17
19
18
20
// Start listening to Docker events
19
21
public static listenToDockerEvents ( ) {
@@ -124,6 +126,61 @@ export default class DockerService {
124
126
return null ;
125
127
}
126
128
129
+ /**
130
+ * Gets the source repository for the specified Docker image.
131
+ * @param imageName - The name of the Docker image.
132
+ * @returns A promise that resolves to the source repository URL.
133
+ * @throws An error if the source repository could not be found.
134
+ */
135
+ public static async getSourceRepo ( imageName : string ) : Promise < string | null > {
136
+ // Check cache first
137
+ if ( DockerService . SourceUrlCache . has ( imageName ) ) {
138
+ return DockerService . SourceUrlCache . get ( imageName ) ?? null ;
139
+ }
140
+
141
+ // Try method 1: Check Docker labels
142
+ const labels = await DockerService . getImageInfo ( imageName ) . then (
143
+ ( info ) => info . Config . Labels
144
+ ) . catch ( ( error ) => {
145
+ logger . error ( "Error getting image info:" , error
146
+ ) ;
147
+ } ) ;
148
+
149
+ if ( labels && labels [ "org.opencontainers.image.source" ] ) {
150
+ const url = labels [ "org.opencontainers.image.source" ] ;
151
+ DockerService . SourceUrlCache . set ( imageName , url ) ;
152
+ return url ;
153
+ }
154
+
155
+ // Try method 2: Check Docker Hub API
156
+ const dockerHubUrl = `https://hub.docker.com/v2/repositories/${ imageName } ` ;
157
+ const response = await axios . get ( dockerHubUrl ) . catch ( ( error ) => {
158
+ if ( error . response . status === 404 ) {
159
+ logger . info ( `Repository not found: ${ imageName } ` ) ;
160
+ } else {
161
+ logger . error ( "Error accessing Docker Hub API:" , error ) ;
162
+ }
163
+ } ) ;
164
+
165
+ if ( response && response . status === 200 ) {
166
+ const data = response . data ;
167
+ const fullDescription = data . full_description || "" ;
168
+ if ( ! fullDescription . toLowerCase ( ) . includes ( "[github]" ) ) {
169
+ return null ;
170
+ }
171
+
172
+ const url = this . parseGithubUrl ( fullDescription ) ;
173
+
174
+ // Cache URL
175
+ if ( url !== null ) {
176
+ DockerService . SourceUrlCache . set ( imageName , url ) ;
177
+ return url ;
178
+ }
179
+ }
180
+
181
+ return null ;
182
+ }
183
+
127
184
/**
128
185
* Gets the inspect information for the specified Docker image.
129
186
*
@@ -349,6 +406,20 @@ export default class DockerService {
349
406
return containers . some ( ( container ) => container . Image . match ( imageWithAnyTag ) ) ;
350
407
} ) ;
351
408
}
409
+
410
+ /**
411
+ * Parses a GitHub URL from a full description.
412
+ * @param fullDescription - The full description to parse.
413
+ * @returns The GitHub URL or `null` if it could not be parsed.
414
+ */
415
+ private static parseGithubUrl ( fullDescription : string ) : string | null {
416
+ const startIndex = fullDescription . indexOf ( "[github" ) ;
417
+ const endIndex = fullDescription . indexOf ( "]" , startIndex ) ;
418
+ if ( startIndex !== - 1 && endIndex !== - 1 ) {
419
+ return fullDescription . slice ( startIndex , endIndex ) . replace ( "[github]" , "" ) ;
420
+ }
421
+ return null ;
422
+ }
352
423
}
353
424
354
425
// Start listening to Docker events
0 commit comments