Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions pulumi/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,6 @@
# Build security groups for load balancers, containers, and our Redis cache
backend_cache_sg, container_sgs, lb_sgs = security_groups(project=project, resources=resources, vpc=vpc)

# Create the Redis memory cache
backend_cache, cache_dns = redis_cache(
cloudflare_zone_id=cloudflare_zone_id,
project=project,
security_group=backend_cache_sg,
vpc=vpc,
resources=resources,
)

# Fargate Service
fargate_clusters, autoscalers = fargate(
container_security_groups=container_sgs,
Expand All @@ -91,6 +82,22 @@
for afc_name, afc_config in resources.get('tb:fargate:AutoscalingFargateCluster', {}).items()
}

# Build a list of SGs to grant access to the new cache replica set
_redis_source_sgs = [backend_cache_sg]
for afc in afcs.values():
for cont_sgs in afc.resources['container_security_groups'].values():
_redis_source_sgs.extend([sg_with_rules for sg_with_rules in cont_sgs.values()])

# Create the Redis memory cache
backend_cache, cache_dns = redis_cache(
cloudflare_zone_id=cloudflare_zone_id,
project=project,
security_group=backend_cache_sg,
security_groups=_redis_source_sgs,
vpc=vpc,
resources=resources,
)

# CloudFront function to handle request rewrites headed to the backend
rewrite_function = cloudfront.rewrite_function(project=project)
project.resources['cf_rewrite_function'] = rewrite_function
Expand Down
40 changes: 37 additions & 3 deletions pulumi/config.dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@
.auth_scheme: &VAR_AUTH_SCHEME {name: "AUTH_SCHEME", value: "oidc"}
.database_engine: &VAR_DATABASE_ENGINE {name: "DATABASE_ENGINE", value: "postgresql"}
.database_port: &VAR_DATABASE_PORT {name: "DATABASE_PORT", value: "5432"}
.frontend_url: &VAR_FRONTEND_URL {name: "FRONTEND_URL", value: "https://appointment-dev.tb.pro"}
.fxa_callback: &VAR_FXA_CALLBACK {name: "FXA_CALLBACK", value: "https://appointment-dev.tb.pro/fxa"}
.google_auth_callback: &VAR_GOOGLE_AUTH_CALLBACK {name: "GOOGLE_AUTH_CALLBACK", value: "https://appointment-dev.tb.pro/api/v1/google/callback"}
.jwt_algo: &VAR_JWT_ALGO {name: "JWT_ALGO", value: "HS256"}
.jwt_expire_in_mins: &VAR_JWT_EXPIRE_IN_MINS {name: "JWT_EXPIRE_IN_MINS", value: "10000"}
.log_level: &VAR_LOG_LEVEL {name: "LOG_LEVEL", value: "ERROR"}
.log_use_stream: &VAR_LOG_USE_STREAM {name: "LOG_USE_STREAM", value: "True"}
.oids_exp_grace_period: &VAR_OIDC_EXP_GRACE_PERIOD {name: "OIDC_EXP_GRACE_PERIOD", value: "60"}
.oidc_exp_grace_period: &VAR_OIDC_EXP_GRACE_PERIOD {name: "OIDC_EXP_GRACE_PERIOD", value: "60"}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also change this to oidc_exp_grace_period for stage and prod for consistency

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did I miss it? I'll double-check.

.oidc_fallback_match_by_email: &VAR_OIDC_FALLBACK_MATCH_BY_EMAIL {name: "OIDC_FALLBACK_MATCH_BY_EMAIL", value: "True"}
.oidc_token_introspection_url: &VAR_OIDC_TOKEN_INTROSPECTION_URL {name: "OIDC_TOKEN_INTROSPECTION_URL", value: "https://auth-dev.tb.pro/realms/tbpro/protocol/openid-connect/token/introspect"}
.frontend_url: &VAR_FRONTEND_URL {name: "FRONTEND_URL", value: "https://appointment-dev.tb.pro"}
.posthog_host: &VAR_POSTHOG_HOST {name: "POSTHOG_HOST", value: "https://us.i.posthog.com"}
.redis_db: &VAR_REDIS_DB {name: "REDIS_DB", value: "0"}
.redis_port: &VAR_REDIS_PORT {name: "REDIS_PORT", value: "6379"}
.redis_use_ssl: &VAR_REDIS_USE_SSL {name: "REDIS_USE_SSL", value: "True"}
Expand All @@ -26,20 +29,43 @@
.sentry_dsn: &VAR_SENTRY_DSN {name: "SENTRY_DSN", value: "https://5dddca3ecc964284bb8008bc2beef808@o4505428107853824.ingest.sentry.io/4505428124827648"}
.service_email: &VAR_SERVICE_EMAIL {name: "SERVICE_EMAIL", value: "no-reply@appointment-dev.tb.pro"}
.short_base_url: &VAR_SHORT_BASE_URL {name: "SHORT_BASE_URL", value: "https://dev.apt.mt"}
.smtp_port: &VAR_SMTP_PORT {name: "SMTP_PORT", value: "587"}
.smtp_security: &VAR_SMTP_SECURITY {name: "SMTP_SECURITY", value: "STARTTLS"}
.tb_accounts_caldav_url: &VAR_TB_ACCOUNTS_CALDAV_URL {name: "TB_ACCOUNTS_CALDAV_URL", value: "https://dev-thundermail.com"}
.tb_accounts_host: &VAR_TB_ACCOUNTS_HOST {name: "TB_ACCOUNTS_HOST", value: "https://accounts-dev.tb.pro"}
.tier_basic_calendar_limit: &VAR_TIER_BASIC_CALENDAR_LIMIT {name: "TIER_BASIC_CALENDAR_LIMIT", value: "3"}
.tier_plus_calendar_limit: &VAR_TIER_PLUS_CALENDAR_LIMIT {name: "TIER_PLUS_CALENDAR_LIMIT", value: "5"}
.tier_pro_calendar_limit: &VAR_TIER_PRO_CALENDAR_LIMIT {name: "TIER_PRO_CALENDAR_LIMIT", value: "10"}
.zoom_api_enabled: &VAR_ZOOM_API_ENABLED {name: "ZOOM_API_ENABLED", value: "True"}
.zoom_api_new_app: &VAR_ZOOM_API_NEW_APP {name: "ZOOM_API_NEW_APP", value: "False"}
.zoom_auth_callback: &VAR_ZOOM_AUTH_CALLBACK {name: "ZOOM_AUTH_CALLBACK", value: "https://appointment-dev.tb.pro/api/v1/zoom/callback"}

# These variables are also common to our environments, but are pulled from secret stores instead
.app_admin_allow_list: &SECRET_APP_ADMIN_ALLOW_LIST {name: "APP_ADMIN_ALLOW_LIST", valueFrom: ""}
.appointment_caldav_secret: &SECRET_APPOINTMENT_CALDAV_SECRET {name: "APPOINTMENT_CALDAV_SECRET", valueFrom: ""}
.database_host: &SECRET_DATABASE_HOST {name: "DATABASE_HOST", valueFrom: "arn:aws:secretsmanager:eu-central-1:768512802988:secret:appointment/dev/database-host-pnM2d9"}
.database_name: &SECRET_DATABASE_NAME {name: "DATABASE_NAME", valueFrom: "arn:aws:secretsmanager:eu-central-1:768512802988:secret:appointment/dev/database-name-slQJtj"}
.database_username: &SECRET_DATABASE_USERNAME {name: "DATABASE_USERNAME", valueFrom: "arn:aws:secretsmanager:eu-central-1:768512802988:secret:appointment/dev/database-user-9EgJWM"}
.database_password: &SECRET_DATABASE_PASSWORD {name: "DATABASE_PASSWORD", valueFrom: "arn:aws:secretsmanager:eu-central-1:768512802988:secret:appointment/dev/database-password-yaGZdw"}
.database_username: &SECRET_DATABASE_USERNAME {name: "DATABASE_USERNAME", valueFrom: "arn:aws:secretsmanager:eu-central-1:768512802988:secret:appointment/dev/database-user-9EgJWM"}
.db_secret: &SECRET_DB_SECRET {name: "DB_SECRET", valueFrom: ""}
.fxa_allow_list: &SECRET_FXA_ALLOW_LIST {name: "FXA_ALLOW_LIST", valueFrom: ""}
.fxa_client_id: &SECRET_FXA_CLIENT_ID {name: "FXA_CLIENT_ID", valueFrom: ""}
.fxa_open_id_config: &SECRET_FXA_OPEN_ID_CONFIG {name: "FXA_OPEN_ID_CONFIG", valueFrom: ""}
.fxa_secret: &SECRET_FXA_SECRET {name: "FXA_SECRET", valueFrom: ""}
.google_auth_client_id: &SECRET_GOOGLE_AUTH_CLIENT_ID {name: "GOOGLE_AUTH_CLIENT_ID", valueFrom: ""}
.google_auth_project_id: &SECRET_GOOGLE_AUTH_PROJECT_ID {name: "GOOGLE_AUTH_PROJECT_ID", valueFrom: ""}
.google_auth_secret: &SECRET_GOOGLE_AUTH_SECRET {name: "GOOGLE_AUTH_SECRET", valueFrom: ""}
.jwt_secret: &SECRET_JWT_SECRET {name: "JWT_SECRET", valueFrom: ""}
.oidc_client_id: &SECRET_OIDC_CLIENT_ID {name: "OIDC_CLIENT_ID", valueFrom: "arn:aws:secretsmanager:eu-central-1:768512802988:secret:appointment/dev/oidc-client-id-sbebkz"}
.oidc_client_secret: &SECRET_OIDC_CLIENT_SECRET {name: "OIDC_CLIENT_SECRET", valueFrom: "arn:aws:secretsmanager:eu-central-1:768512802988:secret:appointment/dev/oidc-client-secret-I9BTf2"}
.posthog_project_key: &SECRET_POSTHOG_PROJECT_KEY {name: "POSTHOG_PROJECT_KEY", valueFrom: ""}
.session_secret: &SECRET_SESSION_SECRET {name: "SESSION_SECRET", valueFrom: ""}
.signed_secret: &SECRET_SIGNED_SECRET {name: "SIGNED_SECRET", valueFrom: ""}
.smtp_pass: &SECRET_SMTP_PASS {name: "SMTP_PASS", valueFrom: ""}
.smtp_url: &SECRET_SMTP_URL {name: "SMTP_URL", valueFrom: ""}
.smtp_user: &SECRET_SMTP_USER {name: "SMTP_USER", valueFrom: ""}
.zoom_api_secret: &SECRET_ZOOM_API_SECRET {name: "ZOOM_API_SECRET", valueFrom: ""}
.zoom_auth_client_id: &SECRET_ZOOM_AUTH_CLIENT_ID {name: "ZOOM_AUTH_CLIENT_ID", valueFrom: ""}

# This forms the base of Appointment-based task definitions. The blank fields commented here must be set on any
# inheriting task definition:
Expand Down Expand Up @@ -508,6 +534,14 @@ resources:
min_capacity: 1
max_capacity: 1

# Additional properties for this environment's cache
tb:elasticache:ElastiCacheReplicationGroup:
backend:
num_cache_nodes: 1
# Settings below should be "yes" when (num_cache_nodes > 1)
automatic_failover_enabled: no
multi_az_enabled: no

tb:cloudfront:CloudFrontS3Service:
frontend:
service_bucket_name: tb-appointment-dev-frontend
Expand Down
8 changes: 8 additions & 0 deletions pulumi/config.prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,14 @@ resources:
min_capacity: 0
max_capacity: 0

# Additional properties for this environment's cache
tb:elasticache:ElastiCacheReplicationGroup:
backend:
num_cache_nodes: 2
# Settings below should be "yes" when (num_cache_nodes > 1)
automatic_failover_enabled: yes
multi_az_enabled: yes

tb:cloudfront:CloudFrontS3Service:
frontend:
service_bucket_name: tb-appointment-prod-frontend
Expand Down
14 changes: 11 additions & 3 deletions pulumi/config.stage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
### Special variables used throughout this file

# Update this value to update all containers based on the thunderbird/appointment image
.apmt_image: &APMT_IMAGE 768512802988.dkr.ecr.eu-central-1.amazonaws.com/thunderbird/appointment:2a8e57c78807346aebf7d89cfbd1f00e065f727f
.apmt_image: &APMT_IMAGE 768512802988.dkr.ecr.eu-central-1.amazonaws.com/thunderbird/appointment:c8caacf81a3f37073588bfed17f13ef6f3131dc1

# These variables are common to Accounts application environments. Some tasks will require additional configuration.
.app_env: &VAR_APP_ENV {name: "APP_ENV", value: "stage"}
Expand Down Expand Up @@ -587,8 +587,16 @@ resources:
min_capacity: 1
max_capacity: 1
flower:
min_capacity: 0
max_capacity: 0
min_capacity: 1
max_capacity: 1

# Additional properties for this environment's cache
tb:elasticache:ElastiCacheReplicationGroup:
backend:
num_cache_nodes: 1
# Settings below should be "yes" when (num_cache_nodes > 1)
automatic_failover_enabled: no
multi_az_enabled: no

tb:cloudfront:CloudFrontS3Service:
frontend:
Expand Down
47 changes: 45 additions & 2 deletions pulumi/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@
import pulumi_cloudflare as cloudflare

from tb_pulumi import ThunderbirdPulumiProject
from tb_pulumi.elasticache import ElastiCacheReplicationGroup
from tb_pulumi.network import MultiCidrVpc, SecurityGroupWithRules


def redis_cache(
cloudflare_zone_id: str,
project: ThunderbirdPulumiProject,
# **IN PROGRESS**
#
# Keep "security_group" as a single group that gets access to the old serverless cache
# -> Add "security_groups" as a list of groups that get access to the new replicaset
# Remove "security_group" when we destroy the old serverless cache
security_group: SecurityGroupWithRules,
security_groups: list[SecurityGroupWithRules],
resources: dict,
vpc: MultiCidrVpc,
):
Expand All @@ -19,9 +26,13 @@ def redis_cache(
:param project: The project to store all the resources in.
:type project: ThunderbirdPulumiProject

:param security_group: The security group to apply to the cache.
:param security_group: The security group to apply to the old serverless cache.
:type security_group: SecurityGroupWithRules

:param security_groups: List of SecurityGroupWithRules resources which should have access to the new cache replica
set.
:type security_groups: list[SecurityGroupWithRules]

:param resources: The full set of configured resources.
:type resources: dict

Expand All @@ -33,6 +44,11 @@ def redis_cache(
- The pulumi_cloudflare.DnsRecord resource pointing to the cache
:rtype: _type_
"""

# **IN PROGRESS**
#
# -> Migrate traffic to new replicaset
# Delete this code block along with relevant config sections
backend_cache = aws.elasticache.ServerlessCache(
f'{project.name_prefix}-cache-backend',
security_group_ids=[security_group.resources.get('sg').id],
Expand All @@ -43,14 +59,41 @@ def redis_cache(
)
project.resources['backend_cache'] = backend_cache

# **IN PROGRESS**
#
# Build new replication group alongside the old cluster
# -> Move DNS to new replication group
# Destroy the old cluster

redis_replica_group = ElastiCacheReplicationGroup(
name=f'{project.name_prefix}-redis-replicaset',
project=project,
at_rest_encryption_enabled=True,
cluster_mode='disabled',
source_sgids=[sg_with_rules.resources['sg'].id for sg_with_rules in security_groups],
subnets=[subnet for subnet in vpc.resources.get('subnets', {})],
transit_encryption_enabled=True,
transit_encryption_mode='preferred',
**resources.get('tb:elasticache:ElastiCacheReplicationGroup', {}).get('backend', {}),
opts=pulumi.ResourceOptions(depends_on=[vpc]),
)
project.resources['backend_cache_replicaset'] = redis_replica_group

# **IN PROGRESS**
#
# Build new replica set
# -> Change DNS to the new replica set in stage
# Change DNS in prod, removing the condition below

backend_cache_primary_endpoint = backend_cache.endpoints.apply(lambda endpoints: endpoints[0]['address'])
redis_replica_group_primary_endpoint = redis_replica_group.resources['replication_group'].primary_endpoint_address
backend_cache_dns = cloudflare.DnsRecord(
f'{project.name_prefix}-dns-redis',
name=resources.get('domains', {}).get('redis', None),
ttl=60,
type='CNAME',
zone_id=cloudflare_zone_id,
content=backend_cache_primary_endpoint,
content=backend_cache_primary_endpoint if project.stack == 'prod' else redis_replica_group_primary_endpoint,
proxied=False,
)
project.resources['backend_cache_dns'] = backend_cache_dns
Expand Down
Loading