Skip to content

Commit

Permalink
Merge pull request #82 from amplify-education/release-5_0_1
Browse files Browse the repository at this point in the history
Update AWS creds initialization
  • Loading branch information
rddimon committed Jan 19, 2024
2 parents 1ac32b0 + 55d3759 commit 992b93e
Show file tree
Hide file tree
Showing 9 changed files with 1,281 additions and 247 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [5.0.1] - 2024-01-19

### Changed

- Updated credentials initialization

## [5.0.0] - 2023-12-15

### Changed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ To run integration tests, set an environment variable TEST\_VPC\_NAME to the VPC
```
export AWS_PROFILE=your_profile
export TEST_VPC_NAME=vpc_name
npm build
npm run build
npm run integration-test
```

Expand Down
1,381 changes: 1,170 additions & 211 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "serverless-vpc-discovery",
"version": "5.0.0",
"version": "5.0.1",
"engines": {
"node": ">=14"
},
Expand Down Expand Up @@ -77,6 +77,9 @@
},
"dependencies": {
"@aws-sdk/client-ec2": "^3.467.0",
"@aws-sdk/credential-providers": "^3.495.0",
"@smithy/config-resolver": "^2.1.0",
"@smithy/node-config-provider": "^2.2.0",
"@smithy/smithy-client": "^2.1.18",
"@smithy/util-retry": "^2.0.8",
"ts-md5": "^1.3.1"
Expand Down
15 changes: 15 additions & 0 deletions src/aws/ec2-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,25 @@ export class EC2Wrapper {
constructor (credentials: any) {
this.ec2 = new EC2Client([{
credentials,
region: Globals.getRegion(),
retryStrategy: Globals.getRetryStrategy()
}]);
}

/**
* Returns the promise that contains the vpc list
* @returns {Promise.<Vpc[]>}
*/
public async getVpcs (): Promise<Vpc[]> {
return await getAWSPagedResults(
this.ec2,
"Vpcs",
"NextToken",
"NextToken",
new DescribeVpcsCommand({})
);
}

/**
* Returns the promise that contains the vpc-id
* @param {string} vpcName
Expand Down
15 changes: 15 additions & 0 deletions src/globals.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ServerlessInstance, ServerlessOptions, ServerlessUtils } from "./types";
import { ConfiguredRetryStrategy } from "@smithy/util-retry";
import { fromIni } from "@aws-sdk/credential-providers";

export default class Globals {
public static pluginName = "Serverless VPC Discovery";
Expand All @@ -8,6 +9,20 @@ export default class Globals {
public static options: ServerlessOptions;
public static v3Utils?: ServerlessUtils;

public static currentRegion: string;
public static credentials: any;

public static defaultRegion = "us-east-1";

public static getRegion () {
const slsRegion = Globals.options.region || Globals.serverless.service.provider.region;
return slsRegion || Globals.currentRegion || Globals.defaultRegion;
}

public static async getProfileCreds (profile: string) {
return await fromIni({ profile })();
}

public static getRetryStrategy (attempts: number = 3, delay: number = 3000, backoff: number = 500) {
return new ConfiguredRetryStrategy(
attempts, // max attempts.
Expand Down
46 changes: 40 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@ import Globals from "./globals";
import { validateVPCDiscoveryConfig } from "./validation";
import { customProperties, functionProperties } from "./schema";
import Logging from "./logging";
import { loadConfig } from "@smithy/node-config-provider";
import { NODE_REGION_CONFIG_FILE_OPTIONS, NODE_REGION_CONFIG_OPTIONS } from "@smithy/config-resolver";

class VPCPlugin {
private serverless: ServerlessInstance;
public hooks: object;
public awsCredentials: any;
public lambdaFunction: LambdaFunction;

constructor (serverless, options, v3Utils?: ServerlessUtils) {
this.serverless = serverless;

Globals.serverless = serverless;
Globals.options = options;

if (v3Utils?.log) {
Globals.v3Utils = v3Utils;
Expand All @@ -36,7 +39,7 @@ class VPCPlugin {
*/
public async hookWrapper (lifecycleFunc: any) {
this.validateCustomVPCDiscoveryConfig();
this.initResources();
await this.initResources();
return await lifecycleFunc.call(this);
}

Expand Down Expand Up @@ -89,12 +92,43 @@ class VPCPlugin {
/**
* Setup AWS resources
*/
public initResources (): void {
this.awsCredentials = this.serverless.providers.aws.getCredentials();
this.awsCredentials.region = this.serverless.providers.aws.getRegion();
public async initResources (): Promise<void> {
// setup AWS resources
await this.initSLSCredentials();
await this.initAWSRegion();

const baseVPCDiscovery = this.serverless.service.custom ? this.serverless.service.custom.vpcDiscovery : null;
this.lambdaFunction = new LambdaFunction(this.awsCredentials, baseVPCDiscovery);
this.lambdaFunction = new LambdaFunction(Globals.credentials, baseVPCDiscovery);

// start of the legacy AWS SDK V2 creds support
// TODO: remove it in case serverless will add V3 support
try {
await this.lambdaFunction.ec2Wrapper.getVpcs();
} catch (error) {
if (error.message.includes("Could not load credentials from any providers")) {
Globals.credentials = this.serverless.providers.aws.getCredentials();
this.lambdaFunction = new LambdaFunction(Globals.credentials, baseVPCDiscovery);
}
}
}

/**
* Init AWS credentials based on sls `provider.profile`
*/
public async initSLSCredentials (): Promise<void> {
const slsProfile = Globals.options["aws-profile"] || Globals.serverless.service.provider.profile;
Globals.credentials = slsProfile ? await Globals.getProfileCreds(slsProfile) : null;
}

/**
* Init AWS current region based on Node options
*/
public async initAWSRegion (): Promise<void> {
try {
Globals.currentRegion = await loadConfig(NODE_REGION_CONFIG_OPTIONS, NODE_REGION_CONFIG_FILE_OPTIONS)();
} catch (err) {
Logging.logInfo("Node region was not found.");
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface VPC {
export interface ServerlessService {
service: string
provider: {
profile?: string,
stage: string,
region?: string
vpc: {},
Expand Down
57 changes: 29 additions & 28 deletions test/unit-tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const vpcId = "vpc-test";
Globals.options = {
stage: "test"
};
Globals.currentRegion = Globals.defaultRegion;

// This will create a mock plugin to be used for testing
const testFuncName = "funcTest";
Expand All @@ -66,7 +67,7 @@ const constructPlugin = (vpcConfig) => {
providers: {
aws: {
getRegion: () => "us-moon-1",
getCredentials: () => new Object()
getCredentials: () => {}
}
},
configSchemaHandler: {
Expand All @@ -76,7 +77,7 @@ const constructPlugin = (vpcConfig) => {
}
}
};
return new VPCPlugin(serverless, null);
return new VPCPlugin(serverless, { });
};

const initMessage = "[Info] Updating VPC config...";
Expand All @@ -103,7 +104,7 @@ describe("Given a vpc,", () => {
securityGroups: securityGroups
});
plugin.validateCustomVPCDiscoveryConfig();
plugin.initResources();
await plugin.initResources();

const expectedResult = {
funcTest: {
Expand All @@ -123,7 +124,7 @@ describe("Given a vpc,", () => {
EC2ClientMock.on(DescribeVpcsCommand).resolves(emptyData);

const plugin = constructPlugin({});
plugin.initResources();
await plugin.initResources();

const funcVPCDiscovery: VPCDiscovery = {
vpcName: "test",
Expand All @@ -132,8 +133,8 @@ describe("Given a vpc,", () => {

return await plugin.lambdaFunction.getFuncVPC("test", funcVPCDiscovery).then(() => {
const expectedMessage = `VPC with tag key 'Name' and tag value '${funcVPCDiscovery.vpcName}' does not exist`;
expect(consoleOutput[0]).to.equal(initFuncMessage.replace(testFuncName, "test"));
expect(consoleOutput[1]).to.contain(expectedMessage);
expect(consoleOutput[1]).to.equal(initFuncMessage.replace(testFuncName, "test"));
expect(consoleOutput[2]).to.contain(expectedMessage);
});
});

Expand All @@ -144,14 +145,14 @@ describe("Given a vpc,", () => {

describe("Given valid inputs for Subnets and Security Groups ", () => {
let plugin;
beforeEach(() => {
beforeEach(async () => {
const EC2ClientMock = mockClient(EC2Client);
EC2ClientMock.on(DescribeVpcsCommand).resolves(testData);
EC2ClientMock.on(DescribeSubnetsCommand).resolves(testData);
EC2ClientMock.on(DescribeSecurityGroupsCommand).resolves(testData);

plugin = constructPlugin({});
plugin.initResources();
await plugin.initResources();
});

it("without wildcards", async () => {
Expand Down Expand Up @@ -199,13 +200,13 @@ describe("Given invalid input for ", () => {
EC2ClientMock.on(DescribeSecurityGroupsCommand).resolves(testData);

const plugin = constructPlugin({});
plugin.initResources();
await plugin.initResources();

await plugin.lambdaFunction.getFuncVPC("test", funcVPCDiscovery).then(() => {
const expectedMessage = `Subnets with vpc id '${vpcId}', tag key 'Name' and tag values '${funcVPCDiscovery.subnets[0].tagValues}' do not exist`;
expect(consoleOutput[0]).to.equal(initFuncMessage.replace(testFuncName, "test"));
expect(consoleOutput[1]).to.equal(foundFuncMessage);
expect(consoleOutput[2]).to.contain(expectedMessage);
expect(consoleOutput[1]).to.equal(initFuncMessage.replace(testFuncName, "test"));
expect(consoleOutput[2]).to.equal(foundFuncMessage);
expect(consoleOutput[3]).to.contain(expectedMessage);
});
});

Expand All @@ -216,13 +217,13 @@ describe("Given invalid input for ", () => {
EC2ClientMock.on(DescribeSecurityGroupsCommand).resolves(emptyData);

const plugin = constructPlugin({});
plugin.initResources();
await plugin.initResources();

await plugin.lambdaFunction.getFuncVPC("test", funcVPCDiscovery).then(() => {
const expectedMessage = `Security groups with vpc id '${vpcId}', names '${securityGroups[0].names[0]}' do not exist`;
expect(consoleOutput[0]).to.equal(initFuncMessage.replace(testFuncName, "test"));
expect(consoleOutput[1]).to.equal(foundFuncMessage);
expect(consoleOutput[2]).to.contain(expectedMessage);
expect(consoleOutput[1]).to.equal(initFuncMessage.replace(testFuncName, "test"));
expect(consoleOutput[2]).to.equal(foundFuncMessage);
expect(consoleOutput[3]).to.contain(expectedMessage);
});
});

Expand All @@ -246,13 +247,13 @@ describe("Given input missing in AWS for ", () => {
securityGroups: [{ names: ["test_group_*"] }]
};
const plugin = constructPlugin({});
plugin.initResources();
await plugin.initResources();

await plugin.lambdaFunction.getFuncVPC("test", funcVPCDiscovery).then(() => {
const expectedMessage = `Subnets with vpc id '${vpcId}', tag key 'Name' and tag values 'missing_subnet' do not exist.`;
expect(consoleOutput[0]).to.equal(initFuncMessage.replace(testFuncName, "test"));
expect(consoleOutput[1]).to.equal(foundFuncMessage);
expect(consoleOutput[2]).to.contain(expectedMessage);
expect(consoleOutput[1]).to.equal(initFuncMessage.replace(testFuncName, "test"));
expect(consoleOutput[2]).to.equal(foundFuncMessage);
expect(consoleOutput[3]).to.contain(expectedMessage);
});
});

Expand All @@ -264,13 +265,13 @@ describe("Given input missing in AWS for ", () => {
};

const plugin = constructPlugin({});
plugin.initResources();
await plugin.initResources();

await plugin.lambdaFunction.getFuncVPC("test", funcVPCDiscovery).then(() => {
const expectedMessage = "Security groups do not exist for the names";
expect(consoleOutput[0]).to.equal(initFuncMessage.replace(testFuncName, "test"));
expect(consoleOutput[1]).to.equal(foundFuncMessage);
expect(consoleOutput[2]).to.contain(expectedMessage);
expect(consoleOutput[1]).to.equal(initFuncMessage.replace(testFuncName, "test"));
expect(consoleOutput[2]).to.equal(foundFuncMessage);
expect(consoleOutput[3]).to.contain(expectedMessage);
});
});

Expand All @@ -290,13 +291,13 @@ describe("Catching errors in updateVpcConfig ", () => {
securityGroups: securityGroups
});
plugin.validateCustomVPCDiscoveryConfig();
plugin.initResources();
await plugin.initResources();

await plugin.updateFunctionsVpcConfig().then(() => {
const expectedErrorMessage = `VPC with tag key 'Name' and tag value '${vpc}' does not exist.`;
expect(consoleOutput[0]).to.equal(initMessage);
expect(consoleOutput[1]).to.equal(initFuncMessage);
expect(consoleOutput[2]).to.contain(expectedErrorMessage);
expect(consoleOutput[1]).to.equal(initMessage);
expect(consoleOutput[2]).to.equal(initFuncMessage);
expect(consoleOutput[3]).to.contain(expectedErrorMessage);
});
});

Expand Down

0 comments on commit 992b93e

Please sign in to comment.