Skip to content

Instantly share code, notes, and snippets.

@abjrcode
Last active April 4, 2024 18:39
Show Gist options
  • Save abjrcode/5e793a0a93af365480ed15db4503ef46 to your computer and use it in GitHub Desktop.
Save abjrcode/5e793a0a93af365480ed15db4503ef46 to your computer and use it in GitHub Desktop.
Pulumi TypeScript component resource to avoid "serial" ECS service updates while still having proper CD

Pulumi has a limitation when it comes to its AWSX Fragate service component as per these two issues:

If you have multiple ECS services, they will update one-by-one instead of in-parallel when continueBeforeSteadyState flag is set to false The flag is essential if you want your deployment build to actually fail when services aren't deployed successfully due to whatever reason Disabling the flag will make everything update in-parallel but if one of the services is not healthy or rolled back then your CI will still not know unless you build custom integrations with AWS EventBridge events

The following is a pulumi component resource that does what Pulumi should do by default and it behaves exactly as you would expect:

import { ECSClient, waitUntilServicesStable } from '@aws-sdk/client-ecs'
import * as pulumi from '@pulumi/pulumi'

export interface EcsAwaiterArgs {
  /**
   * The AWS region where the ECS cluster hosting the services should be awaited.
   */
  awsRegion: pulumi.Input<string>
  /**
   * The name of the ECS cluster where the services should be awaited are located.
   */
  ecsClusterName: pulumi.Input<string>

  /**
   * The names of the ECS services to await.
   */
  ecsServiceNames: pulumi.Input<string>[]

  waitTimeInSeconds: pulumi.Input<number>
}

export class EcsAwaiter extends pulumi.ComponentResource {
  public readonly FinalResult: pulumi.Output<boolean>

  constructor(
    appName: string,
    args: EcsAwaiterArgs,
    opts?: pulumi.ComponentResourceOptions,
  ) {
    super(`pulumi:backend:EcsAwaiter`, appName, {}, opts)

    const allArgs = pulumi.all([
      args.awsRegion,
      args.ecsClusterName,
      args.ecsServiceNames,
      args.waitTimeInSeconds,
    ])

    const waitResult = allArgs.apply(
      async ([awsRegion, ecsClusterName, ecsServiceNames, waitTimeInSeconds]) => {
        const ecsClient = new ECSClient({
          region: awsRegion,
        })

        return await waitUntilServicesStable(
          {
            client: ecsClient,
            maxWaitTime: waitTimeInSeconds,
          },
          { cluster: ecsClusterName, services: ecsServiceNames },
        )
      },
    )

    this.FinalResult = waitResult.apply(resolvedWaitResult => {
      if (resolvedWaitResult.state == 'SUCCESS') {
        void pulumi.log.info('Services are stable')
        return true
      } else {
        throw new Error('Services are not stable')
      }
    })

    this.registerOutputs({
      finalResult: this.FinalResult,
    })
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment