diff --git a/images.CI/azure-pipelines/image-generation.yml b/images.CI/azure-pipelines/image-generation.yml new file mode 100644 index 00000000..332f34ad --- /dev/null +++ b/images.CI/azure-pipelines/image-generation.yml @@ -0,0 +1,69 @@ +# Ideally we would use GitHub Actions for this, but since we use self-hosted machines to run image builds +# we need the following features to use GitHub Actions for Images CI: +# - https://github.community/t5/GitHub-Actions/Make-secrets-available-to-builds-of-forks/m-p/30678#M508 +# - https://github.community/t5/GitHub-Actions/GitHub-Actions-Manual-Trigger-Approvals/td-p/31504 +# - https://github.community/t5/GitHub-Actions/Protecting-github-workflows/td-p/30290 + +jobs: +- job: + pool: ci-agent-pool + timeoutInMinutes: 600 + variables: + - group: Image Generation Variables + + steps: + - task: PowerShell@2 + displayName: 'Download custom repository' + condition: and(ne(variables['CUSTOM_REPOSITORY_URL'], ''), ne(variables['CUSTOM_REPOSITORY_BRANCH'], '')) + inputs: + targetType: 'inline' + script: | + Write-Host "Clean up default repository" + Remove-Item -path './*' -Recurse -Force + Write-Host "Download $(CUSTOM_REPOSITORY_BRANCH) branch from $(CUSTOM_REPOSITORY_URL)" + $env:GIT_REDIRECT_STDERR = '2>&1' + git clone $(CUSTOM_REPOSITORY_URL) . -b $(CUSTOM_REPOSITORY_BRANCH) --single-branch --depth 1 + + - task: PowerShell@2 + displayName: 'Build VM' + inputs: + targetType: filePath + filePath: ./images.CI/build-image.ps1 + arguments: -ResourcesNamePrefix $(Build.BuildId) ` + -ClientId $(CLIENT_ID) ` + -ClientSecret $(CLIENT_SECRET) ` + -Image ${{ parameters.image_type }} ` + -ResourceGroup $(AZURE_RESOURCE_GROUP) ` + -StorageAccount $(AZURE_STORAGE_ACCOUNT) ` + -SubscriptionId $(AZURE_SUBSCRIPTION) ` + -TenantId $(AZURE_TENANT) ` + -Location $(AZURE_LOCATION) ` + -VirtualNetworkName $(BUILD_AGENT_VNET_NAME) ` + -VirtualNetworkRG $(BUILD_AGENT_VNET_RESOURCE_GROUP) ` + -VirtualNetworkSubnet $(BUILD_AGENT_SUBNET_NAME) ` + -GitHubFeedToken $(GITHUB_TOKEN) + + - task: PowerShell@2 + displayName: 'Create release for VM deployment' + inputs: + targetType: filePath + filePath: ./images.CI/create-release.ps1 + arguments: -BuildId $(Build.BuildId) ` + -Organization $(RELEASE_TARGET_ORGANIZATION) ` + -DefinitionId $(RELEASE_TARGET_DEFINITION_ID) ` + -Project $(RELEASE_TARGET_PROJECT) ` + -ImageName ${{ parameters.image_type }} ` + -AccessToken $(RELEASE_TARGET_TOKEN) + + - task: PowerShell@2 + displayName: 'Clean up resources' + condition: always() + inputs: + targetType: filePath + filePath: ./images.CI/cleanup.ps1 + arguments: -ResourcesNamePrefix $(Build.BuildId) ` + -ClientId $(CLIENT_ID) ` + -ClientSecret $(CLIENT_SECRET) ` + -Image ${{ parameters.image_type }} ` + -SubscriptionId $(AZURE_SUBSCRIPTION) ` + -TenantId $(AZURE_TENANT) \ No newline at end of file diff --git a/images.CI/azure-pipelines/ubuntu1604.yml b/images.CI/azure-pipelines/ubuntu1604.yml new file mode 100644 index 00000000..8f6dcef2 --- /dev/null +++ b/images.CI/azure-pipelines/ubuntu1604.yml @@ -0,0 +1,19 @@ +schedules: +- cron: "0 0 * * *" + displayName: Daily + branches: + include: + - master + always: true + +trigger: none +pr: + autoCancel: true + branches: + include: + - master + +jobs: +- template: image-generation.yml + parameters: + image_type: ubuntu1604 \ No newline at end of file diff --git a/images.CI/azure-pipelines/ubuntu1804.yml b/images.CI/azure-pipelines/ubuntu1804.yml new file mode 100644 index 00000000..998ba42e --- /dev/null +++ b/images.CI/azure-pipelines/ubuntu1804.yml @@ -0,0 +1,19 @@ +schedules: +- cron: "0 0 * * *" + displayName: Daily + branches: + include: + - master + always: true + +trigger: none +pr: + autoCancel: true + branches: + include: + - master + +jobs: +- template: image-generation.yml + parameters: + image_type: ubuntu1804 \ No newline at end of file diff --git a/images.CI/azure-pipelines/windows2016.yml b/images.CI/azure-pipelines/windows2016.yml new file mode 100644 index 00000000..667b30bd --- /dev/null +++ b/images.CI/azure-pipelines/windows2016.yml @@ -0,0 +1,19 @@ +schedules: +- cron: "0 0 * * *" + displayName: Daily + branches: + include: + - master + always: true + +trigger: none +pr: + autoCancel: true + branches: + include: + - master + +jobs: +- template: image-generation.yml + parameters: + image_type: Windows2016-Azure \ No newline at end of file diff --git a/images.CI/azure-pipelines/windows2019.yml b/images.CI/azure-pipelines/windows2019.yml new file mode 100644 index 00000000..508fa12b --- /dev/null +++ b/images.CI/azure-pipelines/windows2019.yml @@ -0,0 +1,19 @@ +schedules: +- cron: "0 0 * * *" + displayName: Daily + branches: + include: + - master + always: true + +trigger: none +pr: + autoCancel: true + branches: + include: + - master + +jobs: +- template: image-generation.yml + parameters: + image_type: Windows2019-Azure \ No newline at end of file diff --git a/images.CI/build-image.ps1 b/images.CI/build-image.ps1 new file mode 100644 index 00000000..54748e41 --- /dev/null +++ b/images.CI/build-image.ps1 @@ -0,0 +1,60 @@ +param( + [String] [Parameter (Mandatory=$true)] $Image, + [String] [Parameter (Mandatory=$true)] $ClientId, + [String] [Parameter (Mandatory=$true)] $ClientSecret, + [String] [Parameter (Mandatory=$true)] $GitHubFeedToken, + [String] [Parameter (Mandatory=$true)] $ResourcesNamePrefix, + [String] [Parameter (Mandatory=$true)] $Location, + [String] [Parameter (Mandatory=$true)] $ResourceGroup, + [String] [Parameter (Mandatory=$true)] $StorageAccount, + [String] [Parameter (Mandatory=$true)] $SubscriptionId, + [String] [Parameter (Mandatory=$true)] $TenantId, + [String] [Parameter (Mandatory=$true)] $VirtualNetworkName, + [String] [Parameter (Mandatory=$true)] $VirtualNetworkRG, + [String] [Parameter (Mandatory=$true)] $VirtualNetworkSubnet +) + +$TemplatePath = (Get-ChildItem -Path "images" -Include "$Image.json" -Recurse -Depth 2).FullName +if (-not $TemplatePath) +{ + Write-Error "'-Image' parameter is not valid. You have to specify correct image type." + exit 1 +} + +$TempResourceGroupName = "${ResourcesNamePrefix}_${Image}" +$InstallPassword = [System.GUID]::NewGuid().ToString().ToUpper() + +packer validate -syntax-only $TemplatePath + +$SensitiveData = @( + 'OSType', + 'StorageAccountLocation', + 'OSDiskUri', + 'OSDiskUriReadOnlySas', + 'TemplateUri', + 'TemplateUriReadOnlySas', + ': ->' +) + +Write-Host "Build $Image VM" +packer build -var "capture_name_prefix=$ResourcesNamePrefix" ` + -var "client_id=$ClientId" ` + -var "client_secret=$ClientSecret" ` + -var "install_password=$InstallPassword" ` + -var "github_feed_token=$GitHubFeedToken" ` + -var "location=$Location" ` + -var "resource_group=$ResourceGroup" ` + -var "storage_account=$StorageAccount" ` + -var "subscription_id=$SubscriptionId" ` + -var "temp_resource_group_name=$TempResourceGroupName" ` + -var "tenant_id=$TenantId" ` + -var "virtual_network_name=$VirtualNetworkName" ` + -var "virtual_network_resource_group_name=$VirtualNetworkRG" ` + -var "virtual_network_subnet_name=$VirtualNetworkSubnet" ` + $TemplatePath ` + | Where-Object { + #Filter sensitive data from Packer logs + $currentString = $_ + $sensitiveString = $SensitiveData | Where-Object { $currentString -match $_ } + $sensitiveString -eq $null + } \ No newline at end of file diff --git a/images.CI/cleanup.ps1 b/images.CI/cleanup.ps1 new file mode 100644 index 00000000..00050d55 --- /dev/null +++ b/images.CI/cleanup.ps1 @@ -0,0 +1,21 @@ +param( + [String] [Parameter (Mandatory=$true)] $Image, + [String] [Parameter (Mandatory=$true)] $ResourcesNamePrefix, + [String] [Parameter (Mandatory=$true)] $ClientId, + [String] [Parameter (Mandatory=$true)] $ClientSecret, + [String] [Parameter (Mandatory=$true)] $SubscriptionId, + [String] [Parameter (Mandatory=$true)] $TenantId +) + +az login --service-principal --username $ClientId --password $ClientSecret --tenant $TenantId | Out-Null + +$TempResourceGroupName = "${ResourcesNamePrefix}_${Image}" + +$groupExist = az group exists --name $TempResourceGroupName --subscription $SubscriptionId | Out-Null +if ($groupExist -eq "true") { + Write-Host "Found a match, deleting temporary files" + az group delete --name $TempResourceGroupName --subscription $SubscriptionId --yes | Out-Null + Write-Host "Temporary group was deleted succesfully" -ForegroundColor Green +} else { + Write-Host "No temporary groups found" +} \ No newline at end of file diff --git a/images.CI/create-release.ps1 b/images.CI/create-release.ps1 new file mode 100644 index 00000000..72d0db4e --- /dev/null +++ b/images.CI/create-release.ps1 @@ -0,0 +1,31 @@ +param( + [UInt32] [Parameter (Mandatory)] $BuildId, + [String] [Parameter (Mandatory)] $Organization, + [String] [Parameter (Mandatory)] $Project, + [String] [Parameter (Mandatory)] $ImageName, + [String] [Parameter (Mandatory)] $DefinitionId, + [String] [Parameter (Mandatory)] $AccessToken +) + +$Body = @{ + definitionId = $DefinitionId + variables = @{ + ImageBuildId = @{ + value = $BuildId + } + ImageName = @{ + value = $ImageName + } + } + isDraft = "false" +} | ConvertTo-Json -Depth 3 + +$URL = "https://vsrm.dev.azure.com/$Organization/$Project/_apis/release/releases?api-version=5.1" +$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("'':${AccessToken}")) +$headers = @{ + Authorization = "Basic ${base64AuthInfo}" +} + +$NewRelease = Invoke-RestMethod $URL -Body $Body -Method "POST" -Headers $headers -ContentType "application/json" + +Write-Host "Created release: $($NewRelease._links.web.href)" \ No newline at end of file