@@ -89,96 +89,103 @@ async function createBlankImage(docker: Docker) {
8989 return DOCKER_BLANK_TAG ;
9090}
9191
92- export async function ensureDockerInjectionVolumeExists (
93- certContent : string
94- ) {
95- if ( ! await isDockerAvailable ( ) ) return ;
96-
97- const docker = new Docker ( ) ;
98-
99- const existingVolume = await docker . getVolume ( DOCKER_DATA_VOLUME_NAME ) . inspect ( )
100- . catch < false > ( ( ) => false ) ;
101- const isCertOutdated = existingVolume &&
102- existingVolume . Labels [ DOCKER_VOLUME_CERT_LABEL ] !== certContent ;
103-
104- if ( existingVolume && ! isCertOutdated ) return ; // We're all good!
105-
106- try {
107- const startTime = Date . now ( ) ;
108-
109- // Clean up any leftover setup components that are hanging around (since they might
110- // conflict with these next steps):
111- await cleanupDataInjectionTools ( docker ) ;
112-
113- // If cert is outdated, we just recreate the volume from scratch - cleaner to reset
114- // then try and update, and it only takes a couple of seconds anyway:
115- if ( isCertOutdated ) await docker . getVolume ( DOCKER_DATA_VOLUME_NAME ) . remove ( { force : true } ) ;
116-
117- // No volume. We need to create a Docker volume that contains our override files,
118- // and the CA certificate, which can be mounted into containers for interception.
119- // We can't directly write to volumes, so we work around this by mounting a new volume
120- // inside a stopped empty container, and writing to the container.
121-
122- // First, we need an empty volume, and build blank image for our container:
123- await Promise . all ( [
124- docker . createVolume ( {
125- Name : DOCKER_DATA_VOLUME_NAME ,
126- Labels : {
127- [ DOCKER_VOLUME_LABEL ] : SERVER_VERSION ,
128- [ DOCKER_VOLUME_CERT_LABEL ] : certContent // Used to detect when we need to recreate
92+ // Parallel processing of a single Docker volume and the other assorted containers is asking for trouble,
93+ // and inefficient, so we collapse parallel attempts into one:
94+ let volumeSetupPromise : Promise < void > | undefined ;
95+
96+ export function ensureDockerInjectionVolumeExists ( certContent : string ) {
97+ if ( volumeSetupPromise ) return volumeSetupPromise ;
98+ return volumeSetupPromise = ( async ( ) => { // Run as an async IIFE
99+ if ( ! await isDockerAvailable ( ) ) return ;
100+ const docker = new Docker ( ) ;
101+
102+ const existingVolume = await docker . getVolume ( DOCKER_DATA_VOLUME_NAME ) . inspect ( )
103+ . catch < false > ( ( ) => false ) ;
104+ const isCertOutdated = existingVolume &&
105+ existingVolume . Labels [ DOCKER_VOLUME_CERT_LABEL ] !== certContent ;
106+
107+ if ( existingVolume && ! isCertOutdated ) return ; // We're all good!
108+
109+ try {
110+ const startTime = Date . now ( ) ;
111+
112+ // Clean up any leftover setup components that are hanging around (since they might
113+ // conflict with these next steps):
114+ await cleanupDataInjectionTools ( docker ) ;
115+
116+ // If cert is outdated, we just recreate the volume from scratch - cleaner to reset
117+ // then try and update, and it only takes a couple of seconds anyway:
118+ if ( isCertOutdated ) await docker . getVolume ( DOCKER_DATA_VOLUME_NAME ) . remove ( { force : true } ) ;
119+
120+ // No volume. We need to create a Docker volume that contains our override files,
121+ // and the CA certificate, which can be mounted into containers for interception.
122+ // We can't directly write to volumes, so we work around this by mounting a new volume
123+ // inside a stopped empty container, and writing to the container.
124+
125+ // First, we need an empty volume, and build blank image for our container:
126+ await Promise . all ( [
127+ docker . createVolume ( {
128+ Name : DOCKER_DATA_VOLUME_NAME ,
129+ Labels : {
130+ [ DOCKER_VOLUME_LABEL ] : SERVER_VERSION ,
131+ [ DOCKER_VOLUME_CERT_LABEL ] : certContent // Used to detect when we need to recreate
132+ }
133+ } ) ,
134+ createBlankImage ( docker )
135+ ] ) ;
136+
137+ // Then we create a blank container from the blank image with the volume mounted:
138+ const blankContainer = await docker . createContainer ( {
139+ Image : DOCKER_BLANK_TAG ,
140+ Labels : { [ DOCKER_BLANK_TAG ] : '' } ,
141+ HostConfig : {
142+ Binds : [ `${ DOCKER_DATA_VOLUME_NAME } :/data-volume` ]
129143 }
130- } ) ,
131- createBlankImage ( docker )
132- ] ) ;
133-
134- // Then we create a blank container from the blank image with the volume mounted:
135- const blankContainer = await docker . createContainer ( {
136- Image : DOCKER_BLANK_TAG ,
137- Labels : { [ DOCKER_BLANK_TAG ] : '' } ,
138- HostConfig : {
139- Binds : [ `${ DOCKER_DATA_VOLUME_NAME } :/data-volume` ]
140- }
141- } ) ;
142-
143- const blankContainerSetupTime = Date . now ( ) - startTime ;
144- let overrideStreamTime : number | undefined ;
145-
146- // Then we use the container to write to the volume, without ever starting it:
147- const volumeStream = TarStream . pack ( ) ;
148- // We write the CA cert, read-only:
149- volumeStream . entry ( { name : 'ca.pem' , mode : parseInt ( '444' , 8 ) } , certContent ) ;
150- // And all the override filesL
151- const packOverridesPromise = packOverrideFiles ( volumeStream , '/overrides' )
152- . then ( ( ) => {
153- volumeStream . finalize ( ) ;
154- overrideStreamTime = Date . now ( ) - startTime ;
155144 } ) ;
156145
157- const writeVolumePromise = blankContainer . putArchive (
158- volumeStream ,
159- { path : '/data-volume/' }
160- ) ;
161-
162- await Promise . all ( [ packOverridesPromise , writeVolumePromise ] ) ;
163- const volumeCompleteTime = Date . now ( ) - startTime ;
164- console . log ( `Created Docker injection volume (took ${
165- volumeCompleteTime
166- } ms: ${ blankContainerSetupTime } ms for setup & ${ overrideStreamTime ! } ms to pack)`) ;
167-
168- // After success, we cleanup the tools (blank image & containers etc) and all old HTTP
169- // Toolkit volumes, i.e. all matching volumes _except_ the current one.
170- await cleanupDataInjectionTools ( docker ) ;
171- await cleanupDataInjectionVolumes ( docker , { keepCurrent : true } ) . catch ( console . log ) ;
172- } catch ( e ) {
173- console . warn ( 'Docker injection volume setup error, cleaning up...' ) ;
174-
175- // In a failure case, we delete the setup components and the volume too, so that at
176- // least we have a clean slate next time:
177- await cleanupDataInjectionTools ( docker ) . catch ( console . log ) ;
178- await cleanupDataInjectionVolumes ( docker , { keepCurrent : false } ) . catch ( console . log ) ;
179-
180- throw e ;
181- }
146+ const blankContainerSetupTime = Date . now ( ) - startTime ;
147+ let overrideStreamTime : number | undefined ;
148+
149+ // Then we use the container to write to the volume, without ever starting it:
150+ const volumeStream = TarStream . pack ( ) ;
151+ // We write the CA cert, read-only:
152+ volumeStream . entry ( { name : 'ca.pem' , mode : parseInt ( '444' , 8 ) } , certContent ) ;
153+ // And all the override filesL
154+ const packOverridesPromise = packOverrideFiles ( volumeStream , '/overrides' )
155+ . then ( ( ) => {
156+ volumeStream . finalize ( ) ;
157+ overrideStreamTime = Date . now ( ) - startTime ;
158+ } ) ;
159+
160+ const writeVolumePromise = blankContainer . putArchive (
161+ volumeStream ,
162+ { path : '/data-volume/' }
163+ ) ;
164+
165+ await Promise . all ( [ packOverridesPromise , writeVolumePromise ] ) ;
166+ const volumeCompleteTime = Date . now ( ) - startTime ;
167+ console . log ( `Created Docker injection volume (took ${
168+ volumeCompleteTime
169+ } ms: ${ blankContainerSetupTime } ms for setup & ${ overrideStreamTime ! } ms to pack)`) ;
170+
171+ // After success, we cleanup the tools (blank image & containers etc) and all old HTTP
172+ // Toolkit volumes, i.e. all matching volumes _except_ the current one.
173+ await cleanupDataInjectionTools ( docker ) ;
174+ await cleanupDataInjectionVolumes ( docker , { keepCurrent : true } ) . catch ( console . log ) ;
175+ } catch ( e ) {
176+ console . warn ( 'Docker injection volume setup error, cleaning up...' ) ;
177+
178+ // In a failure case, we delete the setup components and the volume too, so that at
179+ // least we have a clean slate next time:
180+ await cleanupDataInjectionTools ( docker ) . catch ( console . log ) ;
181+ await cleanupDataInjectionVolumes ( docker , { keepCurrent : false } ) . catch ( console . log ) ;
182+
183+ throw e ;
184+ }
185+ } ) ( ) . then ( ( ) => {
186+ // Reset the promise, so we can try again
187+ volumeSetupPromise = undefined ;
188+ } ) ;
182189}
183190
184191async function cleanupDataInjectionVolumes ( docker : Docker , options : { keepCurrent : boolean } ) {
0 commit comments