I have a database with its schema managed using Liquibase. I have a .NET application that uses that database for persistence. I have integration tests that use Testcontainers. So when those tests run each test suite gets its own MySQL database, via Docker, to run its tests against and then those databases get terminated.
Liquibase does not have a native integration with .NET, so to run the database migrations I am calling out to the Liquibase CLI from .NET after the Testcontainers are available. This works perfectly locally, but I am having issues when I try to put this together in GitHub Actions:
jobs:
build-app:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Configure AWS CLI
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-2
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET Core
uses: actions/[email protected]
with:
dotnet-version: 6.0.x
- name: Setup Java
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
- name: Install Liquibase
run: |
mkdir liquibase
chmod +w liquibase
wget https://github.com/liquibase/liquibase/releases/download/v4.26.0/liquibase-4.26.0.tar.gz
tar -xzf liquibase-4.26.0.tar.gz -C liquibase
echo "LIQUIBASE_HOME=${GITHUB_WORKSPACE}/liquibase/liquibase" >> $GITHUB_ENV
chmod -R 755 liquibase
liquibase/liquibase --version
- name: Login to AWS CodeArtifact
run: |
aws codeartifact login --tool dotnet --repository ${{ secrets.AWS_CODEARTIFACT_REPOSITORY }} --domain ${{ secrets.AWS_CODEARTIFACT_DOMAIN }} --domain-owner ${{ secrets.AWS_CODEARTIFACT_ACCOUNT_NUMBER }}
- name: Restore
uses: cake-build/cake-action@v2
with:
target: Restore
- name: Build
uses: cake-build/cake-action@v2
with:
target: Build-CI
arguments: |
versionNumber: ${{inputs.version}}
- name: Configure AWS CLI
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.INTEGRATION_RUNNER_AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.INTEGRATION_RUNNER_AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-2
role-to-assume: ${{ secrets.INTEGRATION_RUNNER_ROLE_ARN }}
- name: Run tests
uses: cake-build/cake-action@v2
with:
target: Test-CI
The call to liquibase/liquibase --version
in the Install Liquibase step runs well, so I know Liquibase is installed correctly. However, once I call out to Liquibase from .NET I get the following error:
Error: Unable to access jarfile /home/runner/work/MyProject/WorkingDirectory/liquibase/liquibase/internal/lib/liquibase-core.jar
This file exists, so it is not a path issue. The .NET code that is doing that is:
private void RunLiquibaseChangesets()
{
const string changelog = "db/root-changelog.xml";
var (username, password, databaseName) = DeconstructConnectionString(_dbContainer.GetConnectionString());
var url = $"jdbc:mysql://localhost:{_dbContainer.GetMappedPublicPort(ContainerPortNumber)}/{databaseName}";
var arguments =
$"update --url={url} --username={username} --password={password} --changeLogFile={changelog} --contexts=test-data";
var fileName = Environment.GetEnvironmentVariable("LIQUIBASE_HOME") ?? throw new InvalidOperationException(
"LIQUIBASE_HOME environment variable not set. Please set it to the liquibase installation directory.");
var rootDirectory = Environment.GetEnvironmentVariable("GITHUB_WORKSPACE") ??
Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..");
var startInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
WorkingDirectory = rootDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = new Process();
process.StartInfo = startInfo;
process.Start();
// Use StringBuilder to capture output
var outputBuilder = new StringBuilder();
var errorBuilder = new StringBuilder();
// Handle the output events
process.OutputDataReceived += (_, args) => outputBuilder.AppendLine(args.Data);
process.ErrorDataReceived += (_, args) => errorBuilder.AppendLine(args.Data);
// Start reading output and error asynchronously
process.BeginOutputReadLine();
process.BeginErrorReadLine();
var exited = process.WaitForExit(10000);
Console.WriteLine("----------------- Liquibase Output Start -----------------");
Console.WriteLine(outputBuilder.ToString());
Console.WriteLine("----------------- Liquibase Output End -----------------");
Console.WriteLine("----------------- Liquibase Error Start -----------------");
Console.WriteLine(errorBuilder.ToString());
Console.WriteLine("----------------- Liquibase Error End -----------------");
// Check to see if the process has exited.
if (!exited)
{
throw new InvalidOperationException($"Process did not exit: Output: {outputBuilder} Error: {errorBuilder}");
}
}
I suspect it is permissions, but I have given full permissions via chmod
so I am stuck as to what to try next.
So it turns out that LIQUIBASE_HOME
has some special meaning to Liquibase even though it is not listed anywhere I can find. So I resolved this by using LIQUIBASE_INSTALL_DIR
instead.
Thanks to @jonrsharpe for nudging me in the right direction.