Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
466
activepieces-fork/deploy/pulumi/index.ts
Normal file
466
activepieces-fork/deploy/pulumi/index.ts
Normal file
@@ -0,0 +1,466 @@
|
||||
import * as aws from "@pulumi/aws";
|
||||
import * as docker from "@pulumi/docker";
|
||||
import * as pulumi from "@pulumi/pulumi";
|
||||
import * as awsx from "@pulumi/awsx";
|
||||
import { ApplicationLoadBalancer } from "@pulumi/awsx/lb/applicationLoadBalancer";
|
||||
import { registerAutoTags } from './autotag';
|
||||
import * as child_process from "child_process";
|
||||
|
||||
const stack = pulumi.getStack();
|
||||
const config = new pulumi.Config();
|
||||
|
||||
const apEncryptionKey = config.getSecret("apEncryptionKey")?.apply(secretValue => {
|
||||
return secretValue || child_process.execSync("openssl rand -hex 16").toString().trim();
|
||||
});
|
||||
const apJwtSecret = config.getSecret("apJwtSecret")?.apply(secretValue => {
|
||||
return secretValue || child_process.execSync("openssl rand -hex 32").toString().trim();
|
||||
});
|
||||
const containerCpu = config.requireNumber("containerCpu");
|
||||
const containerMemory = config.requireNumber("containerMemory");
|
||||
const containerInstances = config.requireNumber("containerInstances");
|
||||
const addIpToPostgresSecurityGroup = config.get("addIpToPostgresSecurityGroup");
|
||||
const domain = config.get("domain");
|
||||
const subDomain = config.get("subDomain");
|
||||
const usePostgres = config.requireBoolean("usePostgres");
|
||||
const useRedis = config.requireBoolean("useRedis");
|
||||
const redisNodeType = config.require("redisNodeType");
|
||||
const dbIsPublic = config.getBoolean("dbIsPublic");
|
||||
const dbUsername = config.get("dbUsername");
|
||||
const dbPassword = config.getSecret("dbPassword");
|
||||
const dbInstanceClass = config.require("dbInstanceClass");
|
||||
|
||||
// Add tags for every resource that allows them, with the following properties.
|
||||
// Useful to know who or what created the resource/service
|
||||
registerAutoTags({
|
||||
"pulumi:Project": pulumi.getProject(),
|
||||
"pulumi:Stack": pulumi.getStack(),
|
||||
"Created by": config.get("author") || child_process.execSync("pulumi whoami").toString().trim().replace('\\', '/')
|
||||
});
|
||||
|
||||
let imageName;
|
||||
|
||||
// Check if we're deploying a local build or direct from Docker Hub
|
||||
if (config.getBoolean("deployLocalBuild")) {
|
||||
|
||||
const repoName = config.require("repoName");
|
||||
|
||||
const repo = new aws.ecr.Repository(repoName, {
|
||||
name: repoName // https://www.pulumi.com/docs/intro/concepts/resources/names/#autonaming
|
||||
}); // Create a private ECR repository
|
||||
|
||||
const repoUrl = pulumi.interpolate`${repo.repositoryUrl}`; // Get registry info (creds and endpoint)
|
||||
const name = pulumi.interpolate`${repoUrl}:latest`;
|
||||
|
||||
// Get the repository credentials we use to push the image to the repository
|
||||
const repoCreds = repo.registryId.apply(async (registryId) => {
|
||||
const credentials = await aws.ecr.getCredentials({
|
||||
registryId: registryId,
|
||||
});
|
||||
const decodedCredentials = Buffer.from(credentials.authorizationToken, "base64").toString();
|
||||
const [username, password] = decodedCredentials.split(":");
|
||||
return {
|
||||
server: credentials.proxyEndpoint,
|
||||
username,
|
||||
password
|
||||
};
|
||||
});
|
||||
|
||||
// Build and publish the container image.
|
||||
const image = new docker.Image(stack, {
|
||||
build: {
|
||||
context: `../../`,
|
||||
dockerfile: `../../Dockerfile`,
|
||||
builderVersion: "BuilderBuildKit",
|
||||
args: {
|
||||
"BUILDKIT_INLINE_CACHE": "1"
|
||||
},
|
||||
},
|
||||
skipPush: pulumi.runtime.isDryRun(),
|
||||
imageName: name,
|
||||
registry: repoCreds
|
||||
});
|
||||
|
||||
imageName = image.imageName;
|
||||
|
||||
pulumi.log.info(`Finished pushing image to ECR`, image);
|
||||
} else {
|
||||
imageName = process.env.IMAGE_NAME || config.get("imageName") || "activepieces/activepieces:latest";
|
||||
}
|
||||
|
||||
const containerEnvironmentVars: awsx.types.input.ecs.TaskDefinitionKeyValuePairArgs[] = [];
|
||||
|
||||
// Allocate a new VPC with the default settings:
|
||||
const vpc = new awsx.ec2.Vpc(`${stack}-vpc`, {
|
||||
numberOfAvailabilityZones: 2,
|
||||
natGateways: {
|
||||
strategy: "Single"
|
||||
},
|
||||
tags: {
|
||||
// For some reason, this is how you name a VPC with AWS:
|
||||
// https://github.com/pulumi/pulumi-terraform/issues/38#issue-262186406
|
||||
Name: `${stack}-vpc`
|
||||
},
|
||||
enableDnsHostnames: true,
|
||||
enableDnsSupport: true
|
||||
});
|
||||
|
||||
const albSecGroup = new aws.ec2.SecurityGroup(`${stack}-alb-sg`, {
|
||||
name: `${stack}-alb-sg`,
|
||||
vpcId: vpc.vpcId,
|
||||
ingress: [{ // Allow only http & https traffic
|
||||
protocol: "tcp",
|
||||
fromPort: 443,
|
||||
toPort: 443,
|
||||
cidrBlocks: ["0.0.0.0/0"]
|
||||
},
|
||||
{
|
||||
protocol: "tcp",
|
||||
fromPort: 80,
|
||||
toPort: 80,
|
||||
cidrBlocks: ["0.0.0.0/0"]
|
||||
}],
|
||||
egress: [{
|
||||
protocol: "-1",
|
||||
fromPort: 0,
|
||||
toPort: 0,
|
||||
cidrBlocks: ["0.0.0.0/0"]
|
||||
}]
|
||||
})
|
||||
|
||||
const fargateSecGroup = new aws.ec2.SecurityGroup(`${stack}-fargate-sg`, {
|
||||
name: `${stack}-fargate-sg`,
|
||||
vpcId: vpc.vpcId,
|
||||
ingress: [
|
||||
{
|
||||
protocol: "tcp",
|
||||
fromPort: 80,
|
||||
toPort: 80,
|
||||
securityGroups: [albSecGroup.id]
|
||||
}
|
||||
],
|
||||
egress: [ // allow all outbound traffic
|
||||
{
|
||||
protocol: "-1",
|
||||
fromPort: 0,
|
||||
toPort: 0,
|
||||
cidrBlocks: ["0.0.0.0/0"]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (usePostgres) {
|
||||
const rdsSecurityGroupArgs: aws.ec2.SecurityGroupArgs = {
|
||||
name: `${stack}-db-sg`,
|
||||
vpcId: vpc.vpcId,
|
||||
ingress: [{
|
||||
protocol: "tcp",
|
||||
fromPort: 5432,
|
||||
toPort: 5432,
|
||||
securityGroups: [fargateSecGroup.id] // The id of the Fargate security group
|
||||
}],
|
||||
egress: [ // allow all outbound traffic
|
||||
{
|
||||
protocol: "-1",
|
||||
fromPort: 0,
|
||||
toPort: 0,
|
||||
cidrBlocks: ["0.0.0.0/0"]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Optionally add the current outgoing public IP address to the CIDR block
|
||||
// so that they can connect directly to the Db during development
|
||||
if (addIpToPostgresSecurityGroup) {
|
||||
|
||||
// @ts-ignore
|
||||
rdsSecurityGroupArgs.ingress.push({
|
||||
protocol: "tcp",
|
||||
fromPort: 5432,
|
||||
toPort: 5432,
|
||||
cidrBlocks: [`${addIpToPostgresSecurityGroup}/32`],
|
||||
description: `Public IP for local connection`
|
||||
});
|
||||
}
|
||||
|
||||
const rdsSecurityGroup = new aws.ec2.SecurityGroup(`${stack}-db-sg`, rdsSecurityGroupArgs);
|
||||
|
||||
const rdsSubnets = new aws.rds.SubnetGroup(`${stack}-db-subnet-group`, {
|
||||
name: `${stack}-db-subnet-group`,
|
||||
subnetIds: dbIsPublic ? vpc.publicSubnetIds : vpc.privateSubnetIds
|
||||
});
|
||||
|
||||
const db = new aws.rds.Instance(stack, {
|
||||
allocatedStorage: 10,
|
||||
engine: "postgres",
|
||||
engineVersion: "14.9",
|
||||
identifier: stack, // In RDS
|
||||
dbName: "postgres", // When connected to the DB host
|
||||
instanceClass: dbInstanceClass,
|
||||
port: 5432,
|
||||
publiclyAccessible: dbIsPublic,
|
||||
skipFinalSnapshot: true,
|
||||
storageType: "gp2",
|
||||
username: dbUsername,
|
||||
password: dbPassword,
|
||||
dbSubnetGroupName: rdsSubnets.id,
|
||||
vpcSecurityGroupIds: [rdsSecurityGroup.id],
|
||||
backupRetentionPeriod: 0,
|
||||
applyImmediately: true,
|
||||
allowMajorVersionUpgrade: true,
|
||||
autoMinorVersionUpgrade: true
|
||||
}, {
|
||||
protect: dbIsPublic === false,
|
||||
deleteBeforeReplace: true
|
||||
});
|
||||
|
||||
containerEnvironmentVars.push(
|
||||
{
|
||||
name: "AP_POSTGRES_DATABASE",
|
||||
value: db.dbName
|
||||
},
|
||||
{
|
||||
name: "AP_POSTGRES_HOST",
|
||||
value: db.address
|
||||
},
|
||||
{
|
||||
name: "AP_POSTGRES_PORT",
|
||||
value: pulumi.interpolate`${db.port}`
|
||||
},
|
||||
{
|
||||
name: "AP_POSTGRES_USERNAME",
|
||||
value: db.username
|
||||
},
|
||||
{
|
||||
name: "AP_POSTGRES_PASSWORD",
|
||||
value: config.requireSecret("dbPassword")
|
||||
},
|
||||
{
|
||||
name: "AP_POSTGRES_USE_SSL",
|
||||
value: "false"
|
||||
});
|
||||
|
||||
} else {
|
||||
containerEnvironmentVars.push(
|
||||
{
|
||||
name: "AP_DB_TYPE",
|
||||
value: "SQLITE3"
|
||||
});
|
||||
}
|
||||
|
||||
if (useRedis) {
|
||||
|
||||
const redisCluster = new aws.elasticache.Cluster(`${stack}-redis-cluster`, {
|
||||
clusterId: `${stack}-redis-cluster`,
|
||||
engine: "redis",
|
||||
engineVersion: '7.0',
|
||||
nodeType: redisNodeType,
|
||||
numCacheNodes: 1,
|
||||
parameterGroupName: "default.redis7",
|
||||
port: 6379,
|
||||
subnetGroupName: new aws.elasticache.SubnetGroup(`${stack}-redis-subnet-group`, {
|
||||
name: `${stack}-redis-subnet-group`,
|
||||
subnetIds: vpc.privateSubnetIds
|
||||
}).id,
|
||||
securityGroupIds: [
|
||||
new aws.ec2.SecurityGroup(`${stack}-redis-sg`, {
|
||||
name: `${stack}-redis-sg`,
|
||||
vpcId: vpc.vpcId,
|
||||
ingress: [{
|
||||
protocol: "tcp",
|
||||
fromPort: 6379, // The standard port for Redis
|
||||
toPort: 6379,
|
||||
securityGroups: [fargateSecGroup.id]
|
||||
}],
|
||||
egress: [{
|
||||
protocol: "-1",
|
||||
fromPort: 0,
|
||||
toPort: 0,
|
||||
cidrBlocks: ["0.0.0.0/0"]
|
||||
}]
|
||||
}).id
|
||||
]
|
||||
});
|
||||
|
||||
const redisUrl = pulumi.interpolate`${redisCluster.cacheNodes[0].address}:${redisCluster.cacheNodes[0].port}`;
|
||||
containerEnvironmentVars.push(
|
||||
{
|
||||
name: "AP_REDIS_URL",
|
||||
value: redisUrl
|
||||
});
|
||||
|
||||
} else {
|
||||
containerEnvironmentVars.push(
|
||||
{
|
||||
name: "AP_QUEUE_MODE",
|
||||
value: "MEMORY"
|
||||
});
|
||||
}
|
||||
|
||||
let alb: ApplicationLoadBalancer;
|
||||
// Export the URL so we can easily access it.
|
||||
let frontendUrl;
|
||||
|
||||
if (subDomain && domain) {
|
||||
const fullDomain = `${subDomain}.${domain}`;
|
||||
|
||||
const exampleCertificate = new aws.acm.Certificate(`${stack}-cert`, {
|
||||
domainName: fullDomain,
|
||||
validationMethod: "DNS",
|
||||
});
|
||||
|
||||
const hostedZoneId = aws.route53.getZone({ name: domain }, { async: true }).then(zone => zone.zoneId);
|
||||
|
||||
// DNS records to verify SSL Certificate
|
||||
const certificateValidationDomain = new aws.route53.Record(`${fullDomain}-validation`, {
|
||||
name: exampleCertificate.domainValidationOptions[0].resourceRecordName,
|
||||
zoneId: hostedZoneId,
|
||||
type: exampleCertificate.domainValidationOptions[0].resourceRecordType,
|
||||
records: [exampleCertificate.domainValidationOptions[0].resourceRecordValue],
|
||||
ttl: 600,
|
||||
});
|
||||
|
||||
const certificateValidation = new aws.acm.CertificateValidation(`${fullDomain}-cert-validation`, {
|
||||
certificateArn: exampleCertificate.arn,
|
||||
validationRecordFqdns: [certificateValidationDomain.fqdn],
|
||||
});
|
||||
|
||||
// Creates an ALB associated with our custom VPC.
|
||||
alb = new awsx.lb.ApplicationLoadBalancer(`${stack}-alb`, {
|
||||
securityGroups: [albSecGroup.id],
|
||||
name: `${stack}-alb`,
|
||||
subnetIds: vpc.publicSubnetIds,
|
||||
listeners: [{
|
||||
port: 80, // port on the docker container
|
||||
protocol: "HTTP",
|
||||
defaultActions: [{
|
||||
type: "redirect",
|
||||
redirect: {
|
||||
protocol: "HTTPS",
|
||||
port: "443",
|
||||
statusCode: "HTTP_301",
|
||||
},
|
||||
}]
|
||||
},
|
||||
{
|
||||
protocol: "HTTPS",
|
||||
port: 443,
|
||||
certificateArn: certificateValidation.certificateArn
|
||||
}],
|
||||
defaultTargetGroup: {
|
||||
name: `${stack}-alb-tg`,
|
||||
port: 80 // port on the docker container ,
|
||||
}
|
||||
});
|
||||
|
||||
// Create a DNS record for the load balancer
|
||||
const albDomain = new aws.route53.Record(fullDomain, {
|
||||
name: fullDomain,
|
||||
zoneId: hostedZoneId,
|
||||
type: "CNAME",
|
||||
records: [alb.loadBalancer.dnsName],
|
||||
ttl: 600,
|
||||
});
|
||||
|
||||
frontendUrl = pulumi.interpolate`https://${subDomain}.${domain}`;
|
||||
|
||||
} else {
|
||||
|
||||
// Creates an ALB associated with our custom VPC.
|
||||
alb = new awsx.lb.ApplicationLoadBalancer(`${stack}-alb`, {
|
||||
securityGroups: [albSecGroup.id],
|
||||
name: `${stack}-alb`,
|
||||
subnetIds: vpc.publicSubnetIds,
|
||||
listeners: [{
|
||||
port: 80, // exposed port from the docker file
|
||||
protocol: "HTTP"
|
||||
}],
|
||||
defaultTargetGroup: {
|
||||
name: `${stack}-alb-tg`,
|
||||
port: 80, // port on the docker container
|
||||
protocol: "HTTP"
|
||||
}
|
||||
});
|
||||
|
||||
frontendUrl = pulumi.interpolate`http://${alb.loadBalancer.dnsName}`;
|
||||
}
|
||||
|
||||
const environmentVariables = [
|
||||
...containerEnvironmentVars,
|
||||
{
|
||||
name: "AP_ENGINE_EXECUTABLE_PATH",
|
||||
value: "dist/packages/engine/main.js"
|
||||
},
|
||||
{
|
||||
name: "AP_ENCRYPTION_KEY",
|
||||
value: apEncryptionKey
|
||||
},
|
||||
{
|
||||
name: "AP_JWT_SECRET",
|
||||
value: apJwtSecret
|
||||
},
|
||||
{
|
||||
name: "AP_ENVIRONMENT",
|
||||
value: "prod"
|
||||
},
|
||||
{
|
||||
name: "AP_FRONTEND_URL",
|
||||
value: frontendUrl
|
||||
},
|
||||
{
|
||||
name: "AP_TRIGGER_DEFAULT_POLL_INTERVAL",
|
||||
value: "5"
|
||||
},
|
||||
{
|
||||
name: "AP_EXECUTION_MODE",
|
||||
value: "UNSANDBOXED"
|
||||
},
|
||||
{
|
||||
name: "AP_REDIS_USE_SSL",
|
||||
value: "false"
|
||||
},
|
||||
{
|
||||
name: "AP_SANDBOX_RUN_TIME_SECONDS",
|
||||
value: "600"
|
||||
},
|
||||
{
|
||||
name: "AP_TELEMETRY_ENABLED",
|
||||
value: "true"
|
||||
},
|
||||
{
|
||||
name: "AP_TEMPLATES_SOURCE_URL",
|
||||
value: "https://cloud.activepieces.com/api/v1/templates"
|
||||
}
|
||||
];
|
||||
|
||||
const fargateService = new awsx.ecs.FargateService(`${stack}-fg`, {
|
||||
name: `${stack}-fg`,
|
||||
cluster: (new aws.ecs.Cluster(`${stack}-cluster`, {
|
||||
name: `${stack}-cluster`
|
||||
})).arn,
|
||||
networkConfiguration: {
|
||||
subnets: vpc.publicSubnetIds,
|
||||
securityGroups: [fargateSecGroup.id],
|
||||
assignPublicIp: true
|
||||
},
|
||||
desiredCount: containerInstances,
|
||||
taskDefinitionArgs: {
|
||||
family: `${stack}-fg-task-definition`,
|
||||
container: {
|
||||
name: "activepieces",
|
||||
image: imageName,
|
||||
cpu: containerCpu,
|
||||
memory: containerMemory,
|
||||
portMappings: [{
|
||||
targetGroup: alb.defaultTargetGroup,
|
||||
}],
|
||||
environment: environmentVariables
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pulumi.log.info("Finished running Pulumi");
|
||||
|
||||
export const _ = {
|
||||
activePiecesUrl: frontendUrl,
|
||||
activepiecesEnv: environmentVariables
|
||||
};
|
||||
Reference in New Issue
Block a user