diff --git a/.github/powershell/APIv1/Add-DeploymentPackage.ps1 b/.github/powershell/APIv1/Add-DeploymentPackage.ps1 deleted file mode 100644 index e312746..0000000 --- a/.github/powershell/APIv1/Add-DeploymentPackage.ps1 +++ /dev/null @@ -1,63 +0,0 @@ -param( - [Parameter(Position=0)] - [string] - $ProjectId, - - [Parameter(Position=1)] - [string] - $DeploymentId, - - [Parameter(Position=2)] - [string] - $ApiKey, - - [Parameter(Position=3)] - [string] - $FilePath, - - [Parameter(Position=4)] - [string] - $BaseUrl = "https://api.cloud.umbraco.com" -) - -### Endpoint docs -# https://docs.umbraco.com/umbraco-cloud/set-up/project-settings/umbraco-cicd/umbracocloudapi#upload-zip-source-file -# -$url = "$BaseUrl/v1/projects/$ProjectId/deployments/$DeploymentId/package" - -$fieldName = 'file' -$contentType = 'application/zip' -$umbracoHeader = @{ 'Umbraco-Cloud-Api-Key' = $ApiKey } - - -$fileStream = [System.IO.FileStream]::new($filePath, [System.IO.FileMode]::Open) -$fileHeader = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new('form-data') -$fileHeader.Name = $fieldName -$fileHeader.FileName = Split-Path -leaf $filePath -$fileContent = [System.Net.Http.StreamContent]::new($fileStream) -$fileContent.Headers.ContentDisposition = $fileHeader -$fileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse($contentType) - -$multipartContent = [System.Net.Http.MultipartFormDataContent]::new() -$multipartContent.Add($fileContent) - -try { - $response = Invoke-WebRequest -Body $multipartContent -Headers $umbracoHeader -Method 'POST' -Uri $url - if ($response.StatusCode -ne 202) - { - Write-Host "---Response Start---" - Write-Host $response - Write-Host "---Response End---" - Write-Host "Unexpected response - see above" - exit 1 - } - - Write-Host $response.Content | ConvertTo-Json - exit 0 -} -catch -{ - Write-Host "---Error---" - Write-Host $_ - exit 1 -} \ No newline at end of file diff --git a/.github/powershell/APIv1/New-Deployment.ps1 b/.github/powershell/APIv1/New-Deployment.ps1 deleted file mode 100644 index d6e0991..0000000 --- a/.github/powershell/APIv1/New-Deployment.ps1 +++ /dev/null @@ -1,79 +0,0 @@ -param( - [Parameter(Position=0)] - [string] - $ProjectId, - - [Parameter(Position=1)] - [string] - $ApiKey, - - [Parameter(Position=2)] - [string] - $CommitMessage, - - [Parameter(Position=3)] - [string] - $PipelineVendor, ## GITHUB or AZUREDEVOPS - - [Parameter(Position=4)] - [string] - $BaseUrl = "https://api.cloud.umbraco.com" -) - -### Endpoint docs -# https://docs.umbraco.com/umbraco-cloud/set-up/project-settings/umbraco-cicd/umbracocloudapi#create-the-deployment -# - -$url = "$BaseUrl/v1/projects/$ProjectId/deployments" - -$headers = @{ - 'Umbraco-Cloud-Api-Key' = $ApiKey - 'Content-Type' = 'application/json' -} - -$body = @{ - 'commitMessage' = $CommitMessage -} | ConvertTo-Json - -Write-Host "Posting to $url with commit message: $CommitMessage" -try { - $response = Invoke-RestMethod -URI $url -Headers $headers -Method POST -Body $body - $status = $response.deploymentState - $deploymentId = $response.deploymentId - - if ($status -eq "Created") { - - Write-Host $response.updateMessage - - switch ($PipelineVendor) { - "GITHUB" { - "deploymentId=$($deploymentId)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append - } - "AZUREDEVOPS" { - Write-Host "##vso[task.setvariable variable=deploymentId;]$($deploymentId)" - } - "TESTRUN" { - Write-Host $PipelineVendor - } - Default { - Write-Host "Please use one of the supported Pipeline Vendors or enhance script to fit your needs" - Write-Host "Currently supported are: GITHUB and AZUREDEVOPS" - Exit 1 - } - } - - Write-Host "Deployment Created Successfully => $($deploymentId)" - exit 0 - } - - Write-Host "---Response Start---" - Write-Host $response - Write-Host "---Response End---" - Write-Host "Unexpected response - see above" - exit 1 -} -catch { - Write-Host "---Error---" - Write-Host $_ - exit 1 -} diff --git a/.github/powershell/APIv1/Start-Deployment.ps1 b/.github/powershell/APIv1/Start-Deployment.ps1 deleted file mode 100644 index 80fc675..0000000 --- a/.github/powershell/APIv1/Start-Deployment.ps1 +++ /dev/null @@ -1,53 +0,0 @@ -param( - [Parameter(Position=0)] - [string] - $ProjectId, - - [Parameter(Position=1)] - [string] - $DeploymentId, - - [Parameter(Position=2)] - [string] - $ApiKey, - - [Parameter(Position=3)] - [string] - $BaseUrl = "https://api.cloud.umbraco.com" -) - -### Endpoint docs -# https://docs.umbraco.com/umbraco-cloud/set-up/project-settings/umbraco-cicd/umbracocloudapi#start-deployment -# -$url = "$BaseUrl/v1/projects/$ProjectId/deployments/$DeploymentId" - -$headers = @{ - 'Umbraco-Cloud-Api-Key' = $ApiKey - 'Content-Type' = 'application/json' -} - -$requestBody = @{ - 'deploymentState' = 'Queued' -} | ConvertTo-Json - -try { - Write-Host "Requesting start Deployment at $url" - $response = Invoke-RestMethod -URI $url -Headers $headers -Method PATCH -Body $requestBody - $status = $response.deploymentState - - if ($status -eq "Queued") { - Write-Host $Response.updateMessage - exit 0 - } - - Write-Host "---Response Start---" - Write-Host $response - Write-Host "---Response End---" - Write-Host "Unexpected response - see above" - exit 1 -} -catch { - Write-Host "---Error---" - Write-Host $_ - exit 1 -} \ No newline at end of file diff --git a/.github/powershell/APIv1/Test-DeploymentStatus.ps1 b/.github/powershell/APIv1/Test-DeploymentStatus.ps1 deleted file mode 100644 index 21387a0..0000000 --- a/.github/powershell/APIv1/Test-DeploymentStatus.ps1 +++ /dev/null @@ -1,95 +0,0 @@ -param( - [Parameter(Position=0)] - [string] - $ProjectId, - - [Parameter(Position=1)] - [string] - $DeploymentId, - - [Parameter(Position=2)] - [string] - $ApiKey, - - [Parameter(Position=3)] - [int] - $TimeoutSeconds = 1200, - - [Parameter(Position=4)] - [string] - $BaseUrl = "https://api.cloud.umbraco.com" -) - -### Endpoint docs -# https://docs.umbraco.com/umbraco-cloud/set-up/project-settings/umbraco-cicd/umbracocloudapi#get-deployment-status -# -$url = "$BaseUrl/v1/projects/$ProjectId/deployments/$DeploymentId" - -$timer = [Diagnostics.Stopwatch]::StartNew() - -$headers = @{ - 'Umbraco-Cloud-Api-Key' = $ApiKey - 'Content-Type' = 'application/json' -} - -function Request-Deployment-Status ([INT]$run){ - Write-Host "=====> Requesting Status - Run number $run" - try { - $response = Invoke-WebRequest -URI $url -Headers $headers - if ($response.StatusCode -eq 200) { - - $jsonResponse = ConvertFrom-Json $([String]::new($response.Content)) - - Write-Host $jsonResponse.updateMessage - return $jsonResponse.deploymentState - } - } - catch - { - Write-Host "---Error---" - Write-Host $_ - exit 1 - } -} - -$run = 1 - -$statusesBeforeCompleted = ("Pending", "InProgress", "Queued") - -do { - $deploymentStatus = Request-Deployment-Status($run) - $run++ - - # Handle timeout - if ($timer.Elapsed.TotalSeconds -gt $TimeoutSeconds){ - throw "Timeout was reached" - } - - # Dont write if Deployment was finished - if ($statusesBeforeCompleted -contains $deploymentStatus){ - Write-Host "=====> Still Deploying - sleeping for 15 seconds" - Start-Sleep -Seconds 15 - } - -} while ( - $statusesBeforeCompleted -contains $deploymentStatus -) - -$timer.Stop() - -# Successfully deployed to cloud -if ($deploymentStatus -eq 'Completed'){ - Write-Host "Deployment completed successfully" - exit 0 -} - -# Deployment has failed -if ($deploymentStatus -eq 'Failed'){ - Write-Host "Deployment Failed" - exit 1 -} - -# Unexpected deployment status - considered a fail -Write-Host "Unexpected status: $deploymentStatus" -exit 1 - diff --git a/.github/powershell/APIv2/Add-DeploymentArtifact.ps1 b/.github/powershell/APIv2/Add-DeploymentArtifact.ps1 new file mode 100644 index 0000000..86741c9 --- /dev/null +++ b/.github/powershell/APIv2/Add-DeploymentArtifact.ps1 @@ -0,0 +1,128 @@ +param( + [Parameter(Position=0)] + [string] + $ProjectId, + + [Parameter(Position=1)] + [string] + $ApiKey, + + [Parameter(Position=2)] + [string] + $FilePath, + + [Parameter(Position=3)] + [string] + $Description = $null, + + [Parameter(Position=4)] + [string] + $Version = $null, + + [Parameter(Position=5)] + [string] + $PipelineVendor, ## GITHUB or AZUREDEVOPS + + [Parameter(Position=6)] + [string] + $BaseUrl = "https://api.cloud.umbraco.com" +) + +### Endpoint docs +# https://docs.umbraco.com/umbraco-cloud/set-up/project-settings/umbraco-cicd/umbracocloudapi/todo-v2 +# +$url = "$BaseUrl/v2/projects/$ProjectId/deployments/artifacts" + +# test if file is present +if (-not $FilePath) { + Write-Host "FilePath is empty" + exit 1 +} + +if (-not (Test-Path -Path $FilePath -PathType Leaf)) { + Write-Host "FilePath does not contain a file" + exit 1 +} +# end test + +$Headers = @{ + 'accept' = 'application/json' + 'Content-Type' = 'multipart/form-data' + 'Umbraco-Cloud-Api-Key' = $ApiKey +} +$contentType = 'application/zip' + +$multipartContent = [System.Net.Http.MultipartFormDataContent]::new() + +$fileStream = [System.IO.FileStream]::new($filePath, [System.IO.FileMode]::Open) +$fileHeader = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new('form-data') +$fileHeader.Name = 'file' +$fileHeader.FileName = Split-Path -leaf $filePath +$fileContent = [System.Net.Http.StreamContent]::new($fileStream) +$fileContent.Headers.ContentDisposition = $fileHeader +$fileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse($contentType) + +$multipartContent.Add($fileContent) + +$stringHeader = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data") +$stringHeader.Name = "description" +$StringContent = [System.Net.Http.StringContent]::new($Description) +$StringContent.Headers.ContentDisposition = $stringHeader +$multipartContent.Add($stringContent) + +$stringHeader = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data") +$stringHeader.Name = "version" +$StringContent = [System.Net.Http.StringContent]::new($Version) +$StringContent.Headers.ContentDisposition = $stringHeader +$multipartContent.Add($stringContent) + + +try { + $response = Invoke-WebRequest -Body $multipartContent -Headers $Headers -Method 'POST' -Uri $url + if ($response.StatusCode -ne 200) + { + Write-Host "---Response Start---" + Write-Host $response + Write-Host "---Response End---" + Write-Host "Unexpected response - see above" + exit 1 + } + + $responseJson = $response.Content | ConvertFrom-Json + $artifactId = $responseJson.artifactId + + switch ($PipelineVendor) { + "GITHUB" { + "artifactId=$($artifactId)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + } + "AZUREDEVOPS" { + Write-Host "##vso[task.setvariable variable=artifactId;isOutput=true]$($artifactId)" + } + "TESTRUN" { + Write-Host $PipelineVendor + } + Default { + Write-Host "Please use one of the supported Pipeline Vendors or enhance script to fit your needs" + Write-Host "Currently supported are: GITHUB and AZUREDEVOPS" + Exit 1 + } + } + + Write-Host "Artifact uploaded - Artifact Id: $($artifactId)" + Write-Host "--- Upload Response ---" + Write-Output $responseJson + + exit 0 +} +catch +{ + Write-Host "---Error---" + Write-Host $_.Exception.Message + if ($null -ne $_.Exception.Response) { + $responseStream = $_.Exception.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($responseStream) + $responseBody = $reader.ReadToEnd() + Write-Host "Response Body: $responseBody" + } + exit 1 +} \ No newline at end of file diff --git a/.github/powershell/APIv2/Apply-Patch.ps1 b/.github/powershell/APIv2/Apply-Patch.ps1 new file mode 100644 index 0000000..f26ba28 --- /dev/null +++ b/.github/powershell/APIv2/Apply-Patch.ps1 @@ -0,0 +1,90 @@ +param ( + [Parameter(Position=0)] + [string] + $PatchFile, + + [Parameter(Position=1)] + [string] + $LatestDeploymentId, + + [Parameter(Position=3)] + [string] + $PipelineVendor, + + [Parameter(Position=3)] + [string] + $GitUserName, + + [Parameter(Position=4)] + [string] + $GitUserEmail +) + +git config user.name $GitUserName +git config user.email $GitUserEmail + +If ($PipelineVendor -eq "AZUREDEVOPS"){ + # we need to checkout the specific branch to be able to commit bad to repo in Azure DevOps + git checkout $env:BUILD_SOURCEBRANCHNAME +} + +Write-Host "Testing the patch - errors might show up, and that is okay" +Write-Host "==========================================================" +# Check if the patch has been applied already, skip if it has +git apply $PatchFile --reverse --ignore-space-change --ignore-whitespace --check +If ($LASTEXITCODE -eq 0) { + Write-Host "Patch already applied === concluding the apply patch part" + Exit 0 +} Else { + Write-Host "Patch not applied yet" +} + +Write-Host "Checking if patch can be applied..." +# Check if the patch can be applied +git apply $PatchFile --ignore-space-change --ignore-whitespace --check +If ($LASTEXITCODE -eq 0) { + Write-Host "Patch needed, trying to apply now" + Write-Host "=================================" + git apply $PatchFile --ignore-space-change --ignore-whitespace + + switch ($PipelineVendor) { + "GITHUB" { + git add * + git commit -m "Adding cloud changes since deployment $LatestDeploymentId [skip ci]" + git push + $updatedSha = git rev-parse HEAD + + # Record the new sha for the deploy + "updatedSha=$($updatedSha)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + } + "AZUREDEVOPS" { + git add --all + git commit -m "Adding cloud changes since deployment $LatestDeploymentId [skip ci]" + git push --set-upstream origin $env:BUILD_SOURCEBRANCHNAME + + # Record the new sha for the deploy + $updatedSha = git rev-parse HEAD + Write-Host "##vso[task.setvariable variable=updatedSha;isOutput=true]$($updatedSha)" + } + "TESTRUN" { + Write-Host $PipelineVendor + } + Default { + Write-Host "Please use one of the supported Pipeline Vendors or enhance script to fit your needs" + Write-Host "Currently supported are: GITHUB and AZUREDEVOPS" + Exit 1 + } + } + + Write-Host "Changes are applied successfully" + Write-Host "" + Write-Host "Updated SHA: $updatedSha" + Exit 0 +} Else { + Write-Host "" + Write-Host "Patch cannot be applied - please check the output below for the problematic parts" + Write-Host "=================================================================================" + Write-Host "" + git apply -v --reject $PatchFile --ignore-space-change --ignore-whitespace --check + Exit 1 +} \ No newline at end of file diff --git a/.github/powershell/APIv2/Get-ChangesById.ps1 b/.github/powershell/APIv2/Get-ChangesById.ps1 new file mode 100644 index 0000000..89e06b8 --- /dev/null +++ b/.github/powershell/APIv2/Get-ChangesById.ps1 @@ -0,0 +1,104 @@ +# Set variables +param( + [Parameter(Position=0)] + [string] + $ProjectId, + + [Parameter(Position=1)] + [string] + $ApiKey, + + [Parameter(Position=2)] + [string] + $DeploymentId, + + [Parameter(Position=3)] + [string] + $TargetEnvironmentAlias, + + [Parameter(Position=4)] + [string] + $DownloadFolder, + + [Parameter(Position=5)] + [string] + $PipelineVendor, ## GITHUB or AZUREDEVOPS + + [Parameter(Position=6)] + [string] + $BaseUrl = "https://api.cloud.umbraco.com" +) + +### Endpoint docs +# https://docs.umbraco.com/umbraco-cloud/set-up/project-settings/umbraco-cicd/umbracocloudapi/todo-v2 +# +$ChangeUrl = "$BaseUrl/v2/projects/$ProjectId/deployments/$DeploymentId/diff?targetEnvironmentAlias=$TargetEnvironmentAlias" + +$Headers = @{ + 'Umbraco-Cloud-Api-Key' = $ApiKey + 'Content-Type' = 'application/json' +} + +if ($GetDiffDeploymentId -eq '') { + Write-Host "I need a DeploymentId of an older deployment to download a git-patch" + Exit 1; +} + +# ensure folder exists +if (!(Test-Path $DownloadFolder -PathType Container)) { + Write-Host "Creting folder $($DownloadFolder)" + New-Item -ItemType Directory -Force -Path $DownloadFolder +} + +try { + $Response = Invoke-WebRequest -URI $ChangeUrl -Headers $Headers + $StatusCode = $Response.StatusCode + + switch ($StatusCode){ + "204" { + Write-Host "No Changes - You can continue" + $remoteChanges = "no" + } + "200" { + Write-Host "Changes detected" + $Response | Select-Object -ExpandProperty Content | Out-File "$DownloadFolder/git-patch.diff" + $remoteChanges = "yes" + } + Default { + Write-Host "---Response Start---" + Write-Host $Response + Write-Host "---Response End---" + Write-Host "Unexpected response - see above" + exit 1 + } + } + + switch ($PipelineVendor) { + "GITHUB" { + "remoteChanges=$($remoteChanges)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + } + "AZUREDEVOPS" { + Write-Host "##vso[task.setvariable variable=remoteChanges;isOutput=true]$($remoteChanges)" + } + "TESTRUN" { + Write-Host $PipelineVendor + } + Default { + Write-Host "Please use one of the supported Pipeline Vendors or enhance script to fit your needs" + Write-Host "Currently supported are: GITHUB and AZUREDEVOPS" + Exit 1 + } + } + Exit 0 +} +catch { + Write-Host "---Error---" + Write-Host $_.Exception.Message + if ($null -ne $_.Exception.Response) { + $responseStream = $_.Exception.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($responseStream) + $responseBody = $reader.ReadToEnd() + Write-Host "Response Body: $responseBody" + } + exit 1 +} \ No newline at end of file diff --git a/.github/powershell/APIv2/Get-LatestDeployment.ps1 b/.github/powershell/APIv2/Get-LatestDeployment.ps1 new file mode 100644 index 0000000..188823b --- /dev/null +++ b/.github/powershell/APIv2/Get-LatestDeployment.ps1 @@ -0,0 +1,105 @@ +# Set variables +param( + [Parameter(Position=0)] + [string] + $ProjectId, + + [Parameter(Position=1)] + [string] + $ApiKey, + + [Parameter(Position=2)] + [string] + $TargetEnvironmentAlias, + + [Parameter(Position=3)] + [string] + $PipelineVendor, ## GITHUB or AZUREDEVOPS + + [Parameter(Position=4)] + [string] + $BaseUrl = "https://api.cloud.umbraco.com" +) + +### Endpoint docs +# https://docs.umbraco.com/umbraco-cloud/set-up/project-settings/umbraco-cicd/umbracocloudapi/todo-v2 +# +# We want the Id of the latest deployment that created changes to cloud +# Filter deployments +$Skip = 0 +$Take = 1 +# Exclude cloud null deployments +$IncludeNullDeployments = $False + + +$DeploymentUrl = "$BaseUrl/v2/projects/$ProjectId/deployments?skip=$Skip&take=$Take&includenulldeployments=$IncludeNullDeployments&targetEnvironmentAlias=$TargetEnvironmentAlias" + +$Headers = @{ + 'Umbraco-Cloud-Api-Key' = $ApiKey + 'Content-Type' = 'application/json' +} + +# Actually calling the endpoint +try{ + $Response = Invoke-WebRequest -URI $DeploymentUrl -Headers $Headers + + if ($Response.StatusCode -eq 200) { + + $JsonResponse = ConvertFrom-Json $([String]::new($Response.Content)) + + $latestDeploymentId = '' + + if ($JsonResponse.data.Count -gt 0){ + + $latestDeploymentId = $JsonResponse.data[0].id + } + + ## Write the latest deployment id to the pipelines variables for use in a later step + switch ($PipelineVendor) { + "GITHUB" { + "latestDeploymentId=$($latestDeploymentId)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + } + "AZUREDEVOPS" { + Write-Host "##vso[task.setvariable variable=latestDeploymentId;isOutput=true]$($latestDeploymentId)" + Write-Host "##vso[task.setvariable variable=latestDeploymentId]$($latestDeploymentId)" + + } + "TESTRUN" { + Write-Host $PipelineVendor + } + Default { + Write-Host "Please use one of the supported Pipeline Vendors or enhance script to fit your needs" + Write-Host "Currently supported are: GITHUB and AZUREDEVOPS" + Exit 1 + } + } + + if ($latestDeploymentId -eq '') { + Write-Host "No latest CICD Flow Deployments found" + Write-Host "----------------------------------------------------------------------------------------" + Write-Host "This is usually because you have yet to make changes to cloud through the CICD endpoints" + } + else { + Write-Host "Latest CICD Flow Deployment:" + Write-Host "$($latestDeploymentId)" + } + exit 0 + } + ## Let errors bubble forward + Write-Host "---Response Start---" + Write-Host $Response + Write-Host "---Response End---" + Write-Host "Unexpected response - see above" + exit 1 +} +catch { + Write-Host "---Error---" + Write-Host $_.Exception.Message + if ($null -ne $_.Exception.Response) { + $responseStream = $_.Exception.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($responseStream) + $responseBody = $reader.ReadToEnd() + Write-Host "Response Body: $responseBody" + } + exit 1 +} \ No newline at end of file diff --git a/.github/powershell/APIv2/Start-Deployment.ps1 b/.github/powershell/APIv2/Start-Deployment.ps1 new file mode 100644 index 0000000..fba2dbe --- /dev/null +++ b/.github/powershell/APIv2/Start-Deployment.ps1 @@ -0,0 +1,125 @@ +param( + [Parameter(Position=0)] + [string] + $ProjectId, + + [Parameter(Position=1)] + [string] + $ApiKey, + + [Parameter(Position=2)] + [string] + $ArtifactId, + + [Parameter(Position=3)] + [string] + $TargetEnvironmentAlias, + + [Parameter(Position=4)] + [string] + $CommitMessage = "", + + [Parameter(Position=5)] + [bool] + $NoBuildAndRestore = $false, + + [Parameter(Position=6)] + [bool] + $SkipVersionCheck = $false, + + [Parameter(Position=7)] + [string] + $PipelineVendor, ## GITHUB or AZUREDEVOPS + + [Parameter(Position=8)] + [string] + $BaseUrl = "https://api.cloud.umbraco.com" +) + +### Endpoint docs +# https://docs.umbraco.com/umbraco-cloud/set-up/project-settings/umbraco-cicd/umbracocloudapi/todo-v2 +# +$url = "$BaseUrl/v2/projects/$ProjectId/deployments" + +$headers = @{ + 'Umbraco-Cloud-Api-Key' = $ApiKey + 'Content-Type' = 'application/json' +} + +$requestBody = @{ + 'targetEnvironmentAlias' = $TargetEnvironmentAlias + 'artifactId' = $ArtifactId + 'commitMessage' = $CommitMessage + 'noBuildAndRestore' = $NoBuildAndRestore + 'skipVersionCheck' = $SkipVersionCheck +} | ConvertTo-Json + +try { + Write-Host "Requesting start Deployment at $url with Request:" + $requestBody | Write-Output + + $response = Invoke-RestMethod -URI $url -Headers $headers -Method POST -Body $requestBody + $deploymentId = $response.deploymentId + + Write-Host "--- --- ---" + Write-Host "Response:" + $response | ConvertTo-Json -Depth 10 | Write-Output + + switch ($PipelineVendor) { + "GITHUB" { + "deploymentId=$($deploymentId)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + } + "AZUREDEVOPS" { + Write-Host "##vso[task.setvariable variable=deploymentId;isOutput=true]$($deploymentId)" + } + "TESTRUN" { + Write-Host $PipelineVendor + } + Default { + Write-Host "Please use one of the supported Pipeline Vendors or enhance script to fit your needs" + Write-Host "Currently supported are: GITHUB and AZUREDEVOPS" + Exit 1 + } + } + + Write-Host "--- --- ---" + Write-Host "Deployment Created Successfully => $($deploymentId)" + exit 0 + + Write-Host "---Response Start---" + Write-Host $response + Write-Host "---Response End---" + Write-Host "Unexpected response - see above" + exit 1 +} +catch { + Write-Host "---Error---" + Write-Host $_.Exception.Message + + if ($_.Exception.Response -is [System.Net.HttpWebResponse]) { + $responseStream = $_.Exception.Response.GetResponseStream() + if ($null -ne $responseStream) { + $reader = New-Object System.IO.StreamReader($responseStream) + try { + $responseBody = $reader.ReadToEnd() + Write-Host "Response Body: $responseBody" + } + finally { + $reader.Dispose() + } + } + } + else { + + try { + $details = $_.ErrorDetails.ToString() | ConvertFrom-Json + $details | Format-List + } + catch { + Write-Host "Could not parse ErrorDetails as JSON." + } + + } + + exit 1 +} \ No newline at end of file diff --git a/.github/powershell/APIv2/Test-DeploymentStatus.ps1 b/.github/powershell/APIv2/Test-DeploymentStatus.ps1 new file mode 100644 index 0000000..e1f5b2e --- /dev/null +++ b/.github/powershell/APIv2/Test-DeploymentStatus.ps1 @@ -0,0 +1,111 @@ +param( + [Parameter(Position=0)] + [string] + $ProjectId, + + [Parameter(Position=1)] + [string] + $ApiKey, + + [Parameter(Position=2)] + [string] + $DeploymentId, + + [Parameter(Position=3)] + [int] + $TimeoutSeconds = 1200, + + [Parameter(Position=4)] + [string] + $BaseUrl = "https://api.cloud.umbraco.com" +) + +### Endpoint docs +# https://docs.umbraco.com/umbraco-cloud/set-up/project-settings/umbraco-cicd/umbracocloudapi/todo-v2 +# +$BaseStatusUrl = "$BaseUrl/v2/projects/$ProjectId/deployments/$DeploymentId" + +$timer = [Diagnostics.Stopwatch]::StartNew() + +$headers = @{ + 'Umbraco-Cloud-Api-Key' = $ApiKey + 'Content-Type' = 'application/json' +} + +function Request-Deployment-Status ([INT]$run){ + Write-Host "=====> Requesting Status - Run number $run" + try { + $response = Invoke-WebRequest -URI $url -Headers $headers -Method Get + + if ($response.StatusCode -eq 200) { + $jsonResponse = ConvertFrom-Json $([String]::new($response.Content)) + + Write-Host "DeploymentStatus: '$($jsonResponse.deploymentState)'" + + foreach ($item in $jsonResponse.deploymentStatusMessages){ + Write-Host "$($item.timestampUtc): $($item.message)" + } + + return $jsonResponse + } + } + catch + { + Write-Host "---Error---" + Write-Host $_.Exception.Message + if ($null -ne $_.Exception.Response) { + $responseStream = $_.Exception.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($responseStream) + $responseBody = $reader.ReadToEnd() + Write-Host "Response Body: $responseBody" + } + exit 1 + } +} + +$run = 1 +$url = $BaseStatusUrl + +$statusesBeforeCompleted = ("Pending", "InProgress", "Queued") +Write-Host "Getting Status for Deployment: $($DeploymentId)" +do { + + $deploymentResponse = Request-Deployment-Status($run) + $run++ + + # Handle timeout + if ($timer.Elapsed.TotalSeconds -gt $TimeoutSeconds){ + throw "Timeout was reached" + } + + # Dont write if Deployment was finished + if ($statusesBeforeCompleted -contains $deploymentResponse.deploymentState){ + $sleepValue = 25 + Write-Host "=====> Still Deploying - sleeping for $sleepValue seconds" + Start-Sleep -Seconds $sleepValue + $LastModifiedUtc = $deploymentResponse.modifiedUtc.ToString("o") + $url = "$BaseStatusUrl\?lastModifiedUtc=$($LastModifiedUtc)" + } + +} while ( + $statusesBeforeCompleted -contains $deploymentResponse.deploymentState +) + +$timer.Stop() + +# Successfully deployed to cloud +if ($deploymentResponse.deploymentState -eq 'Completed'){ + Write-Host "Deployment completed successfully" + exit 0 +} + +# Deployment has failed +if ($deploymentResponse.deploymentState -eq 'Failed'){ + Write-Host "Deployment Failed" + exit 1 +} + +# Unexpected deployment status - considered a fail +Write-Host "Unexpected status: $deploymentResponse.deploymentState" +exit 1 + diff --git a/.github/workflows/package-and-deploy.yml b/.github/workflows/cloud-artifact.yml similarity index 60% rename from .github/workflows/package-and-deploy.yml rename to .github/workflows/cloud-artifact.yml index d0df71a..bc97816 100644 --- a/.github/workflows/package-and-deploy.yml +++ b/.github/workflows/cloud-artifact.yml @@ -1,20 +1,22 @@ -# called by main.yml +name: Prepare and Upload Artifact on: workflow_call: inputs: - pathToWebsite: - required: true + newSha: + required: false type: string - csprojFile: - required: true + pathToWebsite: + required: false type: string pathToFrontendClient: - required: true + required: false + type: string + csprojFile: + required: false type: string - secrets: - projectId: + projectId: required: true umbracoCloudApiKey: required: true @@ -24,17 +26,21 @@ on: required: true formsLicenseKey: required: true + outputs: + artifactId: + description: 'The Id of the uploaded artifact' + value: ${{ jobs.prepareDeployment.outputs.artifactId }} jobs: - - # Do as one job as don't want save the 'real' deployment as an artifact (contains sensitive data) - prepAndDoDeployment: - name: Prep And Do Deployment + prepareDeployment: + name: Prepare Artifact to cloud runs-on: ubuntu-latest outputs: - runningDeploymentId: ${{ steps.deployment-meta.outputs.deploymentId }} + artifactId: ${{ steps.upload-artifact.outputs.artifactId }} steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.newSha }} # Enable/disable package references in .csproj - name: Update csproj @@ -76,7 +82,7 @@ jobs: - name: Prepare Cloud Git Ignore run: cp cloud.gitignore .gitignore shell: bash - + # Setup Node.js for frontend build - name: Setup Node.js uses: actions/setup-node@v4 @@ -93,55 +99,20 @@ jobs: # zip everything, except what is defined in the 'cloud.zipignore' - name: Zip Source Code - run: zip -r sources.zip . -x@cloud.zipignore + run: | + echo "Packing artifact for upload" + zip -r sources.zip . -x@cloud.zipignore shell: bash - # Request to prepare a deployment - # - sets the commit message to be used in cloud - # - supplies you with a deploymentId to be used in the rest of the process - - name: Create Deployment Meta - id: deployment-meta + # Upload your zipped artifact + - name: Post Zipped Artifact + id: upload-artifact shell: pwsh run: > - ${{GITHUB.WORKSPACE}}/.github/powershell/APIv1/New-Deployment.ps1 + ${{GITHUB.WORKSPACE}}/.github/powershell/APIv2/Add-DeploymentArtifact.ps1 -ProjectId ${{ secrets.projectId }} -ApiKey ${{ secrets.umbracoCloudApiKey }} - -CommitMessage "Run number ${{github.run_number}}" - -PipelineVendor GITHUB - - # Upload your zip file - - name: Post Zip - shell: pwsh - run: > - ${{GITHUB.WORKSPACE}}/.github/powershell/APIv1/Add-DeploymentPackage.ps1 - -ProjectId ${{ secrets.projectId }} - -DeploymentId ${{ steps.deployment-meta.outputs.deploymentId }} - -ApiKey ${{ secrets.umbracoCloudApiKey }} - -FilePath sources.zip - - # Request to start the deployment process in cloud - - name: Request Start Deployment - shell: pwsh - run: > - ${{GITHUB.WORKSPACE}}/.github/powershell/APIv1/Start-Deployment.ps1 - -ProjectId ${{ secrets.projectId }} - -DeploymentId ${{ steps.deployment-meta.outputs.deploymentId }} - -ApiKey ${{ secrets.umbracoCloudApiKey }} - - awaitDeploymentFinished: - name: Await deployment to finish - runs-on: ubuntu-latest - needs: prepAndDoDeployment - steps: - - uses: actions/checkout@v4 - - # Poll until deployment finishes - - name: Wait for deployment completed - shell: pwsh - env: - runningDeploymentId: ${{ needs.prepAndDoDeployment.outputs.runningDeploymentId }} - run: > - ${{GITHUB.WORKSPACE}}/.github/powershell/APIv1/Test-DeploymentStatus.ps1 - -ProjectId ${{ secrets.projectId }} - -DeploymentId ${{ env.runningDeploymentId }} - -ApiKey ${{ secrets.umbracoCloudApiKey }} + -FilePath ${{ GITHUB.WORKSPACE }}/sources.zip + -Description "Artifact for ${{github.run_number}}" + -Version "${{github.run_number}}" + -PipelineVendor GITHUB \ No newline at end of file diff --git a/.github/workflows/cloud-deployment.yml b/.github/workflows/cloud-deployment.yml new file mode 100644 index 0000000..8253fa8 --- /dev/null +++ b/.github/workflows/cloud-deployment.yml @@ -0,0 +1,82 @@ +name: Deploy To Cloud + +on: + workflow_dispatch: + inputs: + artifactId: + required: false + type: string + targetEnvironmentAlias: + description: 'The target environment alias to deploy to' + required: true + type: string + + workflow_call: + inputs: + artifactId: + required: true + type: string + targetEnvironmentAlias: + description: 'The target environment alias to deploy to' + required: true + type: string + noBuildAndRestore: + description: 'Skip build and restore steps' + required: false + type: number + default: 0 + skipVersionCheck: + description: 'Skip version check' + required: false + type: number + default: 0 + + secrets: + projectId: + required: true + umbracoCloudApiKey: + required: true + +jobs: + startDeployment: + name: Start Deployment + runs-on: ubuntu-latest + outputs: + runningDeploymentId: ${{ steps.requestStartDeployment.outputs.deploymentId }} + steps: + - uses: actions/checkout@v4 + + # Request to prepare a deployment + # - sets the commit message to be used in cloud + # - supplies you with a deploymentId to be used in the rest of the process + - name: Request Start Deployment + id: requestStartDeployment + shell: pwsh + run: > + ${{GITHUB.WORKSPACE}}/.github/powershell/APIv2/Start-Deployment.ps1 + -ProjectId ${{ secrets.projectId }} + -ApiKey ${{ secrets.umbracoCloudApiKey }} + -ArtifactId ${{ inputs.artifactId }} + -TargetEnvironmentAlias ${{ inputs.targetEnvironmentAlias }} + -CommitMessage "Run number ${{github.run_number}}" + -NoBuildAndRestore ${{ inputs.noBuildAndRestore }} + -SkipVersionCheck ${{ inputs.skipVersionCheck }} + -PipelineVendor GITHUB + + awaitDeploymentFinished: + name: Await deployment to finish + runs-on: ubuntu-latest + needs: startDeployment + steps: + - uses: actions/checkout@v4 + + # Poll until deployment finishes + - name: Wait for deployment completed + shell: pwsh + env: + runningDeploymentId: ${{ needs.startDeployment.outputs.runningDeploymentId }} + run: > + ${{GITHUB.WORKSPACE}}/.github/powershell/APIv2/Test-DeploymentStatus.ps1 + -ProjectId ${{ secrets.projectId }} + -ApiKey ${{ secrets.umbracoCloudApiKey }} + -DeploymentId ${{ env.runningDeploymentId }} \ No newline at end of file diff --git a/.github/workflows/cloud-sync.yml b/.github/workflows/cloud-sync.yml new file mode 100644 index 0000000..a052048 --- /dev/null +++ b/.github/workflows/cloud-sync.yml @@ -0,0 +1,124 @@ +name: Umbraco Cloud Sync + +on: + workflow_call: + outputs: + newSha: + description: 'The new SHA hash if there was a patch' + value: ${{ jobs.applyRemoteChanges.outputs.newSha }} + inputs: + targetEnvironmentAlias: + description: 'The target environment alias to sync with' + required: true + type: string + secrets: + projectId: + required: true + umbracoCloudApiKey: + required: true + +jobs: + preflight: + name: Check Cloud for changes + runs-on: ubuntu-latest + steps: + # Gets the latest CICD Flow deployment to target Environment if there are any + # Will write "latestDeploymentId" to pipeline variables, value can be an uuid or empty string + - uses: actions/checkout@v4 + - name: Get Latest Deployment + id: latest-deployment + shell: pwsh + run: > + ${{GITHUB.WORKSPACE}}/.github/powershell/APIv2/Get-LatestDeployment.ps1 + -ProjectId ${{ secrets.projectId }} + -ApiKey ${{ secrets.umbracoCloudApiKey }} + -TargetEnvironmentAlias ${{ inputs.targetEnvironmentAlias }} + -PipelineVendor GITHUB + outputs: + latestDeploymentId: ${{ steps.latest-deployment.outputs.latestDeploymentId }} + + checkForChanges: + name: Check if there are changes since latest deployment + needs: preflight + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Download git-patch file based on changes since latest deployment to target Environment + # Will write "remoteChanges" to pipeline variables, value can be "yes" or "no" + # When "remoteChanges" is yes, there will also be downloaded a patch-file to the path you specified in -DownloadFolder parameter + - name: Fetch Changes From Cloud + env: + latestDeploymentId: ${{ needs.preflight.outputs.latestDeploymentId }} + if: ${{ env.latestDeploymentId != '' }} + id: latest-changes + shell: pwsh + run: > + ${{GITHUB.WORKSPACE}}/.github/powershell/APIv2/Get-ChangesById.ps1 + -ProjectId ${{ secrets.projectId }} + -ApiKey ${{ secrets.umbracoCloudApiKey }} + -DeploymentId ${{ env.latestDeploymentId }} + -TargetEnvironmentAlias ${{ inputs.targetEnvironmentAlias }} + -DownloadFolder ${{GITHUB.WORKSPACE}}/patch + -PipelineVendor GITHUB + + - name: See diff content if any + if: ${{ steps.latest-changes.outputs.remoteChanges == 'yes' }} + shell: pwsh + run: get-content ${{ GITHUB.WORKSPACE }}/patch/git-patch.diff + + - name: Store diff before applying + if: ${{ steps.latest-changes.outputs.remoteChanges == 'yes' }} + uses: actions/upload-artifact@v4 + with: + name: git-patch + path: ${{ GITHUB.WORKSPACE }}/patch/git-patch.diff + retention-days: 1 + outputs: + remoteChanges: ${{ steps.latest-changes.outputs.remoteChanges }} + + applyRemoteChanges: + name: Apply remote changes + needs: [preflight, checkForChanges] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + env: + remoteChanges: ${{ needs.checkForChanges.outputs.remoteChanges }} + if: ${{ env.remoteChanges == 'yes' }} + with: + fetch-depth: 0 + + - name: Get stored diff + env: + remoteChanges: ${{ needs.checkForChanges.outputs.remoteChanges }} + if: ${{ env.remoteChanges == 'yes' }} + uses: actions/download-artifact@v4 + with: + name: git-patch + path: ${{ GITHUB.WORKSPACE }}/patch + + # Using plain git to try an push changes back to local repo + # Depending on your setup you may need to change settings and permissions to better fit your needs + # This targets the same branch as the pipeline was triggered on. + + ## You can change the gitName and gitEmail to whatever you want, this will show up in you git history on + ## changes coming from Umbraco Cloud + + - name: Applying git patch to branch + id: update-bundle + env: + remoteChanges: ${{ needs.checkForChanges.outputs.remoteChanges }} + latestDeploymentId: ${{ needs.preflight.outputs.latestDeploymentId }} + gitName: github-actions + gitEmail: github-actions@github.com + if: ${{ env.remoteChanges == 'yes' }} + shell: pwsh + run: > + ${{GITHUB.WORKSPACE}}/.github/powershell/APIv2/Apply-Patch.ps1 + -PatchFile ${{ GITHUB.WORKSPACE }}/patch/git-patch.diff + -LatestDeploymentId ${{ env.latestDeploymentId }} + -PipelineVendor GITHUB + -GitUserName ${{ env.gitName }} + -GitUserEmail ${{ env.gitEmail }} + outputs: + newSha: ${{ steps.update-bundle.outputs.updatedSha }} diff --git a/.github/workflows/main-v2.yml b/.github/workflows/main-v2.yml new file mode 100644 index 0000000..3b1a780 --- /dev/null +++ b/.github/workflows/main-v2.yml @@ -0,0 +1,55 @@ +name: pipeline + +on: + push: + branches: + - main + release: + types: [published] + workflow_dispatch: + +jobs: + set-env: + name: "Set Target Environment from Release or Branch" + runs-on: ubuntu-latest + outputs: + targetEnv: ${{ steps.set-env.outputs.environment }} + steps: + - name: Set environment based on trigger + id: set-env + run: | + if [ "${{ github.event_name }}" = "release" ]; then + echo "Release published - deploying to live" + echo "environment=live" >> $GITHUB_OUTPUT + else + echo "Push to main or manual trigger - deploying to stage" + echo "environment=stage" >> $GITHUB_OUTPUT + fi + + # Pack and upload the deployment Artifact + cloud-artifact: + name: "Prepare and Upload Artifact" + needs: [set-env] + uses: ./.github/workflows/cloud-artifact.yml + with: + pathToWebsite: "src/OpenSourceTest.Site" + csprojFile: "OpenSourceTest.Site.csproj" + pathToFrontendClient: "src/OpenSourceTest.MyExtension/Client" + secrets: + projectId: ${{ secrets.PROJECT_ID }} + umbracoCloudApiKey: ${{ secrets.UMBRACO_CLOUD_API_KEY }} + umbracoCloudJson: ${{ secrets.UMBRACO_CLOUD_JSON }} + deployLicenseKey: ${{ secrets.DEPLOY_LICENSE_KEY }} + formsLicenseKey: ${{ secrets.FORMS_LICENSE_KEY }} + + # Deploy to Umbraco Cloud + cloud-deployment: + name: "Deploy to Cloud" + needs: [set-env, cloud-artifact] + uses: ./.github/workflows/cloud-deployment.yml + with: + artifactId: ${{ needs.cloud-artifact.outputs.artifactId }} + targetEnvironmentAlias: ${{ needs.set-env.outputs.targetEnv }} + secrets: + projectId: ${{ secrets.PROJECT_ID }} + umbracoCloudApiKey: ${{ secrets.UMBRACO_CLOUD_API_KEY }} \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 331eeaa..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Deploy to Cloud (v1 API) - -# Trigger when committing to main branch -on: - push: - branches: - - main - workflow_dispatch: # Allow manual triggering of the workflow - -jobs: - # Package and Deploy to Umbraco Cloud (without saving the artifact as contains sensitive data) - cloud-deployment: - name: "Deploy to Cloud" - uses: ./.github/workflows/package-and-deploy.yml - secrets: - projectId: ${{ secrets.PROJECT_ID }} - umbracoCloudApiKey: ${{ secrets.UMBRACO_CLOUD_API_KEY }} - umbracoCloudJson: ${{ secrets.UMBRACO_CLOUD_JSON }} - deployLicenseKey: ${{ secrets.DEPLOY_LICENSE_KEY }} - formsLicenseKey: ${{ secrets.FORMS_LICENSE_KEY }} - with: - pathToWebsite: "src/OpenSourceTest.Site" - csprojFile: "OpenSourceTest.Site.csproj" - pathToFrontendClient: "src/OpenSourceTest.MyExtension/Client" \ No newline at end of file diff --git a/src/OpenSourceTest.Site/Views/master.cshtml b/src/OpenSourceTest.Site/Views/master.cshtml index 6fab132..96b994c 100644 --- a/src/OpenSourceTest.Site/Views/master.cshtml +++ b/src/OpenSourceTest.Site/Views/master.cshtml @@ -10,7 +10,8 @@ @RenderBody() - +
+