@@ -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 . DOCKER_RUNNER_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 ) {
@@ -47,6 +74,7 @@ export class DockerWorkloadManager implements WorkloadManager {
4774 `TRIGGER_RUNNER_ID=${ runnerId } ` ,
4875 `TRIGGER_MACHINE_CPU=${ opts . machine . cpu } ` ,
4976 `TRIGGER_MACHINE_MEMORY=${ opts . machine . memory } ` ,
77+ `PRETTY_LOGS=${ env . RUNNER_PRETTY_LOGS } ` ,
5078 ] ;
5179
5280 if ( this . opts . warmStartUrl ) {
@@ -89,41 +117,103 @@ export class DockerWorkloadManager implements WorkloadManager {
89117 hostConfig . Memory = opts . machine . memory * 1024 * 1024 * 1024 ;
90118 }
91119
120+ let imageRef = opts . image ;
121+
122+ if ( env . DOCKER_STRIP_IMAGE_DIGEST ) {
123+ imageRef = opts . image . split ( "@" ) [ 0 ] ! ;
124+ }
125+
92126 const containerCreateOpts : Docker . ContainerCreateOptions = {
93- Env : envVars ,
94127 name : runnerId ,
95128 Hostname : runnerId ,
96129 HostConfig : hostConfig ,
97- Image : opts . image ,
130+ Image : imageRef ,
98131 AttachStdout : false ,
99132 AttachStderr : false ,
100133 AttachStdin : false ,
101134 } ;
102135
103- try {
104- // Create container
105- const container = await this . docker . createContainer ( containerCreateOpts ) ;
136+ if ( this . platformOverride ) {
137+ containerCreateOpts . platform = this . platformOverride ;
138+ }
139+
140+ const logger = this . logger . child ( { opts, containerCreateOpts } ) ;
141+
142+ const [ inspectError , inspectResult ] = await tryCatch ( this . docker . getImage ( imageRef ) . inspect ( ) ) ;
143+
144+ let shouldPull = ! ! inspectError ;
145+ if ( this . platformOverride ) {
146+ const imageArchitecture = inspectResult ?. Architecture ;
147+
148+ // When the image architecture doesn't match the platform, we need to pull the image
149+ if ( imageArchitecture && ! this . platformOverride . includes ( imageArchitecture ) ) {
150+ shouldPull = true ;
151+ }
152+ }
153+
154+ // If the image is not present, try to pull it
155+ if ( shouldPull ) {
156+ logger . info ( "Pulling image" , {
157+ error : inspectError ,
158+ image : opts . image ,
159+ targetPlatform : this . platformOverride ,
160+ imageArchitecture : inspectResult ?. Architecture ,
161+ } ) ;
106162
107- // If there are multiple networks to attach to we need to attach the remaining ones after creation
108- if ( remainingNetworks . length > 0 ) {
109- await this . attachContainerToNetworks ( {
110- containerId : container . id ,
111- networkNames : remainingNetworks ,
112- } ) ;
163+ // Ensure the image is present
164+ const [ createImageError , imageResponseReader ] = await tryCatch (
165+ this . docker . createImage ( this . auth , {
166+ fromImage : imageRef ,
167+ ...( this . platformOverride ? { platform : this . platformOverride } : { } ) ,
168+ } )
169+ ) ;
170+ if ( createImageError ) {
171+ logger . error ( "Failed to pull image" , { error : createImageError } ) ;
172+ return ;
113173 }
114174
115- // Start container
116- const startResult = await container . start ( ) ;
175+ const [ imageReadError , imageResponse ] = await tryCatch ( readAllChunks ( imageResponseReader ) ) ;
176+ if ( imageReadError ) {
177+ logger . error ( "failed to read image response" , { error : imageReadError } ) ;
178+ return ;
179+ }
180+
181+ logger . debug ( "pulled image" , { image : opts . image , imageResponse } ) ;
182+ } else {
183+ // Image is present, so we can use it to create the container
184+ }
185+
186+ // Create container
187+ const [ createContainerError , container ] = await tryCatch (
188+ this . docker . createContainer ( {
189+ ...containerCreateOpts ,
190+ // Add env vars here so they're not logged
191+ Env : envVars ,
192+ } )
193+ ) ;
117194
118- this . logger . debug ( "create succeeded" , {
119- opts,
120- startResult,
195+ if ( createContainerError ) {
196+ logger . error ( "Failed to create container" , { error : createContainerError } ) ;
197+ return ;
198+ }
199+
200+ // If there are multiple networks to attach to we need to attach the remaining ones after creation
201+ if ( remainingNetworks . length > 0 ) {
202+ await this . attachContainerToNetworks ( {
121203 containerId : container . id ,
122- containerCreateOpts ,
204+ networkNames : remainingNetworks ,
123205 } ) ;
124- } catch ( error ) {
125- this . logger . error ( "create failed:" , { opts, error, containerCreateOpts } ) ;
126206 }
207+
208+ // Start container
209+ const [ startError , startResult ] = await tryCatch ( container . start ( ) ) ;
210+
211+ if ( startError ) {
212+ logger . error ( "Failed to start container" , { error : startError , containerId : container . id } ) ;
213+ return ;
214+ }
215+
216+ logger . debug ( "create succeeded" , { startResult, containerId : container . id } ) ;
127217 }
128218
129219 private async attachContainerToNetworks ( {
@@ -172,3 +262,11 @@ export class DockerWorkloadManager implements WorkloadManager {
172262 } ) ;
173263 }
174264}
265+
266+ async function readAllChunks ( reader : NodeJS . ReadableStream ) {
267+ const chunks = [ ] ;
268+ for await ( const chunk of reader ) {
269+ chunks . push ( chunk . toString ( ) ) ;
270+ }
271+ return chunks ;
272+ }
0 commit comments