Using JSON Output to Track and Log Flyway Migration Activity
There are many occasions when a developer needs to know the current version of a Flyway database, such as when generating a build script, creating a backup, or notifying a team of version changes. Flyway's JSON output now makes it easier to retrieve this and any other data needed for running post-migration tasks. This article demonstrates how to use this output to send simple, human-readable notifications of Flyway activity, helping developers stay informed about changes that could impact their work.
All the main Flyway commands now report their result via JSON output. This gives developers a standard way to gather critical data like version numbers for tasks such as naming scripts, schema models, or backups. To avoid any confusion, these artifacts must be identified by the database version that was used to produce them. For example, you can use a callback script to extract the schema version from the JSON output, use it to name a build file, and even insert a version comment into the script. The JSON output also contains other data that you can’t get anywhere else, such as the database type and database name.
This is also a great opportunity for developers to improve how they track database migration activity. For example, the JSON output from the migrate
command provides almost a complete narrative of what happened during the migration. It includes the database name, initial and target schema versions, execution times, and warnings. It does not include the server, but we can plug that gap easily, by extracting the servername
from the $ENV:FLYWAY_URL
environment variable.
As a working example of these techniques, we’ll demonstrate how to gather data from the JSON output, supplement it with other configuration data, and use it to provide a simple, human-readable log of Flyway activity. If you’re more ambitious you can send the data to a notification or messaging system that can alert the development team to changes. If a developer sees a change affecting their workstream, they can investigate the full details, including which objects were modified and how, using the Flyway HTML reports.
Using the JSON output
If we run a Flyway command, such as migrate, the JSON output tells us what happened during the migration. If we can also read the environment variables that Flyway provides to a callback or script, we then have all the core information we need for any post-migration task and for reporting. We can, if needed, use Flyway placeholders to add other details to the message, such as the name of the project or who executed the Flyway process.
By clever design, the JSON output is sent to the output stream after the migration completes, but before the afterMigrate
callback is executed, so all the details are readily available in the script session invoked by Flyway.
It is slightly frustrating that, in general, the Flyway settings and details are only available to callbacks, because it is sometimes more convenient to run processes separately in the chain. It this therefore very welcome to have JSON output for all Flyway commands. This is particularly the case with the current version number because backups, scripts, and models all need to know at least the current version number.
In previous articles, I’ve demonstrated all sorts of uses for Flyway callbacks, such as the afterMigrate__xxxx
callback, for running essential development tasks after a migration completes. They are also handy for generating development activity logs!
Producing a Flyway activity log
I’ve shown in an earlier article how to use PowerShell to parse the JSON output from the info
command to produce customizable migration reports: Using Flyway JSON Output in PowerShell for Reporting. Here, I’m going to aim for a simple stream of messages that describe the Flyway activity in a form that is immediately intelligible to any ordinary mortal. It is easier to scan a message if it is clear and has all the information you need to make a judgement, or to be sure of the context of the command, especially if there are any warning or failure messages.
It is easy to miss important information if it isn’t immediately clear, so it must be presented in such a way that it is unambiguous however weary you are when you read it. For example:
A successful 'migrate' operation of SQL Server host MyServer, database 'PubsMain' from empty to V1.1.1 ('Initial Build' ) on 12:18:51 PM Mon 30 Sep 2024 took 5 seconds and had no warnings
How does it work?
We save the command output to JSON using -outputType=json
and redirect it to a file so it’s accessible to our callback script. We supplement the information that Flyway provides in the JSON output with the credentials and connection information, stored separately in a Flyway configuration file saved in the secure user area. The callback script uses all this information to construct the messages.
In this article, we write the messages to a simple log file, but it would be easy enough to extend it to send a message to your notification system. I’ve described how to do this in an existing article, Flyway Alerting and Notifications.
The activity log callback
The example callback script below produces a ‘bare-bones’ message like the one shown earlier, summarizing migration details including the initial and target schema versions, execution time, and any warnings. You can customize the message with other project-specific details, as required.
For the callback script to work, you need to specify a filename for the log ($ENV:NarrativeLog
) and ensure the JSON output of the migrate command is saved to a file.
1 |
$ENV:NarrativeLog='MigrationNarrative.txt' |
This is the text of the afterMigrate
callback
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
$weHaveTheSourceData=$false; # if you have put the json output of the migration from Flyway # into a file called migrateFeedback.json if (Test-path -PathType Leaf -Path 'migrateFeedback.json') { $weHaveTheSourceData=$true; } if ($env:NarrativeLog -ne $null -and $weHaveTheSourceData) { $feedback = get-content -raw '.\migrateFeedback.json' | ConvertFrom-JSON $initialVersion = Switch ($feedback.initialSchemaVersion) { { [string]::IsNullOrEmpty($psitem) } { 'empty' } default { "V$psitem" } } $Success = if ($feedback.success) { 'A ' } else { 'An un' } $Warnings = if ($feedback.warnings.count -eq 0) { 'no' } else { "$($feedback.warnings.count)" } $TheMigrationList = $feedback.migrations if ($TheMigrationList -isnot [array]) { $MigrationDescription = $TheMigrationList.description $TheMigrationList.executionTime $From = $initialVersion $To = $TheMigrationList.version } else { $TheMigrationList | foreach -begin { $From = $null, $FromDescription = $null } { if ($From -eq $Null) { $From = $_.version; } if ($FromDescription -eq $Null) { $FromDescription = $_.Description }; $To = $_.version $MigrationDescription = "$FromDescription $( if ($_.Description -ne $FromDescription) { " to $($_.Description)" } else { '' })" } } $timespan = [TimeSpan]::FromMilliseconds($feedback.totalMigrationTime) $HowLongItTook=switch ($timespan.Minutes){ {$psitem -gt 0} { "$($timespan.Minutes) mins, $($timespan.Seconds) secs"} default { "$($timespan.Seconds) seconds"}}; $OurURL = $env:FLYWAY_URL #contains the current database, port and server $FlywayURLRegex = 'jdbc:(?<RDBMS>[\w]{1,20}):(//(?<server>[\w\\\-\.]{1,40})(?<port>:[\d]{1,4}|)|thin:@)((;.*databaseName=|/)(?<database>[\w]{1,20}))?' if ($OurURL -imatch $FlywayURLRegex) #This copes with having no port. { $server = $matches['server'];} #whatever your default else { $server = 'LocalHost'; } "$($success)successful '$($feedback.operation)' operation of $ ($feedback.databaseType) host $server database '$( $feedback.database)' from $initialVersion to V$($feedback.targetSchemaVersion ) ($MigrationDescription) on $(get-date $feedback.timestamp -UFormat " %r %a %e %b %Y" ) took $HowLongItTook and had $warnings warnings">>$ENV:NarrativeLog } |
If you are testing this callback, you’d be able to see each message by sending it to Write-Host
rather than adding it to the end of the message file. If you wish to send it to your messaging service, you’d redirect it as appropriate. I’ve described how you might do it in Flyway Alerting and Notifications.
The demo
To demo this in action, we’ll build the development version of our ‘extended’ Pubs database. It simulates a batch of several migration runs to test the routines out and, with our callback in place, we will generate a log of the migration activity.
The connection and authentication details are extracted from a config file, stored in the user area, using the Add-FlywayEnvironmentConfigItems
routine that I’ve described in a separate article: Making Full Use of Environment Variables for Flyway Settings. In summary, we let Flyway determine the correct settings and just update these with the ‘database environment’ connection settings. The script includes basic error handling to capture migration issues. For example, if Flyway encounters an error during migration, it populates the JSON output with an error message and code. You can log these details or use them to trigger alerts (see Flyway Alerting and Notifications).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<# .NOTES =========================================================================== Created on: 19/09/2024 10:01 Created by: Phil Factor =========================================================================== .DESCRIPTION Demonstrates the way of getting a narrative of project changes #> $VerbosePreference = 'Continue' $ENV:NarrativeLog='MigrationNarrative.txt' # the narrative log in the project directory #Go to our working directory, cd 'S:\work\Github\FlywayTeamwork\Pubs\branches\develop' <# Clear the existing Flyway Environment variables and then read the credential files This function is in the Teamworks resources #> Add-FlywayEnvironmentConfigItems "$($env:USERPROFILE)\Pubs_SQLServer_Main_ MyServer.conf" <# Now clean the source database and then bring it up to the current level of the database so we can use it for our data export#> <a id="post-7007453-_Hlk178602272"></a>Flyway clean -outputType=json >"cleanFeedback.json" @('1.1.1','1.1.2','1.1.5','1.1.10','1.1.13','1.1.15','1.1.16') | foreach { $TheResult=(Flyway migrate -target="`"$_`"" -outputType=json) $TheResult >"MigrateFeedback.json" $WhatHappened=$TheResult|convertFrom-json $TheError=''; if ($WhatHappened.error -ne $null) {$TheError="$WhatHappened.error.Message"} elseif (!($WhatHappened.success)){ $TheError=$WhatHappened.exception} if ($TheError -ne '') {Write-error "$( $TheError.Substring(0, [Math]::Min(200, $TheError.Length)))"} } |
If we take the database up to version 1.1.15 in three different operations, using the previous code, we get the following text report. In this case, everything worked so we don’t see any errors.
A successful 'migrate' operation of SQL Server host MyServer, database 'PubsMain' from empty to V1.1.1 ('Initial Build' ) on 12:18:51 PM Mon 30 Sep 2024 took 5 seconds and had no warnings A successful 'migrate' operation of SQL Server host MyServer, database 'PubsMain' from V1.1.1 to V1.1.2 ('Pubs Original Data' ) on 12:19:27 PM Mon 30 Sep 2024 took 10 seconds and had no warnings A successful 'migrate' operation of SQL Server host MyServer, database 'PubsMain' from V1.1.2 to V1.1.5 ('UseNVarcharetc' to 'Add New Data') on 12:20:06 PM Mon 30 Sep 2024 took 40 seconds and had no warnings A successful 'migrate' operation of SQL Server host MyServer, database 'PubsMain' from V1.1.5 to V1.1.10 ('Add Tags' to 'AddAddressesPhonesEtc') on 12:21:43 PM Mon 30 Sep 2024 took 29 seconds and had no warnings A successful 'migrate' operation of SQL Server host MyServer, database 'PubsMain' from V1.1.10 to V1.1.13 ('AddProcedureWithTest' to 'Accounting') on 12:22:58 PM Mon 30 Sep 2024 took 57 seconds and had no warnings A successful 'migrate' operation of SQL Server host MyServer, database 'PubsMain' from V1.1.13 to V1.1.15 ('AccountingData' to 'SearchFunctionality') on 12:24:55 PM Mon 30 Sep 2024 took 1 mins, 27 secs and had no warnings
You will, I’m sure, want to change the wording or provide additional information such as the name of the project. Flyway can provide any number of placeholders for this type of information
Conclusion
The addition of JSON output for Flyway commands opens numerous possibilities for reporting, logging, and alerting. It also enables the automation of other database processes through Flyway callbacks. In this article, we demonstrated how to use the JSON output to generate a human-readable log of activity for each change in the Flyway schema version. Only the migrate
and clean
commands alter the database version: migrate by applying migrations and clean by resetting the database and removing versioning information.
There’s even more you can do with Flyway’s JSON output. It gives us the option of running a chain of post-migration commands once Flyway has done its work because we now have all the details we need to do so. Several commands are likely to be required after a successful migration run. By incorporating a routine to save the current Flyway settings that I introduced in Making Full Use of Environment Variables for Flyway Settings, we can now add to the connection details, and other configuration settings, the version number of the database, the name, of the database, the server and the database type. With this information, we can tackle even the thorniest of database development tasks such as bulk-loading the data and kicking off the integration test.
We get to use all the post-Flyway callback routines that I’ve covered in previous articles but bypass the problems of working out the settings that Flyway is using when it is reading a complex TOML configuration involving several alternative ‘environments’.