aws-amplify / aws-amplify/amplify-cli

Expose Custom Resources Outputs to aws-exports.js and other resources

Open
#11,113 6 comments 1 reaction 0 assignees View on GitHub
documentation extensibility feature-request p3 platform
Dominant language
TypeScript
Stars
2.9k
Forks
825
Avg merge
11d 23h
Merged PRs (30d)
2

Description

### Is this feature request related to a new or existing Amplify category?

New category

### Is this related to another service?

_No response_

### Describe the feature you'd like to request

I'd like to reprise the request of https://github.com/aws-amplify/amplify-cli/issues/8886 but to caveat that it be without requiring the use of hooks.

> With Amplify CLI Customers can define custom resources via CloudFormation & AWS CDK and these resources can reference the ones created by Amplify. At the moment though, interacting with these resources from the application requires Customers to pass the name/identifier of that resource somewhat manually.
Similarly to what already happens for resources created via Amplify, the CLI could add to the aws-exports.js file all the outputs of the custom resources stacks, so that the application can be aware of them.

### Describe the solution you'd like

Refer to https://github.com/aws-amplify/amplify-cli/issues/8886 for an option, but here is part of what I have done:

The idea being that this could be easily rolled into `amplify push`.

My on-machine deployment flow includes:
```
amplify env checkout $1
amplify push --yes
node post-publish.js # this does the copying of CFN outputs
amplify publish --yes --invalidateCloudFront
```

Here is an abbreviated version, which looks like [`command-hooks`](https://docs.amplify.aws/cli/usage/command-hooks), but is not it:
```js
const fs = require('fs');

const AWS_EXPORTS_PATH = './src/aws-exports.js';
const TEAM_PROVIDER_INFO_PATH = './amplify/team-provider-info.json';
const AMPLIFY_META_PATH = './amplify/backend/amplify-meta.json';

/**
* @param data { { amplify: { environment: { envName: string, projectPath: string, defaultEditor: string }, command: string, subCommand: string, argv: string[] } } }
* @param error { { message: string, stack: string } }
*/
const hookHandler = async function handler(data) {
console.log();
console.log(`EXPOSING CUSTOM RESOURCE OUTPUTS in ${AWS_EXPORTS_PATH}`);
console.log();
console.log(`env: ${data.env}`)
console.log();

const customResourceNames = Object.keys(data.teamProviderInfo.categories.custom)
if (customResourceNames.length > 0) {
data.awsExports.aws_custom_resources = {}
console.log(`Adding Stack Outputs for Custom Resources to ${AWS_EXPORTS_PATH}:`)
customResourceNames.forEach(name => {
console.log(` - ${name}`)
data.awsExports.aws_custom_resources[name] = data.amplifyMeta.custom[name].output;
});
console.log();
}

console.log(`Writing ${AWS_EXPORTS_PATH}...`);
const newAwsExports = [
'/* eslint-disable */',
'// WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.',
'// Regenerated by post-publish.js',
'',
'',
`const awsmobile = ${JSON.stringify(data.awsExports, null, 4)};`,
'',
'',
'export default awsmobile;',
''
].join('\n');
fs.writeFileSync(AWS_EXPORTS_PATH, newAwsExports)
};

const getParameters = async () => {
const env = process.env.AMPLIFY_PROJECT_ENV;

const teamProviderInfo = JSON.parse(fs.readFileSync(TEAM_PROVIDER_INFO_PATH, {
encoding: 'utf8'
}));
const amplifyMeta = JSON.parse(fs.readFileSync(AMPLIFY_META_PATH, {
encoding: 'utf8'
}));

const awsExportsRaw = fs.readFileSync(AWS_EXPORTS_PATH, {
encoding: 'utf8'
});
const awsExportsRawParts = awsExportsRaw.split('\n')
const awsExportsCleaned = awsExportsRawParts.slice(
awsExportsRawParts.indexOf('const awsmobile = {') + 1,
awsExportsRawParts.indexOf('};')
)
awsExportsCleaned.unshift('{');
awsExportsCleaned.push('}');
const awsExports = JSON.parse(awsExportsCleaned.join('\n'))

return {
env,
awsExports,
amplifyMeta,
teamProviderInfo: teamProviderInfo[env],
}
};

getParameters()
.then(data => hookHandler(data))
.catch((err) => {
console.error(err);
process.exitCode = 1;
});
```

What it adds is the CFN outputs to `aws-exports.js`, with the end of the file now looking like:
```js
...

"aws_content_delivery_bucket_region": "rr-lllllll-1",
"aws_content_delivery_url": "https://abcdefg123.cloudfront.net",
"aws_custom_resources": {
"cfnResources": {
"env": "dev",
"SomeOverPermissiveFunctionRoleARN": "arn:aws:iam::123456789:role/amplify-abc-dev-12345-SomeOverPermissiveFuncti-9A9999AAAA99A"
},
"moreCfnResources": {
"env": "dev",
"SomeOverPermissiveFunctionRoleARN": "arn:aws:iam::123456789:role/amplify-abc-dev-12345-SomeOverPermissiveFuncti-8B8888BBBB88B"
}
}
};

export default awsmobile;
```

... which comes from a two of the same template that each containing just a Lambda role with overly broad privileges.

### Describe alternatives you've considered

The above was my alternative, because [`command-hooks`](https://docs.amplify.aws/cli/usage/command-hooks) don't work in a console environment.

### Additional context

See https://github.com/aws-amplify/amplify-cli/issues/8886

### Is this something that you'd be interested in working on?

- [X] 👋 I may be able to implement this feature request

### Would this feature include a breaking change?

- [ ] ⚠️ This feature might incur a breaking change

Contributor guide

Open the contributing guide

Research direction

Start by reviewing issue #8886, the command-hooks documentation, and the mentioned amplify push flow. Trace how amplify-meta.json, team-provider-info.json, and aws-exports.js are generated, then define completion as custom resource stack outputs being available after amplify push without requiring hooks.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, javascript, typescript
Domain
cli, cloud, infrastructure
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.