11import { ArgumentsCamelCase , Argv } from "yargs" ;
2- import Docker from "dockerode" ;
2+ import Docker , { Service } from "dockerode" ;
33import timers from "timers/promises" ;
44import { assertNumber , assertString } from "../asserts.js" ;
55import { yargsAppNameFileOption } from "./deploy-cmd" ;
6+ import assert from "assert" ;
67
78interface Task {
9+ ID : string ;
10+ ServiceID : string ;
11+ Slot : number ;
812 DesiredState : string ;
913 Status : {
1014 State : string ;
@@ -23,50 +27,49 @@ export async function handler (args: ArgumentsCamelCase) {
2327
2428 const dockerode = new Docker ( ) ;
2529
26- console . log ( `Awaiting task reconciliation for a max of ${ timeout } ms` ) ;
30+ console . log ( `Awaiting task reconciliation for ${ timeout } ms` ) ;
2731
28- let services ;
29- let timedout = false ;
30- let reconciled ;
31- let latestTaskError = "" ;
32+ let services : Service [ ] , tasks : Task [ ] , timedout , bail , serviceStateMap ;
3233 const start = Date . now ( ) ;
3334 do {
34- latestTaskError = "" ;
35- reconciled = true ;
35+ serviceStateMap = new Map < string , string > ( ) ;
3636 services = await dockerode . listServices ( { filters : { label : [ `com.docker.stack.namespace=${ appName } ` ] } } ) ;
37+ tasks = await dockerode . listTasks ( { filters : { "label" : [ `com.docker.stack.namespace=${ appName } ` ] , "desired-state" : [ "running" ] } } ) as Task [ ] ;
3738
38- // Check the tasks for failures.
3939 for ( const s of services ) {
40- const tasks = await dockerode . listTasks ( {
41- Filter : `service=${ s . Spec ?. Name } ` ,
42- } ) as Task [ ] ;
43- for ( const t of tasks ) {
44- if ( t . DesiredState === "ready" && t . Status . State != "running" ) {
45- reconciled = false ;
46- }
47- if ( t . Status . State === "rejected" && latestTaskError == "" ) {
48- latestTaskError = t . Status . Err ;
40+ if ( s . UpdateStatus ?. State ) {
41+ serviceStateMap . set ( s . ID , s . UpdateStatus . State ) ;
42+ } else {
43+ const runningTasks = tasks . filter ( t => t . Status . State === "running" && t . ServiceID === s . ID ) ;
44+ const totalTasks = tasks . filter ( t => t . ServiceID === s . ID ) ;
45+ if ( totalTasks . length > runningTasks . length ) {
46+ serviceStateMap . set ( s . ID ?? "unspecified" , "replicating" ) ;
4947 }
5048 }
5149 }
5250
51+ const servicesUpdating = [ ...serviceStateMap ] . filter ( ( [ v ] ) => ! [ "completed" , "rollback_completed" ] . includes ( v ) ) ;
52+ bail = servicesUpdating . length === 0 ;
53+ if ( ! bail ) {
54+ servicesUpdating . forEach ( ( [ serviceId , state ] ) => {
55+ const serviceName = services . find ( s => s . ID === serviceId ) ?. Spec ?. Name ;
56+ assert ( serviceName != null , "serviceName must be a string" ) ;
57+ const errMsg = tasks . find ( t => t . ServiceID === serviceId && t . Status . Err ) ?. Status . Err ;
58+ console . log ( `${ serviceName } is in ${ state } ${ errMsg ? ", error: '" + errMsg + "'" : "" } ` ) ;
59+ } ) ;
60+ }
61+
5362 // To prevent high cpu usage
5463 await timers . setTimeout ( 5000 ) ;
64+ // Calculate timedout
5565 timedout = Date . now ( ) - timeout > start ;
56- if ( ! reconciled && latestTaskError != "" ) {
57- console . error ( latestTaskError ) ;
58- }
59- } while ( ! timedout && ! reconciled ) ;
60-
61- if ( ! reconciled || timedout ) {
62- if ( timedout ) {
63- console . error ( "Reconciliation timed out" ) ;
64- } else {
65- console . error ( "Reconciliation failed" ) ;
66- }
66+ } while ( ! timedout && ! bail ) ;
6767
68+ if ( timedout ) {
69+ console . error ( "Reconciliation timed out" ) ;
6870 process . exit ( 1 ) ;
6971 }
72+
7073 console . log ( "Reconciliation succeeded" ) ;
7174}
7275
0 commit comments