index.ts 5.81 KB
Newer Older
Erin Krengel's avatar
Erin Krengel committed
1
2
3
import * as k8s from "@pulumi/kubernetes";
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
4
import * as acceptance from "./acceptance";
Erin Krengel's avatar
Erin Krengel committed
5
import * as svcKey from "./svcKey";
Erin Krengel's avatar
Erin Krengel committed
6

7
8
9
// Get GCP project configuration value. This allows us to set a "test"
// GCP project for our test resources and then a separate GCP project
// for production.
Erin Krengel's avatar
wip    
Erin Krengel committed
10
11
12
const gcpConfig = new pulumi.Config("gcp");
const project = gcpConfig.require("project");

13
14
15
16
// Get stack's configuration values. Using a configurable environment name,
// we can make multiple test resources in the same project without collisions.
// We use a configurable DOCKER_TAG to ensure we are testing the same immutable
// image that we will also deploy to production.
Erin Krengel's avatar
Erin Krengel committed
17
const config = new pulumi.Config("demo-app");
Erin Krengel's avatar
Erin Krengel committed
18
19
const ENV = config.require("ENV");
const DOCKER_TAG = config.require("DOCKER_TAG");
Erin Krengel's avatar
Erin Krengel committed
20

Erin Krengel's avatar
Erin Krengel committed
21
22
23
24
// Get the global stack reference and its outputs. These are our long-lived
// pieces of infrastructure such as our Kubernetes cluster and two service
// accounts. We have one service account that our application will use and
// one that our acceptance tests will use.
Erin Krengel's avatar
Erin Krengel committed
25
const globalStackRef = new pulumi.StackReference("rocore/global-infra/global-infra");
Sean Holung's avatar
Sean Holung committed
26
const kubeconfigOutput = globalStackRef.getOutput("kubeconfig");
Erin Krengel's avatar
Erin Krengel committed
27
const namespaceOutput = globalStackRef.getOutputSync("namespace");
28
const accTestSvcAccount = globalStackRef.getOutputSync("acceptanceTestSvcAccount");
Erin Krengel's avatar
Erin Krengel committed
29

Erin Krengel's avatar
Erin Krengel committed
30
// By default, we're going to use a "test" service account for our application.
31
32
// If the environment is "prod", we will use the production service account.
let svcAccountOutput = globalStackRef.getOutputSync("testSvcAccount");
Erin Krengel's avatar
Erin Krengel committed
33
34
35
if (ENV === "prod") {
    svcAccountOutput = globalStackRef.getOutputSync("prodSvcAccount");
}
Erin Krengel's avatar
Erin Krengel committed
36

37
38
39
// We prepend the environment to the name used for all resources. This
// allows us to run this same program multiple times for different test
// environments and not have resources with the duplicate names.
Erin Krengel's avatar
Erin Krengel committed
40
41
const name = `${ENV}-demo-app`;

42
43
// Create egress topic and give the app's service account permission
// to publish to the topic.
Erin Krengel's avatar
Erin Krengel committed
44
45
46
47
48
export const topic = new gcp.pubsub.Topic(name);
new gcp.pubsub.TopicIAMMember(name, {
    member: `serviceAccount:${svcAccountOutput.email}`,
    role: "roles/pubsub.publisher",
    topic: topic.name,
Sean Holung's avatar
Sean Holung committed
49
}, { dependsOn: [topic] });
Erin Krengel's avatar
wip    
Erin Krengel committed
50

51
52
// Create bucket and give the app's service account permission to
// create objects in the bucket.
Erin Krengel's avatar
Erin Krengel committed
53
export const bucket = new gcp.storage.Bucket(name, {
54
55
    location: "us-west1",
});
56
new gcp.storage.BucketIAMMember(name, {
Erin Krengel's avatar
Erin Krengel committed
57
    member: `serviceAccount:${svcAccountOutput.email}`,
58
59
    role: "roles/storage.objectCreator",
    bucket: bucket.name,
Erin Krengel's avatar
Erin Krengel committed
60
});
Erin Krengel's avatar
Erin Krengel committed
61

62
63
64
65
66
67
68
// Create a Kubernetes provider from the global stack reference.
// We will use this make sure our K8s resources are created in the
// correct cluster and namespace.
const clusterProvider = new k8s.Provider(name, {
    kubeconfig: kubeconfigOutput,
    namespace: namespaceOutput.metadata.name,
});
Erin Krengel's avatar
wip    
Erin Krengel committed
69

70
71
72
73
// Create a service account key for the app's service account
// and then use that key to create a K8s secret that will be mounted to
// our K8s deployment.
const serviceAccountKey = svcKey.getSvcKey(name, svcAccountOutput.id);
Erin Krengel's avatar
Erin Krengel committed
74
const secret = new k8s.core.v1.Secret(name, {
Erin Krengel's avatar
Erin Krengel committed
75
76
    metadata: { name: `${ENV}-google-cloud-key` },
    stringData: { "key.json": serviceAccountKey },
Erin Krengel's avatar
Erin Krengel committed
77
}, { provider: clusterProvider });
Erin Krengel's avatar
Erin Krengel committed
78

Erin Krengel's avatar
Erin Krengel committed
79
// Create a K8s Deployment for our application.
Erin Krengel's avatar
wip    
Erin Krengel committed
80
const appLabels = { appClass: name };
81
const deployment = new k8s.apps.v1.Deployment(name, {
Erin Krengel's avatar
Erin Krengel committed
82
    metadata: { labels: appLabels },
83
84
85
86
87
    spec: {
        selector: { matchLabels: appLabels },
        template: {
            metadata: { labels: appLabels },
            spec: {
88
89
90
91
92
93
94
95
96
97
98
99
                containers: [{
                    name: name,
                    image: `rocore/demo-app:${DOCKER_TAG}`,
                    imagePullPolicy: "Always",
                    ports: [{ name: "http", containerPort: 8080 }],
                    env: [
                        { name: "TOPIC", value: topic.name },
                        { name: "BUCKET", value: bucket.name },
                        { name: "PROJECT", value: project },
                        {
                            name: "GOOGLE_APPLICATION_CREDENTIALS",
                            value: "/var/secrets/google/key.json"
100
                        },
101
102
103
104
105
106
107
108
109
110
111
                    ],
                    volumeMounts: [{
                        name: "google-cloud-key",
                        mountPath: "/var/secrets/google"
                    }],
                    readinessProbe: {
                        httpGet: { path: "/ping", port: 8080 },
                        initialDelaySeconds: 3,
                        periodSeconds: 3,
                    },
                }],
Erin Krengel's avatar
Erin Krengel committed
112
113
                volumes: [{
                    name: "google-cloud-key",
114
                    secret: { secretName: secret.metadata.name },
Erin Krengel's avatar
Erin Krengel committed
115
                }]
Erin Krengel's avatar
wip    
Erin Krengel committed
116
            }
117
        }
Erin Krengel's avatar
wip    
Erin Krengel committed
118
    },
Erin Krengel's avatar
Erin Krengel committed
119
}, { provider: clusterProvider });
Erin Krengel's avatar
wip    
Erin Krengel committed
120

Sean Holung's avatar
Sean Holung committed
121
// Create a ClusterIP Service for the K8s Deployment.
Erin Krengel's avatar
Erin Krengel committed
122
const service = new k8s.core.v1.Service(name, {
Erin Krengel's avatar
Erin Krengel committed
123
    metadata: { labels: appLabels },
Erin Krengel's avatar
Erin Krengel committed
124
125
126
    spec: {
        ports: [{ port: 80, targetPort: 8080 }],
        selector: appLabels,
Erin Krengel's avatar
wip    
Erin Krengel committed
127
    },
Erin Krengel's avatar
Erin Krengel committed
128
}, { provider: clusterProvider });
Erin Krengel's avatar
wip    
Erin Krengel committed
129

Sean Holung's avatar
Sean Holung committed
130
// If it's a test environment, set up acceptance tests.
131
132
let job: k8s.batch.v1.Job | undefined;
if (ENV.startsWith("test")) {
133
    job = acceptance.setupAcceptanceTests({
134
135
136
        env: ENV,
        project: project,
        topic: topic,
137
        bucket: bucket,
Erin Krengel's avatar
tidying    
Erin Krengel committed
138
139
        accSvcAccountEmail: accTestSvcAccount.email,
        accSvcAccountId: accTestSvcAccount.id,
140
141
142
143
        dockerTag: DOCKER_TAG,
        appDeployment: deployment,
        appService: service,
        clusterProvider: clusterProvider,
Erin Krengel's avatar
Erin Krengel committed
144
    });
145
}
Erin Krengel's avatar
Erin Krengel committed
146
147
148

// Export the acceptance job name, so we can get the logs from our
// acceptance tests.
149
export const acceptanceJobName = job ? job.metadata.name : "unapplicable";