@@ -89,96 +89,103 @@ async function createBlankImage(docker: Docker) {
89
89
return DOCKER_BLANK_TAG ;
90
90
}
91
91
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` ]
129
143
}
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 ;
155
144
} ) ;
156
145
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
+ } ) ;
182
189
}
183
190
184
191
async function cleanupDataInjectionVolumes ( docker : Docker , options : { keepCurrent : boolean } ) {
0 commit comments