-
Notifications
You must be signed in to change notification settings - Fork 2
/
build.fsx
286 lines (227 loc) · 10.4 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
//#r @"packages/build/FAKE/tools/FakeLib.dll"
#r "paket: groupref build //"
#load "./.fake/build.fsx/intellisense.fsx"
#if !FAKE
#r "netstandard"
#r "Facades/netstandard" // https://github.com/ionide/ionide-vscode-fsharp/issues/839#issuecomment-396296095
#endif
open System
open Fake.DotNet
open Fake.Core
open Fake.IO
//https://fake.build/apidocs/v5/fake-core-buildservermodule.html#Functions%20and%20values
//https://fake.build/buildserver.html
//C:\development\comit\templates\saturnapp\paket-files\build\CompositionalIT\fshelpers\src\FsHelpers\ArmHelper
//#load @"paket-files/build/CompositionalIT/fshelpers/src/FsHelpers/ArmHelper/ArmHelper.fs"
#load @"paket-files\build\UACOMTucson\fshelpers\src\FsHelpers\ArmHelper\ArmHelper.fs"
open Cit.Helpers.Arm
open Cit.Helpers.Arm.Parameters
open Microsoft.Azure.Management.ResourceManager.Fluent.Core
let appPath = "./src/Template.Saturn.WebHost/" |> Fake.IO.Path.getFullName
let testsPath = "./src/Template.Saturn.Tests" |> Fake.IO.Path.getFullName
//TODO you wlll need to fill this in if using Fable and SAFE Stack
//let serverPath = Path.getFullName "./src/Server"
let clientPath = Path.getFullName "./src/Template.Saturn.Client"
let deployDir = Path.getFullName "./deploy"
let isTeamCity =
match BuildServer.buildServer with
| TeamCity -> true
| _ -> true
let platformTool tool winTool =
Trace.trace (sprintf "Tool %s and WinTool %s" tool winTool)
let runTool = if Environment.isUnix then tool else winTool
match ProcessUtils.tryFindFileOnPath runTool with
| Some t -> t
| _ -> let errorMsg =
tool + " was not found in path. " +
"Please install it and make sure it's available from your path. " +
"See https://safe-stack.github.io/docs/quickstart/#install-pre-requisites for more info"
failwith errorMsg
let runDotNet cmd workingDir =
let result =
DotNet.exec (DotNet.Options.withWorkingDirectory workingDir) cmd ""
if result.ExitCode <> 0 then failwithf "'dotnet %s' failed in %s" cmd workingDir
let openBrowser url =
//https://github.com/dotnet/corefx/issues/10361
Command.ShellCommand url
|> CreateProcess.fromCommand
|> CreateProcess.ensureExitCodeWithMessage "opening browser failed"
|> Proc.run
|> ignore
Target.create "InstallDotNetCore" (fun _ ->
DotNet.install (fun p -> {p with Version = DotNet.CliVersion.GlobalJson }) |> ignore
)
Target.create "Restore" (fun _ ->
DotNet.restore (fun p -> p) appPath |> ignore
)
open Fake.IO.FileSystemOperators
Target.create "UpdateConfiguration" (fun _ ->
//Common.setAssemblyInfo productName version TODO make this work with FAKE 5
//TODO redo all of this
match BuildServer.buildServer with
| TeamCity ->
Trace.traceEnvironmentVariables |> ignore
//TODO apply teamcity env variables to config file
| _ ->
Trace.traceEnvironmentVariables |> ignore
)
Target.create "Build" (fun _ ->
runDotNet "build" appPath
runDotNet "build" testsPath
)
Target.create "Run" (fun _ ->
let server = async {
runDotNet "watch run" appPath |> ignore
}
let browser = async {
Threading.Thread.Sleep 8000
openBrowser "https://localhost" |> ignore
}
[server; browser;]
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
)
Target.create "Bundle" (fun _ ->
runDotNet (sprintf "publish \"%s\%s\" -c release -o \"%s\"" appPath "Template.Saturn.WebHost.fsproj" deployDir) __SOURCE_DIRECTORY__
Shell.copyDir (Path.combine deployDir "public") (Path.combine clientPath "public") FileFilter.allFiles
)
type ArmOutput =
{ WebAppName : ParameterValue<string>
WebAppPassword : ParameterValue<string> }
let mutable deploymentOutputs : ArmOutput option = None
Trace.trace (sprintf "The build server is %s" (if isTeamCity then "TeamCity" else "Local"))
//let teamCityDeploy
//https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#create-an-azure-active-directory-application
Target.create "ArmTemplate" (fun _ ->
let environment = Environment.environVarOrDefault "environment" (Guid.NewGuid().ToString().ToLower().Split '-' |> Array.head)
let armTemplate = @"arm-template.json" //TODO consider making this a parameter so can deploy to differnt environments
let resourceGroupName =
match Environment.environVar "resourceGroupName" with
| name when String.IsNullOrEmpty(name) -> "experimental-deploy"
| name -> name
Trace.tracefn "RESOURCE GROUP IS %s" resourceGroupName
let tenantId = try Environment.environVar "tenantId" |> Guid.Parse with _ -> failwith "Invalid Tenant ID. This should be your Azure Directory ID."
let developerDeploy () =
//let authCtx =
// You can safely replace these with your own subscription and client IDs hard-coded into this script.
let subscriptionId = try Environment.environVar "subscriptionId" |> Guid.Parse with _ -> failwith "Invalid Subscription ID. This should be your Azure Subscription ID."
let clientId = try Environment.environVar "clientId" |> Guid.Parse with _ -> failwith "Invalid Client ID. This should be the Client ID of a Native application registered in Azure with permission to create resources in your subscription."
Trace.tracefn "Deploying template '%s' to resource group '%s' in subscription '%O'..." armTemplate resourceGroupName subscriptionId
subscriptionId
|> authenticate Trace.trace { ClientId = clientId; TenantId = Some tenantId } //or authenticate and pass secret? but find out what happens when you use a tenantid
//|> authenticateDevice {ClientId = clientId; ClientSecret = clientSecret; TenantId = tenantId}
|> Async.RunSynchronously
let unattendedDeploy () =
let tenantId = try Environment.environVar "tenantId" |> Guid.Parse with _ -> failwith "Invalid Tenant ID. This should be your Azure Directory ID."
let clientSecret =
match Environment.environVar "clientSecret" with
| secret when String.IsNullOrEmpty(secret) -> failwith "Invalid Client ID. This should be your App Registration Secret Key."
| secret -> secret
//let authCtx =
// You can safely replace these with your own subscription and client IDs hard-coded into this script.
let subscriptionId = try Environment.environVar "subscriptionId" |> Guid.Parse with _ -> failwith "Invalid Subscription ID. This should be your Azure Subscription ID."
let clientId = try Environment.environVar "clientId" |> Guid.Parse with _ -> failwith "Invalid Client ID. This should be the Client ID of a Native application registered in Azure with permission to create resources in your subscription."
Trace.tracefn "Deploying template '%s' to resource group '%s' in subscription '%O'..." armTemplate resourceGroupName subscriptionId
subscriptionId
|> authenticateDevice {ClientId = clientId; ClientSecret = clientSecret; TenantId = tenantId}
let deployment =
let location = Environment.environVarOrDefault "location" Region.USSouthCentral.Name
let pricingTier = Environment.environVarOrDefault "pricingTier" "F1"
{ DeploymentName = "SATURN-template-deploy"
ResourceGroup = New(resourceGroupName, Region.Create location)
ArmTemplate = IO.File.ReadAllText armTemplate
Parameters =
Simple
[ "environment", ArmString environment
"location", ArmString location
"pricingTier", ArmString pricingTier ]
DeploymentMode = Incremental }
Trace.trace (sprintf "The build server is %s" (if isTeamCity then "TeamCity" else "Local"))
deployment
|> deployWithProgress (if isTeamCity then unattendedDeploy() else developerDeploy())
|> Seq.iter(function
| DeploymentInProgress (state, operations) -> Trace.tracefn "State is %s, completed %d operations." state operations
| DeploymentError (statusCode, message) -> Trace.traceError <| sprintf "DEPLOYMENT ERROR: %s - '%s'" statusCode message
| DeploymentCompleted d -> deploymentOutputs <- d)
)
open Fake.IO.Globbing.Operators
open System.Net
// https://github.com/SAFE-Stack/SAFE-template/issues/120
// https://stackoverflow.com/a/6994391/3232646
type TimeoutWebClient() =
inherit WebClient()
override this.GetWebRequest uri =
let request = base.GetWebRequest uri
request.Timeout <- 30 * 60 * 1000
request
//Used to be AppService
Target.create "Deploy" (fun _ ->
//create the deploy folder if it does not exist
if not (Shell.testDir deployDir) then
Shell.mkdir deployDir
let zipFile = "deploy.zip"
IO.File.Delete zipFile
Zip.zip deployDir zipFile !!(deployDir + @"\**\**")
let appName = deploymentOutputs.Value.WebAppName.value
let appPassword = deploymentOutputs.Value.WebAppPassword.value
let destinationUri = sprintf "https://%s.scm.azurewebsites.net/api/zipdeploy" appName
let client = new TimeoutWebClient(Credentials = NetworkCredential("$" + appName, appPassword))
Trace.tracefn "Uploading %s to %s" zipFile destinationUri
client.UploadData(destinationUri, IO.File.ReadAllBytes zipFile) |> ignore
)
Target.create "Test" (fun _ ->
//DotNet.test (fun p -> p) testsPath Use this for xunit
runDotNet "run" testsPath |> ignore //Use this for expecto
)
Target.create "Clean" (fun _ ->
() //TODO cleanup the deploy folder
)
Target.create "Publish" (fun _ ->
DotNet.publish (fun p -> { p with OutputPath = Some "./published"} ) appPath
)
//Target.create "DeployToAzure" (fun _ ->
// Target.runOrDefaultWithArguments "AppService"
//)
open Fake.Core.TargetOperators
"Clean"
//==> "InstallDotNetCore"
//==> "InstallClient"
//==> "CopyConfig"
==> "UpdateConfiguration"
==> "Restore"
==> "Build"
"Clean"
//==> "InstallClient"
//==> "CopyConfig"
==> "UpdateConfiguration"
==> "Restore"
==> "Build"
//==> "Bundle"
==> "Test"
==> "ArmTemplate"
==> "Deploy"
"Clean"
//==> "InstallClient"
==> "Restore"
==> "Run"
"Clean"
//==> "InstallDotNetCore"
//==> "InstallClient"
//==> "CopyConfig"
==> "UpdateConfiguration"
==> "Restore"
==> "Build"
==> "Test"
"Clean"
//==> "InstallDotNetCore"
//==> "CopyConfig"
==> "UpdateConfiguration"
==> "Restore"
==> "Build"
==> "Test"
==> "Publish"
//"Clean"
// //==> "CopyConfig"
// ==> "UpdateConfiguration"
Target.runOrDefaultWithArguments "Test"