@@ -6,6 +6,9 @@ import { setTimeout } from 'node:timers/promises';
6
6
// import { ClusterSlotsReply } from '@redis/client/dist/lib/commands/CLUSTER_SLOTS';
7
7
import { execFile as execFileCallback } from 'node:child_process' ;
8
8
import { promisify } from 'node:util' ;
9
+ import * as fs from 'node:fs' ;
10
+ import * as os from 'node:os' ;
11
+ import * as path from 'node:path' ;
9
12
10
13
const execAsync = promisify ( execFileCallback ) ;
11
14
@@ -37,21 +40,37 @@ const portIterator = (async function* (): AsyncIterableIterator<number> {
37
40
throw new Error ( 'All ports are in use' ) ;
38
41
} ) ( ) ;
39
42
40
- export interface RedisServerDockerConfig {
43
+ interface RedisServerDockerConfig {
41
44
image : string ;
42
45
version : string ;
43
46
}
44
47
48
+ interface SentinelConfig {
49
+ mode : "sentinel" ;
50
+ mounts : Array < string > ;
51
+ port : number ;
52
+ }
53
+
54
+ interface ServerConfig {
55
+ mode : "server" ;
56
+ }
57
+
58
+ export type RedisServerDockerOptions = RedisServerDockerConfig & ( SentinelConfig | ServerConfig )
59
+
45
60
export interface RedisServerDocker {
46
61
port : number ;
47
62
dockerId : string ;
48
63
}
49
64
50
- async function spawnRedisServerDocker ( {
51
- image,
52
- version
53
- } : RedisServerDockerConfig , serverArguments : Array < string > , dockerEnv ?: Map < string , string > ) : Promise < RedisServerDocker > {
54
- const port = ( await portIterator . next ( ) ) . value ;
65
+ async function spawnRedisServerDocker (
66
+ options : RedisServerDockerOptions , serverArguments : Array < string > , dockerEnv ?: Map < string , string > ) : Promise < RedisServerDocker > {
67
+ let port ;
68
+ if ( options . mode == "sentinel" ) {
69
+ port = options . port ;
70
+ } else {
71
+ port = ( await portIterator . next ( ) ) . value ;
72
+ }
73
+
55
74
const portStr = port . toString ( ) ;
56
75
57
76
const dockerArgs = [
@@ -60,23 +79,29 @@ async function spawnRedisServerDocker({
60
79
'-e' , `PORT=${ portStr } `
61
80
] ;
62
81
82
+ if ( options . mode == "sentinel" ) {
83
+ options . mounts . forEach ( mount => {
84
+ dockerArgs . push ( '-v' , mount ) ;
85
+ } ) ;
86
+ }
87
+
63
88
dockerEnv ?. forEach ( ( key : string , value : string ) => {
64
89
dockerArgs . push ( '-e' , `${ key } :${ value } ` ) ;
65
90
} ) ;
66
91
67
92
dockerArgs . push (
68
93
'-d' ,
69
94
'--network' , 'host' ,
70
- `${ image } :${ version } `
95
+ `${ options . image } :${ options . version } `
71
96
) ;
72
97
73
98
if ( serverArguments . length > 0 ) {
74
99
for ( let i = 0 ; i < serverArguments . length ; i ++ ) {
75
- dockerArgs . push ( serverArguments [ i ] . replace ( '{port}' , ` ${ portStr } ` ) )
100
+ dockerArgs . push ( serverArguments [ i ] )
76
101
}
77
102
}
78
103
79
- console . log ( `[Docker] Spawning Redis container - Image: ${ image } :${ version } , Port: ${ port } ` ) ;
104
+ console . log ( `[Docker] Spawning Redis container - Image: ${ options . image } :${ options . version } , Port: ${ port } , Mode: ${ options . mode } ` ) ;
80
105
81
106
const { stdout, stderr } = await execAsync ( 'docker' , dockerArgs ) ;
82
107
@@ -95,7 +120,7 @@ async function spawnRedisServerDocker({
95
120
}
96
121
const RUNNING_SERVERS = new Map < Array < string > , ReturnType < typeof spawnRedisServerDocker > > ( ) ;
97
122
98
- export function spawnRedisServer ( dockerConfig : RedisServerDockerConfig , serverArguments : Array < string > ) : Promise < RedisServerDocker > {
123
+ export function spawnRedisServer ( dockerConfig : RedisServerDockerOptions , serverArguments : Array < string > ) : Promise < RedisServerDocker > {
99
124
const runningServer = RUNNING_SERVERS . get ( serverArguments ) ;
100
125
if ( runningServer ) {
101
126
return runningServer ;
@@ -121,7 +146,7 @@ after(() => {
121
146
) ;
122
147
} ) ;
123
148
124
- export interface RedisClusterDockersConfig extends RedisServerDockerConfig {
149
+ export type RedisClusterDockersConfig = RedisServerDockerOptions & {
125
150
numberOfMasters ?: number ;
126
151
numberOfReplicas ?: number ;
127
152
}
@@ -186,7 +211,7 @@ async function spawnRedisClusterNodeDockers(
186
211
}
187
212
188
213
async function spawnRedisClusterNodeDocker (
189
- dockersConfig : RedisClusterDockersConfig ,
214
+ dockersConfig : RedisServerDockerOptions ,
190
215
serverArguments : Array < string > ,
191
216
clientConfig ?: Partial < RedisClusterClientOptions >
192
217
) {
@@ -305,7 +330,7 @@ const RUNNING_NODES = new Map<Array<string>, Array<RedisServerDocker>>();
305
330
const RUNNING_SENTINELS = new Map < Array < string > , Array < RedisServerDocker > > ( ) ;
306
331
307
332
export async function spawnRedisSentinel (
308
- dockerConfigs : RedisServerDockerConfig ,
333
+ dockerConfigs : RedisServerDockerOptions ,
309
334
serverArguments : Array < string > ,
310
335
password ?: string ,
311
336
) : Promise < Array < RedisServerDocker > > {
@@ -351,33 +376,47 @@ export async function spawnRedisSentinel(
351
376
const sentinelPromises : Array < Promise < RedisServerDocker > > = [ ] ;
352
377
const sentinelCount = 3 ;
353
378
379
+ const appPrefix = 'sentinel-config-dir' ;
380
+ const tmpDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , appPrefix ) ) ;
381
+
354
382
for ( let i = 0 ; i < sentinelCount ; i ++ ) {
355
383
sentinelPromises . push ( ( async ( ) => {
356
- const sentinelArgs : Array < string > = [ "sh" , "-c" ] ;
357
-
358
- let sentinelConfig = `
359
- port {port}
384
+ const port = ( await portIterator . next ( ) ) . value ;
385
+
386
+ let sentinelConfig = `port ${ port }
360
387
sentinel monitor mymaster 127.0.0.1 ${ master . port } 2
361
388
sentinel down-after-milliseconds mymaster 5000
362
389
sentinel failover-timeout mymaster 6000
363
390
` ;
364
391
if ( password !== undefined ) {
365
- sentinelConfig += `requirepass ${ password } \n`
366
- sentinelConfig += `sentinel auth-pass mymaster ${ password } \n`
392
+ sentinelConfig += `requirepass ${ password } \n` ;
393
+ sentinelConfig += `sentinel auth-pass mymaster ${ password } \n` ;
367
394
}
368
395
369
- sentinelArgs . push ( `echo "${ sentinelConfig } " > /tmp/sentinel.conf && redis-sentinel /tmp/sentinel.conf` ) ;
370
- return await spawnRedisServerDocker ( { image : "redis" , version : "latest" } , sentinelArgs ) ;
396
+ const dir = fs . mkdtempSync ( path . join ( tmpDir , i . toString ( ) ) ) ;
397
+ fs . writeFile ( `${ dir } /redis.conf` , sentinelConfig , err => { } ) ;
398
+
399
+ return await spawnRedisServerDocker (
400
+ {
401
+ image : dockerConfigs . image ,
402
+ version : dockerConfigs . version ,
403
+ mode : "sentinel" ,
404
+ mounts : [ `${ dir } /redis.conf:/redis/config/node-sentinel-1/redis.conf` ] ,
405
+ port : port ,
406
+ } , serverArguments , dockerEnv ) ;
371
407
} ) ( ) ) ;
372
408
}
373
409
374
410
const sentinelNodes = await Promise . all ( sentinelPromises ) ;
375
411
RUNNING_SENTINELS . set ( serverArguments , sentinelNodes ) ;
376
412
413
+ if ( tmpDir ) {
414
+ fs . rmSync ( tmpDir , { recursive : true } ) ;
415
+ }
416
+
377
417
return sentinelNodes ;
378
418
}
379
419
380
-
381
420
after ( ( ) => {
382
421
return Promise . all (
383
422
[ ...RUNNING_NODES . values ( ) , ...RUNNING_SENTINELS . values ( ) ] . map ( async dockersPromise => {
@@ -386,4 +425,4 @@ after(() => {
386
425
) ;
387
426
} )
388
427
) ;
389
- } ) ;
428
+ } ) ;
0 commit comments