Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 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
3 changes: 2 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,7 +82,7 @@
"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",
"ts-jest": "26.4.4"
},
Expand Down
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);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { $TSContext } from '@aws-amplify/amplify-cli-core';
import {
DeleteParameterCommand,
GetParametersByPathCommand,
DeleteParametersCommand,
GetParametersCommand,
PutParameterCommand,
SSMClient as SSM_Client,
} from '@aws-sdk/client-ssm';
import aws from 'aws-sdk';

export type Secret = {
Expand All @@ -19,7 +27,7 @@ export class SSMClient {
return SSMClient.instance;
};

private constructor(private readonly ssmClient: aws.SSM) {}
private constructor(private readonly ssmClient: aws.SSM | SSM_Client) {}

/**
* Returns a list of secret name value pairs
Expand All @@ -28,6 +36,16 @@ export class SSMClient {
if (!secretNames || secretNames?.length === 0) {
return [];
}
if (this.ssmClient instanceof SSM_Client) {
const result = await this.ssmClient.send(
new GetParametersCommand({
Names: secretNames,
WithDecryption: true,
}),
);

return result.Parameters.map(({ Name, Value }) => ({ secretName: Name, secretValue: Value }));
}
const result = await this.ssmClient
.getParameters({
Names: secretNames,
Expand All @@ -45,20 +63,38 @@ export class SSMClient {
let nextToken: string | undefined;
const secretNames: string[] = [];
do {
const result = await this.ssmClient
.getParametersByPath({
Path: secretPath,
MaxResults: 10,
ParameterFilters: [
{
Key: 'Type',
Option: 'Equals',
Values: ['SecureString'],
},
],
NextToken: nextToken,
})
.promise();
let result;
if (this.ssmClient instanceof SSM_Client) {
result = await this.ssmClient.send(
new GetParametersByPathCommand({
Path: secretPath,
MaxResults: 10,
ParameterFilters: [
{
Key: 'Type',
Option: 'Equals',
Values: ['SecureString'],
},
],
NextToken: nextToken,
}),
);
} else {
result = await this.ssmClient
.getParametersByPath({
Path: secretPath,
MaxResults: 10,
ParameterFilters: [
{
Key: 'Type',
Option: 'Equals',
Values: ['SecureString'],
},
],
NextToken: nextToken,
})
.promise();
}
secretNames.push(...result?.Parameters?.map((param) => param?.Name));
nextToken = result?.NextToken;
} while (nextToken);
Expand All @@ -69,26 +105,48 @@ export class SSMClient {
* Sets the given secretName to the secretValue. If secretName is already present, it is overwritten.
*/
setSecret = async (secretName: string, secretValue: string): Promise<void> => {
await this.ssmClient
.putParameter({
Name: secretName,
Value: secretValue,
Type: 'SecureString',
Overwrite: true,
})
.promise();
if (this.ssmClient instanceof SSM_Client) {
await this.ssmClient.send(
new PutParameterCommand({
Name: secretName,
Value: secretValue,
Type: 'SecureString',
Overwrite: true,
}),
);
} else {
await this.ssmClient
.putParameter({
Name: secretName,
Value: secretValue,
Type: 'SecureString',
Overwrite: true,
})
.promise();
}
};

/**
* Deletes secretName. If it already doesn't exist, this is treated as success. All other errors will throw.
*/
deleteSecret = async (secretName: string): Promise<void> => {
try {
await this.ssmClient.deleteParameter({ Name: secretName }).promise();
} catch (err) {
if (err?.code !== 'ParameterNotFound') {
// if the value didn't exist in the first place, consider it deleted
throw err;
if (this.ssmClient instanceof SSM_Client) {
try {
await this.ssmClient.send(new DeleteParameterCommand({ Name: secretName }));
} catch (err) {
if (err?.name !== 'ParameterNotFound') {
// if the value didn't exist in the first place, consider it deleted
throw err;
}
}
} else {
try {
await this.ssmClient.deleteParameter({ Name: secretName }).promise();
} catch (err) {
if (err?.code !== 'ParameterNotFound') {
// if the value didn't exist in the first place, consider it deleted
throw err;
}
}
}
};
Expand All @@ -97,21 +155,35 @@ export class SSMClient {
* Deletes all secrets in secretNames.If secret doesn't exist, this is treated as success. All other errors will throw.
*/
deleteSecrets = async (secretNames: string[]): Promise<void> => {
try {
await this.ssmClient.deleteParameters({ Names: secretNames }).promise();
} catch (err) {
// if the value didn't exist in the first place, consider it deleted
if (err?.code !== 'ParameterNotFound') {
throw err;
if (this.ssmClient instanceof SSM_Client) {
try {
await this.ssmClient.send(new DeleteParametersCommand({ Names: secretNames }));
} catch (err) {
// if the value didn't exist in the first place, consider it deleted
if (err?.name !== 'ParameterNotFound') {
throw err;
}
}
} else {
try {
await this.ssmClient.deleteParameters({ Names: secretNames }).promise();
} catch (err) {
// if the value didn't exist in the first place, consider it deleted
if (err?.code !== 'ParameterNotFound') {
throw err;
}
}
}
};
}

// The Provider plugin holds all the configured service clients. Fetch from there.
const getSSMClient = async (context: $TSContext): Promise<aws.SSM> => {
const getSSMClient = async (context: $TSContext): Promise<aws.SSM | SSM_Client> => {
const { client } = (await context.amplify.invokePluginMethod(context, 'awscloudformation', undefined, 'getConfiguredSSMClient', [
context,
])) as any;
if ('middlewareStack' in client) {
return client as SSM_Client;
}
return client as aws.SSM;
};
2 changes: 1 addition & 1 deletion packages/amplify-e2e-tests/src/__tests__/api_3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ describe('amplify add api (GraphQL)', () => {
await getAppSyncApi(GraphQLAPIIdOutput, meta.providers.awscloudformation.Region);
expect(true).toBe(false); // expecting failure
} catch (err) {
expect(err.message).toBe(`GraphQL API ${GraphQLAPIIdOutput} not found.`);
expect(err.message).toBe(`API not found.`);
}
});
});
2 changes: 1 addition & 1 deletion packages/amplify-graphql-auth-transformer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@aws-amplify/graphql-searchable-transformer": "2.7.16",
"@aws-amplify/graphql-sql-transformer": "^0.3.9",
"@aws-amplify/graphql-transformer-test-utils": "0.6.7",
"@types/node": "^12.12.6"
"@types/node": "^18.18.0"
},
"peerDependencies": {
"aws-cdk-lib": "^2.187.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/amplify-graphql-model-transformer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@aws-sdk/client-lambda": "^3.624.0",
"@aws-sdk/client-sfn": "^3.624.0",
"@types/aws-lambda": "8.10.119",
"@types/node": "^12.12.6"
"@types/node": "^18.18.0"
},
"jest": {
"transform": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
"devDependencies": {
"@aws-amplify/graphql-transformer-test-utils": "0.6.7",
"@types/node": "^12.12.6"
"@types/node": "^18.18.0"
},
"jest": {
"transform": {
Expand Down
Loading
Loading