Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lambdas/functions/control-plane/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@aws-lambda-powertools/parameters": "^2.31.0",
"@aws-sdk/client-ec2": "^3.984.0",
"@aws-sdk/client-sqs": "^3.984.0",
"@middy/core": "^6.4.5",
"@middy/core": "^7.1.2",
"@octokit/auth-app": "8.2.0",
"@octokit/core": "7.0.6",
"@octokit/plugin-retry": "8.0.3",
Expand Down
14 changes: 3 additions & 11 deletions lambdas/functions/control-plane/src/lambda.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { captureLambdaHandler, logger } from '@aws-github-runner/aws-powertools-util';
import { logger } from '@aws-github-runner/aws-powertools-util';
import { Context, SQSEvent, SQSRecord } from 'aws-lambda';

import { addMiddleware, adjustPool, scaleDownHandler, scaleUpHandler, ssmHousekeeper, jobRetryCheck } from './lambda';
import { adjustPool, scaleDownHandler, scaleUpHandler, ssmHousekeeper, jobRetryCheck } from './lambda';
import { adjust } from './pool/pool';
import ScaleError from './scale-runners/ScaleError';
import { scaleDown } from './scale-runners/scale-down';
import { ActionRequestMessage, scaleUp } from './scale-runners/scale-up';
import { cleanSSMTokens } from './scale-runners/ssm-housekeeper';
import { checkAndRetryJob } from './scale-runners/job-retry';
import { describe, it, expect, vi, MockedFunction, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach } from 'vitest';

const body: ActionRequestMessage = {
eventType: 'workflow_job',
Expand Down Expand Up @@ -260,14 +260,6 @@ describe('Adjust pool.', () => {
});
});

describe('Test middleware', () => {
it('Should have a working middleware', async () => {
const mockedLambdaHandler = captureLambdaHandler as MockedFunction<typeof captureLambdaHandler>;
mockedLambdaHandler.mockReturnValue({ before: vi.fn(), after: vi.fn(), onError: vi.fn() });
expect(addMiddleware).not.toThrowError();
});
});

describe('Test ssm housekeeper lambda wrapper.', () => {
it('Invoke without errors.', async () => {
vi.mocked(cleanSSMTokens).mockResolvedValue();
Expand Down
42 changes: 26 additions & 16 deletions lambdas/functions/control-plane/src/lambda.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import middy from '@middy/core';
import type { MiddlewareObj } from '@middy/core';
import { logger, setContext } from '@aws-github-runner/aws-powertools-util';
import { captureLambdaHandler, tracer } from '@aws-github-runner/aws-powertools-util';
import { Context, type SQSBatchItemFailure, type SQSBatchResponse, SQSEvent } from 'aws-lambda';
Expand All @@ -10,7 +11,13 @@ import { type ActionRequestMessage, type ActionRequestMessageSQS, scaleUp } from
import { SSMCleanupOptions, cleanSSMTokens } from './scale-runners/ssm-housekeeper';
import { checkAndRetryJob } from './scale-runners/job-retry';

export async function scaleUpHandler(event: SQSEvent, context: Context): Promise<SQSBatchResponse> {
// Type assertion helper for AWS PowerTools middleware compatibility with Middy v7
// PowerTools returns MiddlewareLikeObj which is runtime-compatible but has stricter types
const asMiddleware = <TEvent, TResult>(
middleware: ReturnType<typeof captureLambdaHandler>,
): MiddlewareObj<TEvent, TResult, Error, Context> => middleware as MiddlewareObj<TEvent, TResult, Error, Context>;

async function handleScaleUp(event: SQSEvent, context: Context): Promise<SQSBatchResponse> {
setContext(context, 'lambda.ts');
logger.logEventIfEnabled(event);

Expand Down Expand Up @@ -64,7 +71,7 @@ export async function scaleUpHandler(event: SQSEvent, context: Context): Promise
}
}

export async function scaleDownHandler(event: unknown, context: Context): Promise<void> {
async function handleScaleDown(event: unknown, context: Context): Promise<void> {
setContext(context, 'lambda.ts');
logger.logEventIfEnabled(event);

Expand All @@ -75,7 +82,7 @@ export async function scaleDownHandler(event: unknown, context: Context): Promis
}
}

export async function adjustPool(event: PoolEvent, context: Context): Promise<void> {
async function handleAdjustPool(event: PoolEvent, context: Context): Promise<void> {
setContext(context, 'lambda.ts');
logger.logEventIfEnabled(event);

Expand All @@ -87,19 +94,7 @@ export async function adjustPool(event: PoolEvent, context: Context): Promise<vo
return Promise.resolve();
}

export const addMiddleware = () => {
const handler = captureLambdaHandler(tracer);
if (!handler) {
return;
}
middy(scaleUpHandler).use(handler);
middy(scaleDownHandler).use(handler);
middy(adjustPool).use(handler);
middy(ssmHousekeeper).use(handler);
};
addMiddleware();

export async function ssmHousekeeper(event: unknown, context: Context): Promise<void> {
async function handleSSMHousekeeper(event: unknown, context: Context): Promise<void> {
setContext(context, 'lambda.ts');
logger.logEventIfEnabled(event);
const config = JSON.parse(process.env.SSM_CLEANUP_CONFIG) as SSMCleanupOptions;
Expand All @@ -111,6 +106,21 @@ export async function ssmHousekeeper(event: unknown, context: Context): Promise<
}
}

// Export handlers with AWS PowerTools middleware
const powertoolsMiddleware = captureLambdaHandler(tracer);
export const scaleUpHandler = powertoolsMiddleware
? middy(handleScaleUp).use(asMiddleware<SQSEvent, SQSBatchResponse>(powertoolsMiddleware))
: handleScaleUp;
export const scaleDownHandler = powertoolsMiddleware
? middy(handleScaleDown).use(asMiddleware<unknown, void>(powertoolsMiddleware))
: handleScaleDown;
export const adjustPool = powertoolsMiddleware
? middy(handleAdjustPool).use(asMiddleware<PoolEvent, void>(powertoolsMiddleware))
: handleAdjustPool;
export const ssmHousekeeper = powertoolsMiddleware
? middy(handleSSMHousekeeper).use(asMiddleware<unknown, void>(powertoolsMiddleware))
: handleSSMHousekeeper;

export async function jobRetryCheck(event: SQSEvent, context: Context): Promise<void> {
setContext(context, 'lambda.ts');
logger.logEventIfEnabled(event);
Expand Down
2 changes: 1 addition & 1 deletion lambdas/functions/gh-agent-syncer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@aws-github-runner/aws-powertools-util": "*",
"@aws-sdk/client-s3": "^3.984.0",
"@aws-sdk/lib-storage": "^3.984.0",
"@middy/core": "^6.4.5",
"@middy/core": "^7.1.2",
"@octokit/rest": "22.0.1",
"axios": "^1.13.5"
},
Expand Down
14 changes: 12 additions & 2 deletions lambdas/functions/gh-agent-syncer/src/lambda.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import middy from '@middy/core';
import type { MiddlewareObj } from '@middy/core';
import { logger, setContext } from '@aws-github-runner/aws-powertools-util';
import { captureLambdaHandler, tracer } from '@aws-github-runner/aws-powertools-util';
import { Context } from 'aws-lambda';

import { sync } from './syncer/syncer';

middy(handler).use(captureLambdaHandler(tracer));
// Type assertion helper for AWS PowerTools middleware compatibility with Middy v7
const asMiddleware = <TEvent, TResult>(
middleware: ReturnType<typeof captureLambdaHandler>,
): MiddlewareObj<TEvent, TResult, Error, Context> => middleware as MiddlewareObj<TEvent, TResult, Error, Context>;

// eslint-disable-next-line
export async function handler(event: any, context: Context): Promise<void> {
async function handleSync(event: any, context: Context): Promise<void> {
setContext(context, 'lambda.ts');
logger.logEventIfEnabled(event);

Expand All @@ -21,3 +25,9 @@ export async function handler(event: any, context: Context): Promise<void> {
logger.debug('Ignoring error', { error: e });
}
}

// Export handler with AWS PowerTools middleware
const powertoolsMiddleware = captureLambdaHandler(tracer);
export const handler = powertoolsMiddleware
? middy(handleSync).use(asMiddleware<unknown, void>(powertoolsMiddleware))
: handleSync;
2 changes: 1 addition & 1 deletion lambdas/functions/termination-watcher/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"dependencies": {
"@aws-github-runner/aws-powertools-util": "*",
"@aws-sdk/client-ec2": "^3.984.0",
"@middy/core": "^6.4.5"
"@middy/core": "^7.1.2"
},
"nx": {
"includedScripts": [
Expand Down
39 changes: 22 additions & 17 deletions lambdas/functions/termination-watcher/src/lambda.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import middy from '@middy/core';
import type { MiddlewareObj } from '@middy/core';
import { captureLambdaHandler, logger, metrics, setContext, tracer } from '@aws-github-runner/aws-powertools-util';
import { logMetrics } from '@aws-lambda-powertools/metrics/middleware';
import { Context } from 'aws-lambda';
Expand All @@ -8,9 +9,14 @@ import { handle as handleTermination } from './termination';
import { BidEvictedDetail, BidEvictedEvent, SpotInterruptionWarning, SpotTerminationDetail } from './types';
import { Config } from './ConfigResolver';

// Type assertion helper for AWS PowerTools middleware compatibility with Middy v7
const asMiddleware = <TEvent, TResult>(
middleware: ReturnType<typeof captureLambdaHandler> | ReturnType<typeof logMetrics>,
): MiddlewareObj<TEvent, TResult, Error, Context> => middleware as MiddlewareObj<TEvent, TResult, Error, Context>;

const config = new Config();

export async function interruptionWarning(
async function handleInterruptionWarning(
event: SpotInterruptionWarning<SpotTerminationDetail>,
context: Context,
): Promise<void> {
Expand All @@ -25,7 +31,7 @@ export async function interruptionWarning(
}
}

export async function termination(event: BidEvictedEvent<BidEvictedDetail>, context: Context): Promise<void> {
async function handleBidEvicted(event: BidEvictedEvent<BidEvictedDetail>, context: Context): Promise<void> {
setContext(context, 'lambda.ts');
logger.logEventIfEnabled(event);
logger.debug('Configuration of the lambda', { config });
Expand All @@ -37,20 +43,19 @@ export async function termination(event: BidEvictedEvent<BidEvictedDetail>, cont
}
}

const addMiddleware = () => {
const middleware = middy(interruptionWarning);

const c = captureLambdaHandler(tracer);
if (c) {
logger.debug('Adding captureLambdaHandler middleware');
middleware.use(c);
}
// Export handlers with AWS PowerTools middleware
const tracingMiddleware = captureLambdaHandler(tracer);
const metricsMiddleware = logMetrics(metrics);

const l = logMetrics(metrics);
if (l) {
logger.debug('Adding logMetrics middleware');
middleware.use(l);
}
};
const interruptionWarningHandler = middy(handleInterruptionWarning);
if (tracingMiddleware) {
logger.debug('Adding captureLambdaHandler middleware');
interruptionWarningHandler.use(asMiddleware<SpotInterruptionWarning<SpotTerminationDetail>, void>(tracingMiddleware));
}
if (metricsMiddleware) {
logger.debug('Adding logMetrics middleware');
interruptionWarningHandler.use(asMiddleware<SpotInterruptionWarning<SpotTerminationDetail>, void>(metricsMiddleware));
}

addMiddleware();
export const interruptionWarning = interruptionWarningHandler;
export const termination = handleBidEvicted;
2 changes: 1 addition & 1 deletion lambdas/functions/webhook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@aws-github-runner/aws-powertools-util": "*",
"@aws-github-runner/aws-ssm-util": "*",
"@aws-sdk/client-sqs": "^3.984.0",
"@middy/core": "^6.4.5",
"@middy/core": "^7.1.2",
"@octokit/rest": "22.0.1",
"@octokit/types": "^16.0.0",
"@octokit/webhooks": "^14.2.0",
Expand Down
8 changes: 4 additions & 4 deletions lambdas/functions/webhook/src/ConfigLoader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
});

await expect(ConfigWebhook.load()).rejects.toThrow(
'Failed to load config: Failed to load parameter for matcherConfig from path /path/to/matcher/config: Failed to load matcher config', // eslint-disable-line max-len
'Failed to load config: Failed to load parameter for matcherConfig from path /path/to/matcher/config: Failed to load matcher config',
);
});

Expand Down Expand Up @@ -224,8 +224,8 @@
return '';
});

await expect(ConfigWebhook.load()).rejects.toThrow(

Check failure on line 227 in lambdas/functions/webhook/src/ConfigLoader.test.ts

View workflow job for this annotation

GitHub Actions / Build and test lambda functions

src/ConfigLoader.test.ts > ConfigLoader Tests > ConfigWebhook > should throw error if config loading fails from multiple paths

AssertionError: expected [Function] to throw error including 'Failed to load config: Failed to pars…' but got 'Failed to load config: Failed to load…' Expected: "Failed to load config: Failed to parse combined matcher config: Expected ',' or ']' after array element in JSON at position 196" Received: "Failed to load config: Failed to load/parse combined matcher config: Expected ',' or ']' after array element in JSON at position 196 (line 1 column 197), Matcher config is empty" ❯ src/ConfigLoader.test.ts:227:7

Check failure on line 227 in lambdas/functions/webhook/src/ConfigLoader.test.ts

View workflow job for this annotation

GitHub Actions / Build and test lambda functions

src/ConfigLoader.test.ts > ConfigLoader Tests > ConfigWebhook > should throw error if config loading fails from multiple paths

AssertionError: expected [Function] to throw error including 'Failed to load config: Failed to pars…' but got 'Failed to load config: Failed to load…' Expected: "Failed to load config: Failed to parse combined matcher config: Expected ',' or ']' after array element in JSON at position 196" Received: "Failed to load config: Failed to load/parse combined matcher config: Expected ',' or ']' after array element in JSON at position 196 (line 1 column 197), Matcher config is empty" ❯ src/ConfigLoader.test.ts:227:7
"Failed to load config: Failed to load/parse combined matcher config: Expected ',' or ']' after array element in JSON at position 196", // eslint-disable-line max-len
"Failed to load config: Failed to parse combined matcher config: Expected ',' or ']' after array element in JSON at position 196",
);
});
});
Expand Down Expand Up @@ -256,7 +256,7 @@
});

await expect(ConfigWebhookEventBridge.load()).rejects.toThrow(
'Failed to load config: Environment variable for eventBusName is not set and no default value provided., Failed to load parameter for webhookSecret from path undefined: Parameter undefined not found', // eslint-disable-line max-len
'Failed to load config: Environment variable for eventBusName is not set and no default value provided., Failed to load parameter for webhookSecret from path undefined: Parameter undefined not found',
);
});
});
Expand Down Expand Up @@ -323,7 +323,7 @@
});

await expect(ConfigDispatcher.load()).rejects.toThrow(
'Failed to load config: Failed to load parameter for matcherConfig from path undefined: Parameter undefined not found', // eslint-disable-line max-len
'Failed to load config: Failed to load parameter for matcherConfig from path undefined: Parameter undefined not found',
);
});

Expand Down
2 changes: 1 addition & 1 deletion lambdas/functions/webhook/src/ConfigLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ abstract class BaseConfig {
this.loadProperty(propertyName, value);
})
.catch((error) => {
const errorMessage = `Failed to load parameter for ${String(propertyName)} from path ${paramPath}: ${(error as Error).message}`; // eslint-disable-line max-len
const errorMessage = `Failed to load parameter for ${String(propertyName)} from path ${paramPath}: ${(error as Error).message}`;
this.configLoadingErrors.push(errorMessage);
});
}
Expand Down
16 changes: 13 additions & 3 deletions lambdas/functions/webhook/src/lambda.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import middy from '@middy/core';
import type { MiddlewareObj } from '@middy/core';
import { logger, setContext, captureLambdaHandler, tracer } from '@aws-github-runner/aws-powertools-util';
import { APIGatewayEvent, Context } from 'aws-lambda';

Expand All @@ -10,14 +11,17 @@ import { WorkflowJobEvent } from '@octokit/webhooks-types';
import { ConfigDispatcher, ConfigWebhook, ConfigWebhookEventBridge } from './ConfigLoader';
import { dispatch } from './runners/dispatch';

// Type assertion helper for AWS PowerTools middleware compatibility with Middy v7
const asMiddleware = <TEvent, TResult>(
middleware: ReturnType<typeof captureLambdaHandler>,
): MiddlewareObj<TEvent, TResult, Error, Context> => middleware as MiddlewareObj<TEvent, TResult, Error, Context>;

export interface Response {
statusCode: number;
body: string;
}

middy(directWebhook).use(captureLambdaHandler(tracer));

export async function directWebhook(event: APIGatewayEvent, context: Context): Promise<Response> {
async function handleDirectWebhook(event: APIGatewayEvent, context: Context): Promise<Response> {
setContext(context, 'lambda.ts');
logger.logEventIfEnabled(event);

Expand Down Expand Up @@ -93,3 +97,9 @@ function headersToLowerCase(headers: IncomingHttpHeaders): IncomingHttpHeaders {
}
return headers;
}

// Export handlers with AWS PowerTools middleware
const powertoolsMiddleware = captureLambdaHandler(tracer);
export const directWebhook = powertoolsMiddleware
? middy(handleDirectWebhook).use(asMiddleware<APIGatewayEvent, Response>(powertoolsMiddleware))
: handleDirectWebhook;
30 changes: 22 additions & 8 deletions lambdas/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ __metadata:
"@aws-sdk/client-ec2": "npm:^3.984.0"
"@aws-sdk/client-sqs": "npm:^3.984.0"
"@aws-sdk/types": "npm:^3.973.1"
"@middy/core": "npm:^6.4.5"
"@middy/core": "npm:^7.1.2"
"@octokit/auth-app": "npm:8.2.0"
"@octokit/core": "npm:7.0.6"
"@octokit/plugin-retry": "npm:8.0.3"
Expand Down Expand Up @@ -180,7 +180,7 @@ __metadata:
"@aws-sdk/client-s3": "npm:^3.984.0"
"@aws-sdk/lib-storage": "npm:^3.984.0"
"@aws-sdk/types": "npm:^3.973.1"
"@middy/core": "npm:^6.4.5"
"@middy/core": "npm:^7.1.2"
"@octokit/rest": "npm:22.0.1"
"@types/aws-lambda": "npm:^8.10.159"
"@types/node": "npm:^22.19.3"
Expand All @@ -200,7 +200,7 @@ __metadata:
"@aws-github-runner/aws-powertools-util": "npm:*"
"@aws-sdk/client-ec2": "npm:^3.984.0"
"@aws-sdk/types": "npm:^3.973.1"
"@middy/core": "npm:^6.4.5"
"@middy/core": "npm:^7.1.2"
"@types/aws-lambda": "npm:^8.10.159"
"@types/node": "npm:^22.19.3"
"@vercel/ncc": "npm:^0.38.4"
Expand All @@ -217,7 +217,7 @@ __metadata:
"@aws-github-runner/aws-ssm-util": "npm:*"
"@aws-sdk/client-eventbridge": "npm:^3.984.0"
"@aws-sdk/client-sqs": "npm:^3.984.0"
"@middy/core": "npm:^6.4.5"
"@middy/core": "npm:^7.1.2"
"@octokit/rest": "npm:22.0.1"
"@octokit/types": "npm:^16.0.0"
"@octokit/webhooks": "npm:^14.2.0"
Expand Down Expand Up @@ -3302,10 +3302,24 @@ __metadata:
languageName: node
linkType: hard

"@middy/core@npm:^6.4.5":
version: 6.4.5
resolution: "@middy/core@npm:6.4.5"
checksum: 10c0/7fe05cafbaa6b3fe6f827b691808666f9d4bd96ca96496b2f70823d8b90e985488ea203d3fce4fd760605b7a1922c39cb399980a3bae39a905d467dd517ea666
"@middy/core@npm:^7.1.2":
version: 7.1.3
resolution: "@middy/core@npm:7.1.3"
dependencies:
"@middy/util": "npm:7.1.3"
peerDependencies:
"@aws/durable-execution-sdk-js": ^1.0.0
peerDependenciesMeta:
"@aws/durable-execution-sdk-js":
optional: true
checksum: 10c0/66d2d6226fa055d5a09f8fd9f60be2a2314ab93d3431f0477d53d5a6ec962dd09f02c90ef89a3895263c8d72e2489c90442d0a818c9cde63e9b788389ad2d7a7
languageName: node
linkType: hard

"@middy/util@npm:7.1.3":
version: 7.1.3
resolution: "@middy/util@npm:7.1.3"
checksum: 10c0/e21b514f6d4421e2b56f0eb0412da77e4b6ff0a12b9bbb4c24dae51b12cca885e635a0319714ff72379550ad3f26bf0fcab690d82ffb8453b89db66aecbfcbac
languageName: node
linkType: hard

Expand Down
Loading