@aws-amplify/amplify-category-api
Version:
Amplify CLI API Category Plugin
261 lines (221 loc) • 9.46 kB
text/typescript
import * as path from 'path';
import { $TSContext, createDefaultCustomPoliciesFile, pathManager } from '@aws-amplify/amplify-cli-core';
import { printer } from '@aws-amplify/amplify-prompts';
import * as fs from 'fs-extra';
import { v4 as uuid } from 'uuid';
import { NETWORK_STACK_LOGICAL_ID } from '../../category-constants';
import { DEPLOYMENT_MECHANISM } from './base-api-stack';
import { GitHubSourceActionInfo } from './pipeline-with-awaiter';
import { API_TYPE, IMAGE_SOURCE_TYPE, ResourceDependency, ServiceConfiguration } from './service-walkthroughs/containers-walkthrough';
import { ApiResource, generateContainersArtifacts } from './utils/containers-artifacts';
export const addResource = async (
serviceWalkthroughPromise: Promise<ServiceConfiguration>,
context: $TSContext,
category: string,
service,
options,
apiType: API_TYPE,
) => {
const walkthroughOptions = await serviceWalkthroughPromise;
const {
resourceName,
restrictAccess,
imageSource,
gitHubPath,
gitHubToken,
deploymentMechanism,
categoryPolicies,
environmentMap,
dependsOn = [],
mutableParametersState,
} = walkthroughOptions;
const resourceDirPath = pathManager.getResourceDirectoryPath(undefined, category, resourceName);
let [authName, updatedDependsOn] = await getResourceDependencies({ dependsOn, restrictAccess, category, resourceName, context });
let gitHubInfo: GitHubSourceActionInfo;
if (deploymentMechanism === DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED) {
const { StackName } = context.amplify.getProjectDetails().amplifyMeta.providers['awscloudformation'];
const secretName = `${StackName}-${category}-${resourceName}-github-token`;
const { ARN: secretArn } = await context.amplify.executeProviderUtils(context, 'awscloudformation', 'newSecret', {
secret: gitHubToken,
description: 'GitHub OAuth token',
name: secretName,
version: secretName,
});
const gitHubTokenSecretArn = secretArn;
gitHubInfo = {
path: gitHubPath,
tokenSecretArn: gitHubTokenSecretArn,
};
}
const build = deploymentMechanism === DEPLOYMENT_MECHANISM.FULLY_MANAGED;
options = {
resourceName,
dependsOn: updatedDependsOn,
deploymentMechanism,
imageSource,
restrictAccess,
build,
providerPlugin: 'awscloudformation',
service: 'ElasticContainer',
gitHubInfo,
authName,
environmentMap,
categoryPolicies,
mutableParametersState,
skipHashing: false,
apiType,
iamAccessUnavailable: true, // this is because we dont support IAM access to the API yet
};
await context.amplify.updateamplifyMetaAfterResourceAdd(category, resourceName, options);
const apiResource = (await context.amplify.getProjectMeta().api[resourceName]) as ApiResource;
apiResource.category = category;
fs.ensureDirSync(resourceDirPath);
fs.ensureDirSync(path.join(resourceDirPath, 'src'));
if (imageSource.type === IMAGE_SOURCE_TYPE.TEMPLATE) {
fs.copySync(
path.join(__dirname, '..', '..', '..', 'resources', 'awscloudformation/container-templates', imageSource.template),
path.join(resourceDirPath, 'src'),
{ recursive: true },
);
const { exposedContainer } = await generateContainersArtifacts(context, apiResource);
await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'exposedContainer', exposedContainer);
}
createDefaultCustomPoliciesFile(category, resourceName);
const customPoliciesPath = pathManager.getCustomPoliciesPath(category, resourceName);
printer.success(`Successfully added resource ${resourceName} locally.`);
printer.info('');
printer.success('Next steps:');
if (deploymentMechanism === DEPLOYMENT_MECHANISM.FULLY_MANAGED) {
printer.info(
`- Place your Dockerfile, docker-compose.yml and any related container source files in "amplify/backend/api/${resourceName}/src"`,
);
} else if (deploymentMechanism === DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED) {
printer.info(
`- Ensure you have the Dockerfile, docker-compose.yml and any related container source files in your Github path: ${gitHubInfo.path}`,
);
}
printer.info(
`- Amplify CLI infers many configuration settings from the "docker-compose.yaml" file. Learn more: docs.amplify.aws/cli/usage/containers`,
);
printer.info(`- To access AWS resources outside of this Amplify app, edit the ${customPoliciesPath}`);
printer.info('- Run "amplify push" to build and deploy your image');
return resourceName;
};
const getResourceDependencies = async ({
restrictAccess,
dependsOn,
context,
resourceName,
category,
}: {
restrictAccess: boolean;
dependsOn: ResourceDependency[];
context: $TSContext;
category: string;
resourceName: string;
}) => {
let authName;
const updatedDependsOn: ResourceDependency[] = [].concat(dependsOn);
updatedDependsOn.push({
category: '',
resourceName: NETWORK_STACK_LOGICAL_ID,
attributes: ['ClusterName', 'VpcId', 'VpcCidrBlock', 'SubnetIds', 'VpcLinkId', 'CloudMapNamespaceId'],
});
if (restrictAccess) {
const apiRequirements = { authSelections: 'identityPoolAndUserPool' };
// getting requirement satisfaction map
const satisfiedRequirements = await context.amplify.invokePluginMethod(context, 'auth', undefined, 'checkRequirements', [
apiRequirements,
context,
'api',
resourceName,
]);
// checking to see if any requirements are unsatisfied
const foundUnmetRequirements = Object.values(satisfiedRequirements).includes(false);
if (foundUnmetRequirements) {
try {
authName = await context.amplify.invokePluginMethod(context, 'auth', undefined, 'externalAuthEnable', [
context,
'api',
resourceName,
apiRequirements,
]);
} catch (e) {
printer.error(e);
throw e;
}
} else {
[authName] = Object.keys(context.amplify.getProjectDetails().amplifyMeta.auth);
}
// get auth dependency if exists to avoid duplication
const authDependency = updatedDependsOn.find((dependency) => dependency.category === 'auth');
if (authDependency === undefined) {
updatedDependsOn.push({
category: 'auth',
resourceName: authName,
attributes: ['UserPoolId', 'AppClientIDWeb'],
});
} else {
const existingAttributes = authDependency.attributes;
const newAttributes = new Set([...existingAttributes, 'UserPoolId', 'AppClientIDWeb']);
authDependency.attributes = Array.from(newAttributes);
}
}
return [authName, updatedDependsOn];
};
export const updateResource = async (serviceWalkthroughPromise: Promise<ServiceConfiguration>, context: $TSContext, category: string) => {
const options = await serviceWalkthroughPromise;
const {
dependsOn,
restrictAccess,
resourceName,
gitHubPath,
gitHubToken,
gitHubInfo,
mutableParametersState,
categoryPolicies,
environmentMap,
deploymentMechanism,
} = options;
let [authResourceName, updatedDependsOn] = await getResourceDependencies({ dependsOn, restrictAccess, category, resourceName, context });
let newGithubInfo: GitHubSourceActionInfo = {
path: gitHubPath,
tokenSecretArn: gitHubInfo && gitHubInfo.tokenSecretArn,
};
if (gitHubToken) {
// #region Add token to secrets manager and get arn
const { StackName } = context.amplify.getProjectDetails().amplifyMeta.providers['awscloudformation'];
const secretName = `${StackName}-${category}-${resourceName}-github-token`;
const { ARN: secretArn } = await context.amplify.executeProviderUtils(context, 'awscloudformation', 'updateSecret', {
secret: gitHubToken,
description: 'GitHub OAuth token',
name: secretName,
version: uuid(),
});
newGithubInfo.tokenSecretArn = secretArn;
// #endregion
}
if (deploymentMechanism === DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED) {
await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'gitHubInfo', newGithubInfo);
}
await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'restrictAccess', restrictAccess);
await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'authName', authResourceName);
await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'environmentMap', environmentMap);
await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'dependsOn', updatedDependsOn);
await context.amplify.updateamplifyMetaAfterResourceUpdate(
category,
options.resourceName,
'mutableParametersState',
mutableParametersState,
);
await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'categoryPolicies', categoryPolicies);
const apiResource = (await context.amplify.getProjectMeta().api[options.resourceName]) as ApiResource;
apiResource.category = category;
try {
const askForExposedContainer = true;
const { exposedContainer } = await generateContainersArtifacts(context, apiResource, askForExposedContainer);
await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'exposedContainer', exposedContainer);
} catch (err) {
// Best effort to create templates
}
};