@@ -14,9 +14,13 @@ export class DockerWorkloadManager implements WorkloadManager {
1414 private readonly docker : Docker ;
1515
1616 private readonly runnerNetworks : string [ ] ;
17+ private readonly auth ?: Docker . AuthConfig ;
18+ private readonly platformOverride ?: string ;
1719
1820 constructor ( private opts : WorkloadManagerOptions ) {
19- this . docker = new Docker ( ) ;
21+ this . docker = new Docker ( {
22+ version : env . DOCKER_API_VERSION ,
23+ } ) ;
2024
2125 if ( opts . workloadApiDomain ) {
2226 this . logger . warn ( "⚠️ Custom workload API domain" , {
@@ -25,6 +29,29 @@ export class DockerWorkloadManager implements WorkloadManager {
2529 }
2630
2731 this . runnerNetworks = env . RUNNER_DOCKER_NETWORKS . split ( "," ) ;
32+
33+ this . platformOverride = env . DOCKER_PLATFORM ;
34+ if ( this . platformOverride ) {
35+ this . logger . info ( "🖥️ Platform override" , {
36+ targetPlatform : this . platformOverride ,
37+ hostPlatform : process . arch ,
38+ } ) ;
39+ }
40+
41+ if ( env . DOCKER_REGISTRY_USERNAME && env . DOCKER_REGISTRY_PASSWORD && env . DOCKER_REGISTRY_URL ) {
42+ this . logger . info ( "🐋 Using Docker registry credentials" , {
43+ username : env . DOCKER_REGISTRY_USERNAME ,
44+ url : env . DOCKER_REGISTRY_URL ,
45+ } ) ;
46+
47+ this . auth = {
48+ username : env . DOCKER_REGISTRY_USERNAME ,
49+ password : env . DOCKER_REGISTRY_PASSWORD ,
50+ serveraddress : env . DOCKER_REGISTRY_URL ,
51+ } ;
52+ } else {
53+ this . logger . warn ( "🐋 No Docker registry credentials provided, skipping auth" ) ;
54+ }
2855 }
2956
3057 async create ( opts : WorkloadManagerCreateOptions ) {
@@ -45,6 +72,7 @@ export class DockerWorkloadManager implements WorkloadManager {
4572 `TRIGGER_WORKER_INSTANCE_NAME=${ env . TRIGGER_WORKER_INSTANCE_NAME } ` ,
4673 `OTEL_EXPORTER_OTLP_ENDPOINT=${ env . OTEL_EXPORTER_OTLP_ENDPOINT } ` ,
4774 `TRIGGER_RUNNER_ID=${ runnerId } ` ,
75+ `PRETTY_LOGS=${ env . RUNNER_PRETTY_LOGS } ` ,
4876 ] ;
4977
5078 if ( this . opts . warmStartUrl ) {
@@ -90,41 +118,103 @@ export class DockerWorkloadManager implements WorkloadManager {
90118 hostConfig . Memory = opts . machine . memory * 1024 * 1024 * 1024 ;
91119 }
92120
121+ let imageRef = opts . image ;
122+
123+ if ( env . DOCKER_STRIP_IMAGE_DIGEST ) {
124+ imageRef = opts . image . split ( "@" ) [ 0 ] ! ;
125+ }
126+
93127 const containerCreateOpts : Docker . ContainerCreateOptions = {
94- Env : envVars ,
95128 name : runnerId ,
96129 Hostname : runnerId ,
97130 HostConfig : hostConfig ,
98- Image : opts . image ,
131+ Image : imageRef ,
99132 AttachStdout : false ,
100133 AttachStderr : false ,
101134 AttachStdin : false ,
102135 } ;
103136
104- try {
105- // Create container
106- const container = await this . docker . createContainer ( containerCreateOpts ) ;
137+ if ( this . platformOverride ) {
138+ containerCreateOpts . platform = this . platformOverride ;
139+ }
140+
141+ const logger = this . logger . child ( { opts, containerCreateOpts } ) ;
142+
143+ const [ inspectError , inspectResult ] = await tryCatch ( this . docker . getImage ( imageRef ) . inspect ( ) ) ;
144+
145+ let shouldPull = ! ! inspectError ;
146+ if ( this . platformOverride ) {
147+ const imageArchitecture = inspectResult ?. Architecture ;
148+
149+ // When the image architecture doesn't match the platform, we need to pull the image
150+ if ( imageArchitecture && ! this . platformOverride . includes ( imageArchitecture ) ) {
151+ shouldPull = true ;
152+ }
153+ }
154+
155+ // If the image is not present, try to pull it
156+ if ( shouldPull ) {
157+ logger . info ( "Pulling image" , {
158+ error : inspectError ,
159+ image : opts . image ,
160+ targetPlatform : this . platformOverride ,
161+ imageArchitecture : inspectResult ?. Architecture ,
162+ } ) ;
107163
108- // If there are multiple networks to attach to we need to attach the remaining ones after creation
109- if ( remainingNetworks . length > 0 ) {
110- await this . attachContainerToNetworks ( {
111- containerId : container . id ,
112- networkNames : remainingNetworks ,
113- } ) ;
164+ // Ensure the image is present
165+ const [ createImageError , imageResponseReader ] = await tryCatch (
166+ this . docker . createImage ( this . auth , {
167+ fromImage : imageRef ,
168+ ...( this . platformOverride ? { platform : this . platformOverride } : { } ) ,
169+ } )
170+ ) ;
171+ if ( createImageError ) {
172+ logger . error ( "Failed to pull image" , { error : createImageError } ) ;
173+ return ;
114174 }
115175
116- // Start container
117- const startResult = await container . start ( ) ;
176+ const [ imageReadError , imageResponse ] = await tryCatch ( readAllChunks ( imageResponseReader ) ) ;
177+ if ( imageReadError ) {
178+ logger . error ( "failed to read image response" , { error : imageReadError } ) ;
179+ return ;
180+ }
181+
182+ logger . debug ( "pulled image" , { image : opts . image , imageResponse } ) ;
183+ } else {
184+ // Image is present, so we can use it to create the container
185+ }
186+
187+ // Create container
188+ const [ createContainerError , container ] = await tryCatch (
189+ this . docker . createContainer ( {
190+ ...containerCreateOpts ,
191+ // Add env vars here so they're not logged
192+ Env : envVars ,
193+ } )
194+ ) ;
118195
119- this . logger . debug ( "create succeeded" , {
120- opts,
121- startResult,
196+ if ( createContainerError ) {
197+ logger . error ( "Failed to create container" , { error : createContainerError } ) ;
198+ return ;
199+ }
200+
201+ // If there are multiple networks to attach to we need to attach the remaining ones after creation
202+ if ( remainingNetworks . length > 0 ) {
203+ await this . attachContainerToNetworks ( {
122204 containerId : container . id ,
123- containerCreateOpts ,
205+ networkNames : remainingNetworks ,
124206 } ) ;
125- } catch ( error ) {
126- this . logger . error ( "create failed:" , { opts, error, containerCreateOpts } ) ;
127207 }
208+
209+ // Start container
210+ const [ startError , startResult ] = await tryCatch ( container . start ( ) ) ;
211+
212+ if ( startError ) {
213+ logger . error ( "Failed to start container" , { error : startError , containerId : container . id } ) ;
214+ return ;
215+ }
216+
217+ logger . debug ( "create succeeded" , { startResult, containerId : container . id } ) ;
128218 }
129219
130220 private async attachContainerToNetworks ( {
@@ -173,3 +263,11 @@ export class DockerWorkloadManager implements WorkloadManager {
173263 } ) ;
174264 }
175265}
266+
267+ async function readAllChunks ( reader : NodeJS . ReadableStream ) {
268+ const chunks = [ ] ;
269+ for await ( const chunk of reader ) {
270+ chunks . push ( chunk . toString ( ) ) ;
271+ }
272+ return chunks ;
273+ }
0 commit comments