in cdk/root/lib/tenant-infra/tenant-infra-stack.ts [25:271]
constructor(scope: cdk.Construct, id: string, props?: TenantInfraStackProps) {
super(scope, id, props);
const pooledTenantPool = new cognito.UserPool(this, 'PooledTenantsPool', {
userInvitation: {
emailSubject: 'Temporary password for environment EKS SaaS Application',
emailBody: `<b>Welcome to the SaaS Application for EKS Workshop!</b> <br>
<br>
You can log into the app <a href="http://${props?.elbUrl}/app/index.html">here</a>. If that link doesn't work, you can copy this URL into your browser: http://${props?.elbUrl}/app/index.html
<br>
Your username is: <b>{username}</b>
<br>
Your temporary password is: <b>{####}</b>
<br>`,
},
userPoolName: 'eks-ws-pooled',
customAttributes: {
'tenant-id': new cognito.StringAttribute({ mutable: false }),
'company-name': new cognito.StringAttribute({ mutable: false }),
email: new cognito.StringAttribute({ mutable: true }),
},
});
const pooledTenantAppClient = pooledTenantPool.addClient('PooledUserPoolClient', {
generateSecret: false,
authFlows: {
adminUserPassword: true,
custom: true,
userSrp: true,
},
oAuth: {
flows: {
implicitCodeGrant: true,
authorizationCodeGrant: true,
},
scopes: [
cognito.OAuthScope.EMAIL,
cognito.OAuthScope.PHONE,
cognito.OAuthScope.OPENID,
cognito.OAuthScope.PROFILE,
],
callbackUrls: [`https://${props?.elbUrl}/app`],
},
preventUserExistenceErrors: true,
});
this.pooledTenantUserPoolId = pooledTenantPool.userPoolId;
this.pooledTenantAppClientId = pooledTenantAppClient.userPoolClientId;
const pipeline = new codepipeline.Pipeline(this, 'Pipeline', {
pipelineName: `eks-saas-tenant-onboarding-pipeline`,
});
// Import existing CodeCommit sam-app repository
const codeRepo = codecommit.Repository.fromRepositoryName(
this,
'AppRepository',
'aws-saas-factory-eks-workshop'
);
// Declare source code as an artifact
const sourceOutput = new codepipeline.Artifact();
// Add source stage to pipeline
pipeline.addStage({
stageName: 'Source',
actions: [
new codepipeline_actions.CodeCommitSourceAction({
actionName: 'CodeCommit_Source',
repository: codeRepo,
branch: 'main',
output: sourceOutput,
variablesNamespace: 'SourceVariables',
}),
],
});
// Declare build output as artifacts
new codepipeline.Artifact();
// Add the Lambda invoke stage to our pipeline
const pipelinePolicy = new iam.PolicyDocument({
assignSids: false,
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['codepipeline:PutJobSuccessResult', 'codepipeline:PutJobFailureResult'],
resources: ['*'],
}),
],
});
const lambdaRole = new iam.Role(this, 'EksTenantStackLambdaRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
inlinePolicies: {
inlinePolicy: pipelinePolicy,
},
});
this.pipelineFunction = new lambda.Function(this, 'Func', {
runtime: lambda.Runtime.NODEJS_12_X,
handler: 'index.handler',
code: lambda.Code.fromInline(`
var assert = require('assert');
var AWS = require('aws-sdk');
var http = require('http');
var codepipeline = new AWS.CodePipeline();
exports.handler = async (event, context) => {
var outputParams;
var tenantName,appClientId,userPoolId,elbUrl,codeBuildArn,iamRoleArn;
var region = process.env.AWS_REGION;
console.log('event:', event);
// Retrieve the Job ID from the Lambda action
var jobId = event["CodePipeline.job"].id;
// Retrieve the tenant data from Tenant-Stack-Mapping table
const result = await getTenantStackData();
console.log("result====>", result);
result.Items.forEach(function (element, index, array) {
tenantName = element.TenantName.S;
appClientId = element.AppClientId.S;
userPoolId = element.UserPoolId.S;
});
console.log("TenantName====>", tenantName);
console.log("appClientId====>", appClientId);
console.log("userPoolId====>", userPoolId);
console.log("region====>", region);
// Retrieve the stack data from the EKS-SaaS-Stack-Metadata table
const stackMetadata = await getStackMetadata();
console.log("stackMetadata====>", stackMetadata);
stackMetadata.Items.forEach(function (element, index, array) {
elbUrl = element.ELBURL.S;
codeBuildArn = element.CODEBUILD_ARN.S;
iamRoleArn = element.IAM_ROLE_ARN.S;
});
console.log("ELBURL====>", elbUrl);
console.log("CODEBUILD_ARN====>", codeBuildArn);
console.log("IAM_ROLE_ARN====>", iamRoleArn);
outputParams = {
jobId: jobId,
outputVariables: {
TenantName: tenantName,
UserPoolId: userPoolId,
AppClientId: appClientId,
ElbUrl: elbUrl,
CodeBuildArn: codeBuildArn,
IamRoleArn: iamRoleArn,
Region: region,
dateTime: Date(Date.now()).toString(),
}
};
// Notify AWS CodePipeline of a successful job
await putJobSuccess(outputParams);
}
async function putJobSuccess(params) {
return codepipeline.putJobSuccessResult(params).promise();
};
async function getTenantStackData() {
//Query DynamoDB table for tenant stack data
var ddb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
var params = {
ExpressionAttributeValues: {
':s': {S: 'Provisioning'},
},
FilterExpression: 'DeploymentStatus = :s',
TableName: 'EKS-SaaS-Tenant-Stack-Mapping',
};
return ddb.scan(params).promise();
}
async function getStackMetadata() {
//Query DynamoDB table for EKS SaaS stack metadadata
var ddb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
var params = {
ExpressionAttributeValues: {
':s': {S: 'eks-saas'},
},
KeyConditionExpression: 'StackName = :s',
TableName: 'EKS-SaaS-Stack-Metadata',
};
return ddb.query(params).promise();
}
`),
});
const lambdaInvokeAction = new codepipeline_actions.LambdaInvokeAction({
actionName: 'Lambda',
lambda: this.pipelineFunction,
variablesNamespace: 'LambdaVariables',
});
pipeline.addStage({
stageName: 'Lambda',
actions: [lambdaInvokeAction],
});
// Declare build output as artifacts
const buildOutput = new codepipeline.Artifact();
//Declare a new CodeBuild project
const codeBuildRole = getCodeBuildRole(this, this.account, this.region);
const buildProject = new codebuild.PipelineProject(this, 'Build', {
buildSpec: codebuild.BuildSpec.fromSourceFilename('buildspec.yaml'),
role: codeBuildRole,
environment: {
buildImage: codebuild.LinuxBuildImage.STANDARD_4_0,
privileged: true,
},
});
const codeBuildAction = new codepipeline_actions.CodeBuildAction({
actionName: 'Build-And-Deploy-Tenant-K8s-resources',
project: buildProject,
input: sourceOutput,
outputs: [buildOutput],
environmentVariables: {
TenantName: { value: lambdaInvokeAction.variable('TenantName') },
UserPoolId: { value: lambdaInvokeAction.variable('UserPoolId') },
AppClientId: { value: lambdaInvokeAction.variable('AppClientId') },
ElbUrl: { value: lambdaInvokeAction.variable('ElbUrl') },
CodeBuildArn: { value: lambdaInvokeAction.variable('CodeBuildArn') },
IamRoleArn: { value: lambdaInvokeAction.variable('IamRoleArn') },
},
});
// Add the build stage to our pipeline
pipeline.addStage({
stageName: 'Build',
actions: [codeBuildAction],
});
}