AWS Service Catalog enables organizations to create and manage catalogs of IT services that are approved for use on AWS
Service limitations
Soft and hard quotas are presented here https://docs.aws.amazon.com/servicecatalog/latest/adminguide/limits.html.
To check which of them are hard/soft go to AWS Service Quotas console
Configuring the service
We will setup simple case where service catalog product will provision CloudFormation stack with S3 bucket. Service Catalog works with Portfolios and Products. Product relates to Portfolio as many to one. Portfolio manages access and constraints. Product contains info about stack to provision. We will create Portfolio and Product during deployment and then provision the product using lambda.
Product requires CloudFormation template to be created, let's create function that generates template.
product_builder.ts
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { writeFileSync, existsSync, mkdirSync } from 'fs';
import {Stack} from "@aws-cdk/core";
export type ProductTemplate = {
stack: typeof Stack;
name: string;
};
export const getProductTemplate = (product: ProductTemplate) => {
const app = new cdk.App({
runtimeInfo: false,
stackTraces: false,
treeMetadata: false
});
const defEnv = {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION || 'us-east-1'
}
};
new product.stack(app, product.name, defEnv);
const synth = app.synth();
const template = synth.getStackArtifact(product.name).template;
const templateDir = './templates';
if (!existsSync(templateDir)) {
mkdirSync(templateDir);
}
const templatePath = `${templateDir}/${product.name}.template.json`;
writeFileSync(templatePath, JSON.stringify(template, null, 2));
return {
template,
templatePath
};
}
Simple stack that would be provisioned:
simple_stack.ts
import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';
import { ProductTemplate } from "./product_builder";
export class SimpleBucketStack extends cdk.Stack {
public readonly s3Bucket: s3.Bucket;
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
this.s3Bucket = new s3.Bucket(this, 'PropagatedBucket', {
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
}
}
export const SimpleProductTemplate: ProductTemplate = {
stack: SimpleBucketStack,
name: 'S3Product'
};
Create IAM Role that allows lambda to get access to service catalog product and create s3 bucket during provisioning.
iam_stack.ts
import * as cdk from '@aws-cdk/core';
import * as iam from '@aws-cdk/aws-iam';
export class IAMStack extends cdk.Construct {
public readonly serviceCatalogRole: iam.Role;
constructor(private stack: cdk.Stack) {
super(stack, 'IAM');
this.serviceCatalogRole = new iam.Role(this, 'ServiceCatalogLambdaRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [
{managedPolicyArn: 'arn:aws:iam::aws:policy/AWSServiceCatalogEndUserFullAccess'},
{managedPolicyArn: 'arn:aws:iam::aws:policy/AmazonS3FullAccess'},
]
});
}
}
Service Catalog configuration. Minimal amount of settings is set here. Put admin_role_arn to cdk.context.json, it will allow you to see provisioned product in AWS Service Catalog console.
service_catalog_stack.ts
import * as cdk from '@aws-cdk/core';
import * as servicecatalog from '@aws-cdk/aws-servicecatalog';
import { Asset } from '@aws-cdk/aws-s3-assets';
import { getProductTemplate } from './product-builder';
import { PropagatedProductTemplate} from "../../propagated-stack/PropagatedLyrisHqStack";
import {LyrisHqStack} from "../../lyris-hq-stack";
export class ServiceCatalogStack extends cdk.Construct {
private readonly portfolio: servicecatalog.CfnPortfolio;
private readonly product: servicecatalog.CfnCloudFormationProduct;
constructor(scope: LyrisHqStack, id: string) {
super(scope, id);
const template = getProductTemplate(SimpleProductTemplate);
const asset = new Asset(this, 'ServiceCatalogProductAsset', {
path: template.templatePath
});
this.portfolio = new servicecatalog.CfnPortfolio(this,'ServiceCatalogPortfolio', {
displayName: 'ServiceCatalogPortfolio_DisplayName',
providerName: 'ServiceCatalogPortfolio_ProviderName'});
new servicecatalog.CfnPortfolioPrincipalAssociation(this, 'PortfolioPrincipal', {
principalArn: scope.iam.serviceCatalogRole.roleArn,
portfolioId: this.portfolio.ref,
principalType: 'IAM'});
new servicecatalog.CfnPortfolioPrincipalAssociation(this, 'PortfolioAdminPrincipal', {
principalArn: this.node.tryGetContext(admin_role_arn),
portfolioId: this.portfolio.ref,
principalType: 'IAM'
});
this.product = new servicecatalog.CfnCloudFormationProduct(this, 's3-product', {
name: 'Simple S3 Product',
description: 'This is an s3 product',
owner: 'whoever',
provisioningArtifactParameters: [
{
description: 'Product template description',
name: '1.0',
info: {
LoadTemplateFromURL: asset.s3Url
}
}
]
});
new servicecatalog.CfnPortfolioProductAssociation(this, 'ServiceCatalogAssociation', {
portfolioId: this.portfolio.ref,
productId: this.product.ref
});
}
}
Lambdas stack, see lambda code in :
lambdas_stack.ts
import * as cdk from '@aws-cdk/core';
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import {join} from "path";
import { ParentStack } from "./parent_stack";
export class LambdasStack extends cdk.Construct {
public readonly provisionProductLambda: lambda.Function;
constructor(public stack: ParentStack) {
super(stack, 'LyrisLambdas');
this.provisionProductLambda = this.provisionProduct(stack.iam.serviceCatalogRole);
}
private provisionProduct(serviceCatalogRole: iam.Role) {
const lambdaFunction = new lambda.Function(scope, id, {
code: lambda.Code.fromAsset(join(__dirname, '../../dist/lambdas')),
handler: 'handlers/provisionStack.handler',
role: serviceCatalogRole,
runtime: lambda.Runtime.NODEJS_12_X,
retryAttempts: 0,
timeout: cdk.Duration.seconds(30),
});
return lambdaFunction;
}
}
Parent stack:
parent_stack.ts
import * as cdk from '@aws-cdk/core';
import { LambdasStack } from "./lambdas_stack";
import { ServiceCatalogStack } from "./service_catalog_stack";
import { IAMStack } from "./iam_stack";
export class ParentStack extends cdk.Stack {
iam: IAMStack;
lambdas: LambdasStack;
serviceCatalog: ServiceCatalogStack;
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
this.iam = new LyrisIAM(this);
this.lambdas = new LyrisLambdas(this);
this.serviceCatalog = new ServiceCatalogStack(this);
}
}
Deploy
Run CDK deployment
cdk deploy ...
Using the service
Service could be used with AWS SDK. See Developer Guide
provisionStack.ts
import * as AWS from 'aws-sdk';
import { ProvisionProductInput } from "aws-sdk/clients/servicecatalog";
const servicecatalog = new AWS.ServiceCatalog();
export async function handler( ) {
console.log(`Start provisioning`);
const productName = process.env.SERVICE_CATALOG_PRODUCT_NAME || 'S3 Product';
const provisionResult = await servicecatalog.provisionProduct({
ProductName: productName,
ProvisionedProductName: `MyProduct--Provisioned`,
ProvisioningArtifactName: `1.0` // see provisioningArtifactParameters
// on Product creation in ServiceCatalogStack
} as ProvisionProductInput).promise();
console.log(`Provision Result: ${JSON.stringify(provisionResult)}`);
}
Comments
0 comments
Please sign in to leave a comment.