Color-coded Checks for your Current Flyway Environment
This article shows how to provide clearer feedback from the Flyway CLI by using environment-specific placeholders to provide details about the environment's purpose. It then illustrates how you might use this information to allow a scripted Flyway pipeline to give a simple code-coded message confirming the connected environment.
By adding extra placeholders to your environment definitions, you can give your migrations and callbacks access to useful environment-specific information. This might include the environment name, branch, project description…and even what color the console text should be!
Inspired by SQL Prompt’s ability to color‑code SSMS tabs, I’ll show how we can use this extra context in a beforeConnect
callback to persuade a Flyway scripted process to write text messages to its console that are color-coded according to the environment. In big, bold colors, it will tell you exactly which environment it’s pointing at before you run a migration.
Why is this useful? For one thing, you’ll get a bright red “proceed with caution” warning when connected to Staging or Production. Beyond preventing mishaps, these color‑coded messages are a simple, reassuring way to confirm “yes, I’m definitely in the right place” when juggling multiple databases, branches, and environments in team development.
How Flyway environments might trip you up
When you are developing a database interactively and operating several console screens at once, it’s scarily possible to make a change to the wrong database – I’ve done it myself. One tired late-night session, one distraction, and suddenly you’ve dropped a table in production instead of development.
In enterprise-scale developments, you’re usually protected from making serious errors by process – developers simply don’t have production logins. In smaller shops, though, the same person might do both, and therein lies the danger.
There was a time when it was difficult to ‘accidentally’ switch between databases with Flyway without realizing it, but with the greater power of Flyway environments also comes a greater chance of this happening. Flyway’s configuration system is highly flexible and adaptable; we can specify or override the current environment settings in several places:
- User-level – Flyway reads any environment definitions it finds in a user-level TOML file, along with any value for the
flyway.environment
configuration item, specifying the current environment. - Project-level – next, if Flyway finds a TOML file in its working directory (usually the project you’re working on), any environment definitions there will be added or take precedence. So also, the assignment to the current environment.
- Command-line – if you specify a different TOML file on the command line, it overrides both and can change the environment setting. An environment variable could also change the setting of the flyway environment, as I once found out to my cost! Finally, a direct
-environment=
option on the command line will have the last say.
I suspect you could easily end up in the wrong environment, just by accidentally running Flyway from the wrong working directory, or because an automated process picked up an unexpected config, or by specifying the wrong TOML file at the command line. The risk increases further when you’re working under pressure or fixing bugs late at night.
A clear visual reminder of the current environment significantly lowers the risk and does no harm at all to the process.
Getting the Environment details into the Session
Flyway passes resolved configuration values like URL, user, and schemas automatically to migrations and callbacks. However, it doesn’t pass on the name of the environment (e.g. Staging), or any additional context to the callbacks. You need to explicitly define it as a placeholder in your environment’s definition if you want to know what the current environment is. This may change with new releases.
So, if you want your callback to use the name of the environment to build a “Careful! This is Staging!” warning message, in bright red, you need to explicitly provide a “current environment” placeholder, as well as placeholders for message colors, like this:
[environments.PubsStaging.flyway] # per-environment Placeholders for migrations & callbacks placeholders.currentEnvironment = "PubsStaging" placeholders.foregroundColour = "Red" placeholders.backgroundColour = "DarkRed"
From environment placeholders to Flyway console warnings
Our Flyway callback will read our environment placeholders, extract the details of the current environment and the associated color-coding, construct a big, red “Watch out! This is STAGING!” message and somehow send it to the Flyway console.
It sounds simple, but there are a couple of hurdles to overcome. Firstly, for this system to work, we need to be certain that we’ll see the warning messages before any ‘consequential’ Flyway command runs, not after!
Secondly, it’s not entirely straightforward to persuade the Flyway console to burst into different colors at the switch of an environment switch. We need to do a bit of extra scripting
Let’s jump those hurdles one at a time.
Ensuring timely warnings: the beforeConnect callback
On the face of it, it sounds like we’ll need to add a whole range of callbacks (beforeMigrate
, beforeClean
, beforeUndo
etc.) to every project, each one containing the special code that constructs and sends these warning messages.
Fortunately, there’s one callback that can save us from all this trouble: beforeConnect
. It is unusual because, like afterxxxError
, it isn’t associated with a specific command and instead runs before almost every Flyway command. This gives us a single “entry point” to announce the current environment to the user, before anything else happens.
All we need to do to make this system work is run a harmless Flyway command (like info
) at the start of the process, so that Flyway immediately triggers our beforeConnect
callback before any other command runs.
Persuading Flyway to burst into color
Flyway outputs everything to standard streams (stdout
or stderr
). It doesn’t provide separate verbose or debug streams. If you use JSON output, or quiet mode, you’ll suppress any helpful messages. So, if you try to have your callback print a color-coded message directly to the console, it won’t reliably appear.
Flyway will colorize its own output using standard ANSI codes (for example, green for success and red for errors) if you specify -color=auto
or -color=always
. However, when stdout
is a terminal, any output from callbacks will likely just show the raw escape codes, like this..:
e[33;41mWatch out. You're in the production Environment
e[0m
…instead of displaying the message in the intended colors.
To get around this problem, we will have the callback save the information as a tiny PowerShell script (current.ps1), which you run after Flyway finishes its info
command. That script can safely take over the console and display a big, bold message. The advantage of saving a script is that you can put anything you like in it: a flashing warning, a bell sound…even distant screams if you’re making a change to Production.
Bringing it all together
Here’s how all the pieces fit together:
(1) When executing Flyway interactively, from the console or more likely from a bash or PowerShell ‘driver’ script, we start by instructing Flyway to run a harmless command like info
, and then our driver script immediately executes the saved script that will color code Flyway’s output messages.
(2) Behind the scenes, Flyway triggers our beforeConnect
callback, which reads the environment placeholders in our TOML config file, constructs them into a color-coded message and saves the code as a tiny PowerShell script (.\reports\current.ps1), which will look something like this:
1 2 3 |
Write-Host "Using $env:FP__currentEnvironment__ for $env:FP__projectName__ ($env:FP__projectDescription__)" ` -ForegroundColor $env:FP__foregroundColour__ ` -BackgroundColor $env:FP__backgroundColour__ |
Note that Flyway makes all placeholder values available to callbacks as environment variables, using the pattern FP__<placeholder_name>
, where dots are replaced with double underscores. For example, placeholders.projectName
becomes FP__projectName__
.
(3) After Flyway finishes running the callback and the info
command, our driver script executes current.ps1 and this displays the big, colorful “HEY, THIS IS PRODUCTION!” (or Staging/Dev) message. Because this runs outside Flyway itself, it can safely take over the console.
So, let’s see it in action.
The demo
Enough theory. Here’s how it looks in practice.
The environment placeholders
Here’s an example of an extended staging environment called PubsStaging, showing how I use these placeholders to extend the definition of my Staging environment. I’ve used resolvers for the secrets, so this can be done safely in the project TOML file.
I’ve used the dotted notation to emphasize the fact that the Flyway and placeholder namespaces are within the environments
namespace. My aim with the placeholders is just to have a nice informative message. No animated Gif or sounds.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# The database in staging. Careful how you go! [environments.PubsStaging] url = "${localSecret.PubsStaging_URL}" user = "${localSecret. PubsStaging_User}" password = "${localSecret.PubsStaging Password}" schemas = [ "dbo", "classic", "people", "accounting" ] flyway.locations = ["filesystem:<path to>\\FlywayTeamwork\\Pubs\\Migrations"] flyway.cleanDisabled = false flyway.placeholders.branch = "Main" flyway.placeholders.DSN = "PubsDSN" flyway.placeholders.canDoStringAgg = "1=1" flyway.placeholders.projectDescription = "Staging for the Pubs Flyway project" flyway.placeholders.projectName = "Pubs" flyway.placeholders.projectDirectory = "S:\\work\\Github\\FlywayTeamwork\\Pubs" flyway.placeholders.compareVersions = 'Build' flyway.placeholders.currentEnvironment = "PubsStaging " flyway.placeholders.foregroundColour = "Red" flyway.placeholders.backgroundColour = "DarkRed" flyway.placeholders.teamworkVerbosity = 'Verbose' |
This works fine, but if you want to keep connection details separate from Flyway-specific overrides, you can use the more conventional TOML layout with [environments.<name>.flyway]
.
The beforeConnect callback
Even if we use only a single type of callback (beforeConnect
), we’d still need to update it in every project and every branch, each time we modified the color-coding logic. To avoid that maintenance overhead, I keep just a tiny stub (beforeConnect__Announce
) in each branch/project. That stub simply calls a shared script containing the actual color‑coding logic.
The beforeConnect__Announce stub
Here’s the minimal beforeConnect__Announce
stub:
1 2 3 4 5 6 |
# load the script preliminary script if ($Env:FlywayWorkPath -eq $null) { write-Error 'this script needs the environment variable FlywayWorkPath to be set' -WarningAction Stop} if (-not (Test-Path "$Env:FlywayWorkPath\callbacks\ColourCode.ps1")) { write-warning "sorry but "$Env:FlywayWorkPath\callbacks\ColourCode.ps1" must exist"} . "$Env:FlywayWorkPath\callbacks\ColourCode.ps1" |
All it does is check that the shared ColourCode.ps1 script exists, then load it.
The common callback code
By ‘common’ I mean that it is used by all the beforeConnect
callbacks, one for each set of migrations you have. This shared script does the real work. It builds a message using placeholders (like the current environment, project name, and branch) and then writes out the tiny PowerShell script that can be executed after Flyway finishes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<# .NOTES =========================================================================== Created on: 17/09/2024 10:01 Created by: Phil Factor =========================================================================== .DESCRIPTION This routine will create a script that can then be executed by PowerShell. This example merely puts on the screen executed by the presiding PowerShell script rather then Flyway because only then will the placeholders be visible #> $Message="Used $env:FP__currentEnvironment__ for $env:FP__projectName__ ($env:FP__projectDescription__) $env:FP__Branch__ branch " If (-Not (Test-path ".\reports" -Pathtype Container)) { $null = New-Item -ItemType directory -Path ".\reports" -Force } @" Write-Host "$Message" -ForegroundColor $env:FP__foregroundColour__ -BackgroundColor $env:FP__backgroundColour__ "@ >".\reports\current.ps1" <# possible colours are 'Black','DarkBlue','DarkGreen','DarkCyan','DarkRed', 'DarkMagenta','DarkYellow','Gray','DarkGray','Blue', 'Green','Cyan','Red','Magenta','Yellow','White' |
The driver script
To demonstrate, here’s a simple driver script that loops through multiple environments defined in a TOML file, runs a harmless info
command for each, and then immediately executes the warning script (current.ps1) that our beforeConnect
callback generated, so that we see different colored messages each time we switch environments. Once the script completes we can immediately delete it:
1 2 3 4 5 6 7 8 9 10 |
$AllEnvironments.environments.GetEnumerator() | foreach { write-verbose "Changing to $($_.Value.flyway.placeholders.projectDirectory)" cd $_.Value.flyway.placeholders.projectDirectory write-verbose "Executing $($_.Value.flyway.placeholders.projectDescription) on $($_.Name)" If (-Not (Test-path ".\reports" -Pathtype Container)) { $null = New-Item -ItemType directory -Path ".\reports" -Force } flyway info -outputType=json -environment="$($_.Name)">".\reports\$($_.Name)Info.JSON" . ".\reports\current.ps1" del ".\reports\current.ps1" } |
And here’s the color-coded output:
Extending the idea
One case my solution doesn’t currently account for is the use of resolvers that change parts of the configuration automatically, such as using a Git resolver, ${git.branch:ad}
, in your URL:
1 2 |
[environments.dev] url = "jdbc:sqlserver://localhost:1433;database=mydb_${git.branch:ad}" |
In this pattern, the actual database name (and therefore the connection target) changes when you switch Git branches. However, you’re still using the same static dev environment, so the color-coded warning, based on hardcoded placeholders in that environment, won’t reflect the change.
As a result, you could switch from featureA
to Integration
and still see a “safe” green message, even though you’re now connected to a shared integration database or something more dangerous.
To avoid this, you’d also need to dynamically populate the environment’s placeholders (like currentEnvironment
, foregroundColor
, etc.) based on the branch name, perhaps by pre-processing your config or using a custom resolver.
Conclusion
Providing a little console color is a low-effort way to prevent high-impact mistakes. It illustrates some of the power of Flyway’s environment configuration feature, and it’s one more example of how Flyway gives you the hooks to build safe, visible, and elegant workflows around database changes with more focused and relevant feedback.