@@ -26,76 +26,52 @@ export async function handler (args: ArgumentsCamelCase) {
2626 assertNumber ( timeout , "timeout must be a number in ms" ) ;
2727 const interval = args . interval ;
2828 assertNumber ( interval , "interval must be a number in ms" ) ;
29- const stableChecks = args . stableChecks ;
30- assertNumber ( stableChecks , "stableChecks must be a number" ) ;
3129
3230 const dockerode = new Docker ( ) ;
3331
3432 console . log ( `Awaiting task reconciliation for ${ timeout } ms` ) ;
3533
36- let services : Service [ ] , tasks : Task [ ] , timedout , bail , serviceStateMap ;
37- let stableStreak = 0 ;
38- const seenFailedTaskIds = new Set < string > ( ) ;
39- const restartCooldown = new Map < string , number > ( ) ;
40- let firstCheck = true ;
34+ const reportedStates = new Map < string , string > ( ) ;
35+ let services : Service [ ] , tasks : Task [ ] , timedout , settled , unrecoverable ;
4136 const start = Date . now ( ) ;
4237 do {
4338 // To prevent high cpu usage
4439 await timers . setTimeout ( interval ) ;
4540 // Calculate timedout
4641 timedout = Date . now ( ) - timeout > start ;
4742
48- serviceStateMap = new Map < string , string > ( ) ;
4943 services = await dockerode . listServices ( { filters : { label : [ `com.docker.stack.namespace=${ appName } ` ] } } ) ;
5044 tasks = await dockerode . listTasks ( { filters : { "label" : [ `com.docker.stack.namespace=${ appName } ` ] } } ) as Task [ ] ;
5145
46+ settled = true ;
47+ unrecoverable = false ;
5248 for ( const s of services ) {
49+ const serviceName = s . Spec ?. Name ;
50+ assert ( serviceName != null , "serviceName must be a string" ) ;
51+ const updateState = s . UpdateStatus ?. State ?? "deployed" ;
52+ const stalled = updateState === "paused" || updateState . startsWith ( "rollback_" ) ;
5353 const runningTasks = tasks . filter ( ( t ) => t . Status . State === "running" && t . ServiceID === s . ID ) ;
5454 const desiredReplicas = s . Spec ?. Mode ?. Replicated ?. Replicas ?? 0 ;
55+ const state = ! stalled && runningTasks . length < desiredReplicas ? `replicating ${ runningTasks . length } /${ desiredReplicas } ` : updateState ;
5556
56- // Always check replica count first - compare running tasks against desired replicas
57- if ( runningTasks . length < desiredReplicas ) {
58- serviceStateMap . set ( s . ID , "replicating" ) ;
59- } else if ( s . UpdateStatus ?. State && [ "updating" , "paused" , "rollback_started" , "rollback_paused" ] . includes ( s . UpdateStatus . State ) ) {
60- serviceStateMap . set ( s . ID , s . UpdateStatus . State ) ;
61- }
62- }
63-
64- for ( const t of tasks ) {
65- if ( ! [ "failed" , "rejected" ] . includes ( t . Status . State ) || seenFailedTaskIds . has ( t . ID ) ) {
66- continue ;
67- }
68- seenFailedTaskIds . add ( t . ID ) ;
69- if ( ! firstCheck ) {
70- restartCooldown . set ( t . ServiceID , stableChecks ) ;
57+ if ( reportedStates . get ( s . ID ) !== state ) {
58+ const errMsg = tasks . find ( ( t ) => t . ServiceID === s . ID && t . Status . Err ) ?. Status . Err ;
59+ console . log ( `${ serviceName } is in ${ state } state${ errMsg ? ", error: '" + errMsg + "'" : "" } ` ) ;
60+ reportedStates . set ( s . ID , state ) ;
7161 }
72- }
73- firstCheck = false ;
7462
75- for ( const [ serviceId , remaining ] of restartCooldown ) {
76- serviceStateMap . set ( serviceId , "restarting" ) ;
77- restartCooldown . set ( serviceId , remaining - 1 ) ;
78- if ( remaining - 1 <= 0 ) {
79- restartCooldown . delete ( serviceId ) ;
63+ if ( stalled ) {
64+ unrecoverable = true ;
65+ } else if ( ! [ "deployed" , "completed" ] . includes ( state ) ) {
66+ settled = false ;
8067 }
8168 }
69+ } while ( ! timedout && ! unrecoverable && ! settled ) ;
8270
83- const servicesUpdating = [ ...serviceStateMap . entries ( ) ] ;
84-
85- if ( servicesUpdating . length === 0 ) {
86- stableStreak ++ ;
87- } else {
88- stableStreak = 0 ;
89- for ( const [ serviceId , state ] of servicesUpdating ) {
90- const serviceName = services . find ( ( s ) => s . ID === serviceId ) ?. Spec ?. Name ;
91- assert ( serviceName != null , "serviceName must be a string" ) ;
92- const errMsg = tasks . find ( ( t ) => t . ServiceID === serviceId && t . Status . Err ) ?. Status . Err ;
93- console . log ( `${ serviceName } is in ${ state } state${ errMsg ? ", error: '" + errMsg + "'" : "" } ` ) ;
94- }
95- }
96-
97- bail = stableStreak >= stableChecks ;
98- } while ( ! timedout && ! bail ) ;
71+ if ( unrecoverable ) {
72+ console . error ( "This deployment will not complete" ) ;
73+ process . exit ( 1 ) ;
74+ }
9975
10076 if ( timedout ) {
10177 console . error ( "Reconciliation timed out" ) ;
@@ -117,11 +93,6 @@ export function builder (yargs: Argv) {
11793 description : "How often reconciliation should run" ,
11894 default : 5000 ,
11995 } ) ;
120- yargs . positional ( "stableChecks" , {
121- type : "number" ,
122- description : "Consecutive settled checks required before success" ,
123- default : 3 ,
124- } ) ;
12596 yargs . hide ( "help" ) ;
12697 yargs . hide ( "version" ) ;
12798 return yargs ;
0 commit comments