import * as pulumi from '@pulumi/pulumi'; import { deployApp } from './services/app'; import { deployAWSArtifactsLambdaFunction } from './services/aws-artifacts-lambda-function'; import { deployCFBroker } from './services/cf-broker'; import { deployCFCDN } from './services/cf-cdn'; import { deployClickhouse } from './services/clickhouse'; import { deployCloudFlareSecurityTransform } from './services/cloudflare-security'; import { deployCommerce } from './services/commerce'; import { deployDatabaseCleanupJob } from './services/database-cleanup'; import { deployDbMigrations } from './services/db-migrations'; import { configureDocker } from './services/docker'; import { prepareEnvironment } from './services/environment'; import { configureGithubApp } from './services/github'; import { deployGraphQL } from './services/graphql'; import { deployKafka } from './services/kafka'; import { deployObservability } from './services/observability'; import { deployOTELCollector } from './services/otel-collector'; import { deploySchemaPolicy } from './services/policy'; import { deployPostgres } from './services/postgres'; import { deployProxy } from './services/proxy'; import { deployPublicGraphQLAPIGateway } from './services/public-graphql-api-gateway'; import { deployRedis } from './services/redis'; import { deployS3, deployS3AuditLog, deployS3Mirror } from './services/s3'; import { deploySchema } from './services/schema'; import { configureSentry } from './services/sentry'; import { configureSlackApp } from './services/slack-app'; import { deployTokens } from './services/tokens'; import { deployUsage } from './services/usage'; import { deployUsageIngestor } from './services/usage-ingestor'; import { deployWorkflows, PostmarkSecret } from './services/workflows'; import { configureZendesk } from './services/zendesk'; import { optimizeAzureCluster } from './utils/azure-helpers'; import { isDefined } from './utils/helpers'; import { publishAppDeployment } from './utils/publish-app-deployment'; import { publishGraphQLSchema } from './utils/publish-graphql-schema'; import { ServiceSecret } from './utils/secrets'; // eslint-disable-next-line no-process-env const imagesTag = process.env.DOCKER_IMAGE_TAG as string; if (!imagesTag) { throw new Error(`DOCKER_IMAGE_TAG env variable is not set.`); } // eslint-disable-next-line no-process-env const graphqlSchemaAbsolutePath: string | undefined = process.env.GRAPHQL_SCHEMA_ABSOLUTE_PATH; if (!graphqlSchemaAbsolutePath) { throw new Error(`GRAPHQL_SCHEMA_ABSOLUTE_PATH env variable is not set.`); } // eslint-disable-next-line no-process-env let hiveAppPersistedDocumentsAbsolutePath: string | undefined = process.env.HIVE_APP_PERSISTED_DOCUMENTS_ABSOLUTE_PATH; if (!hiveAppPersistedDocumentsAbsolutePath) { console.warn( 'HIVE_APP_PERSISTED_DOCUMENTS_ABSOLUTE_PATH env variable is not set, defaulting to "0" (disabled).', ); hiveAppPersistedDocumentsAbsolutePath = undefined; } optimizeAzureCluster(); const docker = configureDocker(); const envName = pulumi.getStack(); const heartbeatsConfig = new pulumi.Config('heartbeats'); const emailConfig = new pulumi.Config('email'); const postmarkSecret = new PostmarkSecret('postmark', { token: emailConfig.requireSecret('token'), from: emailConfig.require('from'), messageStream: emailConfig.require('messageStream'), }); const sentry = configureSentry(); const environment = prepareEnvironment({ release: imagesTag, environment: envName, rootDns: new pulumi.Config('common').require('dnsZone'), }); const observability = deployObservability({ environment }); const clickhouse = deployClickhouse(); const postgres = deployPostgres(); const redis = deployRedis({ environment }); const kafka = deployKafka(); const s3 = deployS3(); const s3Mirror = deployS3Mirror(); const s3AuditLog = deployS3AuditLog(); const cdn = deployCFCDN({ s3, s3Mirror, sentry, environment, }); const lambdaFunction = deployAWSArtifactsLambdaFunction({ s3Mirror, environment, }); const broker = deployCFBroker({ environment, sentry, }); // eslint-disable-next-line no-process-env const shouldCleanDatabase = process.env.CLEAN_DATABASE === 'true'; const databaseCleanupJob = shouldCleanDatabase ? deployDatabaseCleanupJob({ environment }) : null; // eslint-disable-next-line no-process-env const forceRunDbMigrations = process.env.FORCE_DB_MIGRATIONS === 'true'; const dbMigrations = deployDbMigrations({ clickhouse, docker, postgres, s3, cdn, environment, image: docker.factory.getImageId('storage', imagesTag), force: forceRunDbMigrations, dependencies: [databaseCleanupJob].filter(isDefined), }); const tokens = deployTokens({ image: docker.factory.getImageId('tokens', imagesTag), environment, dbMigrations, docker, postgres, redis, heartbeat: heartbeatsConfig.get('tokens'), sentry, observability, }); const commerce = deployCommerce({ image: docker.factory.getImageId('commerce', imagesTag), docker, environment, clickhouse, dbMigrations, sentry, observability, postgres, }); const usage = deployUsage({ image: docker.factory.getImageId('usage', imagesTag), docker, environment, tokens, redis, postgres, kafka, dbMigrations, commerce, sentry, observability, }); const usageIngestor = deployUsageIngestor({ image: docker.factory.getImageId('usage-ingestor', imagesTag), docker, clickhouse, kafka, environment, dbMigrations, heartbeat: heartbeatsConfig.get('usageIngestor'), sentry, }); const schema = deploySchema({ image: docker.factory.getImageId('schema', imagesTag), docker, environment, redis, broker, sentry, observability, }); const schemaPolicy = deploySchemaPolicy({ image: docker.factory.getImageId('policy', imagesTag), docker, environment, sentry, observability, }); deployWorkflows({ image: docker.factory.getImageId('workflows', imagesTag), docker, environment, postgres, postmarkSecret, observability, sentry, heartbeat: heartbeatsConfig.get('webhooks'), schema, redis, }); const zendesk = configureZendesk({ environment }); const githubApp = configureGithubApp(); const slackApp = configureSlackApp(); const graphql = deployGraphQL({ postgres, environment, clickhouse, image: docker.factory.getImageId('server', imagesTag), docker, tokens, schema, schemaPolicy, dbMigrations, redis, usage, cdn, commerce, s3, s3Mirror, s3AuditLog, zendesk, githubApp, sentry, observability, }); const hiveConfig = new pulumi.Config('hive'); const hiveConfigSecret = new ServiceSecret('hive-config-secret', { usageAccessToken: hiveConfig.requireSecret('cliAccessToken'), }); // You can change this to `false` in cases when you don't want to publish commands. // For example, if the entire env is down or if you are having SSL issues. // eslint-disable-next-line no-process-env const RUN_PUBLISH_COMMANDS: boolean = process.env.SKIP_PUBLISH_COMMANDS !== '1'; const publishGraphQLSchemaCommand = RUN_PUBLISH_COMMANDS ? publishGraphQLSchema({ graphql, registry: { endpoint: `https://${environment.appDns}/registry`, accessToken: hiveConfigSecret.raw.usageAccessToken, target: hiveConfig.require('target'), }, version: { commit: imagesTag, }, schemaPath: graphqlSchemaAbsolutePath, }) : null; let publishAppDeploymentCommand: pulumi.Resource | undefined; if (hiveAppPersistedDocumentsAbsolutePath && RUN_PUBLISH_COMMANDS) { publishAppDeploymentCommand = publishAppDeployment({ appName: 'hive-app', registry: { endpoint: `https://${environment.appDns}/registry`, accessToken: hiveConfigSecret.raw.usageAccessToken, target: hiveConfig.require('target'), }, version: { commit: imagesTag, }, persistedDocumentsPath: hiveAppPersistedDocumentsAbsolutePath, wakeupClickhouse: environment.isProduction ? null : { clickhouse: clickhouse.secret, dockerSecret: docker.secret, }, // We need to wait until the new GraphQL schema is published before we can publish the app deployment. dependsOn: publishGraphQLSchemaCommand ? [publishGraphQLSchemaCommand] : [], }); } const otelCollector = deployOTELCollector({ environment, graphql, dbMigrations, clickhouse, image: docker.factory.getImageId('otel-collector', imagesTag), docker, }); const app = deployApp({ environment, graphql, publishAppDeploymentCommand, dbMigrations, image: docker.factory.getImageId('app', imagesTag), docker, zendesk, commerce, github: githubApp, slackApp, sentry, }); const publicGraphQLAPIGateway = deployPublicGraphQLAPIGateway({ environment, graphql, docker, observability, otelCollector, }); const proxy = deployProxy({ observability, app, graphql, usage, environment, publicGraphQLAPIGateway, otelCollector, }); deployCloudFlareSecurityTransform({ environment, // Paths used by 3rd-party software. // The CF Page Rules should not affect them and do not apply any special security headers. ignoredPaths: [ '/api/auth', '/api/health', '/usage', '/graphql', '/registry', '/server', '/api/github', '/api/slack', ], }); export const graphqlApiServiceId = graphql.service.id; export const usageApiServiceId = usage.service.id; export const usageIngestorApiServiceId = usageIngestor.service.id; export const tokensApiServiceId = tokens.service.id; export const schemaApiServiceId = schema.service.id; export const appId = app.deployment.id; export const otelCollectorId = otelCollector.deployment.id; export const publicIp = proxy.get()!.status.loadBalancer.ingress[0].ip; export const awsLambdaArtifactsFunctionUrl = lambdaFunction;