import * as pulumi from '@pulumi/pulumi'; import * as gcp from '@pulumi/gcp'; import * as k8s from '@pulumi/kubernetes'; import * as k8sInputApi from '@pulumi/kubernetes/types/input'; const serviceAccountKeys = new Map(); /** * Creates a service account and key, and sets the cloudsql.client role in IAM. */ export function createCloudSqlProxyServiceAccount(args: gcp.serviceAccount.AccountArgs) { return pulumi.output(args.accountId).apply(accountId => { const serviceAccount = new gcp.serviceAccount.Account(accountId, args); serviceAccountKeys.set( accountId, new gcp.serviceAccount.Key(`${accountId}-key`, { serviceAccountId: serviceAccount.id, }) ); new gcp.projects.IAMMember(accountId, { member: serviceAccount.email.apply(email => `serviceAccount:${email}`), role: 'roles/cloudsql.client', }); return serviceAccount; }); } const credentialsSecrets = new Map(); function getOrCreateSecret(serviceAccountId: string, namespaceName: pulumi.Input | undefined) { let credentialsSecret = credentialsSecrets.get(serviceAccountId); if (!credentialsSecret) { const serviceAccountKey = serviceAccountKeys.get(serviceAccountId); if (!serviceAccountKey) { throw new pulumi.RunError(`Service account not set up: ${serviceAccountId}`); } credentialsSecret = new k8s.core.v1.Secret(`${serviceAccountId}-credentials`, { metadata: { namespace: namespaceName, }, stringData: { 'credentials.json': serviceAccountKey.privateKey.apply(x => Buffer.from(x, 'base64').toString('utf8')), }, }); credentialsSecrets.set(serviceAccountId, credentialsSecret); } return credentialsSecret; } /** * Injects cloudsql-proxy sidecar and dependencies into a deployment. */ export function injectCloudSqlProxy( dbInstance: gcp.sql.DatabaseInstance, serviceAccountId: pulumi.Input, args: k8sInputApi.apps.v1.Deployment ) { const credentialsSecretName = pulumi.all([args.metadata, serviceAccountId]).apply(([metadata, serviceAccountId]) => { const credentialsSecret = getOrCreateSecret(serviceAccountId, metadata && metadata.namespace); return credentialsSecret.metadata.apply(x => x.name); }); args.spec = pulumi .all([ args.spec, dbInstance.project, dbInstance.region, dbInstance.name, dbInstance.databaseVersion, credentialsSecretName, ]) .apply(([spec, project, region, instanceName, databaseVersion, credentialsSecretName]) => { // project = project || gcp.config.project; region = region || gcp.config.region; // prettier-ignore const dbPort = databaseVersion!.startsWith('MYSQL') ? 3306 : databaseVersion!.startsWith('POSTGRES') ? 5432 : undefined; spec.template = pulumi.output(spec.template!).apply(template => { template.spec = pulumi.output(template.spec!).apply(pod => { pod.containers = pulumi.output(pod.containers).apply(containers => { containers = containers || []; containers.push({ name: 'cloudsql-proxy', image: 'gcr.io/cloudsql-docker/gce-proxy:1.11', command: [ '/cloud_sql_proxy', `-instances=${project}:${region}:${instanceName}=tcp:${dbPort}`, '-credential_file=/var/run/secrets/cloudsql/credentials.json', ], securityContext: { runAsUser: 2, // non-root user allowPrivilegeEscalation: false, }, volumeMounts: [ { name: 'cloudsql-instance-credentials', mountPath: '/var/run/secrets/cloudsql', readOnly: true, }, ], }); return containers; }); pod.volumes = pulumi.output(pod.volumes).apply(volumes => { volumes = volumes || []; volumes.push({ name: 'cloudsql-instance-credentials', secret: { secretName: credentialsSecretName, }, }); return volumes; }); return pod; }); return template; }); return spec; }); return args; }