Skip to content
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

add create and delete BackendEnvironment #61

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
42 changes: 35 additions & 7 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,28 +317,56 @@ Whether to enable termination protection for this stack.

---

##### `amplifyEnvironment`<sup>Required</sup> <a name="@aws-amplify/cdk-exported-backend.AmplifyExportedBackendProps.property.amplifyEnvironment"></a>
##### `path`<sup>Required</sup> <a name="@aws-amplify/cdk-exported-backend.AmplifyExportedBackendProps.property.path"></a>

```typescript
public readonly amplifyEnvironment: string;
public readonly path: string;
```

- *Type:* `string`
- *Default:* is 'dev'

The Amplify CLI environment deploy to The amplify backend requires a stage to deploy.
The path to the exported folder that contains the artifacts for the Amplify CLI backend ex: ./amplify-synth-out/.

---

##### `path`<sup>Required</sup> <a name="@aws-amplify/cdk-exported-backend.AmplifyExportedBackendProps.property.path"></a>
##### `amplifyAppId`<sup>Optional</sup> <a name="@aws-amplify/cdk-exported-backend.AmplifyExportedBackendProps.property.amplifyAppId"></a>

```typescript
public readonly path: string;
public readonly amplifyAppId: string;
```

- *Type:* `string`

The path to the exported folder that contains the artifacts for the Amplify CLI backend ex: ./amplify-synth-out/.
The Amplify App ID to which the new backend environment will be added.

If the Amplify environment is created and managed by CDK exclusively
then provide an AmplifyAppId to ensure the backend environment
shows up in the AWS Amplify App homepage.

If the Amplify environment is created via Amplify CLI, do not
provide an AmplifyAppId. Trying to create an Amplify backend
via CDK which has already been created by the Amplify CLI will result
in the CDK failing to create the backend and automatically deleting
the existing backend when it deletes the Amplify environment it failed
to deploy.

---

##### `amplifyEnvironment`<sup>Optional</sup> <a name="@aws-amplify/cdk-exported-backend.AmplifyExportedBackendProps.property.amplifyEnvironment"></a>

```typescript
public readonly amplifyEnvironment: string;
```

- *Type:* `string`
- *Default:* is 'dev'

An environment name to contain Amplify CLI backend resources.

An Amplify backend is a collection of various AWS
resources organized into categories (api, function, custom, etc) which are deployed
together into an environment. Environments sometimes reflect deployment
stages such as 'dev', 'test', and 'prod'.

---

Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ Add to your CDK app:
import { AmplifyExportedBackend } from '@aws-amplify/cdk-exported-backend';
...
const amplifyExport = new AmplifyExportedBackend(app, 'AmplifyExportedBackend', {
path: './amplify-export-myAmplifyApp',
amplifyEnvironment: 'dev',
path: './amplify-export-myAmplifyApp'
});


Expand All @@ -45,10 +44,12 @@ const amplifyExport = new AmplifyExportedBackend(app, 'AmplifyExportedBackend',

The construct props extend [stack props](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.StackProps.html) and can be used to override the root stack properties.

|Name |Type |Description |Required |Default |
|--- |--- |--- |--- |--- |
|path |String |You can use the absolute or the relative path to the location of the folder. When using relative paths it's important to note that the path is relative to the root of your CDK application |Yes |undefined |
|stage |String |This works similar to Amplify CLI's environment names. The construct makes modification to be able to integrate into the CDK app. |Yes | undefined |
| Name | Type | Description | Required | Default |
|--------------------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------|
| path | String | You can use the absolute or the relative path to the location of the folder. When using relative paths it's important to note that the path is relative to the root of your CDK application. | Yes | undefined |
| amplifyEnvironment | String | The construct emulates Amplify CLI's environment name convention to integrate Amplify exported resources into the CDK app. | No | dev |
| amplifyAppId | String | Create an Amplify backend environment for the specified Amplify App Id. Do not provide an amplifyAppId if the environment was already added by the Amplify CLI. | No | undefined |



Deploy this to your account
Expand Down
34 changes: 27 additions & 7 deletions src/amplify-exported-backend-props.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
import { StackProps } from 'aws-cdk-lib';

export interface AmplifyExportedBackendProps extends StackProps {
/**
* The Amplify CLI environment deploy to
* The amplify backend requires a stage to deploy
* @default is 'dev'
*/
readonly amplifyEnvironment: string;

/**
* The path to the exported folder that contains the artifacts for the Amplify CLI backend
* ex: ./amplify-synth-out/
*/
readonly path: string;

/**
* The Amplify App ID to which the new backend environment will be added.
*
* If the Amplify environment is created and managed by CDK exclusively
* then provide an AmplifyAppId to ensure the backend environment
* shows up in the AWS Amplify App homepage.
*
* If the Amplify environment is created via Amplify CLI, do not
* provide an AmplifyAppId. Trying to create an Amplify backend
* via CDK which has already been created by the Amplify CLI will result
* in the CDK failing to create the backend and automatically deleting
* the existing backend when it deletes the Amplify environment it failed
* to deploy.
*/
readonly amplifyAppId?: string

/**
* An environment name to contain Amplify CLI backend resources.
*
* An Amplify backend is a collection of various AWS
* resources organized into categories (api, function, custom, etc) which are deployed
* together into an environment. Environments sometimes reflect deployment
* stages such as 'dev', 'test', and 'prod'.
* @default is 'dev'
*/
readonly amplifyEnvironment?: string;
}
26 changes: 17 additions & 9 deletions src/base-exported-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,18 @@ const {
AMPLIFY_EXPORT_TAG_FILE,
AMPLIFY_CATEGORY_MAPPING_FILE,
} = Constants;

// conform to Amplify conventions: https://github.com/aws-amplify/amplify-cli/blob/v12.0.3/packages/amplify-cli/src/init-steps/s0-analyzeProject.ts#L206-L208
const isEnvNameValid = (inputEnvName: string): boolean => /^[a-z]{2,10}$/.test(inputEnvName);

function validateEnv(env: string = 'dev') {
if(!isEnvNameValid(env)) throw new Error(`param 'amplifyEnvironment' must be between 2 and 10 characters, and lowercase only. Got '${env}'.`);
return env;
}

class AmplifyCategoryNotFoundError extends Error {
constructor(category: string, service?: string) {
super(`The category: ${category} ${service ? 'of service: ' + service: '' } not found.`);
super(`${service ? `service '${service}' of category '${category}'` : `category '${category}'`} not found`);
}
}

Expand All @@ -30,16 +39,19 @@ export class BaseAmplifyExportedBackend extends Construct {
protected exportBackendManifest: ExportManifest;
protected exportTags: ExportTag[];
protected auxiliaryDeployment?: BucketDeployment;
protected env?: string
protected env: string;
protected appId?: string;
constructor(
scope: Construct,
id: string,
exportPath: string,
amplifyEnvironment: string,
amplifyEnvironment?: string,
amplifyAppId?: string
) {
super(scope, id);

this.env = amplifyEnvironment;
this.env = validateEnv(amplifyEnvironment);
this.appId = amplifyAppId;
this.exportPath = this.validatePath(exportPath);


Expand Down Expand Up @@ -140,11 +152,7 @@ export class BaseAmplifyExportedBackend extends Construct {

private modifyEnv(nameWithEnv: string): string {
let splitValues = nameWithEnv.split('-');
if (this.env) {
splitValues[2] = this.env;
} else {
splitValues.splice(2, 1);
}
splitValues[2] = this.env;
return splitValues.join('-');
}

Expand Down
2 changes: 1 addition & 1 deletion src/export-backend-asset-handler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class AmplifyExportAssetHandler extends Construct {
private categoryStackWithDeployment: CategoryStackMappingWithDeployment[];
private exportPath: string;
private rootStack: Stack;
private env?: string;
private env: string;

private auxiliaryDeployment: BucketDeployment | undefined;

Expand Down
69 changes: 59 additions & 10 deletions src/export-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import {
LambdaFunctionIncludedNestedStack,
} from './include-nested-stacks/lambda-function/lambda-function-nested-stack';
import { CategoryStackMapping } from './types/category-stack-mapping';
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from "aws-cdk-lib/custom-resources";
import { CfnParameter } from 'aws-cdk-lib';
import { CfnRole } from 'aws-cdk-lib/aws-iam';
import { CfnBucket } from 'aws-cdk-lib/aws-s3';

const { API_CATEGORY, AUTH_CATEGORY, FUNCTION_CATEGORY } = Constants;

Expand Down Expand Up @@ -46,7 +50,7 @@ export class AmplifyExportedBackend
id: string,
props: AmplifyExportedBackendProps,
) {
super(scope, id, props.path, props.amplifyEnvironment);
super(scope, id, props.path, props.amplifyEnvironment, props.amplifyAppId);

this.rootStack = new cdk.Stack(scope, `${id}-amplify-backend-stack`, {
...props,
Expand All @@ -59,7 +63,7 @@ export class AmplifyExportedBackend
{
backendPath: props.path,
categoryStackMapping: this.categoryStackMappings,
env: props.amplifyEnvironment ? props.amplifyEnvironment : 'dev',
env: this.env,
exportManifest: this.exportBackendManifest,
},
);
Expand All @@ -76,19 +80,65 @@ export class AmplifyExportedBackend

amplifyExportHandler.setDependencies(include);

this.applyTags(this.rootStack, props.amplifyEnvironment);
// used to emulate the input parameters that the Amplify CLI uses
const deploymentBucket = this.cfnInclude.getResource('DeploymentBucket') as CfnBucket;
const authRole = this.cfnInclude.getResource('AuthRole') as CfnRole;
const unauthRole = this.cfnInclude.getResource('UnauthRole') as CfnRole;
new CfnParameter(this.rootStack, 'AuthRoleName', {
type: 'String',
default: authRole.roleName
});
new CfnParameter(this.rootStack, 'DeploymentBucketName', {
type: 'String',
default: deploymentBucket.bucketName
});
new CfnParameter(this.rootStack, 'UnauthRoleName', {
type: 'String',
default: unauthRole.roleName
});

// just like in amplify env add, we add the backend to the amplify application
// and then deploy the CFN. This also ensures the amplify env only deletes when
// the CFN is gone too.
if(this.appId) {
new AwsCustomResource(this.rootStack, 'CreateBackendEnvironment', {
onCreate: {
service: 'Amplify',
action: 'createBackendEnvironment',
parameters: {
appId: this.appId,
environmentName: this.env,
stackName: this.rootStack.stackName,
deploymentArtifacts: deploymentBucket.bucketName,
},
physicalResourceId: PhysicalResourceId.of(`${this.appId}-${this.env}-backendEnvironment`)
},
onDelete: {
service: 'Amplify',
action: 'deleteBackendEnvironment',
parameters: {
appId: this.appId,
environmentName: this.env
}
},
policy: AwsCustomResourcePolicy.fromSdkCalls({
resources: AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
}

this.applyTags(this.rootStack, this.env);
}

private applyTags(rootStack: cdk.Stack, env: string = 'dev') {
private applyTags(rootStack: cdk.Stack, env: string) {
this.exportTags.forEach((tag) => {
rootStack.tags.setTag(tag.key, tag.value.replace('{project-env}', env));
});
}

/**
* Method to get the auth stack
* @returns the nested stack of type {IAuthIncludeNestedStack}
* @returns the nested stack of type {@link AuthIncludedNestedStack}
* @throws {AmplifyCategoryNotFoundError} if the auth stack doesn't exist
* @method
* @function
Expand All @@ -104,8 +154,7 @@ export class AmplifyExportedBackend

/**
* Use this to get the api graphql stack from the backend
* @returns the nested stack of type {IAPIGraphQLIncludeNestedStack}
* @
* @returns the nested stack of type {@link APIGraphQLIncludedNestedStack}
* @throws {AmplifyCategoryNotFoundError} if the API graphql stack doesn't exist
*/
graphqlNestedStacks(): APIGraphQLIncludedNestedStack {
Expand All @@ -120,7 +169,7 @@ export class AmplifyExportedBackend

/**
* Use this to get all the lambda functions from the backend
* @returns {ILambdaFunctionIncludedNestedStack[]}
* @returns {LambdaFunctionIncludedNestedStack[]}
* @throws {AmplifyCategoryNotFoundError} if the no Lambda Function stacks are found
*/
lambdaFunctionNestedStacks(): LambdaFunctionIncludedNestedStack[] {
Expand All @@ -134,7 +183,7 @@ export class AmplifyExportedBackend

/**
* Use this to get a specific lambda function from the backend
* @returns {ILambdaFunctionIncludedNestedStack}
* @returns {LambdaFunctionIncludedNestedStack}
* @param functionName the function name to get from the nested stack
* @throws {AmplifyCategoryNotFoundError} if the lambda function stack doesn't exist
*/
Expand Down Expand Up @@ -172,7 +221,7 @@ export class AmplifyExportedBackend
/**
* Use this to get rest api stack from the backend
* @param resourceName
* @return {IAPIRestIncludedStack} the nested of type Rest API
* @return {APIRestIncludedStack} the nested of type Rest API
* @throws {AmplifyCategoryNotFoundError} if the API Rest stack doesn't exist
*/
apiRestNestedStack(resourceName: string): APIRestIncludedStack {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@ export class APIRestIncludedStack
/**
* Gets the RestApi of the API stack
* @returns {CfnRestApi}
* @throws {CfnResourceNotFoundError} if not found
*/
restAPI(): CfnRestApi {
return this.getResourceConstruct<CfnRestApi>(this.resourceName);
}


/**
* Gets the Deployment of the Rest API
* @returns {CfnDeployment}
*/
* Gets the Deployment of the Rest API
* @returns {CfnDeployment}
* @throws {CfnResourceNotFoundError} if not found
*/
apiDeployment(): CfnDeployment {
return this.getResourceConstruct<CfnDeployment>(
`DeploymentAPIGW${this.resourceName}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export interface ProviderCredential {
}

export class AuthIncludedNestedStack extends BaseIncludedStack {
/**
* attaches third party auth provider credentials to identity pool
* @throws {CfnResourceNotFoundError} if
*/
hostedUiProviderCredentials(credentials: ProviderCredential[]): void {
const hostedUICustomResourceInputs = this.getResourceConstruct<
CfnCustomResource
Expand All @@ -31,14 +35,15 @@ export class AuthIncludedNestedStack extends BaseIncludedStack {
}
/**
* @returns {CfnIdentityPool} of the auth stack
* @throws {CfnResourceNotFoundError} if not found
*/
userPool(): CfnUserPool {
return this.getResourceConstruct<CfnUserPool>('UserPool');
}

/**
* @returns Cognito UserPool {CfnUserPool} of the auth stack
* @throws {}
* @throws {CfnResourceNotFoundError} if not found
*/
identityPool(): CfnIdentityPool {
return this.getResourceConstruct<CfnIdentityPool>('IdentityPool');
Expand Down
2 changes: 1 addition & 1 deletion src/types/category-stack-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export interface CategoryStackMapping {
readonly category: string;
readonly resourceName: string;
readonly service: string;
};
}

export type CategoryStackMappingWithDeployment = CategoryStackMapping & {
bucketDeployment? :BucketDeployment;
Expand Down
Loading