Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@types/ini": "^1.3.31",
"@types/jest": "^27.5.2",
"@types/jest-dev-server": "^5.0.0",
"@types/node": "^16.11.56",
"@types/node": "^18.18.0",
"@types/react": "18.2.42",
"@types/react-dom": "^18.0.6",
"aws-amplify": "^4.3.30",
Expand Down
78 changes: 76 additions & 2 deletions dependency_licenses.txt

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion packages/amplify-category-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@aws-sdk/client-ec2": "3.624.0",
"@aws-sdk/client-iam": "3.624.0",
"@aws-sdk/client-lambda": "3.624.0",
"@aws-sdk/client-ssm": "3.624.0",
"@graphql-tools/merge": "^6.0.18",
"@octokit/rest": "^20.1.2",
"aws-sdk": "^2.1113.0",
Expand Down Expand Up @@ -81,8 +82,10 @@
"devDependencies": {
"@aws-amplify/graphql-transformer-test-utils": "0.6.7",
"@types/js-yaml": "^4.0.0",
"@types/node": "^12.12.6",
"@types/node": "^18.18.0",
"amplify-util-headless-input": "^1.9.18",
"aws-sdk-client-mock": "3.0.1",
"aws-sdk-client-mock-jest": "3.0.1",
"ts-jest": "26.4.4"
},
"jest": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { $TSContext } from '@aws-amplify/amplify-cli-core';
import { SSMClient } from '../../../../../provider-utils/awscloudformation/utils/rds-resources/ssmClient';
import aws from 'aws-sdk';
import {
DeleteParameterCommand,
DeleteParametersCommand,
GetParametersCommand,
PutParameterCommand,
SSMClient as SSM_Client,
} from '@aws-sdk/client-ssm';
import { mockClient } from 'aws-sdk-client-mock';
import 'aws-sdk-client-mock-jest';

const secretName = 'mock-test-secret-name';
const secretValue = 'mock-test-secret-value';
Expand Down Expand Up @@ -36,6 +45,8 @@ jest.mock('aws-sdk', () => {
};
});

const mockSSMClient = mockClient(SSM_Client);

describe('SSM client configuration', () => {
const mockContext = {
amplify: {
Expand Down Expand Up @@ -91,3 +102,70 @@ describe('SSM client configuration', () => {
});
});
});

describe('SSM V3 Client Configuration', () => {
const mockContext = {
amplify: {
invokePluginMethod: jest.fn().mockResolvedValue({ client: new SSM_Client() }),
},
} as any as $TSContext;

beforeEach(() => {
mockSSMClient.reset();
mockSSMClient.on(PutParameterCommand).resolves({});
mockSSMClient.on(GetParametersCommand).resolves({
Parameters: [{ Name: secretName, Value: secretValue }],
});
mockSSMClient.on(DeleteParameterCommand).resolves({});
mockSSMClient.on(DeleteParameterCommand).resolves({});
});

test('able to get the configured SSM instance via provider plugin', async () => {
(SSMClient as any).instance = undefined;
const ssmClient = await SSMClient.getInstance(mockContext);
expect(ssmClient).toBeDefined();
expect(mockContext.amplify.invokePluginMethod).toBeCalledTimes(1);
expect(mockContext.amplify.invokePluginMethod).toBeCalledWith(mockContext, 'awscloudformation', undefined, 'getConfiguredSSMClient', [
mockContext,
]);
});

test('able to set the secret value', async () => {
const ssmClient = await SSMClient.getInstance(mockContext);

ssmClient.setSecret(secretName, secretValue);
expect(mockSSMClient).toHaveReceivedCommandWith(PutParameterCommand, {
Name: secretName,
Overwrite: true,
Type: 'SecureString',
Value: secretValue,
});
});

test('able to get the secret value', async () => {
const ssmClient = await SSMClient.getInstance(mockContext);
const result = await ssmClient.getSecrets([secretName]);

expect(mockSSMClient).toHaveReceivedCommandWith(GetParametersCommand, {
Names: [secretName],
WithDecryption: true,
});
expect(result).toEqual([{ secretName: secretName, secretValue: secretValue }]);
});

test('able to delete the secret', async () => {
const ssmClient = await SSMClient.getInstance(mockContext);
ssmClient.deleteSecret(secretName);
expect(mockSSMClient).toHaveReceivedCommandWith(DeleteParameterCommand, {
Name: secretName,
});
});

test('able to delete multiple secrets', async () => {
const ssmClient = await SSMClient.getInstance(mockContext);
ssmClient.deleteSecrets([secretName]);
expect(mockSSMClient).toHaveReceivedCommandWith(DeleteParametersCommand, {
Names: [secretName],
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const name = subcommand;
*/
export const run = async (context: $TSContext): Promise<void> => {
try {
const AWS = await getAwsClient(context, 'list');
const AWSConfig = await getAwsClientConfig(context, 'list');

const result: any = await datasourceSelectionPrompt(context, supportedDataSources);

Expand Down Expand Up @@ -76,7 +76,7 @@ export const run = async (context: $TSContext): Promise<void> => {
answers.secretStoreArn,
answers.dbClusterArn,
answers.databaseName,
AWS,
AWSConfig,
);

/**
Expand Down Expand Up @@ -219,12 +219,12 @@ const datasourceSelectionPrompt = async (context: $TSContext, supportedDataSourc
return inquirer.prompt(question).then((answer) => answer.datasource);
};

const getAwsClient = async (context: $TSContext, action: string): Promise<any> => {
const getAwsClientConfig = async (context: $TSContext, action: string): Promise<any> => {
const providerPlugins = context.amplify.getProviderPlugins(context);
// eslint-disable-next-line
const provider = require(providerPlugins[providerName]);

return provider.getConfiguredAWSClient(context, 'aurora-serverless', action);
return provider.getConfiguredAWSClientConfig(context, 'aurora-serverless', action);
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import chalk from 'chalk';
import { DataApiParams } from 'graphql-relational-schema-transformer';
import ora from 'ora';
import { rootStackFileName } from '@aws-amplify/amplify-provider-awscloudformation';
import * as aws from 'aws-sdk';

const spinner = ora('');
const category = 'api';
Expand Down Expand Up @@ -62,30 +63,28 @@ export async function serviceWalkthrough(context: $TSContext, datasourceMetadata
selectedRegion = await promptWalkthroughQuestion(inputs, 0, availableRegions);
}

const AWS = await getAwsClient(context, 'list');
const AWSConfig = await getAwsClientConfig(context, 'list');

// Prepare the SDK with the region
AWS.config.update({
region: selectedRegion,
});
const config = { ...AWSConfig, region: selectedRegion };

// RDS Cluster Question
let selectedClusterArn = cfnJsonParameters?.rdsClusterIdentifier;
let clusterResourceId = getRdsClusterResourceIdFromArn(selectedClusterArn, AWS);
let clusterResourceId = getRdsClusterResourceIdFromArn(selectedClusterArn, config);
if (!selectedClusterArn || !clusterResourceId) {
({ selectedClusterArn, clusterResourceId } = await selectCluster(context, inputs, AWS));
({ selectedClusterArn, clusterResourceId } = await selectCluster(context, inputs, config));
}

// Secret Store Question
let selectedSecretArn = cfnJsonParameters?.rdsSecretStoreArn;
if (!selectedSecretArn) {
selectedSecretArn = await getSecretStoreArn(context, inputs, clusterResourceId, AWS);
selectedSecretArn = await getSecretStoreArn(context, inputs, clusterResourceId, config);
}

// Database Name Question
let selectedDatabase = cfnJsonParameters?.rdsDatabaseName;
if (!selectedDatabase) {
selectedDatabase = await selectDatabase(context, inputs, selectedClusterArn, selectedSecretArn, AWS);
selectedDatabase = await selectDatabase(context, inputs, selectedClusterArn, selectedSecretArn, config);
}

return {
Expand All @@ -97,13 +96,13 @@ export async function serviceWalkthrough(context: $TSContext, datasourceMetadata
};
}

async function getRdsClusterResourceIdFromArn(arn: string | undefined, AWS) {
async function getRdsClusterResourceIdFromArn(arn: string | undefined, AWSConfig) {
// If the arn was not already existing in cloudformation template, return undefined to prompt for input.
if (!arn) {
return;
}

const RDS = new AWS.RDS();
const RDS = new aws.RDS(AWSConfig);
const describeDBClustersResult = await RDS.describeDBClusters().promise();
const rawClusters = describeDBClustersResult.DBClusters;
const identifiedCluster = rawClusters.find((cluster) => cluster.DBClusterArn === arn);
Expand All @@ -114,8 +113,8 @@ async function getRdsClusterResourceIdFromArn(arn: string | undefined, AWS) {
*
* @param {*} inputs
*/
async function selectCluster(context: $TSContext, inputs, AWS) {
const RDS = new AWS.RDS();
async function selectCluster(context: $TSContext, inputs, AWSConfig) {
const RDS = new aws.RDS(AWSConfig);

const describeDBClustersResult = await RDS.describeDBClusters().promise();
const rawClusters = describeDBClustersResult.DBClusters;
Expand Down Expand Up @@ -163,8 +162,8 @@ async function selectCluster(context: $TSContext, inputs, AWS) {
* @param {*} inputs
* @param {*} clusterResourceId
*/
async function getSecretStoreArn(context: $TSContext, inputs, clusterResourceId, AWS) {
const SecretsManager = new AWS.SecretsManager();
async function getSecretStoreArn(context: $TSContext, inputs, clusterResourceId, AWSConfig) {
const SecretsManager = new aws.SecretsManager(AWSConfig);
const NextToken = 'NextToken';
let rawSecrets = [];
const params = {
Expand Down Expand Up @@ -221,9 +220,9 @@ async function getSecretStoreArn(context: $TSContext, inputs, clusterResourceId,
* @param {*} clusterArn
* @param {*} secretArn
*/
async function selectDatabase(context: $TSContext, inputs, clusterArn, secretArn, AWS) {
async function selectDatabase(context: $TSContext, inputs, clusterArn, secretArn, AWSConfig) {
// Database Name Question
const DataApi = new AWS.RDSDataService();
const DataApi = new aws.RDSDataService(AWSConfig);
const params = new DataApiParams();
const databaseList = [];
params.secretArn = secretArn;
Expand Down Expand Up @@ -285,8 +284,8 @@ async function promptWalkthroughQuestion(inputs, questionNumber, choicesList) {
return await prompter.pick(question.message, choicesList);
}

async function getAwsClient(context: $TSContext, action: string) {
async function getAwsClientConfig(context: $TSContext, action: string) {
const providerPlugins = context.amplify.getProviderPlugins(context);
const provider = require(providerPlugins[providerName]);
return await provider.getConfiguredAWSClient(context, 'aurora-serverless', action);
return await provider.getConfiguredAWSClientConfig(context, 'aurora-serverless', action);
}
Loading
Loading