Skip to content

Migrate to AWS SDK V3 - Part 1 #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Aug 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d96015a
Get started on aws sdk v3 upgrade
GrahamCampbell Jun 8, 2025
cad0e90
Complete migration phase 3
GrahamCampbell Jun 8, 2025
0aa768a
Phase 4
GrahamCampbell Jun 8, 2025
0f175aa
Self-review and corrections
GrahamCampbell Jun 8, 2025
03e9b9d
Complete phase 5
GrahamCampbell Jun 8, 2025
63ab7df
Self-review on stage 5
GrahamCampbell Jun 8, 2025
19213fc
Self-review
GrahamCampbell Jun 8, 2025
84185c8
Self-review
GrahamCampbell Jun 8, 2025
2766065
Self-review
GrahamCampbell Jun 8, 2025
2fc0cda
Cleanup
GrahamCampbell Jun 8, 2025
69870c2
Further cleanup
GrahamCampbell Jun 8, 2025
1c122f5
Refactor to a cleaner approach
GrahamCampbell Jun 8, 2025
031b59c
Make internal request methods private
GrahamCampbell Jun 8, 2025
79e9aa0
Run CI on both SDK versions
GrahamCampbell Jun 8, 2025
132431e
Fix env var checking
GrahamCampbell Jun 8, 2025
252ea80
Fix v2 client reuse regression, and make it even better than before
GrahamCampbell Jun 9, 2025
9bdcc72
Revert "Fix v2 client reuse regression, and make it even better than …
GrahamCampbell Jun 9, 2025
985cede
Fix test
GrahamCampbell Jun 9, 2025
4524f01
Apply the correct fix for client re-use
GrahamCampbell Jun 9, 2025
7841f08
Revert formatting changes
mnapoli Jul 30, 2025
dba013f
Remove unused functions
mnapoli Jul 30, 2025
5c9f50a
Remove more unused code
mnapoli Jul 30, 2025
1f828a9
Fix URL
mnapoli Jul 30, 2025
204393a
Remove useless comments
mnapoli Jul 30, 2025
13ebae1
Fix formatting
mnapoli Jul 30, 2025
26965b0
Revert changes to custom resources
mnapoli Aug 4, 2025
57ef13a
Fix workflow config
mnapoli Aug 4, 2025
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
18 changes: 15 additions & 3 deletions .github/workflows/integrate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,24 @@ jobs:
run: |
npm update --no-save
npm update --save-dev --no-save
- name: Unit tests
- name: Unit tests (AWS SDK v2)
# Some tests depend on TTY support, which is missing in GA runner
# Workaround taken from https://github.com/actions/runner/issues/241#issuecomment-577360161
run: script -e -c "npm test -- -b"
- name: Packaging tests
env:
SLS_AWS_SDK_V3: '0'
- name: Unit tests (AWS SDK v3)
run: script -e -c "npm test -- -b"
env:
SLS_AWS_SDK_V3: '1'
- name: Packaging tests (AWS SDK v2)
run: npm run integration-test-run-package
env:
SLS_AWS_SDK_V3: '0'
- name: Packaging tests (AWS SDK v3)
run: npm run integration-test-run-package
env:
SLS_AWS_SDK_V3: '1'

windowsNode16:
name: '[Windows] Node 16: Unit tests'
Expand Down Expand Up @@ -122,7 +134,7 @@ jobs:
integrate:
name: Integrate
runs-on: ubuntu-latest
needs: [linuxNode22, windowsNode22, linuxNode16]
needs: [linuxNode22, windowsNode16, linuxNode16]
timeout-minutes: 30 # Default is 360
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
Expand Down
16 changes: 14 additions & 2 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,24 @@ jobs:
then
npx dump-release-notes-from-cc-changelog $NEW_VERSION
fi
- name: Unit tests
- name: Unit tests (AWS SDK v2)
# Some tests depend on TTY support, which is missing in GA runner
# Workaround taken from https://github.com/actions/runner/issues/241#issuecomment-577360161
run: script -e -c "npm test -- -b"
- name: Packaging tests
env:
SLS_AWS_SDK_V3: '0'
- name: Unit tests (AWS SDK v3)
run: script -e -c "npm test -- -b"
env:
SLS_AWS_SDK_V3: '1'
- name: Packaging tests (AWS SDK v2)
run: npm run integration-test-run-package
env:
SLS_AWS_SDK_V3: '0'
- name: Packaging tests (AWS SDK v3)
run: npm run integration-test-run-package
env:
SLS_AWS_SDK_V3: '1'

windowsNode16:
name: '[Windows] Node 16: Unit tests'
Expand Down
76 changes: 76 additions & 0 deletions lib/aws/client-factory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use strict';

const { APIGatewayClient } = require('@aws-sdk/client-api-gateway');
const { ApiGatewayV2Client } = require('@aws-sdk/client-apigatewayv2');
const { CloudFormationClient } = require('@aws-sdk/client-cloudformation');
const { CloudWatchClient } = require('@aws-sdk/client-cloudwatch');
const { CloudWatchLogsClient } = require('@aws-sdk/client-cloudwatch-logs');
const { CognitoIdentityProviderClient } = require('@aws-sdk/client-cognito-identity-provider');
const { ECRClient } = require('@aws-sdk/client-ecr');
const { EventBridgeClient } = require('@aws-sdk/client-eventbridge');
const { IAMClient } = require('@aws-sdk/client-iam');
const { LambdaClient } = require('@aws-sdk/client-lambda');
const { S3Client } = require('@aws-sdk/client-s3');
const { SSMClient } = require('@aws-sdk/client-ssm');
const { STSClient } = require('@aws-sdk/client-sts');

// Map service names to their client classes
const CLIENT_MAP = {
APIGateway: APIGatewayClient,
ApiGatewayV2: ApiGatewayV2Client,
CloudFormation: CloudFormationClient,
CloudWatch: CloudWatchClient,
CloudWatchLogs: CloudWatchLogsClient,
CognitoIdentityProvider: CognitoIdentityProviderClient,
ECR: ECRClient,
EventBridge: EventBridgeClient,
IAM: IAMClient,
Lambda: LambdaClient,
S3: S3Client,
SSM: SSMClient,
STS: STSClient,
};

class AWSClientFactory {
constructor(baseConfig = {}) {
this.baseConfig = baseConfig;
this.clients = new Map();
}

/**
* Get a configured AWS service client
* @param {string} serviceName - Name of the AWS service (e.g., 'S3', 'CloudFormation')
* @param {Object} overrideConfig - Configuration to override base config
* @returns {Object} AWS SDK v3 client instance
*/
getClient(serviceName, overrideConfig = {}) {
const ClientClass = CLIENT_MAP[serviceName];
if (!ClientClass) {
throw new Error(`Unknown AWS service: ${serviceName}`);
}

// Create a cache key based on service and config
const configKey = JSON.stringify({ serviceName, ...this.baseConfig, ...overrideConfig });

if (!this.clients.has(configKey)) {
const clientConfig = { ...this.baseConfig, ...overrideConfig };
this.clients.set(configKey, new ClientClass(clientConfig));
}

return this.clients.get(configKey);
}

/**
* Send a command to an AWS service
* @param {string} serviceName - Name of the AWS service
* @param {Object} command - AWS SDK v3 command instance
* @param {Object} clientConfig - Optional client configuration override
* @returns {Promise} Result of the AWS API call
*/
async send(serviceName, command, clientConfig = {}) {
const client = this.getClient(serviceName, clientConfig);
return client.send(command);
}
}

module.exports = AWSClientFactory;
259 changes: 259 additions & 0 deletions lib/aws/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
'use strict';

// API Gateway Commands
const {
GetAccountCommand,
UpdateAccountCommand,
GetApiKeyCommand,
CreateStageCommand,
GetUsagePlansCommand,
UpdateUsagePlanCommand,
TagResourceCommand,
UntagResourceCommand,
UpdateStageCommand,
} = require('@aws-sdk/client-api-gateway');

// API Gateway V2 Commands
const { GetApiCommand } = require('@aws-sdk/client-apigatewayv2');

// CloudFormation Commands
const {
CreateStackCommand,
CreateChangeSetCommand,
DeleteChangeSetCommand,
ExecuteChangeSetCommand,
UpdateStackCommand,
DeleteStackCommand,
DescribeStacksCommand,
ValidateTemplateCommand,
SetStackPolicyCommand,
GetTemplateCommand,
ListStackResourcesCommand,
DescribeStackResourceCommand,
DescribeStackEventsCommand,
ListExportsCommand,
} = require('@aws-sdk/client-cloudformation');

// CloudWatch Commands
const { GetMetricStatisticsCommand } = require('@aws-sdk/client-cloudwatch');

// CloudWatch Logs Commands
const {
DescribeLogStreamsCommand,
FilterLogEventsCommand,
DeleteSubscriptionFilterCommand,
} = require('@aws-sdk/client-cloudwatch-logs');

// Cognito Identity Provider Commands
const {
ListUserPoolsCommand,
DescribeUserPoolCommand,
UpdateUserPoolCommand,
} = require('@aws-sdk/client-cognito-identity-provider');

// ECR Commands
const {
DeleteRepositoryCommand,
DescribeRepositoriesCommand,
GetAuthorizationTokenCommand,
CreateRepositoryCommand,
DescribeImagesCommand,
} = require('@aws-sdk/client-ecr');

// EventBridge Commands
const {
CreateEventBusCommand,
DeleteEventBusCommand,
PutRuleCommand,
DeleteRuleCommand,
PutTargetsCommand,
RemoveTargetsCommand,
} = require('@aws-sdk/client-eventbridge');

// IAM Commands
const {
GetRoleCommand,
ListAttachedRolePoliciesCommand,
CreateRoleCommand,
AttachRolePolicyCommand,
} = require('@aws-sdk/client-iam');

// Lambda Commands
const {
GetFunctionCommand,
UpdateFunctionConfigurationCommand,
UpdateFunctionCodeCommand,
InvokeCommand,
ListVersionsByFunctionCommand,
GetLayerVersionCommand,
AddPermissionCommand,
RemovePermissionCommand,
} = require('@aws-sdk/client-lambda');

// S3 Commands
const {
ListObjectsV2Command,
ListObjectVersionsCommand,
DeleteObjectsCommand,
HeadObjectCommand,
PutObjectCommand,
GetObjectCommand,
GetBucketLocationCommand,
HeadBucketCommand,
GetBucketNotificationConfigurationCommand,
PutBucketNotificationConfigurationCommand,
} = require('@aws-sdk/client-s3');

// SSM Commands
const { GetParameterCommand } = require('@aws-sdk/client-ssm');

// STS Commands
const { GetCallerIdentityCommand } = require('@aws-sdk/client-sts');

/**
* Map v2 method names to v3 command classes
* Format: { ServiceName: { methodName: CommandClass } }
*/
const COMMAND_MAP = {
APIGateway: {
getAccount: GetAccountCommand,
updateAccount: UpdateAccountCommand,
getApiKey: GetApiKeyCommand,
createStage: CreateStageCommand,
getUsagePlans: GetUsagePlansCommand,
updateUsagePlan: UpdateUsagePlanCommand,
tagResource: TagResourceCommand,
untagResource: UntagResourceCommand,
updateStage: UpdateStageCommand,
},

ApiGatewayV2: {
getApi: GetApiCommand,
},

CloudFormation: {
createStack: CreateStackCommand,
createChangeSet: CreateChangeSetCommand,
deleteChangeSet: DeleteChangeSetCommand,
executeChangeSet: ExecuteChangeSetCommand,
updateStack: UpdateStackCommand,
deleteStack: DeleteStackCommand,
describeStacks: DescribeStacksCommand,
validateTemplate: ValidateTemplateCommand,
setStackPolicy: SetStackPolicyCommand,
getTemplate: GetTemplateCommand,
listStackResources: ListStackResourcesCommand,
describeStackResource: DescribeStackResourceCommand,
describeStackEvents: DescribeStackEventsCommand,
listExports: ListExportsCommand,
},

CloudWatch: {
getMetricStatistics: GetMetricStatisticsCommand,
},

CloudWatchLogs: {
describeLogStreams: DescribeLogStreamsCommand,
filterLogEvents: FilterLogEventsCommand,
deleteSubscriptionFilter: DeleteSubscriptionFilterCommand,
},

CognitoIdentityProvider: {
listUserPools: ListUserPoolsCommand,
describeUserPool: DescribeUserPoolCommand,
updateUserPool: UpdateUserPoolCommand,
},

ECR: {
deleteRepository: DeleteRepositoryCommand,
describeRepositories: DescribeRepositoriesCommand,
getAuthorizationToken: GetAuthorizationTokenCommand,
createRepository: CreateRepositoryCommand,
describeImages: DescribeImagesCommand,
},

EventBridge: {
createEventBus: CreateEventBusCommand,
deleteEventBus: DeleteEventBusCommand,
putRule: PutRuleCommand,
deleteRule: DeleteRuleCommand,
putTargets: PutTargetsCommand,
removeTargets: RemoveTargetsCommand,
},

IAM: {
getRole: GetRoleCommand,
listAttachedRolePolicies: ListAttachedRolePoliciesCommand,
createRole: CreateRoleCommand,
attachRolePolicy: AttachRolePolicyCommand,
},

Lambda: {
getFunction: GetFunctionCommand,
updateFunctionConfiguration: UpdateFunctionConfigurationCommand,
updateFunctionCode: UpdateFunctionCodeCommand,
invoke: InvokeCommand,
listVersionsByFunction: ListVersionsByFunctionCommand,
getLayerVersion: GetLayerVersionCommand,
addPermission: AddPermissionCommand,
removePermission: RemovePermissionCommand,
},

S3: {
listObjectsV2: ListObjectsV2Command,
listObjectVersions: ListObjectVersionsCommand,
deleteObjects: DeleteObjectsCommand,
headObject: HeadObjectCommand,
putObject: PutObjectCommand,
getObject: GetObjectCommand,
getBucketLocation: GetBucketLocationCommand,
headBucket: HeadBucketCommand,
getBucketNotificationConfiguration: GetBucketNotificationConfigurationCommand,
putBucketNotificationConfiguration: PutBucketNotificationConfigurationCommand,
// Note: upload is handled separately as it's not a direct API call
},

SSM: {
getParameter: GetParameterCommand,
},

STS: {
getCallerIdentity: GetCallerIdentityCommand,
},
};

/**
* Get command class for a service method
* @param {string} serviceName - AWS service name
* @param {string} methodName - Method name from v2 SDK
* @returns {Function} Command class constructor
*/
function getCommand(serviceName, methodName) {
const serviceCommands = COMMAND_MAP[serviceName];
if (!serviceCommands) {
throw new Error(`Unknown AWS service: ${serviceName}`);
}

const CommandClass = serviceCommands[methodName];
if (!CommandClass) {
throw new Error(`Unknown method '${methodName}' for service '${serviceName}'`);
}

return CommandClass;
}

/**
* Create a command instance for a service method
* @param {string} serviceName - AWS service name
* @param {string} methodName - Method name from v2 SDK
* @param {Object} params - Parameters for the command
* @returns {Object} Command instance
*/
function createCommand(serviceName, methodName, params = {}) {
const CommandClass = getCommand(serviceName, methodName);
return new CommandClass(params);
}

module.exports = {
createCommand,
};
Loading
Loading