Octopus.AwsRunScript exported 2023-11-16 by mcasperson belongs to ‘Octopus’ category.
This step queries each workspace in the Terraform state for downstream Octopus CaC enabled projects, extracts the Git repo associated with the CaC project, and determines if there are any changes to merge into the downstream project from the upstream project.
This indicates if changes to an upstream project are available to be merged into a downstream project, either automatically, or after resolving merge conflicts.
Parameters
When steps based on the template are included in a project’s deployment process, the parameters below can be set.
Git Username
FindConflicts.Git.Credentials.Username = x-access-token
The git repo username. When using GitHub with an access token, the value is x-access-token
.
Git Password
FindConflicts.Git.Credentials.Password =
The git repo password or access token.
Git Protocol
FindConflicts.Git.Url.Protocol = https
The git repo protocol.
Git Hostname
FindConflicts.Git.Url.Host = github.com
The git repo host name.
Git Organization
FindConflicts.Git.Url.Organization =
The git repo owner or organization i.e. owner
in the url https://github.com/owner/repo
.
Git Template Repo
FindConflicts.Git.Url.Template =
The repo holding the upstream, or template, CaC project i.e. repo
in the url https://github.com/owner/repo
.
AWS Region
FindConflicts.Terraform.Backend.S3Region =
The AWS region hosting the S3 bucket persisting the Terraform state.
S3 Key
FindConflicts.Terraform.Backend.S3Key = Project_#{Octopus.Project.Name | Replace "[^A-Za-z0-9]" "_"}
The name of the file in the S3 bucket hosting the Terraform state.
S3 Bucket
FindConflicts.Terraform.Backend.S3Bucket =
The name of the S3 bucket hosting the Terraform state.
AWS Account
FindConflicts.Terraform.Aws.Account =
The AWS account used to access the S3 bucket.
Script body
Steps based on this template will execute the following PowerShell script.
# Check to see if $IsWindows is available
if ($null -eq $IsWindows) {
Write-Host "Determining Operating System..."
$IsWindows = ([System.Environment]::OSVersion.Platform -eq "Win32NT")
$IsLinux = ([System.Environment]::OSVersion.Platform -eq "Unix")
}
Function Get-GitExecutable
{
# Define parameters
param (
$WorkingDirectory
)
# Define variables
$gitExe = "PortableGit-2.41.0.3-64-bit.7z.exe"
$gitDownloadUrl = "https://github.com/git-for-windows/git/releases/download/v2.41.0.windows.3/$gitExe"
$gitDownloadArguments = @{}
$gitDownloadArguments.Add("Uri", $gitDownloadUrl)
$gitDownloadArguments.Add("OutFile", "$WorkingDirectory/git/$gitExe")
# This makes downloading faster
$ProgressPreference = 'SilentlyContinue'
# Check to see if git subfolder exists
if ((Test-Path -Path "$WorkingDirectory/git") -eq $false)
{
# Create subfolder
New-Item -Path "$WorkingDirectory/git" -ItemType Directory | Out-Null
}
# Check PowerShell version
if ($PSVersionTable.PSVersion.Major -lt 6)
{
# Use basic parsing is required
$gitDownloadArguments.Add("UseBasicParsing", $true)
}
# Download Git
Write-Host "Downloading Git ..."
Invoke-WebRequest @gitDownloadArguments
# Extract Git
$gitExtractArguments = @()
$gitExtractArguments += "-o"
$gitExtractArguments += "$WorkingDirectory\git"
$gitExtractArguments += "-y"
$gitExtractArguments += "-bd"
Write-Host "Extracting Git download ..."
& "$WorkingDirectory\git\$gitExe" $gitExtractArguments
# Wait until unzip action is complete
while ($null -ne (Get-Process | Where-Object {$_.ProcessName -eq ($gitExe.Substring(0, $gitExe.LastIndexOf(".")))}))
{
Start-Sleep 5
}
# Add bin folder to path
$env:PATH = "$WorkingDirectory\git\bin$([IO.Path]::PathSeparator)" + $env:PATH
# Disable promopt for credential helper
Invoke-CustomCommand "git" @("config", "--system", "--unset", "credential.helper") | Write-Results
}
Function Invoke-CustomCommand
{
Param (
$commandPath,
$commandArguments,
$workingDir = (Get-Location),
$path = @()
)
$path += $env:PATH
$newPath = $path -join [IO.Path]::PathSeparator
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = $commandPath
$pinfo.WorkingDirectory = $workingDir
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = $commandArguments
$pinfo.EnvironmentVariables["PATH"] = $newPath
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
# Capture output during process execution so we don't hang
# if there is too much output.
# Microsoft documents a C# solution here:
# https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.redirectstandardoutput?view=net-7.0&redirectedfrom=MSDN#remarks
# This code is based on https://stackoverflow.com/a/74748844
$stdOut = [System.Text.StringBuilder]::new()
$stdErr = [System.Text.StringBuilder]::new()
do
{
if (!$p.StandardOutput.EndOfStream)
{
$stdOut.AppendLine($p.StandardOutput.ReadLine())
}
if (!$p.StandardError.EndOfStream)
{
$stdErr.AppendLine($p.StandardError.ReadLine())
}
Start-Sleep -Milliseconds 10
}
while (-not $p.HasExited)
# Capture any standard output generated between our last poll and process end.
while (!$p.StandardOutput.EndOfStream)
{
$stdOut.AppendLine($p.StandardOutput.ReadLine())
}
# Capture any error output generated between our last poll and process end.
while (!$p.StandardError.EndOfStream)
{
$stdErr.AppendLine($p.StandardError.ReadLine())
}
$p.WaitForExit()
$executionResults = [pscustomobject]@{
StdOut = $stdOut.ToString()
StdErr = $stdErr.ToString()
ExitCode = $p.ExitCode
}
return $executionResults
}
function Write-Results
{
[cmdletbinding()]
param (
[Parameter(Mandatory=$True,ValuefromPipeline=$True)]
$results
)
if (![String]::IsNullOrWhiteSpace($results.StdOut))
{
Write-Verbose $results.StdOut
}
if (![String]::IsNullOrWhiteSpace($results.StdErr))
{
Write-Verbose $results.StdErr
}
}
function Write-TerraformBackend {
Set-Content -Path 'backend.tf' -Value @"
terraform {
backend "s3" {}
required_providers {
octopusdeploy = { source = "OctopusDeployLabs/octopusdeploy", version = "0.14.9" }
}
}
"@
}
function Format-StringAsNullOrTrimmed {
[cmdletbinding()]
param (
[Parameter(ValuefromPipeline=$True)]
$input
)
if ([string]::IsNullOrWhitespace($input)) {
return $null
}
return $input.Trim()
}
$username = $OctopusParameters["FindConflicts.Git.Credentials.Username"]
$password = $OctopusParameters["FindConflicts.Git.Credentials.Password"]
$protocol = $OctopusParameters["FindConflicts.Git.Url.Protocol"]
$gitHost = $OctopusParameters["FindConflicts.Git.Url.Host"]
$org = $OctopusParameters["FindConflicts.Git.Url.Organization"]
$repo = $OctopusParameters["FindConflicts.Git.Url.Template"]
$region = $OctopusParameters["FindConflicts.Terraform.Backend.S3Region"]
$key = $OctopusParameters["FindConflicts.Terraform.Backend.S3Key"]
$bucket = $OctopusParameters["FindConflicts.Terraform.Backend.S3Bucket"]
# Validate the inputs
if ([string]::IsNullOrWhitespace($username)) {
Write-Error "The FindConflicts.Git.Credentials.Username variable must be provided"
}
if ([string]::IsNullOrWhitespace($password)) {
Write-Error "The FindConflicts.Git.Credentials.Password variable must be provided"
}
if ([string]::IsNullOrWhitespace($protocol)) {
Write-Error "The FindConflicts.Git.Url.Protocol variable must be provided"
}
if ([string]::IsNullOrWhitespace($gitHost)) {
Write-Error "The FindConflicts.Git.Url.Host variable must be provided"
}
if ([string]::IsNullOrWhitespace($org)) {
Write-Error "The FindConflicts.Git.Url.Organization variable must be provided"
}
if ([string]::IsNullOrWhitespace($repo)) {
Write-Error "The FindConflicts.Git.Url.Template variable must be provided"
}
if ([string]::IsNullOrWhitespace($region)) {
Write-Error "The FindConflicts.Terraform.Backend.S3Region variable must be provided"
}
if ([string]::IsNullOrWhitespace($key)) {
Write-Error "The FindConflicts.Terraform.Backend.S3Key variable must be provided"
}
if ([string]::IsNullOrWhitespace($bucket)) {
Write-Error "The FindConflicts.Terraform.Backend.S3Bucket variable must be provided"
}
$templateRepoUrl = $protocol + "://" + $gitHost + "/" + $org + "/" + $repo + ".git"
$templateRepo = $protocol + "://" + $username + ":" + $password + "@" + $gitHost + "/" + $org + "/" + $repo + ".git"
$branch = "main"
# Check to see if it's Windows
if ($IsWindows -and $OctopusParameters['Octopus.Workerpool.Name'] -eq "Hosted Windows")
{
# Dynamic worker don't have git, download portable version and add to path for execution
Write-Host "Detected usage of Windows Dynamic Worker ..."
Get-GitExecutable -WorkingDirectory $PWD
}
Write-TerraformBackend
Invoke-CustomCommand "git" @("config", "--global", "user.email", "octopus@octopus.com") | Write-Results
Invoke-CustomCommand "git" @("config", "--global", "user.name", "Octopus Server") | Write-Results
Invoke-CustomCommand "terraform" @("init", "-no-color", "-backend-config=`"bucket=$bucket`"", "-backend-config=`"region=$region`"", "-backend-config=`"key=$key`"") | Write-Results
Write-Host "- Up to date"
Write-Host "> Can automatically merge"
Write-Host "× Merge conflict"
Write-Host "Verbose logs contain instructions for resolving merge conflicts."
$workspaces = Invoke-CustomCommand "terraform" @("workspace", "list")
Write-Results $workspaces
$parsedWorkspaces = $workspaces.StdOut.Replace("*", "").Split("`n")
$downstreamCount = 0
foreach ($workspace in $parsedWorkspaces)
{
$trimmedWorkspace = $workspace | Format-StringAsNullOrTrimmed
if ($trimmedWorkspace -eq "default" -or [string]::IsNullOrWhitespace($trimmedWorkspace))
{
continue
}
Write-Verbose "Processing workspace $trimmedWorkspace"
Invoke-CustomCommand "terraform" @("workspace", "select", $trimmedWorkspace) | Write-Results
$state = Invoke-CustomCommand "terraform" @("show", "-json")
# state might include sensitive values, so don't print it unless there was an error
if (-not $state.ExitCode -eq 0)
{
Write-Results $state
continue
}
$parsedState = $state.StdOut | ConvertFrom-Json
$resources = $parsedState.values.root_module.resources | Where-Object {
$_.type -eq "octopusdeploy_project"
}
# The outputs allow us to contact the downstream instance)
$spaceId = Invoke-CustomCommand "terraform" @("output", "-raw", "octopus_space_id")
$spaceName = Invoke-CustomCommand "terraform" @("output", "-raw", "octopus_space_name")
$space = if ([string]::IsNullOrWhitespace($spaceName.StdOut))
{
$spaceId.StdOut | Format-StringAsNullOrTrimmed
}
else
{
$spaceName.StdOut | Format-StringAsNullOrTrimmed
}
foreach ($resource in $resources)
{
$url = $resource.values.git_library_persistence_settings.url | Format-StringAsNullOrTrimmed
$spaceId = $resource.values.space_id | Format-StringAsNullOrTrimmed
$name = $resource.values.name | Format-StringAsNullOrTrimmed
if (-not [string]::IsNullOrWhitespace($url))
{
$downstreamCount++
mkdir $trimmedWorkspace | Out-Null
Invoke-CustomCommand "git" @("clone", $url, $trimmedWorkspace) | Write-Results
Invoke-CustomCommand "git" @("remote", 'add', 'upstream', $templateRepo) $trimmedWorkspace | Write-Results
Invoke-CustomCommand "git" @("fetch", "--all") $trimmedWorkspace | Write-Results
Invoke-CustomCommand "git" @("checkout", "-b", "upstream-$branch", "upstream/$branch") $trimmedWorkspace | Write-Results
if (-not($branch -eq "master" -or $branch -eq "main"))
{
Invoke-CustomCommand "git" @("checkout", "-b", $branch, "origin/$branch") $trimmedWorkspace | Write-Results
}
else
{
Invoke-CustomCommand "git" @("checkout", $branch) $trimmedWorkspace | Write-Results
}
$mergeBase = Invoke-CustomCommand "git" @("merge-base", $branch, "upstream-$branch") $trimmedWorkspace
Write-Results $mergeBase
$mergeSourceCurrentCommit = Invoke-CustomCommand "git" @("rev-parse", "upstream-$branch") $trimmedWorkspace
Write-Results $mergeSourceCurrentCommit
$mergeResult = Invoke-CustomCommand "git" @("merge", "--no-commit", "--no-ff", "upstream-$branch") $trimmedWorkspace
Write-Results $mergeResult
if ($mergeBase.StdOut -eq $mergeSourceCurrentCommit.StdOut)
{
Write-Host "$space `"$name`" $url -"
}
elseif (-not $mergeResult.ExitCode -eq 0)
{
Write-Host "$space `"$name`" $url ×"
Write-Verbose "To resolve the conflicts, run the following commands:"
Write-Verbose "mkdir cac"
Write-Verbose "cd cac"
Write-Verbose "git clone $url ."
Write-Verbose "git remote add upstream $templateRepoUrl"
Write-Verbose "git fetch --all"
Write-Verbose "git checkout -b upstream-$branch upstream/$branch"
if (-not($branch -eq "master" -or $branch -eq "main"))
{
Write-Verbose "git checkout -b $branch origin/$branch"
}
else
{
Write-Verbose "git checkout $branch"
Write-Verbose "git merge-base $branch upstream-$branch"
Write-Verbose "git merge --no-commit --no-ff upstream-$branch"
}
}
else
{
Write-Host "$space `"$name`" $url >"
}
}
else {
Write-Verbose "`"$name`" is not a CaC project"
}
}
}
Provided under the Apache License version 2.0.
To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.
{
"Id": "05210515-1a52-45d9-8be3-16caef808326",
"Name": "Octopus - Find CaC Updates (S3 Backend)",
"Description": "This step queries each workspace in the Terraform state for downstream Octopus CaC enabled projects, extracts the Git repo associated with the CaC project, and determines if there are any changes to merge into the downstream project from the upstream project.\n\nThis indicates if changes to an upstream project are available to be merged into a downstream project, either automatically, or after resolving merge conflicts.",
"Version": 4,
"ExportedAt": "2023-11-16T20:09:13.659Z",
"ActionType": "Octopus.AwsRunScript",
"Author": "mcasperson",
"Packages": [],
"Parameters": [
{
"Id": "1eea75a0-74c7-4af9-8569-a9e9ece1bd55",
"Name": "FindConflicts.Git.Credentials.Username",
"Label": "Git Username",
"HelpText": "The git repo username. When using GitHub with an access token, the value is `x-access-token`.",
"DefaultValue": "x-access-token",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "1cf36824-079a-4cf1-b67c-36f07933f642",
"Name": "FindConflicts.Git.Credentials.Password",
"Label": "Git Password",
"HelpText": "The git repo password or access token.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "Sensitive"
}
},
{
"Id": "e6a7414d-8d84-4fb8-9149-2a87f20502bf",
"Name": "FindConflicts.Git.Url.Protocol",
"Label": "Git Protocol",
"HelpText": "The git repo protocol.",
"DefaultValue": "https",
"DisplaySettings": {
"Octopus.ControlType": "Select",
"Octopus.SelectOptions": "https|HTTPS\nhttp|HTTP"
}
},
{
"Id": "f5d0d829-ef6c-4e06-b115-7b2845339544",
"Name": "FindConflicts.Git.Url.Host",
"Label": "Git Hostname",
"HelpText": "The git repo host name.",
"DefaultValue": "github.com",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "8b6e78e2-07fa-4783-9f9a-c23f5295f146",
"Name": "FindConflicts.Git.Url.Organization",
"Label": "Git Organization",
"HelpText": "The git repo owner or organization i.e. `owner` in the url `https://github.com/owner/repo`.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "1e99c543-9e80-41b2-9384-8cbefd5c3ee6",
"Name": "FindConflicts.Git.Url.Template",
"Label": "Git Template Repo",
"HelpText": "The repo holding the upstream, or template, CaC project i.e. `repo` in the url `https://github.com/owner/repo`.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "b0131561-4928-4dfb-85a9-a1282ac4a1be",
"Name": "FindConflicts.Terraform.Backend.S3Region",
"Label": "AWS Region",
"HelpText": "The AWS region hosting the S3 bucket persisting the Terraform state.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "652fa73d-99fa-4c81-9ef8-2e79d985222d",
"Name": "FindConflicts.Terraform.Backend.S3Key",
"Label": "S3 Key",
"HelpText": "The name of the file in the S3 bucket hosting the Terraform state.",
"DefaultValue": "Project_#{Octopus.Project.Name | Replace \"[^A-Za-z0-9]\" \"_\"}",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "7102a4ab-0f05-4b97-be26-8361b23df361",
"Name": "FindConflicts.Terraform.Backend.S3Bucket",
"Label": "S3 Bucket",
"HelpText": "The name of the S3 bucket hosting the Terraform state.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "7015ee07-f265-41d4-863a-d1f13fcfbc68",
"Name": "FindConflicts.Terraform.Aws.Account",
"Label": "AWS Account",
"HelpText": "The AWS account used to access the S3 bucket.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "AmazonWebServicesAccount"
}
}
],
"Properties": {
"Octopus.Action.Script.ScriptSource": "Inline",
"Octopus.Action.Script.Syntax": "PowerShell",
"Octopus.Action.Aws.AssumeRole": "False",
"Octopus.Action.AwsAccount.UseInstanceRole": "False",
"OctopusUseBundledTooling": "False",
"Octopus.Action.Script.ScriptBody": "# Check to see if $IsWindows is available\nif ($null -eq $IsWindows) {\n Write-Host \"Determining Operating System...\"\n $IsWindows = ([System.Environment]::OSVersion.Platform -eq \"Win32NT\")\n $IsLinux = ([System.Environment]::OSVersion.Platform -eq \"Unix\")\n}\n\nFunction Get-GitExecutable\n{\n # Define parameters\n param (\n $WorkingDirectory\n )\n\n # Define variables\n $gitExe = \"PortableGit-2.41.0.3-64-bit.7z.exe\"\n $gitDownloadUrl = \"https://github.com/git-for-windows/git/releases/download/v2.41.0.windows.3/$gitExe\"\n $gitDownloadArguments = @{}\n $gitDownloadArguments.Add(\"Uri\", $gitDownloadUrl)\n $gitDownloadArguments.Add(\"OutFile\", \"$WorkingDirectory/git/$gitExe\")\n\n # This makes downloading faster\n $ProgressPreference = 'SilentlyContinue'\n\n # Check to see if git subfolder exists\n if ((Test-Path -Path \"$WorkingDirectory/git\") -eq $false)\n {\n # Create subfolder\n New-Item -Path \"$WorkingDirectory/git\" -ItemType Directory | Out-Null\n }\n\n # Check PowerShell version\n if ($PSVersionTable.PSVersion.Major -lt 6)\n {\n # Use basic parsing is required\n $gitDownloadArguments.Add(\"UseBasicParsing\", $true)\n }\n\n # Download Git\n Write-Host \"Downloading Git ...\"\n Invoke-WebRequest @gitDownloadArguments\n\n # Extract Git\n $gitExtractArguments = @()\n $gitExtractArguments += \"-o\"\n $gitExtractArguments += \"$WorkingDirectory\\git\"\n $gitExtractArguments += \"-y\"\n $gitExtractArguments += \"-bd\"\n\n Write-Host \"Extracting Git download ...\"\n & \"$WorkingDirectory\\git\\$gitExe\" $gitExtractArguments\n\n # Wait until unzip action is complete\n while ($null -ne (Get-Process | Where-Object {$_.ProcessName -eq ($gitExe.Substring(0, $gitExe.LastIndexOf(\".\")))}))\n {\n Start-Sleep 5\n }\n\n # Add bin folder to path\n $env:PATH = \"$WorkingDirectory\\git\\bin$([IO.Path]::PathSeparator)\" + $env:PATH\n\n # Disable promopt for credential helper\n Invoke-CustomCommand \"git\" @(\"config\", \"--system\", \"--unset\", \"credential.helper\") | Write-Results\n}\n\nFunction Invoke-CustomCommand\n{\n Param (\n $commandPath,\n $commandArguments,\n $workingDir = (Get-Location),\n $path = @()\n )\n\n $path += $env:PATH\n $newPath = $path -join [IO.Path]::PathSeparator\n\n $pinfo = New-Object System.Diagnostics.ProcessStartInfo\n $pinfo.FileName = $commandPath\n $pinfo.WorkingDirectory = $workingDir\n $pinfo.RedirectStandardError = $true\n $pinfo.RedirectStandardOutput = $true\n $pinfo.UseShellExecute = $false\n $pinfo.Arguments = $commandArguments\n $pinfo.EnvironmentVariables[\"PATH\"] = $newPath\n $p = New-Object System.Diagnostics.Process\n $p.StartInfo = $pinfo\n $p.Start() | Out-Null\n\n # Capture output during process execution so we don't hang\n # if there is too much output.\n # Microsoft documents a C# solution here:\n # https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.redirectstandardoutput?view=net-7.0&redirectedfrom=MSDN#remarks\n # This code is based on https://stackoverflow.com/a/74748844\n $stdOut = [System.Text.StringBuilder]::new()\n $stdErr = [System.Text.StringBuilder]::new()\n do\n {\n if (!$p.StandardOutput.EndOfStream)\n {\n $stdOut.AppendLine($p.StandardOutput.ReadLine())\n }\n if (!$p.StandardError.EndOfStream)\n {\n $stdErr.AppendLine($p.StandardError.ReadLine())\n }\n\n Start-Sleep -Milliseconds 10\n }\n while (-not $p.HasExited)\n\n # Capture any standard output generated between our last poll and process end.\n while (!$p.StandardOutput.EndOfStream)\n {\n $stdOut.AppendLine($p.StandardOutput.ReadLine())\n }\n\n # Capture any error output generated between our last poll and process end.\n while (!$p.StandardError.EndOfStream)\n {\n $stdErr.AppendLine($p.StandardError.ReadLine())\n }\n\n $p.WaitForExit()\n\n $executionResults = [pscustomobject]@{\n StdOut = $stdOut.ToString()\n StdErr = $stdErr.ToString()\n ExitCode = $p.ExitCode\n }\n\n return $executionResults\n\n}\n\nfunction Write-Results\n{\n [cmdletbinding()]\n param (\n [Parameter(Mandatory=$True,ValuefromPipeline=$True)]\n $results\n )\n\n if (![String]::IsNullOrWhiteSpace($results.StdOut))\n {\n Write-Verbose $results.StdOut\n }\n\n if (![String]::IsNullOrWhiteSpace($results.StdErr))\n {\n Write-Verbose $results.StdErr\n }\n}\n\nfunction Write-TerraformBackend {\n Set-Content -Path 'backend.tf' -Value @\"\nterraform {\n backend \"s3\" {}\n required_providers {\n octopusdeploy = { source = \"OctopusDeployLabs/octopusdeploy\", version = \"0.14.9\" }\n }\n }\n\"@\n}\n\nfunction Format-StringAsNullOrTrimmed {\n [cmdletbinding()]\n param (\n [Parameter(ValuefromPipeline=$True)]\n $input\n )\n\n if ([string]::IsNullOrWhitespace($input)) {\n return $null\n }\n\n return $input.Trim()\n}\n\n$username = $OctopusParameters[\"FindConflicts.Git.Credentials.Username\"]\n$password = $OctopusParameters[\"FindConflicts.Git.Credentials.Password\"]\n$protocol = $OctopusParameters[\"FindConflicts.Git.Url.Protocol\"]\n$gitHost = $OctopusParameters[\"FindConflicts.Git.Url.Host\"]\n$org = $OctopusParameters[\"FindConflicts.Git.Url.Organization\"]\n$repo = $OctopusParameters[\"FindConflicts.Git.Url.Template\"]\n$region = $OctopusParameters[\"FindConflicts.Terraform.Backend.S3Region\"]\n$key = $OctopusParameters[\"FindConflicts.Terraform.Backend.S3Key\"]\n$bucket = $OctopusParameters[\"FindConflicts.Terraform.Backend.S3Bucket\"]\n\n# Validate the inputs\nif ([string]::IsNullOrWhitespace($username)) {\n Write-Error \"The FindConflicts.Git.Credentials.Username variable must be provided\"\n}\n\nif ([string]::IsNullOrWhitespace($password)) {\n Write-Error \"The FindConflicts.Git.Credentials.Password variable must be provided\"\n}\n\nif ([string]::IsNullOrWhitespace($protocol)) {\n Write-Error \"The FindConflicts.Git.Url.Protocol variable must be provided\"\n}\n\nif ([string]::IsNullOrWhitespace($gitHost)) {\n Write-Error \"The FindConflicts.Git.Url.Host variable must be provided\"\n}\n\nif ([string]::IsNullOrWhitespace($org)) {\n Write-Error \"The FindConflicts.Git.Url.Organization variable must be provided\"\n}\n\nif ([string]::IsNullOrWhitespace($repo)) {\n Write-Error \"The FindConflicts.Git.Url.Template variable must be provided\"\n}\n\nif ([string]::IsNullOrWhitespace($region)) {\n Write-Error \"The FindConflicts.Terraform.Backend.S3Region variable must be provided\"\n}\n\nif ([string]::IsNullOrWhitespace($key)) {\n Write-Error \"The FindConflicts.Terraform.Backend.S3Key variable must be provided\"\n}\n\nif ([string]::IsNullOrWhitespace($bucket)) {\n Write-Error \"The FindConflicts.Terraform.Backend.S3Bucket variable must be provided\"\n}\n\n$templateRepoUrl = $protocol + \"://\" + $gitHost + \"/\" + $org + \"/\" + $repo + \".git\"\n$templateRepo = $protocol + \"://\" + $username + \":\" + $password + \"@\" + $gitHost + \"/\" + $org + \"/\" + $repo + \".git\"\n$branch = \"main\"\n\n# Check to see if it's Windows\nif ($IsWindows -and $OctopusParameters['Octopus.Workerpool.Name'] -eq \"Hosted Windows\")\n{\n # Dynamic worker don't have git, download portable version and add to path for execution\n Write-Host \"Detected usage of Windows Dynamic Worker ...\"\n Get-GitExecutable -WorkingDirectory $PWD\n}\n\nWrite-TerraformBackend\n\nInvoke-CustomCommand \"git\" @(\"config\", \"--global\", \"user.email\", \"octopus@octopus.com\") | Write-Results\nInvoke-CustomCommand \"git\" @(\"config\", \"--global\", \"user.name\", \"Octopus Server\") | Write-Results\n\nInvoke-CustomCommand \"terraform\" @(\"init\", \"-no-color\", \"-backend-config=`\"bucket=$bucket`\"\", \"-backend-config=`\"region=$region`\"\", \"-backend-config=`\"key=$key`\"\") | Write-Results\n\nWrite-Host \"- Up to date\"\nWrite-Host \"> Can automatically merge\"\nWrite-Host \"× Merge conflict\"\nWrite-Host \"Verbose logs contain instructions for resolving merge conflicts.\"\n\n$workspaces = Invoke-CustomCommand \"terraform\" @(\"workspace\", \"list\")\n\nWrite-Results $workspaces\n\n$parsedWorkspaces = $workspaces.StdOut.Replace(\"*\", \"\").Split(\"`n\")\n\n$downstreamCount = 0\nforeach ($workspace in $parsedWorkspaces)\n{\n $trimmedWorkspace = $workspace | Format-StringAsNullOrTrimmed\n\n if ($trimmedWorkspace -eq \"default\" -or [string]::IsNullOrWhitespace($trimmedWorkspace))\n {\n continue\n }\n\n Write-Verbose \"Processing workspace $trimmedWorkspace\"\n\n Invoke-CustomCommand \"terraform\" @(\"workspace\", \"select\", $trimmedWorkspace) | Write-Results\n\n $state = Invoke-CustomCommand \"terraform\" @(\"show\", \"-json\")\n\n # state might include sensitive values, so don't print it unless there was an error\n\n if (-not $state.ExitCode -eq 0)\n {\n Write-Results $state\n continue\n }\n\n $parsedState = $state.StdOut | ConvertFrom-Json\n\n $resources = $parsedState.values.root_module.resources | Where-Object {\n $_.type -eq \"octopusdeploy_project\"\n }\n\n # The outputs allow us to contact the downstream instance)\n $spaceId = Invoke-CustomCommand \"terraform\" @(\"output\", \"-raw\", \"octopus_space_id\")\n $spaceName = Invoke-CustomCommand \"terraform\" @(\"output\", \"-raw\", \"octopus_space_name\")\n $space = if ([string]::IsNullOrWhitespace($spaceName.StdOut))\n {\n $spaceId.StdOut | Format-StringAsNullOrTrimmed\n }\n else\n {\n $spaceName.StdOut | Format-StringAsNullOrTrimmed\n }\n\n foreach ($resource in $resources)\n {\n $url = $resource.values.git_library_persistence_settings.url | Format-StringAsNullOrTrimmed\n $spaceId = $resource.values.space_id | Format-StringAsNullOrTrimmed\n $name = $resource.values.name | Format-StringAsNullOrTrimmed\n\n if (-not [string]::IsNullOrWhitespace($url))\n {\n $downstreamCount++\n\n mkdir $trimmedWorkspace | Out-Null\n\n Invoke-CustomCommand \"git\" @(\"clone\", $url, $trimmedWorkspace) | Write-Results\n Invoke-CustomCommand \"git\" @(\"remote\", 'add', 'upstream', $templateRepo) $trimmedWorkspace | Write-Results\n Invoke-CustomCommand \"git\" @(\"fetch\", \"--all\") $trimmedWorkspace | Write-Results\n Invoke-CustomCommand \"git\" @(\"checkout\", \"-b\", \"upstream-$branch\", \"upstream/$branch\") $trimmedWorkspace | Write-Results\n\n if (-not($branch -eq \"master\" -or $branch -eq \"main\"))\n {\n Invoke-CustomCommand \"git\" @(\"checkout\", \"-b\", $branch, \"origin/$branch\") $trimmedWorkspace | Write-Results\n }\n else\n {\n Invoke-CustomCommand \"git\" @(\"checkout\", $branch) $trimmedWorkspace | Write-Results\n }\n\n $mergeBase = Invoke-CustomCommand \"git\" @(\"merge-base\", $branch, \"upstream-$branch\") $trimmedWorkspace\n\n Write-Results $mergeBase\n\n $mergeSourceCurrentCommit = Invoke-CustomCommand \"git\" @(\"rev-parse\", \"upstream-$branch\") $trimmedWorkspace\n\n Write-Results $mergeSourceCurrentCommit\n\n $mergeResult = Invoke-CustomCommand \"git\" @(\"merge\", \"--no-commit\", \"--no-ff\", \"upstream-$branch\") $trimmedWorkspace\n\n Write-Results $mergeResult\n\n if ($mergeBase.StdOut -eq $mergeSourceCurrentCommit.StdOut)\n {\n Write-Host \"$space `\"$name`\" $url -\"\n }\n elseif (-not $mergeResult.ExitCode -eq 0)\n {\n Write-Host \"$space `\"$name`\" $url ×\"\n Write-Verbose \"To resolve the conflicts, run the following commands:\"\n Write-Verbose \"mkdir cac\"\n Write-Verbose \"cd cac\"\n Write-Verbose \"git clone $url .\"\n Write-Verbose \"git remote add upstream $templateRepoUrl\"\n Write-Verbose \"git fetch --all\"\n Write-Verbose \"git checkout -b upstream-$branch upstream/$branch\"\n if (-not($branch -eq \"master\" -or $branch -eq \"main\"))\n {\n Write-Verbose \"git checkout -b $branch origin/$branch\"\n }\n else\n {\n Write-Verbose \"git checkout $branch\"\n Write-Verbose \"git merge-base $branch upstream-$branch\"\n Write-Verbose \"git merge --no-commit --no-ff upstream-$branch\"\n }\n }\n else\n {\n Write-Host \"$space `\"$name`\" $url >\"\n }\n }\n else {\n Write-Verbose \"`\"$name`\" is not a CaC project\"\n }\n }\n}",
"Octopus.Action.Aws.Region": "#{FindConflicts.Terraform.Backend.S3Region}",
"Octopus.Action.AwsAccount.Variable": "#{FindConflicts.Terraform.Aws.Account}"
},
"Category": "Octopus",
"HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/octopus-find-cac-updates.json",
"Website": "/step-templates/05210515-1a52-45d9-8be3-16caef808326",
"Logo": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAC1QTFRFT6Tl////L5Pg8vj9Y67omsvwPJrisdfzfbzs5fL7y+T32Ov5isLucLXqvt31CJPHWwAABMJJREFUeNrs3deW4jAMAFDF3U75/89dlp0ZhiU4blJEjvQ8hYubLJsA00UCBCIQgQhEIAIRiEAEIhCBCEQgAhGIQAQiEIEIhD8kJm+t+QprfdKfB9HbYpx6CWfspj8HMi+gMgHL/AmQA8W3JTKH+ALFvzCeL0RbpyoCPE9IJeNOSQwh5Z3qd6yRGWQ2qi2cZQWxqj1WzQYSjeoJmJlAklOd4VlArOqPhQEkqBERToeMcfRJBkC0Uep8CfBpjz4JsHJ0zF3dkEWNje0kiB/sUC6eApndaIiCMyAa1PiwJ0AWhRGJHJJQHG2dC7h1rNbO1QOxSA7lNCkkKrQIpJCAB1GREILYIC1NAiwbpKFJgGWDNExcwGstfExcZBCHC6nOglshHtmhViLIig1RNBCN7qjtW8C0Z1UvJcC1Z9XmwMBzzvobmgAyEzgq91dtEEsBsQSQQAFZCSBAATEEEApHZbrVBIkkEIUPSVeB+KtALA0kXQUSrwKZBCIQBnk8Y4i5CsReBeKvkqLM+BCSDWJlrZFvGk9SRTHshkgjZCGAaArIxm3H3grhVzFlW2msfl1ca79UJ1bofYvsDHHlNdTZnlh5MghuPd5NdBDUNZHyCkfktIh03XzALGRPlBDPac7qgWjHZzWcmF5zmmkhidMQ6boKiDXcDTUEaylZqCGJ0Vjvu/fLJtHqhSANEvqb2OYqkOUqEHuVMbJcZdZCGiPhKhC4yjqiIjEE7XThMp8fAWII3mY3kUIQD+AMKQTzPiBhgQ63HlT/KSvgtoi0dq5mCPah1UIE0eh3sT0NhOByvKeAkFzi8PgQomumFhsyOxpIzZN4gLOj5plVwNpR0b2AuePWKBEHQu24pSsJA+LVCeHHQxZ1SiyDIdqok8IOhSSnTottHEQTdyt4ettAj4KkzA4dMikk2Dht2S5ptm1vswnPDxn0YyDZ5oDM3iToo2T5voWaYe+Q+vdjH80QyAzZhCgcDtLMI1Tmtz9w++XHgziHQHJJu/OZ3bs9Xn8gQ72NcP3dKqEfkp10F51xhoIi2I91R+LurXV/5q7pH+wx061CzO16oSQleMyr8fXvwMA0Pro8432DPD/ySx8XrHfSuDAM8n6UhnjQabaiXf5Bq/lREHvEeNtn1rJ08+C/uXkQZHeguxAPC3UvtcJYUogLzZX5hhZZvS6onG5lxXtzWGaygwb79vT/IXhdlNibwlKYOR6T8xjI7W8n+xV7T+GH4tMzWwR+lZhRkJYSsC0thpmCYqyngOz3rN2FLBZ2wZflBCggUHF0Vnp88JKienzIXLSEZCZqU7IKr/gQW9yx3pzV7Y9kvWZWTRRIqDmTtRUnU7b2lLcTYmoqHqnmiO1poER0SPkAeZMAZxaJx0Y3TCdAclsIqDz03ALcyxfTCZBsthoGXWmigGyVhWPLFJJfuuKQWycoEFdXbH4dJJoJxNR1eD/kshz6yn48cF8yW8sFoitflB1w6Q8n+/15Za7oA17/pYNmYgP5fmWm8L1NOHPWgK8kuFew1/JXtOA0yJCv7ah7X8ObUuT5kObU30+fDZm8+zqP+HTIpK0xQ796b5Kv2hSIQAQiEIEIRCACEYhABCIQgQhEIAIRiEAEIpBf8UeAAQAEjtYmlDTcCgAAAABJRU5ErkJggg==",
"$Meta": {
"Type": "ActionTemplate"
}
}
Page updated on Thursday, November 16, 2023