diff --git a/packages/cli/src/constructs/check-group.ts b/packages/cli/src/constructs/check-group.ts index 88e1d294..f3a1d629 100644 --- a/packages/cli/src/constructs/check-group.ts +++ b/packages/cli/src/constructs/check-group.ts @@ -14,6 +14,7 @@ import { ApiCheckDefaultConfig } from './api-check' import { pathToPosix } from '../services/util' import type { Region } from '..' import type { Frequency } from './frequency' +import type { RetryStrategy } from './retry-strategy' const defaultApiCheckDefaults: ApiCheckDefaultConfig = { headers: [], @@ -93,6 +94,10 @@ export interface CheckGroupProps { */ localTearDownScript?: string apiCheckDefaults?: ApiCheckDefaultConfig + /** + * Sets a retry policy for the group. Use RetryStrategyBuilder to create a retry policy. + */ + retryStrategy?: RetryStrategy } /** @@ -119,6 +124,7 @@ export class CheckGroup extends Construct { localTearDownScript?: string apiCheckDefaults: ApiCheckDefaultConfig browserChecks?: BrowserCheckConfig + retryStrategy?: RetryStrategy static readonly __checklyType = 'check-group' @@ -156,6 +162,7 @@ export class CheckGroup extends Construct { this.alertChannels = props.alertChannels ?? [] this.localSetupScript = props.localSetupScript this.localTearDownScript = props.localTearDownScript + this.retryStrategy = props.retryStrategy // `browserChecks` is not a CheckGroup resource property. Not present in synthesize() this.browserChecks = props.browserChecks const fileAbsolutePath = Session.checkFileAbsolutePath! @@ -247,6 +254,7 @@ export class CheckGroup extends Construct { localTearDownScript: this.localTearDownScript, apiCheckDefaults: this.apiCheckDefaults, environmentVariables: this.environmentVariables, + retryStrategy: this.retryStrategy, } } } diff --git a/packages/cli/src/constructs/check.ts b/packages/cli/src/constructs/check.ts index 689f3032..990d589b 100644 --- a/packages/cli/src/constructs/check.ts +++ b/packages/cli/src/constructs/check.ts @@ -10,6 +10,7 @@ import type { Region } from '..' import type { CheckGroup } from './check-group' import { PrivateLocation } from './private-location' import { PrivateLocationCheckAssignment } from './private-location-check-assignment' +import { RetryStrategy } from './retry-strategy' export interface CheckProps { /** @@ -80,6 +81,10 @@ export interface CheckProps { * Determines if the check is available only when 'test' runs (not included when 'deploy' is executed). */ testOnly?: boolean + /** + * Sets a retry policy for the check. Use RetryStrategyBuilder to create a retry policy. + */ + retryStrategy?: RetryStrategy } // This is an abstract class. It shouldn't be used directly. @@ -99,6 +104,7 @@ export abstract class Check extends Construct { groupId?: Ref alertChannels?: Array testOnly?: boolean + retryStrategy?: RetryStrategy __checkFilePath?: string // internal variable to filter by check file name from the CLI static readonly __checklyType = 'check' @@ -134,6 +140,7 @@ export abstract class Check extends Construct { // alertSettings, useGlobalAlertSettings, groupId, groupOrder this.testOnly = props.testOnly ?? false + this.retryStrategy = props.retryStrategy this.__checkFilePath = Session.checkFilePath } @@ -209,6 +216,7 @@ export abstract class Check extends Construct { frequencyOffset: this.frequencyOffset, groupId: this.groupId, environmentVariables: this.environmentVariables, + retryStrategy: this.retryStrategy, } } } diff --git a/packages/cli/src/constructs/index.ts b/packages/cli/src/constructs/index.ts index cbeb9097..e310ae81 100644 --- a/packages/cli/src/constructs/index.ts +++ b/packages/cli/src/constructs/index.ts @@ -22,3 +22,4 @@ export * from './private-location-group-assignment' export * from './check' export * from './dashboard' export * from './phone-call-alert-channel' +export * from './retry-strategy' diff --git a/packages/cli/src/constructs/retry-strategy.ts b/packages/cli/src/constructs/retry-strategy.ts new file mode 100644 index 00000000..0e0785f2 --- /dev/null +++ b/packages/cli/src/constructs/retry-strategy.ts @@ -0,0 +1,67 @@ +export type RetryStrategyType = 'LINEAR' | 'EXPONENTIAL' | 'FIXED' + +export interface RetryStrategy { + type: RetryStrategyType, + /** + * The number of seconds to wait before the first retry attempt. + */ + baseBackoffSeconds?: number, + /** + * The maximum number of attempts to retry the check. Value must be between 1 and 10. + */ + maxAttempts?: number, + /** + * The total amount of time to continue retrying the check (maximum 600 seconds). + */ + maxDurationSeconds?: number, + /** + * Whether retries should be run in the same region as the initial check run. + */ + sameRegion?: boolean, +} + +export type RetryStrategyOptions = Pick + +export class RetryStrategyBuilder { + private static readonly DEFAULT_BASE_BACKOFF_SECONDS = 60 + private static readonly DEFAULT_MAX_ATTEMPTS = 2 + private static readonly DEFAULT_MAX_DURATION_SECONDS = 60 * 10 + private static readonly DEFAULT_SAME_REGION = false + + /** + * Each retry is run with the same backoff between attempts. + */ + static fixedStrategy (options: RetryStrategyOptions): RetryStrategy { + return RetryStrategyBuilder.retryStrategy('FIXED', options) + } + + /** + * The delay between retries increases linearly + * + * The delay between retries is calculated using `baseBackoffSeconds * attempt`. + * For example, retries will be run with a backoff of 10s, 20s, 30s, and so on. + */ + static linearStrategy (options: RetryStrategyOptions): RetryStrategy { + return RetryStrategyBuilder.retryStrategy('LINEAR', options) + } + + /** + * The delay between retries increases exponentially + * + * The delay between retries is calculated using `baseBackoffSeconds ^ attempt`. + * For example, retries will be run with a backoff of 10s, 100s, 1000s, and so on. + */ + static exponentialStrategy (options: RetryStrategyOptions): RetryStrategy { + return RetryStrategyBuilder.retryStrategy('EXPONENTIAL', options) + } + + private static retryStrategy (type: RetryStrategyType, options: RetryStrategyOptions): RetryStrategy { + return { + type, + baseBackoffSeconds: options.baseBackoffSeconds ?? RetryStrategyBuilder.DEFAULT_BASE_BACKOFF_SECONDS, + maxAttempts: options.maxAttempts ?? RetryStrategyBuilder.DEFAULT_MAX_ATTEMPTS, + maxDurationSeconds: options.maxDurationSeconds ?? RetryStrategyBuilder.DEFAULT_MAX_DURATION_SECONDS, + sameRegion: options.sameRegion ?? RetryStrategyBuilder.DEFAULT_SAME_REGION, + } + } +}