@@ -5,7 +5,7 @@ import {Job, JobRule} from "./job.js";
55import fs from "fs-extra" ;
66import checksum from "checksum" ;
77import base64url from "base64url" ;
8- import execa from "execa" ;
8+ import execa , { ExecaError } from "execa" ;
99import assert from "assert" ;
1010import { CICDVariable } from "./variables-from-files.js" ;
1111import { GitData } from "./git-data.js" ;
@@ -407,6 +407,101 @@ export class Utils {
407407 throw new Error ( `Unhandled case ${ param } ` ) ;
408408 }
409409
410+ static async dockerVolumeFileExists ( containerExecutable : string , path : string , volume : string ) : Promise < boolean > {
411+ try {
412+ await Utils . spawn ( [ containerExecutable , "run" , "--rm" , "-v" , `${ volume } :/mnt/vol` , "alpine" , "ls" , `/mnt/vol/${ path } ` ] ) ;
413+ return true ;
414+ } catch {
415+ return false ;
416+ }
417+ }
418+
419+ static gclRegistryPrefix : string = "registry.gcl.local" ;
420+ static async startDockerRegistry ( argv : Argv ) : Promise < void > {
421+ const gclRegistryCertVol = `${ this . gclRegistryPrefix } .certs` ;
422+ const gclRegistryDataVol = `${ this . gclRegistryPrefix } .data` ;
423+ const gclRegistryNet = `${ this . gclRegistryPrefix } .net` ;
424+
425+ // create cert volume
426+ try {
427+ await Utils . spawn ( `${ argv . containerExecutable } volume create ${ gclRegistryCertVol } ` . split ( " " ) ) ;
428+ } catch ( err ) {
429+ if ( err instanceof Error && ! err . message . endsWith ( "already exists" ) )
430+ throw err ;
431+ }
432+
433+ // create self-signed cert/key files for https support
434+ if ( ! await this . dockerVolumeFileExists ( argv . containerExecutable , `${ this . gclRegistryPrefix } .crt` , gclRegistryCertVol ) ) {
435+ const opensslArgs = [
436+ "req" , "-newkey" , "rsa:4096" , "-nodes" , "-sha256" ,
437+ "-keyout" , `/certs/${ this . gclRegistryPrefix } .key` ,
438+ "-x509" , "-days" , "365" ,
439+ "-out" , `/certs/${ this . gclRegistryPrefix } .crt` ,
440+ "-subj" , `/CN=${ this . gclRegistryPrefix } ` ,
441+ "-addext" , `subjectAltName=DNS:${ this . gclRegistryPrefix } ` ,
442+ ] ;
443+ const generateCertsInPlace = [
444+ argv . containerExecutable , "run" , "--rm" , "-v" , `${ gclRegistryCertVol } :/certs` , "--entrypoint" , "sh" , "alpine/openssl" , "-c" ,
445+ [
446+ "openssl" , ...opensslArgs ,
447+ "&&" , "mkdir" , "-p" , `/certs/${ this . gclRegistryPrefix } ` ,
448+ "&&" , "cp" , `/certs/${ this . gclRegistryPrefix } .crt` , `/certs/${ this . gclRegistryPrefix } /ca.crt` ,
449+ ] . join ( " " ) ,
450+ ] ;
451+ await Utils . spawn ( generateCertsInPlace ) ;
452+ }
453+
454+ // create data volume
455+ try {
456+ await Utils . spawn ( [ argv . containerExecutable , "volume" , "create" , gclRegistryDataVol ] ) ;
457+ } catch ( err ) {
458+ if ( err instanceof Error && ! err . message . endsWith ( "already exists" ) )
459+ throw err ;
460+ }
461+
462+ // create network
463+ try {
464+ await Utils . spawn ( [ argv . containerExecutable , "network" , "create" , gclRegistryNet ] ) ;
465+ } catch ( err ) {
466+ if ( err instanceof Error && ! err . message . includes ( "already exists" ) )
467+ throw err ;
468+ }
469+
470+ await Utils . spawn ( [ argv . containerExecutable , "rm" , "-f" , this . gclRegistryPrefix ] ) ;
471+ await Utils . spawn ( [
472+ argv . containerExecutable , "run" , "-d" , "--name" , this . gclRegistryPrefix ,
473+ "--network" , gclRegistryNet ,
474+ "--volume" , `${ gclRegistryDataVol } :/var/lib/registry` ,
475+ "--volume" , `${ gclRegistryCertVol } :/certs:ro` ,
476+ "-e" , "REGISTRY_HTTP_ADDR=0.0.0.0:443" ,
477+ "-e" , `REGISTRY_HTTP_TLS_CERTIFICATE=/certs/${ this . gclRegistryPrefix } .crt` ,
478+ "-e" , `REGISTRY_HTTP_TLS_KEY=/certs/${ this . gclRegistryPrefix } .key` ,
479+ "registry" ,
480+ ] ) ;
481+
482+ try {
483+ await execa ( argv . containerExecutable , [
484+ "run" , "--rm" ,
485+ "--network" , gclRegistryNet ,
486+ "--entrypoint" , "sh" ,
487+ "curlimages/curl" ,
488+ "-c" , `until [ "$(curl -s -o /dev/null -k -w "%{http_code}" https://${ this . gclRegistryPrefix } :443)" = "200" ]; do sleep 1; done;` ,
489+ ] , {
490+ timeout : 4000 ,
491+ } ) ;
492+ } catch ( err ) {
493+ await this . stopDockerRegistry ( argv . containerExecutable ) ;
494+ if ( ( err as ExecaError ) . timedOut ) {
495+ throw "local docker registry port check timed out" ;
496+ }
497+ throw err ;
498+ }
499+ }
500+
501+ static async stopDockerRegistry ( containerExecutable : string ) : Promise < void > {
502+ await Utils . spawn ( [ containerExecutable , "rm" , "-f" , this . gclRegistryPrefix ] ) ;
503+ }
504+
410505 static async getTrackedFiles ( cwd : string ) : Promise < string [ ] > {
411506 const lsFilesRes = await Utils . bash ( "git ls-files --deduplicate" , cwd ) ;
412507 if ( lsFilesRes . exitCode != 0 ) {
0 commit comments