-
Notifications
You must be signed in to change notification settings - Fork 70
/
Connect-AppVeyorToDocker.ps1
317 lines (252 loc) · 13.8 KB
/
Connect-AppVeyorToDocker.ps1
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
Function Connect-AppVeyorToDocker {
<#
.SYNOPSIS
Command to enable Docker builds. Works with both hosted AppVeyor and AppVeyor Server.
.DESCRIPTION
You can connect your AppVeyor account (on both hosted AppVeyor and on-premise AppVeyor Server) to Docker for AppVeyor to instantiate build containers on it.
.PARAMETER AppVeyorUrl
AppVeyor URL. For hosted AppVeyor it is https://ci.appveyor.com. For Appveyor Server users it is URL of on-premise AppVeyor Server installation
.PARAMETER ApiToken
API key for specific account (not 'All accounts'). Hosted AppVeyor users can find it at https://ci.appveyor.com/api-keys. Appveyor Server users can find it at <appveyor_server_url>/api-keys.
.PARAMETER ImageOs
Operating system of container image. Valid values: 'Windows', 'Linux'.
.PARAMETER ImageName
Description to be used for AppVeyor image.
.PARAMETER ImageTemplate
Docker image name.
.PARAMETER ImageFeatures
Optional comma-separated list of image products/tools/libraries that should be installed on top of the base image.
.PARAMETER ImageCustomScriptsUrl
Optional URL to a repository or gist with custom scripts that should be run during image building.
.EXAMPLE
Connect-AppVeyorToDocker
Let command collect all required information
.EXAMPLE
Connect-AppVeyorToDocker -ApiToken XXXXXXXXXXXXXXXXXXXXX -AppVeyorUrl "https://ci.appveyor.com" -ImageOs Windows -ImageName Windows -ImageTemplate 'appveyor/build-image:minimal-nanoserver-1809'
Run command with all required parameters so command will ask no questions. It will pull Docker image and configure Docker build cloud in AppVeyor.
#>
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true,HelpMessage="AppVeyor URL`nFor hosted AppVeyor it is https://ci.appveyor.com`nFor Appveyor Server users it is URL of on-premise AppVeyor Server installation")]
[string]$AppVeyorUrl,
[Parameter(Mandatory=$true,HelpMessage="API key for specific account (not 'All accounts')`nHosted AppVeyor users can find it at https://ci.appveyor.com/api-keys`nAppveyor Server users can find it at <appveyor_server_url>/api-keys")]
[string]$ApiToken,
[Parameter(Mandatory=$true)]
[ValidateSet('Windows','Linux')]
[string]$ImageOs,
[Parameter(Mandatory=$true)]
[string]$ImageName,
[Parameter(Mandatory=$true)]
[string]$ImageTemplate,
[Parameter(Mandatory=$false)]
[string]$ImageFeatures,
[Parameter(Mandatory=$false)]
[string]$ImageCustomScript
)
function ExitScript {
# some cleanup?
break all
}
$ErrorActionPreference = "Stop"
$StopWatch = New-Object System.Diagnostics.Stopwatch
$StopWatch.Start()
#Sanitize input
$AppVeyorUrl = $AppVeyorUrl.TrimEnd("/")
#Validate AppVeyor API access
$headers = ValidateAppVeyorApiAccess $AppVeyorUrl $ApiToken
EnsureElevatedModeOnWindows
try {
$hostName = $env:COMPUTERNAME # Windows
if ($isLinux) {
# Linux
$hostName = (hostname)
} elseif ($isMacOS) {
# macOS
$hostName = (hostname)
}
# make sure Docker is installed and available in the path
Write-Host "`nEnsure Docker engine is installed and available in PATH" -ForegroundColor Cyan
if (-not (Get-Command docker -ErrorAction Ignore)) {
Write-Warning "Looks like Docker is not installed. Please install Docker and re-run the command."
return
} else {
Write-Host "Docker is installed"
}
$isWindowsOs = (-not $isLinux -and -not $isMacOS)
# ensure Docker experimental mode is enabled or Docker is in Linux mode if Linux image on Windows is selected
if ($isWindowsOs -and $ImageOs -eq 'Linux') {
Write-Host "`nChecking if Docker engine is in experimental or Linux mode to run Linux images on Windows" -ForegroundColor Cyan
$dockerVersion = (docker version -f "{{json .}}") | ConvertFrom-Json
if ($dockerVersion.Server.Os -ne 'linux' -and -not $dockerVersion.Server.Experimental) {
Write-Warning "To configure Linux-based image on Windows platform the Docker should be either in experimental mode (with LCOW enabled) or switched into Linux mode (if it's Docker CE)."
return
} else {
Write-Host "Docker engine is configured to run Linux images"
}
}
Write-Host "`nConfiguring 'Docker' build cloud in AppVeyor" -ForegroundColor Cyan
$build_cloud_name = "$hostName Docker"
$hostAuthorizationToken = [Guid]::NewGuid().ToString('N')
$dockerImageTag = CreateSlug "appveyor-byoc-$ImageName"
# base image name
$baseImageName = $ImageTemplate
$idx = $baseImageName.IndexOf(':')
if ($idx -ne -1) {
$baseImageName = $ImageTemplate.Substring(0, $idx)
}
$dockerImageName = "$dockerImageTag"
$clouds = Invoke-RestMethod -Uri "$AppVeyorUrl/api/build-clouds" -Headers $headers -Method Get
$cloud = $clouds | Where-Object ({$_.name -eq $build_cloud_name})[0]
if (-not $cloud) {
# check if there is a cloud already with the name "$build_cloud_name" and grab $hostAuthorizationToken from there
$process_build_cloud_name = "$hostName"
$processCloud = $clouds | Where-Object ({$_.name -eq $process_build_cloud_name})[0]
if ($processCloud -and $processCloud.CloudType -eq 'Process') {
Write-Host "There is an existing 'Process' cloud for that computer. Reading Host Agent authorization token from Process cloud." -ForegroundColor DarkGray
$settings = Invoke-RestMethod -Uri "$AppVeyorUrl/api/build-clouds/$($processCloud.buildCloudId)" -Headers $headers -Method Get
$hostAuthorizationToken = $settings.hostAuthorizationToken
}
# Add new build cloud
$body = @{
cloudType = "Docker"
name = $build_cloud_name
hostAuthorizationToken = $hostAuthorizationToken
workersCapacity = 20
settings = @{
failureStrategy = @{
jobStartTimeoutSeconds = 60
provisioningAttempts = 2
}
cloudSettings = @{
general = @{
}
networking = @{
}
images = @(@{
name = $ImageName
dockerImageName = $dockerImageName
})
}
}
}
$jsonBody = $body | ConvertTo-Json -Depth 10
Invoke-RestMethod -Uri "$AppVeyorUrl/api/build-clouds" -Headers $headers -Body $jsonBody -Method Post | Out-Null
$clouds = Invoke-RestMethod -Uri "$AppVeyorUrl/api/build-clouds" -Headers $headers -Method Get
$cloud = $clouds | Where-Object ({$_.name -eq $build_cloud_name})[0]
Write-Host "A new AppVeyor build cloud '$build_cloud_name' has been added."
} else {
Write-Host "AppVeyor cloud '$build_cloud_name' already exists." -ForegroundColor DarkGray
if ($cloud.CloudType -eq 'Docker') {
Write-Host "Reading Host Agent authorization token from the existing cloud."
$settings = Invoke-RestMethod -Uri "$AppVeyorUrl/api/build-clouds/$($cloud.buildCloudId)" -Headers $headers -Method Get
$hostAuthorizationToken = $settings.hostAuthorizationToken
# check if the image already added
$image = $settings.settings.cloudSettings.images | Where-Object ({$_.name -eq $ImageName})[0]
if ($image) {
Write-host "Image '$ImageName' is already configured on cloud settings." -ForegroundColor DarkGray
$image.dockerImageName = $dockerImageName
Write-Host "Updating Docker image name to $dockerImageName"
} else {
Write-host "Adding new '$ImageName' image to the cloud settings."
Write-Host "Docker image name is $dockerImageName"
$image = @{
'name' = $ImageName
'dockerImageName' = $dockerImageName
}
$image = $image | ConvertTo-Json | ConvertFrom-Json
$settings.settings.cloudSettings.images += $image
}
$jsonBody = $settings | ConvertTo-Json -Depth 10
Invoke-RestMethod -Uri "$AppVeyorUrl/api/build-clouds"-Headers $headers -Body $jsonBody -Method Put | Out-Null
Write-Host "Cloud settings updated."
} else {
throw "Existing build cloud '$build_cloud_name' is not of 'Process' type."
}
}
# pull base Docker image and then build a new (optionally) and tag
Write-Host "`nPulling base Docker image $ImageTemplate" -ForegroundColor Cyan
docker pull $ImageTemplate
# tag image
if ($ImageFeatures -or $ImageCustomScript) {
# build new image
Write-Host "`nBuilding a new Docker image with custom features and/or script" -ForegroundColor Cyan
$tmp = $env:TEMP
if ($isMacOS -or $isLinux) {
$tmp = "/tmp"
}
# create temp dir for Dockerfile
$dockerTempPath = Join-Path -Path $tmp -ChildPath ([Guid]::NewGuid().ToString('N'))
New-Item $dockerTempPath -Type Directory | Out-Null
$dockerfilePath = Join-Path -Path $dockerTempPath -ChildPath 'Dockerfile'
$dockerfile = @()
$dockerfile += "FROM $ImageTemplate"
if ($ImageOs -eq 'Linux') {
# build Linux image
if (-not $ImageTemplate.StartsWith('appveyor/build-image')) {
# full image
$scriptsPath = Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'scripts') -ChildPath 'Ubuntu'
$destPath = Join-Path -Path (Join-Path -Path $dockerTempPath -ChildPath 'scripts') -ChildPath 'Ubuntu'
Copy-Item $scriptsPath $destPath -Recurse
if ($ImageFeatures) {
$dockerfile += "ENV OPT_FEATURES=$ImageFeatures"
}
$dockerfile += "ENV IS_DOCKER=true"
$dockerfile += "COPY ./scripts/Ubuntu ./scripts"
$dockerfile += "RUN chmod +x ./scripts/minimalconfig.sh && ./scripts/minimalconfig.sh"
}
if ($ImageCustomScript) {
$customScriptPath = Join-Path -Path $dockerTempPath -ChildPath 'script.sh'
$decodedScript = [Text.Encoding]::UTF8.GetString(([Convert]::FromBase64String($ImageCustomScript)))
[IO.File]::WriteAllText($customScriptPath, $decodedScript.Replace("`r`n", "`n"))
$dockerfile += "COPY ./script.sh ."
$dockerfile += "RUN sudo chmod +x ./script.sh && ./script.sh"
}
$dockerfile += "USER appveyor"
$dockerfile += "CMD [ `"/bin/bash`", `"/scripts/entrypoint.sh`" ]"
} else {
# build Windows image
if ($ImageCustomScript) {
$customScriptPath = Join-Path -Path $dockerTempPath -ChildPath 'script.ps1'
$decodedScript = [Text.Encoding]::UTF8.GetString(([Convert]::FromBase64String($ImageCustomScript)))
[IO.File]::WriteAllText($customScriptPath, "`$ErrorActionPreference = `"Stop`"`n$decodedScript")
$dockerfile += "COPY script.ps1 ."
$dockerfile += "RUN pwsh -noni -ep unrestricted .\script.ps1"
}
}
# write and build Dockerfile
[IO.File]::WriteAllLines($dockerfilePath, $dockerfile)
docker build -t $dockerImageName -f $dockerfilePath $dockerTempPath
Remove-Item $dockerTempPath -Force -Recurse
} else {
# just tag existing one
Write-host "No custom image has been built - just tagging '$ImageTemplate' image as '$dockerImageName'" -ForegroundColor DarkGray
docker tag $ImageTemplate $dockerImageName
}
Write-host "`nEnsure build worker image is available for AppVeyor projects" -ForegroundColor Cyan
$images = Invoke-RestMethod -Uri "$AppVeyorUrl/api/build-worker-images" -Headers $headers -Method Get
$image = $images | Where-Object ({$_.name -eq $ImageName})[0]
if (-not $image) {
$body = @{
name = $imageName
osType = $ImageOs
}
$jsonBody = $body | ConvertTo-Json
Invoke-RestMethod -Uri "$AppVeyorUrl/api/build-worker-images" -Headers $headers -Body $jsonBody -Method Post | Out-Null
Write-host "AppVeyor build worker image '$ImageName' has been created."
} else {
Write-host "AppVeyor build worker image '$ImageName' already exists." -ForegroundColor DarkGray
}
# Install Host Agent
InstallAppVeyorHostAgent $AppVeyorUrl $hostAuthorizationToken
$StopWatch.Stop()
$completed = "{0:hh}:{0:mm}:{0:ss}" -f $StopWatch.elapsed
Write-Host "`nThe script successfully completed in $completed." -ForegroundColor Green
#Report results and next steps
PrintSummary 'Docker' $AppVeyorUrl $cloud.buildCloudId $build_cloud_name $imageName
}
catch {
Write-Error $_
ExitScript
}
}