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:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,5 @@
# Ignore downloaded Helm chart dependencies
charts/*.tgz
# Ignore Chart.lock file variations
Chart.lock

View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@@ -0,0 +1,39 @@
apiVersion: v2
name: activepieces
description: A Helm chart for Activepieces
icon: https://cdn.activepieces.com/brand/logo.svg
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.3.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "0.71.2"
dependencies:
- name: kubernetes-secret-generator
version: "3.4.1"
repository: "https://helm.mittwald.de"
condition: kubernetes-secret-generator.enabled
- name: postgresql
version: "11.7.2"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
- name: redis
version: "17.4.2"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled

View File

@@ -0,0 +1 @@
Activepieces has been deployed successfully!

View File

@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "activepieces.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "activepieces.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "activepieces.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "activepieces.labels" -}}
helm.sh/chart: {{ include "activepieces.chart" . }}
{{ include "activepieces.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "activepieces.selectorLabels" -}}
app.kubernetes.io/name: {{ include "activepieces.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "activepieces.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "activepieces.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,469 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "activepieces.fullname" . }}
labels:
{{- include "activepieces.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "activepieces.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "activepieces.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "activepieces.serviceAccountName" . }}
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
{{- with .Values.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.container.port }}
protocol: TCP
env:
# Core Activepieces configuration
{{- if .Values.activepieces.frontendUrl }}
- name: AP_FRONTEND_URL
value: {{ .Values.activepieces.frontendUrl | quote }}
{{- end }}
{{- if .Values.activepieces.apiKey }}
- name: AP_API_KEY
value: {{ .Values.activepieces.apiKey | quote }}
{{- end }}
{{- if .Values.activepieces.clientRealIpHeader }}
- name: AP_CLIENT_REAL_IP_HEADER
value: {{ .Values.activepieces.clientRealIpHeader | quote }}
{{- end }}
{{- if .Values.activepieces.configPath }}
- name: AP_CONFIG_PATH
value: {{ .Values.activepieces.configPath | quote }}
{{- end }}
{{- if .Values.activepieces.devPieces }}
- name: AP_DEV_PIECES
value: {{ .Values.activepieces.devPieces | quote }}
{{- end }}
{{- if .Values.activepieces.internalUrl }}
- name: AP_INTERNAL_URL
value: {{ .Values.activepieces.internalUrl | quote }}
{{- end }}
{{- if .Values.activepieces.maxConcurrentJobsPerProject }}
- name: AP_MAX_CONCURRENT_JOBS_PER_PROJECT
value: {{ .Values.activepieces.maxConcurrentJobsPerProject | quote }}
{{- end }}
{{- if .Values.activepieces.perplexityBaseUrl }}
- name: AP_PERPLEXITY_BASE_URL
value: {{ .Values.activepieces.perplexityBaseUrl | quote }}
{{- end }}
{{- if .Values.activepieces.piecesSyncMode }}
- name: AP_PIECES_SYNC_MODE
value: {{ .Values.activepieces.piecesSyncMode | quote }}
{{- end }}
{{- if .Values.activepieces.featurebaseApiKey }}
- name: AP_FEATUREBASE_API_KEY
value: {{ .Values.activepieces.featurebaseApiKey | quote }}
{{- end }}
{{- if .Values.activepieces.enableFlowOnPublish }}
- name: AP_ENABLE_FLOW_ON_PUBLISH
value: {{ .Values.activepieces.enableFlowOnPublish | quote }}
{{- end }}
{{- if .Values.activepieces.issueArchiveDays }}
- name: AP_ISSUE_ARCHIVE_DAYS
value: {{ .Values.activepieces.issueArchiveDays | quote }}
{{- end }}
{{- if .Values.activepieces.pausedFlowTimeoutDays }}
- name: AP_PAUSED_FLOW_TIMEOUT_DAYS
value: {{ .Values.activepieces.pausedFlowTimeoutDays | quote }}
{{- end }}
{{- if .Values.activepieces.logLevel }}
- name: AP_LOG_LEVEL
value: {{ .Values.activepieces.logLevel | quote }}
{{- end }}
{{- if .Values.activepieces.logPretty }}
- name: AP_LOG_PRETTY
value: {{ .Values.activepieces.logPretty | quote }}
{{- end }}
{{- if .Values.activepieces.appWebhookSecrets }}
- name: AP_APP_WEBHOOK_SECRETS
value: {{ .Values.activepieces.appWebhookSecrets | quote }}
{{- end }}
{{- if .Values.activepieces.maxFileSizeMb }}
- name: AP_MAX_FILE_SIZE_MB
value: {{ .Values.activepieces.maxFileSizeMb | quote }}
{{- end }}
{{- if .Values.activepieces.sandboxMemoryLimit }}
- name: AP_SANDBOX_MEMORY_LIMIT
value: {{ .Values.activepieces.sandboxMemoryLimit | quote }}
{{- end }}
{{- if .Values.activepieces.sandboxPropagatedEnvVars }}
- name: AP_SANDBOX_PROPAGATED_ENV_VARS
value: {{ .Values.activepieces.sandboxPropagatedEnvVars | quote }}
{{- end }}
{{- if .Values.activepieces.piecesSource }}
- name: AP_PIECES_SOURCE
value: {{ .Values.activepieces.piecesSource | quote }}
{{- end }}
{{- if .Values.activepieces.apiRateLimiting.authn.enabled }}
- name: AP_API_RATE_LIMIT_AUTHN_ENABLED
value: {{ .Values.activepieces.apiRateLimiting.authn.enabled | quote }}
{{- end }}
{{- if .Values.activepieces.apiRateLimiting.authn.max }}
- name: AP_API_RATE_LIMIT_AUTHN_MAX
value: {{ .Values.activepieces.apiRateLimiting.authn.max | quote }}
{{- end }}
{{- if .Values.activepieces.apiRateLimiting.authn.window }}
- name: AP_API_RATE_LIMIT_AUTHN_WINDOW
value: {{ .Values.activepieces.apiRateLimiting.authn.window | quote }}
{{- end }}
{{- if .Values.activepieces.projectRateLimiter.enabled }}
- name: AP_PROJECT_RATE_LIMITER_ENABLED
value: {{ .Values.activepieces.projectRateLimiter.enabled | quote }}
{{- end }}
- name: AP_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: {{ include "activepieces.fullname" . }}-secrets
key: encryption-key
- name: AP_JWT_SECRET
valueFrom:
secretKeyRef:
name: {{ include "activepieces.fullname" . }}-jwt-secret
key: jwt-secret
{{- if .Values.activepieces.environment }}
- name: AP_ENVIRONMENT
value: {{ .Values.activepieces.environment | quote }}
{{- end }}
{{- if .Values.activepieces.edition }}
- name: AP_EDITION
value: {{ .Values.activepieces.edition | quote }}
{{- end }}
{{- if .Values.activepieces.executionMode }}
- name: AP_EXECUTION_MODE
value: {{ .Values.activepieces.executionMode | quote }}
{{- end }}
{{- if .Values.activepieces.telemetryEnabled }}
- name: AP_TELEMETRY_ENABLED
value: {{ .Values.activepieces.telemetryEnabled | quote }}
{{- end }}
{{- if .Values.activepieces.templatesSourceUrl }}
- name: AP_TEMPLATES_SOURCE_URL
value: {{ .Values.activepieces.templatesSourceUrl | quote }}
{{- end }}
{{- if .Values.activepieces.flowWorkerConcurrency }}
- name: AP_FLOW_WORKER_CONCURRENCY
value: {{ .Values.activepieces.flowWorkerConcurrency | quote }}
{{- end }}
{{- if .Values.activepieces.scheduledWorkerConcurrency }}
- name: AP_SCHEDULED_WORKER_CONCURRENCY
value: {{ .Values.activepieces.scheduledWorkerConcurrency | quote }}
{{- end }}
{{- if .Values.activepieces.triggerDefaultPollInterval }}
- name: AP_TRIGGER_DEFAULT_POLL_INTERVAL
value: {{ .Values.activepieces.triggerDefaultPollInterval | quote }}
{{- end }}
{{- if .Values.activepieces.triggerTimeoutSeconds }}
- name: AP_TRIGGER_TIMEOUT_SECONDS
value: {{ .Values.activepieces.triggerTimeoutSeconds | quote }}
{{- end }}
{{- if .Values.activepieces.flowTimeoutSeconds }}
- name: AP_FLOW_TIMEOUT_SECONDS
value: {{ .Values.activepieces.flowTimeoutSeconds | quote }}
{{- end }}
{{- if .Values.activepieces.webhookTimeoutSeconds }}
- name: AP_WEBHOOK_TIMEOUT_SECONDS
value: {{ .Values.activepieces.webhookTimeoutSeconds | quote }}
{{- end }}
{{- if .Values.activepieces.executionDataRetentionDays }}
- name: AP_EXECUTION_DATA_RETENTION_DAYS
value: {{ .Values.activepieces.executionDataRetentionDays | quote }}
{{- end }}
{{- if or .Values.postgresql.enabled (or .Values.postgresql.host .Values.postgresql.url) }}
# PostgreSQL configuration (subchart or external)
- name: AP_DB_TYPE
value: "POSTGRES"
{{- if .Values.postgresql.auth.database }}
- name: AP_POSTGRES_DATABASE
value: {{ .Values.postgresql.auth.database | quote }}
{{- end }}
{{- if .Values.postgresql.host }}
- name: AP_POSTGRES_HOST
value: {{ .Values.postgresql.host | quote }}
{{- else if .Values.postgresql.enabled }}
- name: AP_POSTGRES_HOST
value: {{ printf "%s-postgresql" (include "activepieces.fullname" .) | quote }}
{{- end }}
{{- if .Values.postgresql.port }}
- name: AP_POSTGRES_PORT
value: {{ .Values.postgresql.port | quote }}
{{- end }}
{{- if .Values.postgresql.auth.username }}
- name: AP_POSTGRES_USERNAME
value: {{ .Values.postgresql.auth.username | quote }}
{{- end }}
{{- if .Values.postgresql.auth.externalSecret }}
- name: AP_POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.auth.externalSecret.name | quote }}
key: {{ .Values.postgresql.auth.externalSecret.key | quote }}
{{- else if .Values.postgresql.auth.password }}
- name: AP_POSTGRES_PASSWORD
value: {{ .Values.postgresql.auth.password | quote }}
{{- else if .Values.postgresql.enabled }}
- name: AP_POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "activepieces.fullname" . }}-postgresql
key: postgres-password
{{- end }}
{{- if .Values.postgresql.useSSL }}
- name: AP_POSTGRES_USE_SSL
value: {{ .Values.postgresql.useSSL | quote }}
{{- end }}
{{- if .Values.postgresql.sslCa }}
- name: AP_POSTGRES_SSL_CA
value: {{ .Values.postgresql.sslCa | quote }}
{{- end }}
{{- if .Values.postgresql.url }}
- name: AP_POSTGRES_URL
value: {{ .Values.postgresql.url | quote }}
{{- end }}
{{- else }}
# SQLite configuration (fallback)
- name: AP_DB_TYPE
value: "SQLITE3"
{{- end }}
{{- if or .Values.redis.enabled (or .Values.redis.host .Values.redis.url) }}
# Redis configuration (subchart or external)
{{- if .Values.redis.host }}
- name: AP_REDIS_HOST
value: {{ .Values.redis.host | quote }}
{{- else if .Values.redis.enabled }}
- name: AP_REDIS_HOST
value: {{ printf "%s-redis-master" (include "activepieces.fullname" .) | quote }}
{{- end }}
{{- if .Values.redis.port }}
- name: AP_REDIS_PORT
value: {{ .Values.redis.port | quote }}
{{- end }}
{{- if .Values.redis.useSSL }}
- name: AP_REDIS_USE_SSL
value: {{ .Values.redis.useSSL | quote }}
{{- end }}
{{- if .Values.redis.sslCaFile }}
- name: AP_REDIS_SSL_CA_FILE
value: {{ .Values.redis.sslCaFile | quote }}
{{- end }}
{{- if .Values.redis.db }}
- name: AP_REDIS_DB
value: {{ .Values.redis.db | quote }}
{{- end }}
{{- if .Values.redis.user }}
- name: AP_REDIS_USER
value: {{ .Values.redis.user | quote }}
{{- end }}
{{- if .Values.redis.url }}
- name: AP_REDIS_URL
value: {{ .Values.redis.url | quote }}
{{- end }}
{{- if eq .Values.redis.type "sentinel" }}
- name: AP_REDIS_TYPE
value: "SENTINEL"
{{- else }}
# Default to DEFAULT for standalone Redis
- name: AP_REDIS_TYPE
value: "DEFAULT"
{{- end }}
{{- if .Values.redis.sentinel.role }}
- name: AP_REDIS_SENTINEL_ROLE
value: {{ .Values.redis.sentinel.role | quote }}
{{- end }}
{{- if .Values.redis.sentinel.hosts }}
- name: AP_REDIS_SENTINEL_HOSTS
value: {{ .Values.redis.sentinel.hosts | quote }}
{{- end }}
{{- if .Values.redis.sentinel.name }}
- name: AP_REDIS_SENTINEL_NAME
value: {{ .Values.redis.sentinel.name | quote }}
{{- end }}
{{- if .Values.redis.failedJob.retentionDays }}
- name: AP_REDIS_FAILED_JOB_RETENTION_DAYS
value: {{ .Values.redis.failedJob.retentionDays | quote }}
{{- end }}
{{- if .Values.redis.failedJob.retentionMaxCount }}
- name: AP_REDIS_FAILED_JOB_RETENTION_MAX_COUNT
value: {{ .Values.redis.failedJob.retentionMaxCount | quote }}
{{- end }}
{{- if .Values.redis.auth.externalSecret }}
- name: AP_REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.redis.auth.externalSecret.name | quote }}
key: {{ .Values.redis.auth.externalSecret.key | quote }}
{{- else if .Values.redis.auth.password }}
- name: AP_REDIS_PASSWORD
value: {{ .Values.redis.auth.password | quote }}
{{- else if and .Values.redis.enabled .Values.redis.auth.enabled }}
- name: AP_REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-redis
key: redis-password
{{- end }}
{{- else }}
# Memory queue (fallback)
- name: AP_REDIS_TYPE
value: "MEMORY"
{{- end }}
{{- if .Values.smtp.enabled }}
# SMTP configuration
{{- if .Values.smtp.host }}
- name: AP_SMTP_HOST
value: {{ .Values.smtp.host | quote }}
{{- end }}
{{- if .Values.smtp.port }}
- name: AP_SMTP_PORT
value: {{ .Values.smtp.port | quote }}
{{- end }}
{{- if .Values.smtp.username }}
- name: AP_SMTP_USERNAME
value: {{ .Values.smtp.username | quote }}
{{- end }}
{{- if .Values.smtp.password }}
- name: AP_SMTP_PASSWORD
value: {{ .Values.smtp.password | quote }}
{{- end }}
{{- if .Values.smtp.senderEmail }}
- name: AP_SMTP_SENDER_EMAIL
value: {{ .Values.smtp.senderEmail | quote }}
{{- end }}
{{- if .Values.smtp.senderName }}
- name: AP_SMTP_SENDER_NAME
value: {{ .Values.smtp.senderName | quote }}
{{- end }}
{{- end }}
{{- if .Values.s3.enabled }}
# S3 configuration
{{- if .Values.s3.accessKeyId }}
- name: AP_S3_ACCESS_KEY_ID
value: {{ .Values.s3.accessKeyId | quote }}
{{- end }}
{{- if .Values.s3.secretAccessKey }}
- name: AP_S3_SECRET_ACCESS_KEY
value: {{ .Values.s3.secretAccessKey | quote }}
{{- end }}
{{- if .Values.s3.bucket }}
- name: AP_S3_BUCKET
value: {{ .Values.s3.bucket | quote }}
{{- end }}
{{- if .Values.s3.endpoint }}
- name: AP_S3_ENDPOINT
value: {{ .Values.s3.endpoint | quote }}
{{- end }}
{{- if .Values.s3.region }}
- name: AP_S3_REGION
value: {{ .Values.s3.region | quote }}
{{- end }}
{{- if .Values.s3.useSignedUrls }}
- name: AP_S3_USE_SIGNED_URLS
value: {{ .Values.s3.useSignedUrls | quote }}
{{- end }}
{{- if .Values.s3.useIrsa }}
- name: AP_S3_USE_IRSA
value: {{ .Values.s3.useIrsa | quote }}
{{- end }}
{{- end }}
{{- if .Values.queueUi.enabled }}
# Queue UI configuration
- name: AP_QUEUE_UI_ENABLED
value: {{ .Values.queueUi.enabled | quote }}
{{- if .Values.queueUi.username }}
- name: AP_QUEUE_UI_USERNAME
value: {{ .Values.queueUi.username | quote }}
{{- end }}
{{- if .Values.queueUi.password }}
- name: AP_QUEUE_UI_PASSWORD
value: {{ .Values.queueUi.password | quote }}
{{- end }}
{{- end }}
{{- if .Values.activepieces.engineExecutablePath }}
- name: AP_ENGINE_EXECUTABLE_PATH
value: {{ .Values.activepieces.engineExecutablePath | quote }}
{{- end }}
# OpenTelemetry configuration
{{- if .Values.activepieces.otel.enabled }}
- name: AP_OTEL_ENABLED
value: {{ .Values.activepieces.otel.enabled | quote }}
{{- end }}
{{- if .Values.activepieces.otel.exporterOtlpEndpoint }}
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: {{ .Values.activepieces.otel.exporterOtlpEndpoint | quote }}
{{- end }}
{{- if .Values.activepieces.otel.exporterOtlpHeaders }}
- name: OTEL_EXPORTER_OTLP_HEADERS
value: {{ .Values.activepieces.otel.exporterOtlpHeaders | quote }}
{{- end }}
{{- with .Values.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- if .Values.persistence.enabled }}
- name: cache
mountPath: {{ .Values.persistence.mountPath | quote }}
{{- end }}
{{- with .Values.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumes:
{{- if .Values.persistence.enabled }}
- name: cache
persistentVolumeClaim:
claimName: {{ include "activepieces.fullname" . }}-cache
{{- end }}
{{- with .Values.volumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,32 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "activepieces.fullname" . }}
labels:
{{- include "activepieces.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "activepieces.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,43 @@
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "activepieces.fullname" . }}
labels:
{{- include "activepieces.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- with .pathType }}
pathType: {{ . }}
{{- end }}
backend:
service:
name: {{ include "activepieces.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,14 @@
{{- if .Values.persistence.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "activepieces.fullname" . }}-cache
labels:
{{- include "activepieces.labels" . | nindent 4 }}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- end }}

View File

@@ -0,0 +1,24 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ include "activepieces.fullname" . }}-secrets
labels:
{{- include "activepieces.labels" . | nindent 4 }}
annotations:
secret-generator.v1.mittwald.de/autogenerate: encryption-key
secret-generator.v1.mittwald.de/encoding: hex
secret-generator.v1.mittwald.de/length: "32"
type: Opaque
data: {}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ include "activepieces.fullname" . }}-jwt-secret
labels:
{{- include "activepieces.labels" . | nindent 4 }}
annotations:
secret-generator.v1.mittwald.de/autogenerate: jwt-secret
secret-generator.v1.mittwald.de/length: "64"
type: Opaque
data: {}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "activepieces.fullname" . }}
labels:
{{- include "activepieces.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "activepieces.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "activepieces.serviceAccountName" . }}
labels:
{{- include "activepieces.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "activepieces.fullname" . }}-test-connection"
labels:
{{- include "activepieces.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "activepieces.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

View File

@@ -0,0 +1,324 @@
# Default values for activepieces.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
replicaCount: 1
# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
image:
repository: ghcr.io/activepieces/activepieces
# This sets the pull policy for images.
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets: []
# This is to override the chart name.
nameOverride: ""
fullnameOverride: ""
# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
serviceAccount:
# Specifies whether a service account should be created
create: true
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
# This is for setting Kubernetes Annotations to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
podAnnotations: {}
# This is for setting Kubernetes Labels to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
podLabels: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
# Container configuration
container:
port: 80
# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/
service:
# This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
type: ClusterIP
# This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports
port: 80
# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/
ingress:
enabled: false
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
livenessProbe:
httpGet:
path: /v1/health
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /v1/health
port: http
initialDelaySeconds: 5
periodSeconds: 5
# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
persistence:
enabled: true
size: 2Gi
mountPath: "/usr/src/app/cache"
volumes: []
volumeMounts: []
nodeSelector: {}
tolerations: []
affinity: {}
activepieces:
# ============================================================================
# REQUIRED: Core configuration that must be set for production deployment
# ============================================================================
# The URL where Activepieces will be accessible (e.g., https://activepieces.yourdomain.com)
frontendUrl: "http://localhost:4200"
# Edition: ce (Community) or ee (Enterprise)
# Community Edition (ce) is the default open-source version
edition: "ce"
# Execution mode for flows - see: https://www.activepieces.com/docs/install/architecture/workers
# Options: SANDBOX_CODE_ONLY, UNSANDBOXED
executionMode: "SANDBOX_CODE_ONLY"
# Environment: prod or dev
environment: "prod"
# ============================================================================
# OPTIONAL: Additional configuration (defaults are usually sufficient)
# ============================================================================
telemetryEnabled: true
templatesSourceUrl: "https://cloud.activepieces.com/api/v1/templates"
flowWorkerConcurrency: ""
scheduledWorkerConcurrency: ""
triggerDefaultPollInterval: ""
triggerTimeoutSeconds: ""
flowTimeoutSeconds: ""
webhookTimeoutSeconds: ""
executionDataRetentionDays: ""
issueArchiveDays: ""
pausedFlowTimeoutDays: ""
logLevel: "info"
logPretty: false
apiRateLimiting:
authn:
enabled: false
max: 100
window: 60
projectRateLimiter:
enabled: false
piecesSource: ""
devPieces: false
piecesSyncMode: ""
# ============================================================================
# OPTIONAL: Advanced configuration (only set if needed)
# ============================================================================
# Networking & API
clientRealIpHeader: "" # Header to extract real client IP (e.g., X-Forwarded-For)
apiKey: "" # API key for platform authentication
internalUrl: "" # Internal URL for service communication
# Performance & Limits
maxConcurrentJobsPerProject: "" # Limit concurrent jobs per project
maxFileSizeMb: "" # Maximum file upload size in MB
sandboxMemoryLimit: "" # Memory limit for sandbox execution
sandboxPropagatedEnvVars: "" # Environment variables to pass to sandbox
# Integrations
perplexityBaseUrl: "" # Custom Perplexity AI base URL
featurebaseApiKey: "" # Featurebase API key for feedback
appWebhookSecrets: "" # Secrets for app webhooks
# UI & Behavior
showChangelog: true # Show changelog to users
enableFlowOnPublish: true # Auto-enable flows when published
# System Paths
configPath: "" # Custom config file path
engineExecutablePath: "dist/packages/engine/main.js" # Engine executable path
# OpenTelemetry Configuration (for observability)
otel:
enabled: false
exporterOtlpEndpoint: ""
exporterOtlpHeaders: ""
# ============================================================================
# Database Configuration
# PostgreSQL is deployed by default using the Bitnami subchart
# ============================================================================
postgresql:
# Set to true to deploy PostgreSQL subchart (Bitnami)
# Set to false and provide host/url to use external PostgreSQL
enabled: true
# External PostgreSQL host (required if enabled=false and using external PostgreSQL)
host: ""
# PostgreSQL port
port: 5432
# Enable SSL for PostgreSQL connection
useSSL: false
# PostgreSQL connection URL (alternative to host/port/auth)
# If provided, host/port/auth will be ignored
url: ""
# SSL CA certificate for PostgreSQL (if useSSL is true)
sslCa: ""
auth:
database: "activepieces"
username: "postgres"
# Password for PostgreSQL
# - If using subchart (enabled=true), password is automatically generated if not provided
# - If using external PostgreSQL, provide password here or use externalSecret
password: ""
# External secret reference for password (alternative to password field)
# Use this when password is stored in a Kubernetes secret (e.g., External Secrets Operator)
# externalSecret:
# name: "postgresql-credentials"
# key: "password"
primary:
persistence:
enabled: true
# ============================================================================
# Queue Configuration
# Redis is deployed by default using the Bitnami subchart
# ============================================================================
redis:
# Set to true to deploy Redis subchart (Bitnami)
# Set to false and provide host/url to use external Redis
enabled: true
# External Redis host (required if enabled=false and using external Redis)
host: ""
# Redis port
port: 6379
# Enable SSL for Redis connection
useSSL: false
# SSL CA certificate file path for Redis (if useSSL is true)
sslCaFile: ""
# Redis database number
db: 0
# Redis connection URL (alternative to host/port/auth)
# If provided, host/port/auth will be ignored
url: ""
# Redis type: "standalone" or "sentinel"
type: "standalone"
# Redis username (for Redis 6+ ACL)
user: ""
sentinel:
name: ""
hosts: ""
role: ""
failedJob:
retentionDays: 7
retentionMaxCount: 100
auth:
# Set to true if Redis requires authentication
# - If using subchart (enabled=true), password is automatically generated if not provided
# - If using external Redis, provide password or use externalSecret
enabled: true
# Password for Redis
password: ""
# External secret reference for password (alternative to password field)
# Use this when password is stored in a Kubernetes secret (e.g., External Secrets Operator)
# externalSecret:
# name: "redis-credentials"
# key: "password"
master:
persistence:
enabled: true
# ============================================================================
# OPTIONAL: Email Configuration
# Enable SMTP to send email notifications
# ============================================================================
smtp:
enabled: false # Set to true and configure below to enable emails
host: ""
port: 587
username: ""
password: ""
senderEmail: ""
senderName: ""
# ============================================================================
# OPTIONAL: S3 Storage Configuration
# Enable S3 for file storage (alternative to local storage)
# ============================================================================
s3:
enabled: false # Set to true and configure below to use S3
accessKeyId: ""
secretAccessKey: ""
bucket: ""
endpoint: "" # For S3-compatible services like MinIO
region: ""
useSignedUrls: false
useIrsa: false # Use IAM Roles for Service Accounts (EKS)
# ============================================================================
# OPTIONAL: Queue UI Configuration
# Enable BullMQ Board for monitoring job queues
# ============================================================================
queueUi:
enabled: false # Set to true and configure credentials below
username: ""
password: ""

View File

@@ -0,0 +1,33 @@
/.pulumi/
/.vscode/
/.vs/
bin/
build/
node_modules/
*.pyc
.Python
venv/
include/
lib/
yarn.lock
package-lock.json
# https://www.pulumi.com/blog/iac-recommended-practices-developer-stacks-git-branches/#using-developer-stacks
# Pulumi.*.yaml
# Pulumi.*dev*.yaml
.idea/
.ionide/
*.iml
key.rsa*
obj/
vendor
Gopkg.lock
**/.DS_Store
**/ci-scripts
# Java app
.gradle/
.settings/
.project
.classpath
target/

View File

@@ -0,0 +1,22 @@
encryptionsalt: v1:wHCVNl3bj/g=:v1:mxogi9ZeBjIcxNZC:q+bjpLv9rnJnu8qq7xwKGLd/GAZOqA==
config:
activepieces:environment: "dev"
activepieces:apEncryptionKey:
activepieces:apJwtSecret:
activepieces:deployLocalBuild: "false"
activepieces:repoName:
activepieces:containerCpu: "256"
activepieces:containerMemory: "512"
activepieces:containerInstances: "1"
activepieces:usePostgres: "false"
activepieces:dbInstanceClass: "db.t3.small"
activepieces:dbUsername: "postgres"
activepieces:dbIsPublic: "false"
activepieces:dbPassword:
secure: v1:MXNSOcqZCp10X2PX:mU2iTrcETjdisk8FkD5yHLJYUxRei/9l
activepieces:addIpToPostgresSecurityGroup:
activepieces:useRedis: "false"
activepieces:redisNodeType: "cache.t3.small"
activepieces:domain:
activepieces:subDomain:
aws:region: "us-east-1"

View File

@@ -0,0 +1,19 @@
encryptionsalt: v1:icXg2cmIvSc=:v1:y8+4YhdMCPPDY26J:5cNYmimH353n8sjUDDc6srvcPgb+8Q==
config:
activepieces:environment: "prod"
activepieces:apEncryptionKey:
activepieces:apJwtSecret:
activepieces:deployLocalBuild: "true"
activepieces:repoName: "activepieces-prod-repo"
activepieces:containerCpu: "512"
activepieces:containerMemory: "1024"
activepieces:containerInstances: "1"
activepieces:usePostgres: "true"
activepieces:dbInstanceClass: "db.t3.small"
activepieces:dbIsPublic: "false"
activepieces:dbPassword:
secure: v1:MXNSOcqZCp10X2PX:mU2iTrcETjdisk8FkD5yHLJYUxRei/9l
activepieces:dbUsername: "postgres"
activepieces:useRedis: "true"
activepieces:redisNodeType: "cache.t3.small"
aws:region: "us-east-1"

View File

@@ -0,0 +1,56 @@
runtime: nodejs
name: activepieces
description: A Pulumi template to deploy Activepieces in a development or production configuration.
stack: activepieces-dev
template:
description: Deploy Activepieces into into an ECS Fargate instance & optionally add Postgres, Redis and a DNS registration with SSL.
config:
aws:region:
description: The AWS region to deploy into
default: us-west-2
environment:
description: Environment
default: prod
containerCpu:
description: The amount of CPU to allocate for the container
default: 256
containerMemory:
description: The amount of memory to allocate for the container
default: 512
containerInstances:
description: Number of running containers behind load balancer
default: 1
usePostgres:
description: Add Postgres for storage or use SQLite3 locally
default: true
dbIsPublic:
description: Should Db be publicly reachable. Ignored if usePostgres is false.
default: false
dbUsername:
description: Default username for the Postgres. Ignored if usePostgres is false
default: postgres
dbPassword:
description: Defaults to "postgres". Ignored if usePostgres is false
default: postgres
secret: true
dbInstanceClass:
description: The size of the RDS instance
default: db.t3.micro
useRedis:
description: Use a single node Redis cluster or in-memory
default: true
redisNodeType:
description: Node type for the Redis 7 cluster
default: cache.t3.micro
domain:
description: Optional - E.g. "yourdomain.com". Hosted zone must already exist in Route 53. Creates SSL cert
subDomain:
description: Optional - E.g. "activepieces". "domain" must be set
addIpToPostgresSecurityGroup:
description: Optional - An IP address to add to the allowed inbound traffic for the Postgres
apEncryptionKey:
description: Optional - Run 'openssl rand -hex 16' locally to generate or leave blank to auto-generate
secret: true
apJwtSecret:
description: Optional - Run 'openssl rand -hex 32' locally to generate or leave blank to auto-generate
secret: true

View File

@@ -0,0 +1,3 @@
# Getting Started
See instruction on https://www.activepieces.com/docs/install/options/aws

View File

@@ -0,0 +1,16 @@
import * as pulumi from "@pulumi/pulumi";
import { isTaggable } from "./taggable";
/**
* registerAutoTags registers a global stack transformation that merges a set
* of tags with whatever was also explicitly added to the resource definition.
*/
export function registerAutoTags(autoTags: Record<string, string>): void {
pulumi.runtime.registerStackTransformation((args) => {
if (isTaggable(args.type)) {
args.props["tags"] = { ...args.props["tags"], ...autoTags };
return { props: args.props, opts: args.opts };
}
return undefined;
});
}

View 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
};

View File

@@ -0,0 +1,13 @@
{
"name": "pulumi",
"main": "index.ts",
"devDependencies": {
"@types/node": "^18"
},
"dependencies": {
"@pulumi/pulumi": "^3.0.0",
"@pulumi/aws": "^6.0.0",
"@pulumi/awsx": "^2.20.0",
"@pulumi/docker": "^4.4.0"
}
}

View File

@@ -0,0 +1,237 @@
/**
* isTaggable returns true if the given resource type is an AWS resource that supports tags.
*/
export function isTaggable(t: string): boolean {
return (taggableResourceTypes.indexOf(t) !== -1);
}
// taggableResourceTypes is a list of known AWS type tokens that are taggable.
const taggableResourceTypes = [
"aws:accessanalyzer/analyzer:Analyzer",
"aws:acm/certificate:Certificate",
"aws:acmpca/certificateAuthority:CertificateAuthority",
"aws:alb/loadBalancer:LoadBalancer",
"aws:alb/targetGroup:TargetGroup",
"aws:apigateway/apiKey:ApiKey",
"aws:apigateway/clientCertificate:ClientCertificate",
"aws:apigateway/domainName:DomainName",
"aws:apigateway/restApi:RestApi",
"aws:apigateway/stage:Stage",
"aws:apigateway/usagePlan:UsagePlan",
"aws:apigateway/vpcLink:VpcLink",
"aws:applicationloadbalancing/loadBalancer:LoadBalancer",
"aws:applicationloadbalancing/targetGroup:TargetGroup",
"aws:appmesh/mesh:Mesh",
"aws:appmesh/route:Route",
"aws:appmesh/virtualNode:VirtualNode",
"aws:appmesh/virtualRouter:VirtualRouter",
"aws:appmesh/virtualService:VirtualService",
"aws:appsync/graphQLApi:GraphQLApi",
"aws:athena/workgroup:Workgroup",
"aws:autoscaling/group:Group",
"aws:backup/plan:Plan",
"aws:backup/vault:Vault",
"aws:cfg/aggregateAuthorization:AggregateAuthorization",
"aws:cfg/configurationAggregator:ConfigurationAggregator",
"aws:cfg/rule:Rule",
"aws:cloudformation/stack:Stack",
"aws:cloudformation/stackSet:StackSet",
"aws:cloudfront/distribution:Distribution",
"aws:cloudhsmv2/cluster:Cluster",
"aws:cloudtrail/trail:Trail",
"aws:cloudwatch/eventRule:EventRule",
"aws:cloudwatch/logGroup:LogGroup",
"aws:cloudwatch/metricAlarm:MetricAlarm",
"aws:codebuild/project:Project",
"aws:codecommit/repository:Repository",
"aws:codepipeline/pipeline:Pipeline",
"aws:codepipeline/webhook:Webhook",
"aws:codestarnotifications/notificationRule:NotificationRule",
"aws:cognito/identityPool:IdentityPool",
"aws:cognito/userPool:UserPool",
"aws:datapipeline/pipeline:Pipeline",
"aws:datasync/agent:Agent",
"aws:datasync/efsLocation:EfsLocation",
"aws:datasync/locationSmb:LocationSmb",
"aws:datasync/nfsLocation:NfsLocation",
"aws:datasync/s3Location:S3Location",
"aws:datasync/task:Task",
"aws:dax/cluster:Cluster",
"aws:directconnect/connection:Connection",
"aws:directconnect/hostedPrivateVirtualInterfaceAccepter:HostedPrivateVirtualInterfaceAccepter",
"aws:directconnect/hostedPublicVirtualInterfaceAccepter:HostedPublicVirtualInterfaceAccepter",
"aws:directconnect/hostedTransitVirtualInterfaceAcceptor:HostedTransitVirtualInterfaceAcceptor",
"aws:directconnect/linkAggregationGroup:LinkAggregationGroup",
"aws:directconnect/privateVirtualInterface:PrivateVirtualInterface",
"aws:directconnect/publicVirtualInterface:PublicVirtualInterface",
"aws:directconnect/transitVirtualInterface:TransitVirtualInterface",
"aws:directoryservice/directory:Directory",
"aws:dlm/lifecyclePolicy:LifecyclePolicy",
"aws:dms/endpoint:Endpoint",
"aws:dms/replicationInstance:ReplicationInstance",
"aws:dms/replicationSubnetGroup:ReplicationSubnetGroup",
"aws:dms/replicationTask:ReplicationTask",
"aws:docdb/cluster:Cluster",
"aws:docdb/clusterInstance:ClusterInstance",
"aws:docdb/clusterParameterGroup:ClusterParameterGroup",
"aws:docdb/subnetGroup:SubnetGroup",
"aws:dynamodb/table:Table",
"aws:ebs/snapshot:Snapshot",
"aws:ebs/snapshotCopy:SnapshotCopy",
"aws:ebs/volume:Volume",
"aws:ec2/ami:Ami",
"aws:ec2/amiCopy:AmiCopy",
"aws:ec2/amiFromInstance:AmiFromInstance",
"aws:ec2/capacityReservation:CapacityReservation",
"aws:ec2/customerGateway:CustomerGateway",
"aws:ec2/defaultNetworkAcl:DefaultNetworkAcl",
"aws:ec2/defaultRouteTable:DefaultRouteTable",
"aws:ec2/defaultSecurityGroup:DefaultSecurityGroup",
"aws:ec2/defaultSubnet:DefaultSubnet",
"aws:ec2/defaultVpc:DefaultVpc",
"aws:ec2/defaultVpcDhcpOptions:DefaultVpcDhcpOptions",
"aws:ec2/eip:Eip",
"aws:ec2/fleet:Fleet",
"aws:ec2/instance:Instance",
"aws:ec2/internetGateway:InternetGateway",
"aws:ec2/keyPair:KeyPair",
"aws:ec2/launchTemplate:LaunchTemplate",
"aws:ec2/natGateway:NatGateway",
"aws:ec2/networkAcl:NetworkAcl",
"aws:ec2/networkInterface:NetworkInterface",
"aws:ec2/placementGroup:PlacementGroup",
"aws:ec2/routeTable:RouteTable",
"aws:ec2/securityGroup:SecurityGroup",
"aws:ec2/spotInstanceRequest:SpotInstanceRequest",
"aws:ec2/subnet:Subnet",
"aws:ec2/vpc:Vpc",
"aws:ec2/vpcDhcpOptions:VpcDhcpOptions",
"aws:ec2/vpcEndpoint:VpcEndpoint",
"aws:ec2/vpcEndpointService:VpcEndpointService",
"aws:ec2/vpcPeeringConnection:VpcPeeringConnection",
"aws:ec2/vpcPeeringConnectionAccepter:VpcPeeringConnectionAccepter",
"aws:ec2/vpnConnection:VpnConnection",
"aws:ec2/vpnGateway:VpnGateway",
"aws:ec2clientvpn/endpoint:Endpoint",
"aws:ec2transitgateway/routeTable:RouteTable",
"aws:ec2transitgateway/transitGateway:TransitGateway",
"aws:ec2transitgateway/vpcAttachment:VpcAttachment",
"aws:ec2transitgateway/vpcAttachmentAccepter:VpcAttachmentAccepter",
"aws:ecr/repository:Repository",
"aws:ecs/capacityProvider:CapacityProvider",
"aws:ecs/cluster:Cluster",
"aws:ecs/service:Service",
"aws:ecs/taskDefinition:TaskDefinition",
"aws:efs/fileSystem:FileSystem",
"aws:eks/cluster:Cluster",
"aws:eks/fargateProfile:FargateProfile",
"aws:eks/nodeGroup:NodeGroup",
"aws:elasticache/cluster:Cluster",
"aws:elasticache/replicationGroup:ReplicationGroup",
"aws:elasticbeanstalk/application:Application",
"aws:elasticbeanstalk/applicationVersion:ApplicationVersion",
"aws:elasticbeanstalk/environment:Environment",
"aws:elasticloadbalancing/loadBalancer:LoadBalancer",
"aws:elasticloadbalancingv2/loadBalancer:LoadBalancer",
"aws:elasticloadbalancingv2/targetGroup:TargetGroup",
"aws:elasticsearch/domain:Domain",
"aws:elb/loadBalancer:LoadBalancer",
"aws:emr/cluster:Cluster",
"aws:fsx/lustreFileSystem:LustreFileSystem",
"aws:fsx/windowsFileSystem:WindowsFileSystem",
"aws:gamelift/alias:Alias",
"aws:gamelift/build:Build",
"aws:gamelift/fleet:Fleet",
"aws:gamelift/gameSessionQueue:GameSessionQueue",
"aws:glacier/vault:Vault",
"aws:glue/crawler:Crawler",
"aws:glue/job:Job",
"aws:glue/trigger:Trigger",
"aws:iam/role:Role",
"aws:iam/user:User",
"aws:inspector/resourceGroup:ResourceGroup",
"aws:kinesis/analyticsApplication:AnalyticsApplication",
"aws:kinesis/firehoseDeliveryStream:FirehoseDeliveryStream",
"aws:kinesis/stream:Stream",
"aws:kms/externalKey:ExternalKey",
"aws:kms/key:Key",
"aws:lambda/function:Function",
"aws:lb/loadBalancer:LoadBalancer",
"aws:lb/targetGroup:TargetGroup",
"aws:licensemanager/licenseConfiguration:LicenseConfiguration",
"aws:lightsail/instance:Instance",
"aws:mediaconvert/queue:Queue",
"aws:mediapackage/channel:Channel",
"aws:mediastore/container:Container",
"aws:mq/broker:Broker",
"aws:mq/configuration:Configuration",
"aws:msk/cluster:Cluster",
"aws:neptune/cluster:Cluster",
"aws:neptune/clusterInstance:ClusterInstance",
"aws:neptune/clusterParameterGroup:ClusterParameterGroup",
"aws:neptune/eventSubscription:EventSubscription",
"aws:neptune/parameterGroup:ParameterGroup",
"aws:neptune/subnetGroup:SubnetGroup",
"aws:opsworks/stack:Stack",
"aws:organizations/account:Account",
"aws:pinpoint/app:App",
"aws:qldb/ledger:Ledger",
"aws:ram/resourceShare:ResourceShare",
"aws:rds/cluster:Cluster",
"aws:rds/clusterEndpoint:ClusterEndpoint",
"aws:rds/clusterInstance:ClusterInstance",
"aws:rds/clusterParameterGroup:ClusterParameterGroup",
"aws:rds/clusterSnapshot:ClusterSnapshot",
"aws:rds/eventSubscription:EventSubscription",
"aws:rds/instance:Instance",
"aws:rds/optionGroup:OptionGroup",
"aws:rds/parameterGroup:ParameterGroup",
"aws:rds/securityGroup:SecurityGroup",
"aws:rds/snapshot:Snapshot",
"aws:rds/subnetGroup:SubnetGroup",
"aws:redshift/cluster:Cluster",
"aws:redshift/eventSubscription:EventSubscription",
"aws:redshift/parameterGroup:ParameterGroup",
"aws:redshift/snapshotCopyGrant:SnapshotCopyGrant",
"aws:redshift/snapshotSchedule:SnapshotSchedule",
"aws:redshift/subnetGroup:SubnetGroup",
"aws:resourcegroups/group:Group",
"aws:route53/healthCheck:HealthCheck",
"aws:route53/resolverEndpoint:ResolverEndpoint",
"aws:route53/resolverRule:ResolverRule",
"aws:route53/zone:Zone",
"aws:s3/bucket:Bucket",
"aws:s3/bucketObject:BucketObject",
"aws:sagemaker/endpoint:Endpoint",
"aws:sagemaker/endpointConfiguration:EndpointConfiguration",
"aws:sagemaker/model:Model",
"aws:sagemaker/notebookInstance:NotebookInstance",
"aws:secretsmanager/secret:Secret",
"aws:servicecatalog/portfolio:Portfolio",
"aws:sfn/activity:Activity",
"aws:sfn/stateMachine:StateMachine",
"aws:sns/topic:Topic",
"aws:sqs/queue:Queue",
"aws:ssm/activation:Activation",
"aws:ssm/document:Document",
"aws:ssm/maintenanceWindow:MaintenanceWindow",
"aws:ssm/parameter:Parameter",
"aws:ssm/patchBaseline:PatchBaseline",
"aws:storagegateway/cachesIscsiVolume:CachesIscsiVolume",
"aws:storagegateway/gateway:Gateway",
"aws:storagegateway/nfsFileShare:NfsFileShare",
"aws:storagegateway/smbFileShare:SmbFileShare",
"aws:swf/domain:Domain",
"aws:transfer/server:Server",
"aws:transfer/user:User",
"aws:waf/rateBasedRule:RateBasedRule",
"aws:waf/rule:Rule",
"aws:waf/ruleGroup:RuleGroup",
"aws:waf/webAcl:WebAcl",
"aws:wafregional/rateBasedRule:RateBasedRule",
"aws:wafregional/rule:Rule",
"aws:wafregional/ruleGroup:RuleGroup",
"aws:wafregional/webAcl:WebAcl",
"aws:workspaces/directory:Directory",
"aws:workspaces/ipGroup:IpGroup",
];

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2016",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": false,
"types": ["node"]
},
"files": [
"index.ts"
]
}