-
Notifications
You must be signed in to change notification settings - Fork 2
/
build.fsx
360 lines (265 loc) · 9.9 KB
/
build.fsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
#!/usr/bin/env -S dotnet fsi
#r "nuget: Fake.Core.Target"
// Workaround for FAKE not yet supporting Dotnet versions > 6
System.Environment.GetCommandLineArgs()
|> Array.skip 2
|> Array.toList
|> Fake.Core.Context.FakeExecutionContext.Create false __SOURCE_FILE__
|> Fake.Core.Context.RuntimeContext.Fake
|> Fake.Core.Context.setExecutionContext
open System
open Fake.Core
open Fake.Core.TargetOperators
open Fake.IO
open Globbing.Operators
let projects = !! "**/*Web.fsproj" ++ "**/*.Service.fsproj"
let appEntryDir = "./Web"
let testDir = "./Test"
type ImageName = string
type DirectoryName = string
type ImageBuilder = {
Program: string
GetArgs: ImageName -> DirectoryName -> string
}
type Env = { Pulumi: string; Dotnet: string }
let envs = {|
Local = {
Pulumi = "local"
Dotnet = "Development"
}
Staging = {
Pulumi = "staging"
Dotnet = "Staging"
}
Production = {
Pulumi = "production"
Dotnet = "Production"
}
|}
let imageBuilders = {|
docker = {
Program = "docker"
GetArgs =
fun imageName dirName -> $"build -t {imageName}:latest {dirName}"
}
minikube = {
Program = "minikube"
GetArgs =
fun imageName dirName -> $"image build -t {imageName}:latest {dirName}"
}
|}
let publishProject project =
Trace.trace $"Publishing project {project}..."
Shell.Exec("dotnet", $"publish {project}") |> ignore
let dockerImageNameFromProject (fsproj: string) =
match IO.Path.GetFileNameWithoutExtension fsproj with
| "Web" -> Some "web"
| "Account.Service" -> Some "account"
| "Scheduler.Service" -> Some "scheduler"
| "MockDomesticTransferProcessor.Web" ->
Some "mock-domestic-transfer-processor"
| _ -> None
let buildImage (builder: ImageBuilder) fsprojPath =
let imageNameOpt = dockerImageNameFromProject fsprojPath
match imageNameOpt with
| None ->
failwithf "Docker image name not specified for project %s" fsprojPath
| Some imageName ->
Trace.trace $"Building docker image {imageName}"
let dirName = IO.Path.GetDirectoryName fsprojPath
Shell.Exec(builder.Program, builder.GetArgs imageName dirName) |> ignore
Target.create "Clean" (fun _ ->
let dirs = !! "**/bin/" ++ "**/obj/" ++ "./UI/dist/" ++ "./UI/node_modules/"
Trace.trace $"Cleaning directories: {dirs}"
Shell.cleanDirs dirs
Shell.rm $"{appEntryDir}/logs.json")
Target.create "BuildUI" (fun _ ->
Shell.cd "./UI/"
let exitCode = Shell.Exec("npm", "install")
if exitCode <> 0 then
failwith "Error running npm install"
let exitCode = Shell.Exec("npm", "run build")
if exitCode <> 0 then
failwith "Error running npm build"
Shell.cd "../"
Shell.copyDir "./Web/wwwroot/" "./UI/dist" (fun _ -> true))
Target.create "Publish" (fun o ->
let paths = o.Context.Arguments
let sources =
if paths.IsEmpty then
Seq.toArray projects
else
List.toArray paths
Array.iter publishProject sources)
Target.create "BuildDockerImages" (fun o ->
let paths = o.Context.Arguments
let sources =
if paths.IsEmpty then
Seq.toArray projects
else
List.toArray paths
Array.Parallel.iter (buildImage imageBuilders.docker) sources)
// NOTE: Pushes app images to public docker hub repos.
// Currently pulling public app images into Azure Staging environment.
// TODO: Research Azure Container Registry & versioning docker images.
let pushImageToDockerHub (image: string) =
let tag = $"danne931/akka-dotnet-bank-{image}"
Trace.trace $"Uploading {tag} to public docker hub"
let exitCode = Shell.Exec("docker", $"tag {image} {tag}")
if exitCode <> 0 then
Trace.traceError $"Error creating docker tag {tag}"
let exitCode = Shell.Exec("docker", $"push {tag}")
if exitCode <> 0 then
Trace.traceError $"Error uploading {tag} to public docker hub"
Target.create "DockerHubPublic" (fun _ ->
projects
|> Seq.toArray
|> Array.Parallel.iter (
dockerImageNameFromProject >> Option.iter pushImageToDockerHub
))
Target.create "RunDockerApp" (fun _ ->
Shell.Exec("docker", "compose up") |> ignore)
Target.create "StartK8s" (fun _ ->
//Shell.Exec("minikube", "start --cpus 4 --memory 8192") |> ignore)
Shell.Exec("minikube", "start --alsologtostderr -v=2") |> ignore
Shell.Exec(
"minikube",
"kubectl -- config set-context minikube --namespace akkabank"
)
|> ignore)
Target.create "BuildDockerImagesForK8s" (fun o ->
let paths = o.Context.Arguments
let sources =
if paths.IsEmpty then
Seq.toArray projects
else
List.toArray paths
Array.Parallel.iter (buildImage imageBuilders.minikube) sources)
Target.create "VerifyPulumiLogin" (fun _ ->
if Shell.Exec("pulumi", "whoami") <> 0 then
failwith
"\n\nLogin to pulumi via command line before proceeding: pulumi login")
Target.create "VerifyAzureLogin" (fun _ ->
let azureLoginResult = Shell.Exec("az", "login")
if azureLoginResult <> 0 then
failwith "Issue logging in to Azure.")
Target.create "ApplyK8sResources" (fun _ ->
let env = envs.Local
let pulumiStack = $"k8s-{env.Pulumi}"
Shell.cd "Deploy/K8s"
Shell.Exec("pulumi", $"stack init {pulumiStack}") |> ignore
let selectResult = Shell.Exec("pulumi", $"stack select {pulumiStack}")
if selectResult <> 0 then
failwith $"Could not select {pulumiStack} stack"
Shell.Exec("pulumi", $"config set environment {env.Dotnet}") |> ignore
Shell.Exec("pulumi", "config set defaultK8Namespace akkabank") |> ignore
Shell.Exec("pulumi", "config set postgresDatabase akkabank") |> ignore
Shell.Exec("pulumi", "config set postgresUser testuser") |> ignore
Shell.Exec("pulumi", "config set --secret postgresPassword testpass")
|> ignore
let result = Shell.Exec("pulumi", "up --skip-preview")
if result <> 0 then
failwith "Error applying K8s resources to minikube cluster")
let openK8sAppInBrowser () =
Shell.Exec("minikube", "kubectl -- get all") |> ignore
Shell.Exec(
"minikube",
"kubectl -- wait --for=condition=ready pod -l app=web-cluster --timeout=240s"
)
|> ignore
Shell.Exec("minikube", "kubectl -- get all") |> ignore
// Start tunnel for service web-cluster-http & open app in browser
Shell.Exec("minikube", "service web-cluster-http -n akkabank") |> ignore
Target.create "RunK8sApp" (fun _ -> openK8sAppInBrowser ())
// no-spinner option fixes intermittent hanging of Expecto
Target.create "Test" (fun _ ->
Shell.Exec("dotnet", "run --no-spinner", dir = testDir) |> ignore)
let pulumiAzure (env: Env) =
let pulumiStack = $"azure-{env.Pulumi}"
Trace.trace $"Deploying Azure resources to Pulumi stack {pulumiStack}"
Shell.cd "Deploy/Azure"
Shell.Exec("pulumi", $"stack init {pulumiStack}") |> ignore
let selectResult = Shell.Exec("pulumi", $"stack select {pulumiStack}")
if selectResult <> 0 then
failwith $"Could not select {pulumiStack} stack"
let azureDeployResult = Shell.Exec("pulumi", "up --skip-preview")
if azureDeployResult <> 0 then
failwith "Error deploying Azure resources"
Shell.cd "../../"
let pulumiK8s (env: Env) =
let pulumiStack = $"k8s-{env.Pulumi}"
Trace.trace $"Deploying K8s resources to Pulumi stack {pulumiStack}"
Shell.cd "Deploy/K8s"
Shell.Exec("pulumi", $"stack init {pulumiStack}") |> ignore
let selectResult = Shell.Exec("pulumi", $"stack select {pulumiStack}")
if selectResult <> 0 then
failwith $"Could not select {pulumiStack} stack"
let whoAmI =
CreateProcess.fromRawCommand "pulumi" [ "whoami" ]
|> CreateProcess.redirectOutput
|> Proc.run
if whoAmI.ExitCode <> 0 then
failwith "Could not determine current logged-in Pulumi user"
let org = whoAmI.Result.Output |> String.removeLineBreaks
let qualified = $"{org}/{env.Pulumi}"
Shell.Exec("pulumi", $"env init {qualified}") |> ignore
Shell.Exec(
"pulumi",
$"env set {qualified} pulumiConfig.environment {env.Dotnet}"
)
|> ignore
Shell.Exec(
"pulumi",
$"env set {qualified} pulumiConfig.defaultK8Namespace akkabank"
)
|> ignore
Shell.Exec(
"pulumi",
$"env set {qualified} pulumiConfig.postgresDatabase akkabank"
)
|> ignore
Shell.Exec(
"pulumi",
$"env set {qualified} pulumiConfig.postgresUser testuser"
)
|> ignore
Shell.Exec(
"pulumi",
$"env set {qualified} pulumiConfig.postgresPassword testpass --secret"
)
|> ignore
let k8sDeployResult = Shell.Exec("pulumi", "up --skip-preview")
if k8sDeployResult <> 0 then
failwith "Error deploying K8s resources to AKS"
Shell.cd "../../"
Target.create "DeployAzureStaging" (fun _ -> pulumiAzure envs.Staging)
Target.create "DeployK8sStaging" (fun _ -> pulumiK8s envs.Staging)
Target.create "DeployAllStaging" (fun _ ->
pulumiAzure envs.Staging
pulumiK8s envs.Staging
Trace.trace "Finished deploying")
"Clean" ==> "BuildUI" ==> "Publish"
"Publish" ==> "BuildDockerImages"
"BuildDockerImages" ==> "RunDockerApp"
"Publish" ==> "StartK8s" ==> "BuildDockerImagesForK8s"
"VerifyPulumiLogin" ?=> "Clean"
"VerifyPulumiLogin" ==> "ApplyK8sResources"
"BuildDockerImagesForK8s" ==> "ApplyK8sResources"
"ApplyK8sResources" ==> "RunK8sApp"
// NOTE:
// Currently hosting images in public Docker Hub repos for Azure
// staging deployment to retrieve.
// TODO: Host containers in private Azure Container Registry.
"BuildDockerImages" ==> "DockerHubPublic"
"VerifyAzureLogin" ==> "DeployAllStaging"
"VerifyPulumiLogin" ==> "DeployAllStaging"
Target.runOrDefaultWithArguments "Clean"
// NOTE:
// Run app via docker compose: sh build.sh -t RunDockerApp
// Run app via kubernetes minikube: sh build.sh -t RunK8sApp
// Deploy app to Azure AKS staging environment: sh build.sh -t DeployAllStaging
//
// Selectively rebuild images for docker compose:
// sh build.sh -t BuildDockerImages ./Web/Web.fsproj ./Scheduler.Service/Scheduler.Service.fsproj
//
// Test: sh build.sh -t Test