Skip to content

Commit

Permalink
Start of Twilio Provisioning
Browse files Browse the repository at this point in the history
  • Loading branch information
docwho2 committed Nov 19, 2023
1 parent 64f9db3 commit 8a59370
Show file tree
Hide file tree
Showing 21 changed files with 755 additions and 72 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ name: Deploy CDK Stack
on:
push:
branches: [ "main" ]
paths-ignore:
- '**.png'
- '**.md'
- '**.sh'


permissions:
Expand Down
8 changes: 8 additions & 0 deletions config.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Common stuff between deploy and destroy scripts

# Stack name for the SMA general deployment
STACK_NAME=chime-sdk-cdk-provisioning


# Regions we will deploy to (the only supported US regions for Chime PSTN SDK)
declare -a regions=( us-east-1 us-west-2)
5 changes: 0 additions & 5 deletions deploy.bash

This file was deleted.

6 changes: 6 additions & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)


cdk deploy -c accountId=${ACCOUNT_ID} -c stackName=${CDK_STACK_NAME} -c regionEast=${regions[0]} -c regionWest=${regions[1]} --concurrency=3 --all --require-approval=never
5 changes: 0 additions & 5 deletions destroy.bash

This file was deleted.

25 changes: 25 additions & 0 deletions destroy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash

# Stack names and regions
source config.sh

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

# Exit immediately if a command exits with a non-zero status.
set -e
cdk destroy -c accountId=${ACCOUNT_ID} --all --force

set +e

# delete things in each region
for region in "${regions[@]}"; do

echo
echo "Deleting Log Groups starting with /aws/lambda/${STACK_NAME} in region ${region}"
declare -a LGS=($(aws logs describe-log-groups --region ${region} --log-group-name-prefix /aws/lambda/${STACK_NAME} --query logGroups[].logGroupName --output text))
for logGroup in "${LGS[@]}"; do
echo " Delete Log group [${logGroup}]"
aws logs delete-log-group --region ${region} --log-group-name "${logGroup}" > /dev/null
done

done
2 changes: 0 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
</plugins>
</build>


<dependencies>
<!-- AWS Cloud Development Kit -->
<dependency>
Expand All @@ -51,7 +50,6 @@
</dependency>

<!-- Respective AWS Construct Libraries -->

<dependency>
<groupId>software.constructs</groupId>
<artifactId>constructs</artifactId>
Expand Down
31 changes: 23 additions & 8 deletions src/main/java/cloud/cleo/chimesma/cdk/InfrastructureApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,47 @@
public final class InfrastructureApp {

private static final String STACK_DESC = "Provision Chime Voice SDK resources (VoiceConnector, SIP Rule, SIP Media App)";

/**
* If set in the environment, setup Origination to point to it and allow from termination as well
*/
private final static String PBX_HOSTNAME = System.getenv("PBX_HOSTNAME");

private final static String TWILIO = System.getenv("TWILIO");

public static void main(final String[] args) {
final var app = new App();

// Required Param
String accountId = (String) app.getNode().tryGetContext("accountId");
requireNonEmpty(accountId, "accountId is required via -c parameter to cdk");

// Optional Params
String stackName = getParamOrDefault(app, "stackName", "chime-sdk-cdk-provisioning");
String stackName = getParamOrDefault(app, "stackName", "chime-sdk-cdk-provision");
String regionEast = getParamOrDefault(app, "regionEast", "us-east-1");
String regionWest = getParamOrDefault(app, "regionWest", "us-west-2");


new InfrastructureStack(app, "east", StackProps.builder()
final var east = new InfrastructureStack(app, "east", StackProps.builder()
.description(STACK_DESC)
.stackName(stackName)
.env(makeEnv(accountId, regionEast))
.build());
new InfrastructureStack(app, "west", StackProps.builder()

final var west = new InfrastructureStack(app, "west", StackProps.builder()
.description(STACK_DESC)
.stackName(stackName)
.env(makeEnv(accountId, regionWest))
.build());

if (TWILIO != null && !TWILIO.isBlank()) {
new TwilioStack(app, "twilio", StackProps.builder()
.description("Provision Twilio Resources")
.stackName(stackName + "-twilio")
.env(makeEnv(accountId, regionEast))
.crossRegionReferences(Boolean.TRUE)
.build(), east.getVCHostName(), west.getVCHostName());
}

app.synth();

}
Expand All @@ -46,9 +61,9 @@ static Environment makeEnv(String accountId, String region) {

static String getParamOrDefault(App app, String param, String defaultValue) {
final var val = (String) app.getNode().tryGetContext(param);
return val == null || val.isBlank() ? defaultValue : val;
return val == null || val.isBlank() ? defaultValue : val;
}

static void requireNonEmpty(String string, String message) {
if (string == null || string.isBlank()) {
throw new IllegalArgumentException(message);
Expand Down
49 changes: 28 additions & 21 deletions src/main/java/cloud/cleo/chimesma/cdk/InfrastructureStack.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import software.constructs.Construct;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.sam.CfnFunction;
import software.amazon.awscdk.services.lambda.Function;
import software.amazon.awscdk.services.ssm.StringParameter;
import software.amazon.awscdk.services.ssm.StringParameterProps;

Expand All @@ -24,11 +24,12 @@
*/
public class InfrastructureStack extends Stack {

/**
/**
* If set in the environment, setup Origination to point to it and allow from termination as well
*/
private final static String PBX_HOSTNAME = System.getenv("PBX_HOSTNAME");


private final ChimeVoiceConnector vc;

public InfrastructureStack(final App parent, final String id) {
this(parent, id, null);
Expand All @@ -38,31 +39,33 @@ public InfrastructureStack(final Construct parent, final String id, final StackP
super(parent, id, props);

// Simple SMA Handler that speaks prompt and hangs up
CfnFunction lambda = new ChimeSMAFunction(this, "sma-lambda");
Function lambda = new ChimeSMAFunction(this, "sma-lambda");

new StringParameter(this, "LAMBDAARN" , StringParameterProps.builder()
new StringParameter(this, "LAMBDAARN", StringParameterProps.builder()
.parameterName("/" + getStackName() + "/LAMBDA_ARN")
.description("The Lambda Arn for the Hello World Lambda")
.stringValue(lambda.getAtt("Arn").toString())
.stringValue(lambda.getFunctionArn())
.build());

// SMA pointing to lambda handler
ChimeSipMediaApp sma = new ChimeSipMediaApp(this, lambda.getAtt("Arn"));
ChimeSipMediaApp sma = new ChimeSipMediaApp(this, lambda.getFunctionArn());

// Start with list of Twilio NA ranges for SIP Trunking
var cidrAllowList = List.of(AclCidr.ipv4("54.172.60.0/30"), AclCidr.ipv4("54.244.51.0/30"));
if (PBX_HOSTNAME != null && ! PBX_HOSTNAME.isBlank()) {
var cidrAllowList = List.of(AclCidr.ipv4("54.172.60.0/30") , AclCidr.ipv4("54.244.51.0/30"),
// Europe, Ireland and Frankfurt
AclCidr.ipv4("54.171.127.192/30") , AclCidr.ipv4("35.156.191.128/30") );
if (PBX_HOSTNAME != null && !PBX_HOSTNAME.isBlank()) {
cidrAllowList = new ArrayList(cidrAllowList);
cidrAllowList.add(AclCidr.ipv4(PBX_HOSTNAME + "/32"));
}

// Voice Connector
ChimeVoiceConnector vc = new ChimeVoiceConnector(this,cidrAllowList,PBX_HOSTNAME);
vc = new ChimeVoiceConnector(this, cidrAllowList, PBX_HOSTNAME);

// SIP rule that associates the SMA with the Voice Connector
new ChimeSipRuleVC(this, vc, List.of(sma));
new StringParameter(this, "SMA_ID_PARAM" , StringParameterProps.builder()

new StringParameter(this, "SMA_ID_PARAM", StringParameterProps.builder()
.parameterName("/" + getStackName() + "/SMA_ID")
.description("The ID for the Session Media App (SMA)")
.stringValue(sma.getSMAId())
Expand All @@ -72,23 +75,23 @@ public InfrastructureStack(final Construct parent, final String id, final StackP
.description("The ID for the Session Media App (SMA)")
.value(sma.getSMAId())
.build());
new StringParameter(this, "VC_HOSTNAME_PARAM" , StringParameterProps.builder()

new StringParameter(this, "VC_HOSTNAME_PARAM", StringParameterProps.builder()
.parameterName("/" + getStackName() + "/VC_HOSTNAME")
.description("The Hostname for the Voice Connector")
.stringValue(vc.getOutboundName())
.build());

// If there is no PBX in play for SIP routing, set to PSTN to indicate to SMA Lambda that all transfers are PSTN
// IE, no need for VC_ARN to be set
String vc_arn;
if (PBX_HOSTNAME != null && ! PBX_HOSTNAME.isBlank() ) {
if (PBX_HOSTNAME != null && !PBX_HOSTNAME.isBlank()) {
vc_arn = vc.getArn();
} else {
vc_arn = "PSTN";
}
new StringParameter(this, "VC_ARN_PARAM" , StringParameterProps.builder()

new StringParameter(this, "VC_ARN_PARAM", StringParameterProps.builder()
.parameterName("/" + getStackName() + "/VC_ARN")
.description("The ARN for the Voice Connector")
.stringValue(vc_arn)
Expand All @@ -99,5 +102,9 @@ public InfrastructureStack(final Construct parent, final String id, final StackP
.value(vc.getOutboundName())
.build());
}

public String getVCHostName() {
return vc.getOutboundName();
}

}
32 changes: 32 additions & 0 deletions src/main/java/cloud/cleo/chimesma/cdk/TwilioStack.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cloud.cleo.chimesma.cdk;

import cloud.cleo.chimesma.cdk.twilio.*;
import software.amazon.awscdk.App;
import software.constructs.Construct;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;

/**
* CDK Stack
*
* @author sjensen
*/
public class TwilioStack extends Stack {

/**
* If set in the environment, setup Origination to point to it and allow from termination as well
*/
private final static String PBX_HOSTNAME = System.getenv("PBX_HOSTNAME");

public TwilioStack(final App parent, final String id, String vc1, String vc2) {
this(parent, id, null,vc1,vc2);
}

public TwilioStack(final Construct parent, final String id, final StackProps props, String vc1, String vc2) {
super(parent, id, props);

new TwilioSipTrunk(this,vc1, vc2);
new TwilioRule(this);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import software.amazon.awscdk.Reference;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.customresources.AwsCustomResource;
import software.amazon.awscdk.customresources.AwsCustomResourcePolicy;
Expand Down Expand Up @@ -36,7 +35,7 @@ public class ChimeSipMediaApp extends AwsCustomResource {
private static final String SMA_ID = "SipMediaApplication.SipMediaApplicationId";
private static final String SMA_ARN = "SipMediaApplication.SipMediaApplicationArn";

public ChimeSipMediaApp(Stack scope, Reference lambdaArn) {
public ChimeSipMediaApp(Stack scope, String lambdaArn) {
super(scope, ID, AwsCustomResourceProps.builder()
.resourceType("Custom::SipMediaApplication")
.installLatestAwsSdk(Boolean.FALSE)
Expand All @@ -47,7 +46,7 @@ public ChimeSipMediaApp(Stack scope, Reference lambdaArn) {
.service("@aws-sdk/client-chime-sdk-voice")
.action("CreateSipMediaApplicationCommand")
.physicalResourceId(PhysicalResourceId.fromResponse(SMA_ID))
.parameters(new SMAParameters(scope.getRegion(),scope.getStackName() + "-sma", List.of(new SipMediaApplicationEndpoint(lambdaArn.toString()))))
.parameters(new SMAParameters(scope.getRegion(),scope.getStackName() + "-sma", List.of(new SipMediaApplicationEndpoint(lambdaArn))))
.build())
.onDelete(AwsSdkCall.builder()
.service("@aws-sdk/client-chime-sdk-voice")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,32 @@
*/
package cloud.cleo.chimesma.cdk.resources;

import software.amazon.awscdk.RemovalPolicy;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.services.logs.LogGroup;
import software.amazon.awscdk.services.logs.LogGroupProps;
import software.amazon.awscdk.services.lambda.Code;
import software.amazon.awscdk.services.lambda.Function;
import software.amazon.awscdk.services.lambda.FunctionProps;
import static software.amazon.awscdk.services.lambda.Runtime.*;
import software.amazon.awscdk.services.logs.RetentionDays;
import software.amazon.awscdk.services.sam.CfnFunction;
import software.amazon.awscdk.services.sam.CfnFunctionProps;

/**
* Simple SMA Handler that calls speak action to play message and hang up
*
* @author sjensen
*/
public class ChimeSMAFunction extends CfnFunction {
public class ChimeSMAFunction extends Function {

/**
* @param scope
* @param id
*/
public ChimeSMAFunction(Stack scope, String id) {
super(scope, id, CfnFunctionProps.builder()
.handler("index.handler")
.runtime(software.amazon.awscdk.services.lambda.Runtime.NODEJS_LATEST.getName())
.build());

setInlineCode("""
/**
* @param scope
* @param id
*/
public ChimeSMAFunction(Stack scope, String id) {
super(scope, id, FunctionProps.builder()
.handler("index.handler")
.runtime(NODEJS_LATEST)
.logRetention(RetentionDays.ONE_MONTH)
.description("Hello World Chime SMA Handler that greets and hangs up")
.code(Code.fromInline(
"""
exports.handler = async (event, context, callback) => {
return {
SchemaVersion: '1.0',
Expand Down Expand Up @@ -77,17 +77,9 @@ public ChimeSMAFunction(Stack scope, String id) {
);
return participantA.CallId;
};
""");

// Assocaite log group so we can set a retention
new LogGroup(scope, id + "-LOG", LogGroupProps.builder()
.logGroupName("/aws/lambda/" + this.getRef())
.retention(RetentionDays.ONE_MONTH)
.removalPolicy(RemovalPolicy.DESTROY)
.build()
);

"""))
.build());

}
}

}
Loading

0 comments on commit 8a59370

Please sign in to comment.