From b8696fa25a8465f001e1908cb6bb56330337cfbb Mon Sep 17 00:00:00 2001 From: Twerthi Date: Fri, 31 Oct 2025 08:49:20 -0700 Subject: [PATCH 1/2] Adding flyway state based migration template --- .../flyway-state-based-migration.json | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 step-templates/flyway-state-based-migration.json diff --git a/step-templates/flyway-state-based-migration.json b/step-templates/flyway-state-based-migration.json new file mode 100644 index 000000000..cb1b99710 --- /dev/null +++ b/step-templates/flyway-state-based-migration.json @@ -0,0 +1,192 @@ +{ + "Id": "67a28755-049e-4cec-b45c-3e5047350d8d", + "Name": "Flyway State Based Migration", + "Description": "Step template to leverage Flyway to deploy migration scripts. This is the latest and greatest Flyway step template that leverages all the newest features of both Flyway and Octopus Deploy.\n\n- You can include the flyway executables in your package, if you include the `flyway` (Linux) or `flyway.cmd` (Windows) in the root of the package this step template will automatically find them.\n- You can use this with an execution container, negating the need to include Flyway in the package. If Flyway isn't found in the package it will attempt to find `/flyway/flyway` (when using Linux containers) or `flyway` in the environment path and use that.\n- Support for Flyway State Based commands, including the `undo` command.\n- Support for flyway community, teams, enterprise, and pro editions. \n\nPlease note this requires Octopus Deploy **2019.10.0** or newer along with PowerShell Core installed on the machines running this step.\nAWS EC2 IAM Authentication requires the AWS CLI to be installed.", + "ActionType": "Octopus.Script", + "Version": 1, + "CommunityActionTemplateId": null, + "Packages": [ + { + "Id": "0c0d333c-d794-4a16-a3a2-4bbba4550763", + "Name": "Flyway.Package.Value", + "PackageId": null, + "FeedId": null, + "AcquisitionLocation": "Server", + "Properties": { + "Extract": "True", + "SelectionMode": "deferred", + "PackageParameterName": "Flyway.Package.Value" + } + } + ], + "GitDependencies": [], + "Properties": { + "Octopus.Action.Script.ScriptSource": "Inline", + "Octopus.Action.Script.Syntax": "PowerShell", + "Octopus.Action.Script.ScriptBody": "$VerboseActionPreference=\"Continue\"\n\n# Fix ANSI Color on PWSH Core issues when displaying objects\nif ($PSEdition -eq \"Core\") {\n $PSStyle.OutputRendering = \"PlainText\"\n}\n\nfunction Get-FlywayExecutablePath\n{\n\tparam (\n \t$providedPath\n )\n \n if ([string]::IsNullOrWhiteSpace($providedPath) -eq $false)\n {\n \tWrite-Host \"The executable path was provided, testing to see if it is absolute or relative\"\n\t\tif ([IO.Path]::IsPathRooted($providedPath))\n {\n \tWrite-Host \"The provided path is absolute, using that\"\n \n \treturn $providedPath\n }\n \n Write-Host \"The provided path was relative, combining $(Get-Location) with $providedPath\"\n return Join-Path $(Get-Location) $providedPath\n }\n \n Write-Host \"Checking to see if we are currently running on Linux\"\n if ($IsLinux) \n {\n \tWrite-Host \"Currently running on Linux\"\n \tWrite-Host \"Checking to see if flyway was included with the package\"\n \tif (Test-Path \"./flyway\")\n {\n \tWrite-Host \"It was, using that version of flyway\"\n \treturn \"flyway\"\n }\n \n Write-Host \"Testing to see if we are on an execution container with /flyway/flyway as the path\"\n \tif (Test-Path \"/flyway/flyway\")\n {\n \tWrite-Host \"We are, using /flyway/flyway\"\n \treturn \"/flyway/flyway\"\n } \n }\n \n Write-Host \"Currently running on Windows\"\n \n Write-Host \"Testing to see if flyway.cmd was included with the package\"\n if (Test-Path \".\\flyway.cmd\")\n {\n \tWrite-Host \"It was, using that version.\"\n \treturn \".\\flyway.cmd\"\n }\n \n Write-Host \"Testing to see if flyway can be found in the env path\"\n $flywayExecutable = (Get-Command \"flyway\" -ErrorAction SilentlyContinue)\n if ($null -ne $flywayExecutable)\n {\n \tWrite-Host \"The flyway folder is part of the environment path\"\n return $flywayExecutable.Source\n }\n \n Fail-Step \"Unable to find flyway executable. Please include it as part of the package, or provide the path to it.\"\n}\n\nfunction Test-AddParameterToCommandline\n{\n\tparam (\n \t$acceptedCommands,\n $selectedCommand,\n $parameterValue,\n $defaultValue,\n $parameterName\n )\n \n if ([string]::IsNullOrWhiteSpace($parameterValue) -eq $true)\n { \t\n \tWrite-Verbose \"$parameterName is empty, returning false\"\n \treturn $false\n }\n \n if ([string]::IsNullOrWhiteSpace($defaultValue) -eq $false -and $parameterValue.ToLower().Trim() -eq $defaultValue.ToLower().Trim())\n {\n \tWrite-Verbose \"$parameterName is matches the default value, returning false\"\n \treturn $false\n }\n \n if ([string]::IsNullOrWhiteSpace($acceptedCommands) -eq $true -or $acceptedCommands -eq \"any\")\n {\n \tWrite-Verbose \"$parameterName has a value and this is for any command, returning true\"\n \treturn $true\n }\n \n $acceptedCommandArray = $acceptedCommands -split \",\"\n foreach ($command in $acceptedCommandArray)\n {\n \tif ($command.ToLower().Trim() -eq $selectedCommand.ToLower().Trim())\n {\n \tWrite-Verbose \"$parameterName has a value and the current command $selectedCommand matches the accepted command $command, returning true\"\n \treturn $true\n }\n }\n \n Write-Verbose \"$parameterName has a value but is not accepted in the current command, returning false\"\n return $false\n}\n\nfunction Get-ParsedUrl\n{\n\t# Define parameters\n param (\n \t$ConnectionUrl\n )\n \n # Remove the 'jdbc:' portion from the $ConnectionUrl parameter\n $ConnectionUrl = $ConnectionUrl.ToLower().Replace(\"jdbc:\", \"\")\n \n # Parse and return the url\n return [System.Uri]$ConnectionUrl\n}\n\nfunction Execute-FlywayCommand\n{\n # Define parameters\n param(\n $BinaryFilePath,\n $CommandArguments\n )\n\n # Display what's going to be run\n if (![string]::IsNullOrWhitespace($flywayUserPassword))\n {\n $flywayDisplayArguments = $CommandArguments.PSObject.Copy()\n $arrayIndex = 0\n for ($i = 0; $i -lt $flywayDisplayArguments.Count; $i++)\n {\n if ($null -ne $flywayDisplayArguments[$i])\n {\n if ($flywayDisplayArguments[$i].Contains($flywayUserPassword))\n {\n $flywayDisplayArguments[$i] = $flywayDisplayArguments[$i].Replace($flywayUserPassword, \"****\")\n }\n }\n }\n\n Write-Host \"Executing the following command: $flywayCmd $flywayDisplayArguments\"\n }\n else\n {\n Write-Host \"Executing the following command: $flywayCmd $arguments\"\n } \n\n # Adjust call to flyway command based on OS\n if ($IsLinux)\n {\n & bash $BinaryFilePath $CommandArguments\n }\n else\n {\n & $BinaryFilePath $CommandArguments\n } \n}\n\n# Declaring the path to the NuGet package\n$flywayPackagePath = $OctopusParameters[\"Octopus.Action.Package[Flyway.Package.Value].ExtractedPath\"]\n$flywayUrl = $OctopusParameters[\"Flyway.Target.Url\"]\n$flywayUser = $OctopusParameters[\"Flyway.Database.User\"]\n$flywayUserPassword = $OctopusParameters[\"Flyway.Database.User.Password\"]\n$flywayCommand = $OctopusParameters[\"Flyway.Command.Value\"]\n$flywayLicenseEmail = $OctopusParameters[\"Flyway.Email.Address\"]\n$flywayLicensePAT = $OctopusParameters[\"Flyway.PersonalAccessToken\"]\n$flywayExecutablePath = $OctopusParameters[\"Flyway.Executable.Path\"]\n$flywaySchemas = $OctopusParameters[\"Flyway.Command.Schemas\"]\n$flywayAuthenticationMethod = $OctopusParameters[\"Flyway.Authentication.Method\"]\n$flywayAdditionalArguments = $OctopusParameters[\"Flyway.Additional.Arguments\"]\n$flywayStepName = $OctopusParameters[\"Octopus.Action.StepName\"]\n$flywayEnvironment = $OctopusParameters[\"Octopus.Environment.Name\"]\n$flywayTargetSchema = $OctopusParameters[\"Flyway.Target.Schema\"]\n$flywaySourceSchema = $OctopusParameters[\"Flyway.Source.Schema.Model\"]\n$flywayExecuteInTransaction = $OctopusParameters[\"Flyway.Transaction\"]\n$flywayUndoScript = [System.Convert]::ToBoolean($OctopusParameters[\"Flyway.Generate.Undo\"])\n\n\n# Logging for troubleshooting\nWrite-Host \"*******************************************\"\nWrite-Host \"Logging variables:\"\nWrite-Host \" - - - - - - - - - - - - - - - - - - - - -\"\nWrite-Host \"PackagePath: $flywayPackagePath\"\nWrite-Host \"Flyway Executable Path: $flywayExecutablePath\"\nWrite-Host \"Flyway Command: $flywayCommand\"\nWrite-Host \"-url: $flywayUrl\"\nWrite-Host \"-user: $flywayUser\"\nWrite-Host \"-schemas: $flywaySchemas\"\nWrite-Host \"Source Schema Model: $flywaySourceSchema\"\nWrite-Host \"Target Schema: $flywayTargetSchema\"\nWrite-Host \"Execute in transaction: $flywayExecuteInTransaction\"\nWrite-Host \"Additional Arguments: $flywayAdditionalArguments\"\nWrite-Host \"Generate Undo script: $flywayUndoScript\"\nWrite-Host \"*******************************************\"\n\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\nWrite-Host \"Setting execution location to: $flywayPackagePath\"\nSet-Location $flywayPackagePath\n\n$flywayCmd = Get-FlywayExecutablePath -providedPath $flywayExecutablePath\n\n$commandToUse = $flywayCommand\n\n$arguments = @()\n\n# Deteremine authentication method\nswitch ($flywayAuthenticationMethod)\n{\n\t\"awsiam\"\n {\n\t\t# Check to see if OS is Windows and running in a container\n if ($IsWindows -and $env:DOTNET_RUNNING_IN_CONTAINER)\n {\n \tthrow \"IAM Role authentication is not supported in a Windows container.\"\n }\n\n\t\t# Get parsed connection string url\n $parsedUrl = Get-ParsedUrl -ConnectionUrl $flywayUrl\n \n # Region is part of the RDS endpoint, extract\n $region = ($parsedUrl.Host.Split(\".\"))[2]\n\n\t\tWrite-Host \"Generating AWS IAM token ...\"\n\t\t$flywayUserPassword = (aws rds generate-db-auth-token --hostname $parsedUrl.Host --region $region --port $parsedUrl.Port --username $flywayUser)\n\n\t\t$arguments += \"-user=`\"$flywayUser`\"\"\n \t$arguments += \"-password=`\"$flywayUserPassword`\"\"\n\n\t\tbreak\n }\n\t\"azuremanagedidentity\"\n {\n\t\t# Check to see if OS is Windows and running in a container\n if ($IsWindows -and $env:DOTNET_RUNNING_IN_CONTAINER)\n {\n \tthrow \"Azure Managed Identity is not supported in a Windows container.\"\n }\n \n # SQL Server driver doesn't assign password\n if (!$flywayUrl.ToLower().Contains(\"jdbc:sqlserver:\"))\n { \n # Get login token\n Write-Host \"Generating Azure Managed Identity token ...\"\n $token = Invoke-RestMethod -Method GET -Uri \"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://ossrdbms-aad.database.windows.net\" -Headers @{\"MetaData\" = \"true\"} -UseBasicParsing\n\n $flywayUserPassword = $token.access_token\n $arguments += \"-password=`\"$flywayUserPassword`\"\"\n $arguments += \"-user=`\"$flywayUser`\"\"\n }\n else\n {\n \n\t\t\t# Check to see if the querstring parameter for Azure Managed Identity is present\n if (!$flywayUrl.ToLower().Contains(\"authentication=activedirectorymsi\"))\n {\n # Add the authentication piece to the jdbc url\n if (!$flywayUrl.EndsWith(\";\"))\n {\n \t# Add the separator\n $flywayUrl += \";\"\n }\n \n # Add authentication piece\n $flywayUrl += \"Authentication=ActiveDirectoryMSI\"\n }\n }\n \n break\n }\n \"gcpserviceaccount\"\n {\n\t\t# Check to see if OS is Windows and running in a container\n if ($IsWindows -and $env:DOTNET_RUNNING_IN_CONTAINER)\n {\n \tthrow \"GCP Service Account authentication is not supported in a Windows container.\"\n }\n \n # Define header\n $header = @{ \"Metadata-Flavor\" = \"Google\"}\n\n # Retrieve service accounts\n $serviceAccounts = Invoke-RestMethod -Method Get -Uri \"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/\" -Headers $header -UseBasicParsing\n\n # Results returned in plain text format, get into array and remove empty entries\n $serviceAccounts = $serviceAccounts.Split([Environment]::NewLine, [StringSplitOptions]::RemoveEmptyEntries)\n\n # Retreive the specific service account assigned to the VM\n $serviceAccount = $serviceAccounts | Where-Object {$_.ToLower().Contains(\"iam.gserviceaccount.com\") }\n\n\t\tWrite-Host \"Generating GCP IAM token ...\"\n # Retrieve token for account\n $token = Invoke-RestMethod -Method Get -Uri \"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/$serviceAccount/token\" -Headers $header -UseBasicParsing\n \n $flywayUserPassword = $token.access_token\n \n $arguments += \"-user=`\"$flywayUser`\"\"\n $arguments += \"-password=`\"$flywayUserPassword`\"\"\n #$env:FLYWAY_PASSWORD = $flywayUserPassword\n \n break\n } \n \"usernamepassword\"\n {\n \t# Add password\n Write-Host \"Testing for parameters that can be applied to any command\"\n if (Test-AddParameterToCommandline -parameterValue $flywayUser -acceptedCommands \"any\" -selectedCommand $flywayCommand -parameterName \"-user\")\n {\n Write-Host \"User provided, adding user and password command line argument\"\n $arguments += \"-user=`\"$flywayUser`\"\"\n $arguments += \"-password=`\"$flywayUserPassword`\"\"\n }\n \n break\n }\n \"windowsauthentication\"\n {\n \t# Display to the user they've selected windows authentication. Though this is dictated by the jdbc url, this is added to make sure the user knows that's what is\n # being used\n Write-Host \"Using Windows Authentication\"\n \n # Check for integratedauthentication=true in url\n if (!$flywayUrl.ToLower().Contains(\"integratedsecurity=true\"))\n {\n \t# Check to see if the connection url ends with a ;\n if (!$flywayUrl.EndsWith(\";\"))\n {\n \t# Add the ;\n $flywayUrl += \";\"\n }\n \n $flywayUrl += \"integratedSecurity=true;\"\n }\n break\n }\n}\n\n$arguments += \"-url=`\"$flywayUrl`\"\"\n\nif (Test-AddParameterToCommandline -parameterValue $flywaySchemas -acceptedCommands \"any\" -selectedCommand $flywayCommand -parameterName \"-schemas\")\n{\n\tWrite-Host \"Schemas provided, adding schemas command line argument\"\n\t$arguments += \"-schemas=`\"$flywaySchemas`\"\" \n}\n\nif (![string]::IsNullOrWhiteSpace($flywayLicenseEmail) -and ![string]::IsNullOrWhiteSpace($flywayLicensePAT))\n{\n if (Test-AddParameterToCommandline -parameterValue $flywayLicensePAT -acceptedCommands \"any\" -selectedCommand $flywayCommand -parameterName \"-token\")\n {\n Write-Host \"Personal Access Token provided, adding -email and -token command line arguments\"\n $arguments += @(\"-email=`\"$flywayLicenseEmail`\"\", \"-token=`\"$flywayLicensePAT`\"\")\n }\n}\n\nWrite-Host \"Performing diff of schema model against $($flywayUrl)/$($flywayDatabase) ...\"\n\n# Locate schema-model folder\n$packageFolders = Get-ChildItem -Path $flywayPackagePath -Recurse | ?{ $_.PSIsContainer } | Where-Object {$_.Name -eq \"schema-model\"}\n\nif ($packageFolders -is [array])\n{\n Write-Error \"Multiple 'schema-model' folders found!\"\n}\n\n$modelFolderPath = $packageFolders.FullName\n\n$arguments += \"-environments.$flywayEnvironment.url=$flywayUrl\"\n$arguments += \"-environment=$flywayEnvironment\"\n$arguments += \"-schemaModelLocation=$modelFolderPath\"\nif (![string]::IsNullOrWhitespace($flywaySourceSchema))\n{\n $arguments += \"-schemaModelSchemas=$flywaySourceSchema\"\n}\nif (![string]::IsNullOrWhitespace($flywayTargetSchema))\n{\n $arguments += \"-environments.$flywayEnvironment.schemas=$flywayTargetSchema\"\n}\n\n# Execute diff\n$diffArguments = @(\"diff\")\n$diffArguments += $arguments\n$diffArguments += \"-diff.source=schemaModel\"\n$diffArguments += \"-diff.target=env:$flywayEnvironment\"\n$diffArguments += \"-diff.artifactFilename=$flywayPackagePath/artifact.diff\"\n\nExecute-FlywayCommand -BinaryFilePath $flywayCmd -CommandArguments $diffArguments\n\n<#\nWrite-Host \"Finished testing for parameters that can be applied to any command, moving onto command specific parameters\"\n\nif (Test-AddParameterToCommandline -parameterValue $flywayInfoSinceVersion -acceptedCommands \"info\" -selectedCommand $flywayCommand -parameterName \"-infoSinceVersion\")\n{\n\tWrite-Host \"Info since version has been provided, adding that to the command line arguments\"\n\t$arguments += \"-infoSinceVersion=`\"$flywayInfoSinceVersion`\"\"\n} \n#>\n\n# Check to see if there's any additional arguments to add\nif (![string]::IsNullOrWhitespace($flywayAdditionalArguments))\n{\n\t# Split on space\n $flywayAdditionalArgumentsArray = ($flywayAdditionalArguments.Split(\" \", [System.StringSplitOptions]::RemoveEmptyEntries))\n\n # Loop through array\n foreach ($newArgument in $flywayAdditionalArgumentsArray)\n {\n \t# Add the arguments\n \t$arguments += $newArgument\n }\n}\n\n\n\n# Attempt to find driver path for java\n$driverPath = (Get-ChildItem -Path (Get-ChildItem -Path $flywayCmd).Directory -Recurse | Where-Object {$_.PSIsContainer -eq $true -and $_.Name -eq \"drivers\"})\n\n# If found, add driver path to the PATH environment varaible\nif ($null -ne $driverPath)\n{\n\t$env:PATH += \"$([IO.Path]::PathSeparator)$($driverPath.FullName)\"\n}\n\n$currentDate = Get-Date\n$currentDateFormatted = $currentDate.ToString(\"yyyyMMdd_HHmmss\")\n\n$prepareArguments = @(\"prepare\")\n$prepareArguments += $arguments\n$prepareArguments += \"-prepare.source=schemaModel\"\n$prepareArguments += \"-prepare.target=env:$flywayEnvironment\"\n$prepareArguments += \"-prepare.artifactFilename=$flywayPackagePath/artifact.diff\"\n$prepareArguments += \"-prepare.scriptFilename=$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\"\n\nif ($flywayUndoScript)\n{\n $prepareArguments += \"-prepare.undoFilename=$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).undo.sql\"\n $prepareArguments += \"-prepare.types=`\"deploy,undo`\"\"\n}\n\nExecute-FlywayCommand -BinaryFilePath $flywayCmd -CommandArguments $prepareArguments\n\nswitch($flywayCommand)\n{\n \"prepare\"\n {\n if ((Test-Path -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\") -eq $true)\n {\n $fileContents = Get-Content -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\"\n Set-OctopusVariable -name \"ScriptFile\" -value \"$fileContents\"\n Write-Host \"Output variable with script contents: #{Octopus.Action[$flywayStepName].Output.Scriptfile}\"\n\n if ($flywayUndoScript)\n {\n $fileContents = Get-Content -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).undo.sql\"\n Set-OctopusVariable -name \"UndoScriptFile\" -value \"$fileContents\"\n Write-Host \"Output variable with script contents: #{Octopus.Action[$flywayStepName].Output.UndoScriptfile}\"\n }\n }\n\n break\n }\n \"deploy\"\n {\n if ((Test-Path -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\") -eq $true)\n {\n # Define deploy arguments\n $deployArguments = @(\"deploy\")\n $deployArguments += $arguments\n $deployArguments += \"-deploy.scriptFilename=$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\"\n $deployArguments += \"-executeInTransaction=$flywayExecuteInTransaction\"\n\n Execute-FlywayCommand -BinaryFilePath $flywayCmd -CommandArguments $deployArguments\n\n New-OctopusArtifact -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\" -Name \"$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\"\n\n if ($flywayUndoScript)\n {\n New-OctopusArtifact -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).undo.sql\" -Name \"$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).undo.sql\"\n }\n }\n else\n {\n Write-Host \"Script file not found!\"\n }\n }\n}", + "Octopus.Action.PowerShell.Edition": "Core", + "Octopus.Action.EnabledFeatures": "Octopus.Features.SelectPowerShellEditionForWindows" + }, + "Parameters": [ + { + "Id": "5952088c-d399-4050-9474-d93aef737a8d", + "Name": "Flyway.Package.Value", + "Label": "Flyway Package", + "HelpText": "**Required**\n\nThe package containing the migration scripts you want Flyway to run. Please refer to [documentation](https://flywaydb.org/documentation/concepts/migrations) for core concepts and naming conventions.", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "Package" + } + }, + { + "Id": "1a2c32aa-5bf5-41ef-a916-4fa5c4039748", + "Name": "Flyway.Executable.Path", + "Label": "Flyway Executable Path", + "HelpText": "**Optional**\n\nThe path of the flyway executable. It can either be a relative path or an absolute path.\n\nWhen not provided, this step template will test for the following. The step template places precedence on the version of the flyway included in the package. If Flyway is NOT found in the package, it will attempt to see if it is installed on the server by checking common paths.\n\nRunning on `Linux`:\n- `.flyway`: the package being deployed includes flyway and is running on Linux\n- `/flyway/flyway`: The default path for the Linux execution container.\n\nRunning on Windows:\n- `\\.flyway.cmd`: the package being deployed includes flyway and is running on Windows\n- `flyway`: the package is in the path on the Windows VM or Windows Execution container.", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "SingleLineText" + } + }, + { + "Id": "ce396114-d0e6-433f-a160-6ea92b26b1b2", + "Name": "Flyway.Command.Value", + "Label": "Flyway Command", + "HelpText": "**Required**\n\nThe [flyway command](https://flywaydb.org/documentation/usage/commandline/) you wish to run.\n\n- `Prepare`: Compares the schema model against the target database and generates scripts to make the database conform to the model.\n- `Deploy`: Executes the scripts generated to make the target database look like the model.", + "DefaultValue": "prepare", + "DisplaySettings": { + "Octopus.ControlType": "Select", + "Octopus.SelectOptions": "prepare|Prepare\ndeploy|Deploy" + } + }, + { + "Id": "b05cb50e-1e34-4c2d-bf3f-b7bbdc555f05", + "Name": "Flyway.Email.Address", + "Label": "Email address", + "HelpText": "The email address associated with the Personal Access Token (PAT).", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "SingleLineText" + } + }, + { + "Id": "24c6fe79-6fbd-4f19-9f74-a36cda746ced", + "Name": "Flyway.PersonalAccessToken", + "Label": "Personal Access Token (PAT)", + "HelpText": "The Personal Access Token (PAT) to authenticate with for Enterprise features. \n\nReplaces the License Key.", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "Sensitive" + } + }, + { + "Id": "522f4dc8-3a0a-486d-a9cd-9c2ef94d1a8e", + "Name": "Flyway.Target.Url", + "Label": "-Url", + "HelpText": "**Required**\n\nThe [URL](https://flywaydb.org/documentation/configuration/parameters/url) parameter used in Flyway. This is the URL of the database to run the migration scripts on in the format specified in the default flyway.conf file.\n\nExamples:\n- SQL Server: `jdbc:sqlserver://host:port;databaseName=database`\n- Oracle: `jdbc:oracle:thin:@//host:port/service` or `jdbc:oracle:thin:@tns_entry`\n- MySQL: `jdbc:mysql://host:port/database`\n- PostgreSQL: `jdbc:postgresql://host:port/database`\n- SQLite: `jdbc:sqlite:database`\n\nPlease refer to [documentation](https://flywaydb.org/documentation/database/sqlserver) for further examples.", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "SingleLineText" + } + }, + { + "Id": "349f8a5f-5a18-4d23-a717-1c74a03029eb", + "Name": "Flyway.Source.Schema.Model", + "Label": "Source Schema Model (optional).", + "HelpText": "Name of the source schema model used for the comparison operation. Not all database technologies, such as MSSQL, use this option.", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "SingleLineText" + } + }, + { + "Id": "195e2486-f286-4a72-a97a-f40b7a44a609", + "Name": "Flyway.Target.Schema", + "Label": "Target Schema (optional)", + "HelpText": "Name of the target schema to deploy to.", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "SingleLineText" + } + }, + { + "Id": "16d4243a-28b0-4e8d-beec-854ca4c781b0", + "Name": "Flyway.Authentication.Method", + "Label": "Authentication Method", + "HelpText": "Method used to authenticate to the database server.", + "DefaultValue": "usernamepassword", + "DisplaySettings": { + "Octopus.ControlType": "Select", + "Octopus.SelectOptions": "awsiam|AWS EC2 IAM Role\nazuremanagedidentity|Azure Managed Identity\ngcpserviceaccount|GCP Service Account\nusernamepassword|Username\\Password\nwindowsauthentication|Windows Authentication" + } + }, + { + "Id": "3f733c39-c99a-40d0-9dfd-e67d72c87883", + "Name": "Flyway.Database.User", + "Label": "-User", + "HelpText": "**Optional**\n\nThe [user](https://flywaydb.org/documentation/configuration/parameters/user) used to connect to the database.", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "SingleLineText" + } + }, + { + "Id": "7509e8ee-c781-4ca2-bbe4-6e5e348d3d44", + "Name": "Flyway.Database.User.Password", + "Label": "-Password", + "HelpText": "**Optional**\n\nThe [password](https://flywaydb.org/documentation/configuration/parameters/password) used to connect to the database.", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "Sensitive" + } + }, + { + "Id": "6d4946a4-0746-40e9-9b6e-c85c220c2dbe", + "Name": "Flyway.Command.Schemas", + "Label": "-Schemas", + "HelpText": "**Optional**\n\nComma-separated case-sensitive list of [schemas](https://flywaydb.org/documentation/configuration/parameters/schemas) managed by Flyway. \n\nExample: `schema1,schema2`\n\nFlyway will attempt to create these schemas if they do not already exist and will clean them in the order of this list. If Flyway created them, then the schemas themselves will be dropped when cleaning.\n\nThe first schema in the list will act as the default schema.", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "SingleLineText" + } + }, + { + "Id": "12168dc6-261b-4853-89ea-ef4e36e17452", + "Name": "Flyway.Additional.Arguments", + "Label": "Additional arguments", + "HelpText": "Any additional arguments that need to be passed (ie `-table=\"MyTable\")", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "SingleLineText" + } + }, + { + "Id": "7c7a8183-963d-40e5-94e9-b7a6aa2217df", + "Name": "Flyway.Transaction", + "Label": "Execute in transaction?", + "HelpText": "Tick this box if you want the deployment to execute within a transaction.", + "DefaultValue": "False", + "DisplaySettings": { + "Octopus.ControlType": "Checkbox" + } + }, + { + "Id": "291b9675-e737-4dfe-98fc-bbf303a87653", + "Name": "Flyway.Generate.Undo", + "Label": "Generate Undo Script?", + "HelpText": "Tick this box if you want Flwyay to generate an `Undo` script. `Undo` scripts will be created as an output variable for `Prepare` and attached as an Octopus Artifact for `Deploy`.", + "DefaultValue": "False", + "DisplaySettings": { + "Octopus.ControlType": "Checkbox" + } + } + ], + "StepPackageId": "Octopus.Script", + "$Meta": { + "ExportedAt": "2025-10-31T15:45:05.259Z", + "OctopusVersion": "2025.3.14410", + "Type": "ActionTemplate" + }, + "LastModifiedBy": "twerthi", + "Category": "flyway" +} From 36701a77b1c1d43609878a8a414ba03b1e13d396 Mon Sep 17 00:00:00 2001 From: Twerthi Date: Mon, 3 Nov 2025 10:37:07 -0800 Subject: [PATCH 2/2] Removing unneeded function --- step-templates/flyway-state-based-migration.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/step-templates/flyway-state-based-migration.json b/step-templates/flyway-state-based-migration.json index cb1b99710..65847c880 100644 --- a/step-templates/flyway-state-based-migration.json +++ b/step-templates/flyway-state-based-migration.json @@ -23,7 +23,7 @@ "Properties": { "Octopus.Action.Script.ScriptSource": "Inline", "Octopus.Action.Script.Syntax": "PowerShell", - "Octopus.Action.Script.ScriptBody": "$VerboseActionPreference=\"Continue\"\n\n# Fix ANSI Color on PWSH Core issues when displaying objects\nif ($PSEdition -eq \"Core\") {\n $PSStyle.OutputRendering = \"PlainText\"\n}\n\nfunction Get-FlywayExecutablePath\n{\n\tparam (\n \t$providedPath\n )\n \n if ([string]::IsNullOrWhiteSpace($providedPath) -eq $false)\n {\n \tWrite-Host \"The executable path was provided, testing to see if it is absolute or relative\"\n\t\tif ([IO.Path]::IsPathRooted($providedPath))\n {\n \tWrite-Host \"The provided path is absolute, using that\"\n \n \treturn $providedPath\n }\n \n Write-Host \"The provided path was relative, combining $(Get-Location) with $providedPath\"\n return Join-Path $(Get-Location) $providedPath\n }\n \n Write-Host \"Checking to see if we are currently running on Linux\"\n if ($IsLinux) \n {\n \tWrite-Host \"Currently running on Linux\"\n \tWrite-Host \"Checking to see if flyway was included with the package\"\n \tif (Test-Path \"./flyway\")\n {\n \tWrite-Host \"It was, using that version of flyway\"\n \treturn \"flyway\"\n }\n \n Write-Host \"Testing to see if we are on an execution container with /flyway/flyway as the path\"\n \tif (Test-Path \"/flyway/flyway\")\n {\n \tWrite-Host \"We are, using /flyway/flyway\"\n \treturn \"/flyway/flyway\"\n } \n }\n \n Write-Host \"Currently running on Windows\"\n \n Write-Host \"Testing to see if flyway.cmd was included with the package\"\n if (Test-Path \".\\flyway.cmd\")\n {\n \tWrite-Host \"It was, using that version.\"\n \treturn \".\\flyway.cmd\"\n }\n \n Write-Host \"Testing to see if flyway can be found in the env path\"\n $flywayExecutable = (Get-Command \"flyway\" -ErrorAction SilentlyContinue)\n if ($null -ne $flywayExecutable)\n {\n \tWrite-Host \"The flyway folder is part of the environment path\"\n return $flywayExecutable.Source\n }\n \n Fail-Step \"Unable to find flyway executable. Please include it as part of the package, or provide the path to it.\"\n}\n\nfunction Test-AddParameterToCommandline\n{\n\tparam (\n \t$acceptedCommands,\n $selectedCommand,\n $parameterValue,\n $defaultValue,\n $parameterName\n )\n \n if ([string]::IsNullOrWhiteSpace($parameterValue) -eq $true)\n { \t\n \tWrite-Verbose \"$parameterName is empty, returning false\"\n \treturn $false\n }\n \n if ([string]::IsNullOrWhiteSpace($defaultValue) -eq $false -and $parameterValue.ToLower().Trim() -eq $defaultValue.ToLower().Trim())\n {\n \tWrite-Verbose \"$parameterName is matches the default value, returning false\"\n \treturn $false\n }\n \n if ([string]::IsNullOrWhiteSpace($acceptedCommands) -eq $true -or $acceptedCommands -eq \"any\")\n {\n \tWrite-Verbose \"$parameterName has a value and this is for any command, returning true\"\n \treturn $true\n }\n \n $acceptedCommandArray = $acceptedCommands -split \",\"\n foreach ($command in $acceptedCommandArray)\n {\n \tif ($command.ToLower().Trim() -eq $selectedCommand.ToLower().Trim())\n {\n \tWrite-Verbose \"$parameterName has a value and the current command $selectedCommand matches the accepted command $command, returning true\"\n \treturn $true\n }\n }\n \n Write-Verbose \"$parameterName has a value but is not accepted in the current command, returning false\"\n return $false\n}\n\nfunction Get-ParsedUrl\n{\n\t# Define parameters\n param (\n \t$ConnectionUrl\n )\n \n # Remove the 'jdbc:' portion from the $ConnectionUrl parameter\n $ConnectionUrl = $ConnectionUrl.ToLower().Replace(\"jdbc:\", \"\")\n \n # Parse and return the url\n return [System.Uri]$ConnectionUrl\n}\n\nfunction Execute-FlywayCommand\n{\n # Define parameters\n param(\n $BinaryFilePath,\n $CommandArguments\n )\n\n # Display what's going to be run\n if (![string]::IsNullOrWhitespace($flywayUserPassword))\n {\n $flywayDisplayArguments = $CommandArguments.PSObject.Copy()\n $arrayIndex = 0\n for ($i = 0; $i -lt $flywayDisplayArguments.Count; $i++)\n {\n if ($null -ne $flywayDisplayArguments[$i])\n {\n if ($flywayDisplayArguments[$i].Contains($flywayUserPassword))\n {\n $flywayDisplayArguments[$i] = $flywayDisplayArguments[$i].Replace($flywayUserPassword, \"****\")\n }\n }\n }\n\n Write-Host \"Executing the following command: $flywayCmd $flywayDisplayArguments\"\n }\n else\n {\n Write-Host \"Executing the following command: $flywayCmd $arguments\"\n } \n\n # Adjust call to flyway command based on OS\n if ($IsLinux)\n {\n & bash $BinaryFilePath $CommandArguments\n }\n else\n {\n & $BinaryFilePath $CommandArguments\n } \n}\n\n# Declaring the path to the NuGet package\n$flywayPackagePath = $OctopusParameters[\"Octopus.Action.Package[Flyway.Package.Value].ExtractedPath\"]\n$flywayUrl = $OctopusParameters[\"Flyway.Target.Url\"]\n$flywayUser = $OctopusParameters[\"Flyway.Database.User\"]\n$flywayUserPassword = $OctopusParameters[\"Flyway.Database.User.Password\"]\n$flywayCommand = $OctopusParameters[\"Flyway.Command.Value\"]\n$flywayLicenseEmail = $OctopusParameters[\"Flyway.Email.Address\"]\n$flywayLicensePAT = $OctopusParameters[\"Flyway.PersonalAccessToken\"]\n$flywayExecutablePath = $OctopusParameters[\"Flyway.Executable.Path\"]\n$flywaySchemas = $OctopusParameters[\"Flyway.Command.Schemas\"]\n$flywayAuthenticationMethod = $OctopusParameters[\"Flyway.Authentication.Method\"]\n$flywayAdditionalArguments = $OctopusParameters[\"Flyway.Additional.Arguments\"]\n$flywayStepName = $OctopusParameters[\"Octopus.Action.StepName\"]\n$flywayEnvironment = $OctopusParameters[\"Octopus.Environment.Name\"]\n$flywayTargetSchema = $OctopusParameters[\"Flyway.Target.Schema\"]\n$flywaySourceSchema = $OctopusParameters[\"Flyway.Source.Schema.Model\"]\n$flywayExecuteInTransaction = $OctopusParameters[\"Flyway.Transaction\"]\n$flywayUndoScript = [System.Convert]::ToBoolean($OctopusParameters[\"Flyway.Generate.Undo\"])\n\n\n# Logging for troubleshooting\nWrite-Host \"*******************************************\"\nWrite-Host \"Logging variables:\"\nWrite-Host \" - - - - - - - - - - - - - - - - - - - - -\"\nWrite-Host \"PackagePath: $flywayPackagePath\"\nWrite-Host \"Flyway Executable Path: $flywayExecutablePath\"\nWrite-Host \"Flyway Command: $flywayCommand\"\nWrite-Host \"-url: $flywayUrl\"\nWrite-Host \"-user: $flywayUser\"\nWrite-Host \"-schemas: $flywaySchemas\"\nWrite-Host \"Source Schema Model: $flywaySourceSchema\"\nWrite-Host \"Target Schema: $flywayTargetSchema\"\nWrite-Host \"Execute in transaction: $flywayExecuteInTransaction\"\nWrite-Host \"Additional Arguments: $flywayAdditionalArguments\"\nWrite-Host \"Generate Undo script: $flywayUndoScript\"\nWrite-Host \"*******************************************\"\n\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\nWrite-Host \"Setting execution location to: $flywayPackagePath\"\nSet-Location $flywayPackagePath\n\n$flywayCmd = Get-FlywayExecutablePath -providedPath $flywayExecutablePath\n\n$commandToUse = $flywayCommand\n\n$arguments = @()\n\n# Deteremine authentication method\nswitch ($flywayAuthenticationMethod)\n{\n\t\"awsiam\"\n {\n\t\t# Check to see if OS is Windows and running in a container\n if ($IsWindows -and $env:DOTNET_RUNNING_IN_CONTAINER)\n {\n \tthrow \"IAM Role authentication is not supported in a Windows container.\"\n }\n\n\t\t# Get parsed connection string url\n $parsedUrl = Get-ParsedUrl -ConnectionUrl $flywayUrl\n \n # Region is part of the RDS endpoint, extract\n $region = ($parsedUrl.Host.Split(\".\"))[2]\n\n\t\tWrite-Host \"Generating AWS IAM token ...\"\n\t\t$flywayUserPassword = (aws rds generate-db-auth-token --hostname $parsedUrl.Host --region $region --port $parsedUrl.Port --username $flywayUser)\n\n\t\t$arguments += \"-user=`\"$flywayUser`\"\"\n \t$arguments += \"-password=`\"$flywayUserPassword`\"\"\n\n\t\tbreak\n }\n\t\"azuremanagedidentity\"\n {\n\t\t# Check to see if OS is Windows and running in a container\n if ($IsWindows -and $env:DOTNET_RUNNING_IN_CONTAINER)\n {\n \tthrow \"Azure Managed Identity is not supported in a Windows container.\"\n }\n \n # SQL Server driver doesn't assign password\n if (!$flywayUrl.ToLower().Contains(\"jdbc:sqlserver:\"))\n { \n # Get login token\n Write-Host \"Generating Azure Managed Identity token ...\"\n $token = Invoke-RestMethod -Method GET -Uri \"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://ossrdbms-aad.database.windows.net\" -Headers @{\"MetaData\" = \"true\"} -UseBasicParsing\n\n $flywayUserPassword = $token.access_token\n $arguments += \"-password=`\"$flywayUserPassword`\"\"\n $arguments += \"-user=`\"$flywayUser`\"\"\n }\n else\n {\n \n\t\t\t# Check to see if the querstring parameter for Azure Managed Identity is present\n if (!$flywayUrl.ToLower().Contains(\"authentication=activedirectorymsi\"))\n {\n # Add the authentication piece to the jdbc url\n if (!$flywayUrl.EndsWith(\";\"))\n {\n \t# Add the separator\n $flywayUrl += \";\"\n }\n \n # Add authentication piece\n $flywayUrl += \"Authentication=ActiveDirectoryMSI\"\n }\n }\n \n break\n }\n \"gcpserviceaccount\"\n {\n\t\t# Check to see if OS is Windows and running in a container\n if ($IsWindows -and $env:DOTNET_RUNNING_IN_CONTAINER)\n {\n \tthrow \"GCP Service Account authentication is not supported in a Windows container.\"\n }\n \n # Define header\n $header = @{ \"Metadata-Flavor\" = \"Google\"}\n\n # Retrieve service accounts\n $serviceAccounts = Invoke-RestMethod -Method Get -Uri \"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/\" -Headers $header -UseBasicParsing\n\n # Results returned in plain text format, get into array and remove empty entries\n $serviceAccounts = $serviceAccounts.Split([Environment]::NewLine, [StringSplitOptions]::RemoveEmptyEntries)\n\n # Retreive the specific service account assigned to the VM\n $serviceAccount = $serviceAccounts | Where-Object {$_.ToLower().Contains(\"iam.gserviceaccount.com\") }\n\n\t\tWrite-Host \"Generating GCP IAM token ...\"\n # Retrieve token for account\n $token = Invoke-RestMethod -Method Get -Uri \"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/$serviceAccount/token\" -Headers $header -UseBasicParsing\n \n $flywayUserPassword = $token.access_token\n \n $arguments += \"-user=`\"$flywayUser`\"\"\n $arguments += \"-password=`\"$flywayUserPassword`\"\"\n #$env:FLYWAY_PASSWORD = $flywayUserPassword\n \n break\n } \n \"usernamepassword\"\n {\n \t# Add password\n Write-Host \"Testing for parameters that can be applied to any command\"\n if (Test-AddParameterToCommandline -parameterValue $flywayUser -acceptedCommands \"any\" -selectedCommand $flywayCommand -parameterName \"-user\")\n {\n Write-Host \"User provided, adding user and password command line argument\"\n $arguments += \"-user=`\"$flywayUser`\"\"\n $arguments += \"-password=`\"$flywayUserPassword`\"\"\n }\n \n break\n }\n \"windowsauthentication\"\n {\n \t# Display to the user they've selected windows authentication. Though this is dictated by the jdbc url, this is added to make sure the user knows that's what is\n # being used\n Write-Host \"Using Windows Authentication\"\n \n # Check for integratedauthentication=true in url\n if (!$flywayUrl.ToLower().Contains(\"integratedsecurity=true\"))\n {\n \t# Check to see if the connection url ends with a ;\n if (!$flywayUrl.EndsWith(\";\"))\n {\n \t# Add the ;\n $flywayUrl += \";\"\n }\n \n $flywayUrl += \"integratedSecurity=true;\"\n }\n break\n }\n}\n\n$arguments += \"-url=`\"$flywayUrl`\"\"\n\nif (Test-AddParameterToCommandline -parameterValue $flywaySchemas -acceptedCommands \"any\" -selectedCommand $flywayCommand -parameterName \"-schemas\")\n{\n\tWrite-Host \"Schemas provided, adding schemas command line argument\"\n\t$arguments += \"-schemas=`\"$flywaySchemas`\"\" \n}\n\nif (![string]::IsNullOrWhiteSpace($flywayLicenseEmail) -and ![string]::IsNullOrWhiteSpace($flywayLicensePAT))\n{\n if (Test-AddParameterToCommandline -parameterValue $flywayLicensePAT -acceptedCommands \"any\" -selectedCommand $flywayCommand -parameterName \"-token\")\n {\n Write-Host \"Personal Access Token provided, adding -email and -token command line arguments\"\n $arguments += @(\"-email=`\"$flywayLicenseEmail`\"\", \"-token=`\"$flywayLicensePAT`\"\")\n }\n}\n\nWrite-Host \"Performing diff of schema model against $($flywayUrl)/$($flywayDatabase) ...\"\n\n# Locate schema-model folder\n$packageFolders = Get-ChildItem -Path $flywayPackagePath -Recurse | ?{ $_.PSIsContainer } | Where-Object {$_.Name -eq \"schema-model\"}\n\nif ($packageFolders -is [array])\n{\n Write-Error \"Multiple 'schema-model' folders found!\"\n}\n\n$modelFolderPath = $packageFolders.FullName\n\n$arguments += \"-environments.$flywayEnvironment.url=$flywayUrl\"\n$arguments += \"-environment=$flywayEnvironment\"\n$arguments += \"-schemaModelLocation=$modelFolderPath\"\nif (![string]::IsNullOrWhitespace($flywaySourceSchema))\n{\n $arguments += \"-schemaModelSchemas=$flywaySourceSchema\"\n}\nif (![string]::IsNullOrWhitespace($flywayTargetSchema))\n{\n $arguments += \"-environments.$flywayEnvironment.schemas=$flywayTargetSchema\"\n}\n\n# Execute diff\n$diffArguments = @(\"diff\")\n$diffArguments += $arguments\n$diffArguments += \"-diff.source=schemaModel\"\n$diffArguments += \"-diff.target=env:$flywayEnvironment\"\n$diffArguments += \"-diff.artifactFilename=$flywayPackagePath/artifact.diff\"\n\nExecute-FlywayCommand -BinaryFilePath $flywayCmd -CommandArguments $diffArguments\n\n<#\nWrite-Host \"Finished testing for parameters that can be applied to any command, moving onto command specific parameters\"\n\nif (Test-AddParameterToCommandline -parameterValue $flywayInfoSinceVersion -acceptedCommands \"info\" -selectedCommand $flywayCommand -parameterName \"-infoSinceVersion\")\n{\n\tWrite-Host \"Info since version has been provided, adding that to the command line arguments\"\n\t$arguments += \"-infoSinceVersion=`\"$flywayInfoSinceVersion`\"\"\n} \n#>\n\n# Check to see if there's any additional arguments to add\nif (![string]::IsNullOrWhitespace($flywayAdditionalArguments))\n{\n\t# Split on space\n $flywayAdditionalArgumentsArray = ($flywayAdditionalArguments.Split(\" \", [System.StringSplitOptions]::RemoveEmptyEntries))\n\n # Loop through array\n foreach ($newArgument in $flywayAdditionalArgumentsArray)\n {\n \t# Add the arguments\n \t$arguments += $newArgument\n }\n}\n\n\n\n# Attempt to find driver path for java\n$driverPath = (Get-ChildItem -Path (Get-ChildItem -Path $flywayCmd).Directory -Recurse | Where-Object {$_.PSIsContainer -eq $true -and $_.Name -eq \"drivers\"})\n\n# If found, add driver path to the PATH environment varaible\nif ($null -ne $driverPath)\n{\n\t$env:PATH += \"$([IO.Path]::PathSeparator)$($driverPath.FullName)\"\n}\n\n$currentDate = Get-Date\n$currentDateFormatted = $currentDate.ToString(\"yyyyMMdd_HHmmss\")\n\n$prepareArguments = @(\"prepare\")\n$prepareArguments += $arguments\n$prepareArguments += \"-prepare.source=schemaModel\"\n$prepareArguments += \"-prepare.target=env:$flywayEnvironment\"\n$prepareArguments += \"-prepare.artifactFilename=$flywayPackagePath/artifact.diff\"\n$prepareArguments += \"-prepare.scriptFilename=$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\"\n\nif ($flywayUndoScript)\n{\n $prepareArguments += \"-prepare.undoFilename=$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).undo.sql\"\n $prepareArguments += \"-prepare.types=`\"deploy,undo`\"\"\n}\n\nExecute-FlywayCommand -BinaryFilePath $flywayCmd -CommandArguments $prepareArguments\n\nswitch($flywayCommand)\n{\n \"prepare\"\n {\n if ((Test-Path -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\") -eq $true)\n {\n $fileContents = Get-Content -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\"\n Set-OctopusVariable -name \"ScriptFile\" -value \"$fileContents\"\n Write-Host \"Output variable with script contents: #{Octopus.Action[$flywayStepName].Output.Scriptfile}\"\n\n if ($flywayUndoScript)\n {\n $fileContents = Get-Content -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).undo.sql\"\n Set-OctopusVariable -name \"UndoScriptFile\" -value \"$fileContents\"\n Write-Host \"Output variable with script contents: #{Octopus.Action[$flywayStepName].Output.UndoScriptfile}\"\n }\n }\n\n break\n }\n \"deploy\"\n {\n if ((Test-Path -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\") -eq $true)\n {\n # Define deploy arguments\n $deployArguments = @(\"deploy\")\n $deployArguments += $arguments\n $deployArguments += \"-deploy.scriptFilename=$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\"\n $deployArguments += \"-executeInTransaction=$flywayExecuteInTransaction\"\n\n Execute-FlywayCommand -BinaryFilePath $flywayCmd -CommandArguments $deployArguments\n\n New-OctopusArtifact -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\" -Name \"$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\"\n\n if ($flywayUndoScript)\n {\n New-OctopusArtifact -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).undo.sql\" -Name \"$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).undo.sql\"\n }\n }\n else\n {\n Write-Host \"Script file not found!\"\n }\n }\n}", + "Octopus.Action.Script.ScriptBody": "$VerboseActionPreference=\"Continue\"\n\n# Fix ANSI Color on PWSH Core issues when displaying objects\nif ($PSEdition -eq \"Core\") {\n $PSStyle.OutputRendering = \"PlainText\"\n}\n\nfunction Get-FlywayExecutablePath\n{\n\tparam (\n \t$providedPath\n )\n \n if ([string]::IsNullOrWhiteSpace($providedPath) -eq $false)\n {\n \tWrite-Host \"The executable path was provided, testing to see if it is absolute or relative\"\n\t\tif ([IO.Path]::IsPathRooted($providedPath))\n {\n \tWrite-Host \"The provided path is absolute, using that\"\n \n \treturn $providedPath\n }\n \n Write-Host \"The provided path was relative, combining $(Get-Location) with $providedPath\"\n return Join-Path $(Get-Location) $providedPath\n }\n \n Write-Host \"Checking to see if we are currently running on Linux\"\n if ($IsLinux) \n {\n \tWrite-Host \"Currently running on Linux\"\n \tWrite-Host \"Checking to see if flyway was included with the package\"\n \tif (Test-Path \"./flyway\")\n {\n \tWrite-Host \"It was, using that version of flyway\"\n \treturn \"flyway\"\n }\n \n Write-Host \"Testing to see if we are on an execution container with /flyway/flyway as the path\"\n \tif (Test-Path \"/flyway/flyway\")\n {\n \tWrite-Host \"We are, using /flyway/flyway\"\n \treturn \"/flyway/flyway\"\n } \n }\n \n Write-Host \"Currently running on Windows\"\n \n Write-Host \"Testing to see if flyway.cmd was included with the package\"\n if (Test-Path \".\\flyway.cmd\")\n {\n \tWrite-Host \"It was, using that version.\"\n \treturn \".\\flyway.cmd\"\n }\n \n Write-Host \"Testing to see if flyway can be found in the env path\"\n $flywayExecutable = (Get-Command \"flyway\" -ErrorAction SilentlyContinue)\n if ($null -ne $flywayExecutable)\n {\n \tWrite-Host \"The flyway folder is part of the environment path\"\n return $flywayExecutable.Source\n }\n \n Fail-Step \"Unable to find flyway executable. Please include it as part of the package, or provide the path to it.\"\n}\n\nfunction Get-ParsedUrl\n{\n\t# Define parameters\n param (\n \t$ConnectionUrl\n )\n \n # Remove the 'jdbc:' portion from the $ConnectionUrl parameter\n $ConnectionUrl = $ConnectionUrl.ToLower().Replace(\"jdbc:\", \"\")\n \n # Parse and return the url\n return [System.Uri]$ConnectionUrl\n}\n\nfunction Execute-FlywayCommand\n{\n # Define parameters\n param(\n $BinaryFilePath,\n $CommandArguments\n )\n\n # Display what's going to be run\n if (![string]::IsNullOrWhitespace($flywayUserPassword))\n {\n $flywayDisplayArguments = $CommandArguments.PSObject.Copy()\n $arrayIndex = 0\n for ($i = 0; $i -lt $flywayDisplayArguments.Count; $i++)\n {\n if ($null -ne $flywayDisplayArguments[$i])\n {\n if ($flywayDisplayArguments[$i].Contains($flywayUserPassword))\n {\n $flywayDisplayArguments[$i] = $flywayDisplayArguments[$i].Replace($flywayUserPassword, \"****\")\n }\n }\n }\n\n Write-Host \"Executing the following command: $flywayCmd $flywayDisplayArguments\"\n }\n else\n {\n Write-Host \"Executing the following command: $flywayCmd $arguments\"\n } \n\n # Adjust call to flyway command based on OS\n if ($IsLinux)\n {\n & bash $BinaryFilePath $CommandArguments\n }\n else\n {\n & $BinaryFilePath $CommandArguments\n } \n}\n\n# Declaring the path to the NuGet package\n$flywayPackagePath = $OctopusParameters[\"Octopus.Action.Package[Flyway.Package.Value].ExtractedPath\"]\n$flywayUrl = $OctopusParameters[\"Flyway.Target.Url\"]\n$flywayUser = $OctopusParameters[\"Flyway.Database.User\"]\n$flywayUserPassword = $OctopusParameters[\"Flyway.Database.User.Password\"]\n$flywayCommand = $OctopusParameters[\"Flyway.Command.Value\"]\n$flywayLicenseEmail = $OctopusParameters[\"Flyway.Email.Address\"]\n$flywayLicensePAT = $OctopusParameters[\"Flyway.PersonalAccessToken\"]\n$flywayExecutablePath = $OctopusParameters[\"Flyway.Executable.Path\"]\n$flywaySchemas = $OctopusParameters[\"Flyway.Command.Schemas\"]\n$flywayAuthenticationMethod = $OctopusParameters[\"Flyway.Authentication.Method\"]\n$flywayAdditionalArguments = $OctopusParameters[\"Flyway.Additional.Arguments\"]\n$flywayStepName = $OctopusParameters[\"Octopus.Action.StepName\"]\n$flywayEnvironment = $OctopusParameters[\"Octopus.Environment.Name\"]\n$flywayTargetSchema = $OctopusParameters[\"Flyway.Target.Schema\"]\n$flywaySourceSchema = $OctopusParameters[\"Flyway.Source.Schema.Model\"]\n$flywayExecuteInTransaction = $OctopusParameters[\"Flyway.Transaction\"]\n$flywayUndoScript = [System.Convert]::ToBoolean($OctopusParameters[\"Flyway.Generate.Undo\"])\n\n\n# Logging for troubleshooting\nWrite-Host \"*******************************************\"\nWrite-Host \"Logging variables:\"\nWrite-Host \" - - - - - - - - - - - - - - - - - - - - -\"\nWrite-Host \"PackagePath: $flywayPackagePath\"\nWrite-Host \"Flyway Executable Path: $flywayExecutablePath\"\nWrite-Host \"Flyway Command: $flywayCommand\"\nWrite-Host \"-url: $flywayUrl\"\nWrite-Host \"-user: $flywayUser\"\nWrite-Host \"-schemas: $flywaySchemas\"\nWrite-Host \"Source Schema Model: $flywaySourceSchema\"\nWrite-Host \"Target Schema: $flywayTargetSchema\"\nWrite-Host \"Execute in transaction: $flywayExecuteInTransaction\"\nWrite-Host \"Additional Arguments: $flywayAdditionalArguments\"\nWrite-Host \"Generate Undo script: $flywayUndoScript\"\nWrite-Host \"*******************************************\"\n\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\nWrite-Host \"Setting execution location to: $flywayPackagePath\"\nSet-Location $flywayPackagePath\n\n$flywayCmd = Get-FlywayExecutablePath -providedPath $flywayExecutablePath\n\n$commandToUse = $flywayCommand\n\n$arguments = @()\n\n# Deteremine authentication method\nswitch ($flywayAuthenticationMethod)\n{\n\t\"awsiam\"\n {\n\t\t# Check to see if OS is Windows and running in a container\n if ($IsWindows -and $env:DOTNET_RUNNING_IN_CONTAINER)\n {\n \tthrow \"IAM Role authentication is not supported in a Windows container.\"\n }\n\n\t\t# Get parsed connection string url\n $parsedUrl = Get-ParsedUrl -ConnectionUrl $flywayUrl\n \n # Region is part of the RDS endpoint, extract\n $region = ($parsedUrl.Host.Split(\".\"))[2]\n\n\t\tWrite-Host \"Generating AWS IAM token ...\"\n\t\t$flywayUserPassword = (aws rds generate-db-auth-token --hostname $parsedUrl.Host --region $region --port $parsedUrl.Port --username $flywayUser)\n\n\t\t$arguments += \"-user=`\"$flywayUser`\"\"\n \t$arguments += \"-password=`\"$flywayUserPassword`\"\"\n\n\t\tbreak\n }\n\t\"azuremanagedidentity\"\n {\n\t\t# Check to see if OS is Windows and running in a container\n if ($IsWindows -and $env:DOTNET_RUNNING_IN_CONTAINER)\n {\n \tthrow \"Azure Managed Identity is not supported in a Windows container.\"\n }\n \n # SQL Server driver doesn't assign password\n if (!$flywayUrl.ToLower().Contains(\"jdbc:sqlserver:\"))\n { \n # Get login token\n Write-Host \"Generating Azure Managed Identity token ...\"\n $token = Invoke-RestMethod -Method GET -Uri \"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://ossrdbms-aad.database.windows.net\" -Headers @{\"MetaData\" = \"true\"} -UseBasicParsing\n\n $flywayUserPassword = $token.access_token\n $arguments += \"-password=`\"$flywayUserPassword`\"\"\n $arguments += \"-user=`\"$flywayUser`\"\"\n }\n else\n {\n \n\t\t\t# Check to see if the querstring parameter for Azure Managed Identity is present\n if (!$flywayUrl.ToLower().Contains(\"authentication=activedirectorymsi\"))\n {\n # Add the authentication piece to the jdbc url\n if (!$flywayUrl.EndsWith(\";\"))\n {\n \t# Add the separator\n $flywayUrl += \";\"\n }\n \n # Add authentication piece\n $flywayUrl += \"Authentication=ActiveDirectoryMSI\"\n }\n }\n \n break\n }\n \"gcpserviceaccount\"\n {\n\t\t# Check to see if OS is Windows and running in a container\n if ($IsWindows -and $env:DOTNET_RUNNING_IN_CONTAINER)\n {\n \tthrow \"GCP Service Account authentication is not supported in a Windows container.\"\n }\n \n # Define header\n $header = @{ \"Metadata-Flavor\" = \"Google\"}\n\n # Retrieve service accounts\n $serviceAccounts = Invoke-RestMethod -Method Get -Uri \"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/\" -Headers $header -UseBasicParsing\n\n # Results returned in plain text format, get into array and remove empty entries\n $serviceAccounts = $serviceAccounts.Split([Environment]::NewLine, [StringSplitOptions]::RemoveEmptyEntries)\n\n # Retreive the specific service account assigned to the VM\n $serviceAccount = $serviceAccounts | Where-Object {$_.ToLower().Contains(\"iam.gserviceaccount.com\") }\n\n\t\tWrite-Host \"Generating GCP IAM token ...\"\n # Retrieve token for account\n $token = Invoke-RestMethod -Method Get -Uri \"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/$serviceAccount/token\" -Headers $header -UseBasicParsing\n \n $flywayUserPassword = $token.access_token\n \n $arguments += \"-user=`\"$flywayUser`\"\"\n $arguments += \"-password=`\"$flywayUserPassword`\"\"\n #$env:FLYWAY_PASSWORD = $flywayUserPassword\n \n break\n } \n \"usernamepassword\"\n {\n Write-Host \"User provided, adding user and password command line argument\"\n $arguments += \"-user=`\"$flywayUser`\"\"\n $arguments += \"-password=`\"$flywayUserPassword`\"\"\n \n break\n }\n \"windowsauthentication\"\n {\n \t# Display to the user they've selected windows authentication. Though this is dictated by the jdbc url, this is added to make sure the user knows that's what is\n # being used\n Write-Host \"Using Windows Authentication\"\n \n # Check for integratedauthentication=true in url\n if (!$flywayUrl.ToLower().Contains(\"integratedsecurity=true\"))\n {\n \t# Check to see if the connection url ends with a ;\n if (!$flywayUrl.EndsWith(\";\"))\n {\n \t# Add the ;\n $flywayUrl += \";\"\n }\n \n $flywayUrl += \"integratedSecurity=true;\"\n }\n break\n }\n}\n\n$arguments += \"-url=`\"$flywayUrl`\"\"\n\nif (![String]::IsNullOrWhitespace($flywaySchemas))\n{\n\tWrite-Host \"Schemas provided, adding schemas command line argument\"\n\t$arguments += \"-schemas=`\"$flywaySchemas`\"\" \n}\n\nif (![string]::IsNullOrWhiteSpace($flywayLicenseEmail) -and ![string]::IsNullOrWhiteSpace($flywayLicensePAT))\n{\n Write-Host \"Personal Access Token provided, adding -email and -token command line arguments\"\n $arguments += @(\"-email=`\"$flywayLicenseEmail`\"\", \"-token=`\"$flywayLicensePAT`\"\")\n}\n\nWrite-Host \"Performing diff of schema model against $($flywayUrl)/$($flywayDatabase) ...\"\n\n# Locate schema-model folder\n$packageFolders = Get-ChildItem -Path $flywayPackagePath -Recurse | ?{ $_.PSIsContainer } | Where-Object {$_.Name -eq \"schema-model\"}\n\nif ($packageFolders -is [array])\n{\n Write-Error \"Multiple 'schema-model' folders found!\"\n}\n\n$modelFolderPath = $packageFolders.FullName\n\n$arguments += \"-environments.$flywayEnvironment.url=$flywayUrl\"\n$arguments += \"-environment=$flywayEnvironment\"\n$arguments += \"-schemaModelLocation=$modelFolderPath\"\nif (![string]::IsNullOrWhitespace($flywaySourceSchema))\n{\n $arguments += \"-schemaModelSchemas=$flywaySourceSchema\"\n}\nif (![string]::IsNullOrWhitespace($flywayTargetSchema))\n{\n $arguments += \"-environments.$flywayEnvironment.schemas=$flywayTargetSchema\"\n}\n\n# Execute diff\n$diffArguments = @(\"diff\")\n$diffArguments += $arguments\n$diffArguments += \"-diff.source=schemaModel\"\n$diffArguments += \"-diff.target=env:$flywayEnvironment\"\n$diffArguments += \"-diff.artifactFilename=$flywayPackagePath/artifact.diff\"\n\nExecute-FlywayCommand -BinaryFilePath $flywayCmd -CommandArguments $diffArguments\n\n# Check to see if there's any additional arguments to add\nif (![string]::IsNullOrWhitespace($flywayAdditionalArguments))\n{\n\t# Split on space\n $flywayAdditionalArgumentsArray = ($flywayAdditionalArguments.Split(\" \", [System.StringSplitOptions]::RemoveEmptyEntries))\n\n # Loop through array\n foreach ($newArgument in $flywayAdditionalArgumentsArray)\n {\n \t# Add the arguments\n \t$arguments += $newArgument\n }\n}\n\n\n\n# Attempt to find driver path for java\n$driverPath = (Get-ChildItem -Path (Get-ChildItem -Path $flywayCmd).Directory -Recurse | Where-Object {$_.PSIsContainer -eq $true -and $_.Name -eq \"drivers\"})\n\n# If found, add driver path to the PATH environment varaible\nif ($null -ne $driverPath)\n{\n\t$env:PATH += \"$([IO.Path]::PathSeparator)$($driverPath.FullName)\"\n}\n\n$currentDate = Get-Date\n$currentDateFormatted = $currentDate.ToString(\"yyyyMMdd_HHmmss\")\n\n$prepareArguments = @(\"prepare\")\n$prepareArguments += $arguments\n$prepareArguments += \"-prepare.source=schemaModel\"\n$prepareArguments += \"-prepare.target=env:$flywayEnvironment\"\n$prepareArguments += \"-prepare.artifactFilename=$flywayPackagePath/artifact.diff\"\n$prepareArguments += \"-prepare.scriptFilename=$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\"\n\nif ($flywayUndoScript)\n{\n $prepareArguments += \"-prepare.undoFilename=$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).undo.sql\"\n $prepareArguments += \"-prepare.types=`\"deploy,undo`\"\"\n}\n\nExecute-FlywayCommand -BinaryFilePath $flywayCmd -CommandArguments $prepareArguments\n\nswitch($flywayCommand)\n{\n \"prepare\"\n {\n if ((Test-Path -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\") -eq $true)\n {\n $fileContents = Get-Content -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\"\n Set-OctopusVariable -name \"ScriptFile\" -value \"$fileContents\"\n Write-Host \"Output variable with script contents: #{Octopus.Action[$flywayStepName].Output.Scriptfile}\"\n\n if ($flywayUndoScript)\n {\n $fileContents = Get-Content -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).undo.sql\"\n Set-OctopusVariable -name \"UndoScriptFile\" -value \"$fileContents\"\n Write-Host \"Output variable with script contents: #{Octopus.Action[$flywayStepName].Output.UndoScriptfile}\"\n }\n }\n\n break\n }\n \"deploy\"\n {\n if ((Test-Path -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\") -eq $true)\n {\n # Define deploy arguments\n $deployArguments = @(\"deploy\")\n $deployArguments += $arguments\n $deployArguments += \"-deploy.scriptFilename=$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\"\n $deployArguments += \"-executeInTransaction=$flywayExecuteInTransaction\"\n\n Execute-FlywayCommand -BinaryFilePath $flywayCmd -CommandArguments $deployArguments\n\n New-OctopusArtifact -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\" -Name \"$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).sql\"\n\n if ($flywayUndoScript)\n {\n New-OctopusArtifact -Path \"$flywayPackagePath/$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).undo.sql\" -Name \"$($flywayStepName)_$($flywayEnvironment)_$($currentDateFormatted).undo.sql\"\n }\n }\n else\n {\n Write-Host \"Script file not found!\"\n }\n }\n}", "Octopus.Action.PowerShell.Edition": "Core", "Octopus.Action.EnabledFeatures": "Octopus.Features.SelectPowerShellEditionForWindows" },