Рубрики
Uncategorized

Как Firstport выполняет базу данных в качестве стратегии кода, используя действия DBUP, Terraform & GitHub

Введение Цели DBUP HTML Отчет создать прикладную программу DBUP Console.cs file GitHub … Tagged with Github, Terraform, DevOps, Tuperial.

  • Вступление
    • Цели
    • DBUP
    • Отчет HTML
    • Создать приложение консоли DBUP
    • Program.cs Файл
    • Конфигурация действий GitHub
    • Резюме

Как многие из вас уже знают, я являюсь руководителем отдела технологий в Первый порт Анкет

Ключевой частью моей роли является предоставление видения первогопорта «People First». Для этого крайне важно, чтобы я выбрал правильную технологию для лечения предоставления услуг, которые помогают облегчить жизнь клиентов.

Сегодня я хочу поговорить о своем выборе Dbup

DBUP-это открытый .NET .NET Библиотека, которая помогает вам развернуть изменения в базах данных SQL Server. Он отслеживает, какие сценарии SQL уже запускались, и запускает сценарии изменения, необходимые для актуальности вашей базы данных.

Вы можете задать вопрос, почему Dbup а не Ef миграции ?

Я использовал их много, и я должен сказать, что DBUP кажется мне самым чистым решением. Мне не нравится C# «обертки», чтобы сгенерировать SQL для меня. DDL — это простой язык, и я не думаю, что нам нужен специальный инструмент для его создания.

Здесь, в Firstport, мы также используем Terraform Чтобы построить инфраструктуру SQL Azure и, конечно же, Github Actions. Тем не менее, в основном основное внимание будет уделено DBUP и на рабочих процессах Action GitHub.

Цели

Есть ряд целей, к которым мы стремимся:

  1. Мы хотим, чтобы это было просто
  2. Мы хотим, чтобы это было повторяемым
  3. Мы хотим использовать тот же процесс для разработки, QA и производства наших изменений

DBUP

По своей сути DBUP — бегун сценария. Изменения, внесенные в базу данных, сделаны с помощью скрипта:

Script001_addtablex.sql Script002_addcolumnfirstportIdTotablex.sql script003_addcolumncustomeridTotablex.sql

DBUP работает через консольное приложение, которое вы пишете сами, поэтому вы контролируете, какие параметры использовать, и вам не нужно много кода.

Вы связываете эти сценарии и говорите DBUP, чтобы запустить их. Он сравнивает этот список с списком, хранящимся в базе данных назначения. Будут запускаться любые сценарии, которых нет в списке баз данных этого назначения. Сценарии выполняются в алфавитном порядке, а результаты каждого сценария отображаются на консоли. Очень просто внедрить и понять.

Checking whether journal table exists..
Journal table does not exist
Is upgrade required: True
Beginning database upgrade
Checking whether journal table exists..
Journal table does not exist
Executing Database Server script 'DbUpLeaseExtract.BeforeDeploymentScripts.001_CreateLeaseExtractSchemaIfNotExists.sql'
Checking whether journal table exists..
Creating the [SchemaVersions] table
The [SchemaVersions] table has been created
Upgrade successful
Success!

Это прекрасно работает, когда вы развертываете в среде разработки или тестирования. В Firstport мы предпочитаем, чтобы наши DBA одобряли сценарии перед тем, как отправиться на производство. Может быть, и среда для проведения или предварительного производства. Этот процесс утверждения имеет важное значение, особенно когда вы впервые начинаете развертывание баз данных.

Отчет HTML

Миграционные сценарии-обоюдоострый меч. У вас есть полный контроль, что дает вам большую силу. Тем не менее, это также легко испортить. Все зависит от типа совершенных изменений и навыков SQL писателя. Доверие DBA к этому процессу будет низким, когда неопытные разработчики C# пишут эти миграционные сценарии.

В Firstport мы добавили некоторый код для создания отчета HTML. Это метод расширения, в котором вы даете ему путь отчета, который вы хотите генерировать. Это означает, что этот раздел уходит от:

                var result = upgrader.PerformUpgrade();

                // Display the result
                if (result.Successful)
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine("Success!");
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine(result.Error);
                    Console.WriteLine("Failed!");
                }

К:

            if (args.Any(a => a.StartsWith("--PreviewReportPath", StringComparison.InvariantCultureIgnoreCase)))
            {
                // Generate a preview file so GitHub Actions can generate an artifact for approvals
                var report = args.FirstOrDefault(x => x.StartsWith("--PreviewReportPath", StringComparison.OrdinalIgnoreCase));
                report = report.Substring(report.IndexOf("=") + 1).Replace(@"""", string.Empty);

                var fullReportPath = Path.Combine(report, "UpgradeReport.html");

                Console.WriteLine($"Generating the report at {fullReportPath}");

                upgrader.GenerateUpgradeHtmlReport(fullReportPath);
            }
            else
            {
                var result = upgrader.PerformUpgrade();

                // Display the result
                if (result.Successful)
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine("Success!");
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine(result.Error);
                    Console.WriteLine("Failed!");
                }
            }
        }

Этот код будет генерировать отчет, содержащий все сценарии, которые будут запускаться.

Создать приложение консоли DBUP

С приведенными выше функциями мы собрали .NET Core DBUP Console Console приложение для развертывания в Azure SQL. Затем мы составим процесс в действиях GitHub, чтобы запустить это приложение консоли.

Я выбрал .net Ядро Over .net Структура, потому что его можно построить и работать где угодно. DBUP — это .NET Стандартная библиотека. DBUP будет работать так же хорошо в .net Платформное приложение.

Давайте запустим нашу IDE по выбору и создадим .NET Приложение основной консоли. Я использую VSCODE Чтобы построить это приложение консоли. Я предпочитаю это полной взорванной Visual Studio.

Консольное приложение требует некоторых сценариев для развертывания. Я собираюсь добавить три папки и заполнить их некоторыми файлами сценариев:

Я рекомендую вам добавить префикс, такой как 001, 002 и т. Д., В начало имени файла сценария. DBUP запускает сценарии в алфавитном порядке, и этот префикс помогает убедиться, что сценарии выполняются в правильном порядке.

По умолчанию .NET не будет включать эти файлы сценариев при создании приложения консоли, и мы хотим включить эти файлы сценариев в качестве встроенных ресурсов. К счастью, мы можем легко добавить ссылку на эти файлы, включив этот код в файл .csproj:

    
        
        
        
    

Весь файл выглядит так:



    
        Exe
        net5.0
    

    
        
        
        
    

    
      
    


Program.cs Файл

Последний шаг, направленный на то, чтобы сделать это приложение, — добавить необходимый код в программе. Приложение принимает параметры из командной строки, а действия GitHub будут настроены для отправки в следующих параметрах:

ConnectionString : Для этой демонстрации мы отправляем это как параметр вместо хранения его в файле конфигурации. PreviewReportPath : Полный путь для сохранения отчета о предварительном просмотре. Полный параметр пути является необязательным. Когда он отправляется, мы генерируем предварительный отчет HTML для действий GitHub для загрузки в хранилище Blob -Blob. Когда он не будет отправлен, код сделает фактическое развертывание. Давайте начнем с выталкивания строки подключения из аргумента командной строки:

         static void Main(string[] args)
        {
            var connectionString = args.FirstOrDefault(x => x.StartsWith("--ConnectionString", StringComparison.OrdinalIgnoreCase));

            connectionString = connectionString.Substring(connectionString.IndexOf("=") + 1).Replace(@"""", string.Empty);

DBUP использует беглый API. Нам нужно рассказать об наших папках, тип сценария каждую папку и порядок, в котором мы хотим запустить сценарии. Если вы используете сценарии, встроенные в параметр сборки с помощью поиска STARTSWITH, вам необходимо предоставить полное пространство имен в вашем поиске.

            var upgradeEngineBuilder = DeployChanges.To
                .SqlDatabase(connectionString, null)
                .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), x => x.StartsWith("DbUpLeaseExtract.BeforeDeploymentScripts."), new SqlScriptOptions { ScriptType = ScriptType.RunAlways, RunGroupOrder = 0 })
                .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), x => x.StartsWith("DbUpLeaseExtract.DeploymentScripts"), new SqlScriptOptions { ScriptType = ScriptType.RunOnce, RunGroupOrder = 1 })
                .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), x => x.StartsWith("DbUpLeaseExtract.PostDeploymentScripts."), new SqlScriptOptions { ScriptType = ScriptType.RunAlways, RunGroupOrder = 2 })
                .WithTransactionPerScript()
                .LogToConsole();

            var upgrader = upgradeEngineBuilder.Build();

            Console.WriteLine("Is upgrade required: " + upgrader.IsUpgradeRequired());

Обновление было построено, и он готов к запуску. В этом разделе мы вводем проверку для параметра отчета об обновлении. Если этот параметр установлен, не запускайте обновление. Вместо этого генерируйте отчет для действий GitHub для загрузки в хранилище Blob Blob:

            if (args.Any(a => a.StartsWith("--PreviewReportPath", StringComparison.InvariantCultureIgnoreCase)))
            {
                // Generate a preview file so GitHub Actions can generate an artifact for approvals
                var report = args.FirstOrDefault(x => x.StartsWith("--PreviewReportPath", StringComparison.OrdinalIgnoreCase));
                report = report.Substring(report.IndexOf("=") + 1).Replace(@"""", string.Empty);

                var fullReportPath = Path.Combine(report, "UpgradeReport.html");

                Console.WriteLine($"Generating the report at {fullReportPath}");

                upgrader.GenerateUpgradeHtmlReport(fullReportPath);
            }
            else
            {
                var result = upgrader.PerformUpgrade();

                // Display the result
                if (result.Successful)
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine("Success!");
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine(result.Error);
                    Console.WriteLine("Failed!");
                }
            }

Когда мы собираем все это вместе, это выглядит так:

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using DbUp;
using DbUp.Engine;
using DbUp.Helpers;
using DbUp.Support;

namespace DbUpLeaseExtract
{
    class Program
    {
        static void Main(string[] args)
        {
            var connectionString = args.FirstOrDefault(x => x.StartsWith("--ConnectionString", StringComparison.OrdinalIgnoreCase));

            connectionString = connectionString.Substring(connectionString.IndexOf("=") + 1).Replace(@"""", string.Empty);

            var upgradeEngineBuilder = DeployChanges.To
                .SqlDatabase(connectionString, null)
                .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), x => x.StartsWith("DbUpLeaseExtract.BeforeDeploymentScripts."), new SqlScriptOptions { ScriptType = ScriptType.RunAlways, RunGroupOrder = 0 })
                .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), x => x.StartsWith("DbUpLeaseExtract.DeploymentScripts"), new SqlScriptOptions { ScriptType = ScriptType.RunOnce, RunGroupOrder = 1 })
                .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), x => x.StartsWith("DbUpLeaseExtract.PostDeploymentScripts."), new SqlScriptOptions { ScriptType = ScriptType.RunAlways, RunGroupOrder = 2 })
                .WithTransactionPerScript()
                .LogToConsole();

            var upgrader = upgradeEngineBuilder.Build();

            Console.WriteLine("Is upgrade required: " + upgrader.IsUpgradeRequired());

            if (args.Any(a => a.StartsWith("--PreviewReportPath", StringComparison.InvariantCultureIgnoreCase)))
            {
                // Generate a preview file so GitHub Actions can generate an artifact for approvals
                var report = args.FirstOrDefault(x => x.StartsWith("--PreviewReportPath", StringComparison.OrdinalIgnoreCase));
                report = report.Substring(report.IndexOf("=") + 1).Replace(@"""", string.Empty);

                var fullReportPath = Path.Combine(report, "UpgradeReport.html");

                Console.WriteLine($"Generating the report at {fullReportPath}");

                upgrader.GenerateUpgradeHtmlReport(fullReportPath);
            }
            else
            {
                var result = upgrader.PerformUpgrade();

                // Display the result
                if (result.Successful)
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine("Success!");
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine(result.Error);
                    Console.WriteLine("Failed!");
                }
            }
        }
    }
}

Конфигурация действий GitHub

У нас есть пара разных рабочих процессов. Один запускается по запросу на притяжение, чтобы сгенерировать отчет HTML, а другой — на слиянии, чтобы запустить фактические сценарии базы данных против экземпляра Target Azure SQL.

Сначала у нас есть это, чтобы убедиться, что он работает только по запросам на привлечение изменений в каталоге DB-reploy:

name: Create DB Delta Report
on:
  pull_request:
    branches:
      - develop
    paths:
      - 'db-deploy/**'

Далее мы проверяем код и пронзитель, используя супер-линтер :

jobs:
  db-delta-report:
    name: db-delta-report
    runs-on: ubuntu-latest

    steps:

      - name: Checkout
        uses: actions/checkout@master

      - name: Lint Code Base
        uses: github/super-linter@master
        env:
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
          VALIDATE_ALL_CODEBASE: true
          VALIDATE_MD: true
          VALIDATE_CSHARP: true
          VALIDATE_SQL: true

Далее мы настроем .net ядро и создайте проект:

      - name: Setup .NET Core
        uses: actions/setup-dotnet@main
        with:
          dotnet-version: '5.0.x'

      - name: Cache Packages
        uses: actions/cache@v2
        with:
          path: ~/.nuget/packages
          key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
          restore-keys: |
            ${{ runner.os }}-nuget

      - name: Restore dependencies 
        working-directory: db-deploy/lease_extract
        run: dotnet restore

      - name: Build Console App
        working-directory: db-deploy/lease_extract
        run: dotnet publish --no-restore --output DbUpLeaseExtract

Этот следующий шаг вызывает сценарий PowerShell, который запускает приложение Console. Мы используем Зашифрованные секреты Чтобы пройти в строке подключения к базе данных:

      - name: Create DB Delta Report
        env: 
          LEASE_EXTRACT_DB_CONNECTION_STRING_DEV: ${{ secrets.LEASE_EXTRACT_DB_CONNECTION_STRING_DEV }}
        run: ./db-deploy/scripts/db-delta-report-dev.ps1
        shell: pwsh

Это сценарий PowerShell, который звонит:

$packagePath = "db-deploy/lease_extract/DbUpLeaseExtract"
$connectionString = $Env:LEASE_EXTRACT_DB_CONNECTION_STRING_DEV
$reportPath = "db-deploy/lease_extract/DbUpLeaseExtract"
$dllToRun = "$packagePath/DbUpLeaseExtract.dll"
$generatedReport = "$reportPath/UpgradeReport.html"

if ((test-path $reportPath) -eq $false){
    New-Item $reportPath -ItemType "directory"
}

dotnet $dllToRun --ConnectionString="$connectionString" --PreviewReportPath="$reportPath"

В настоящее время API GitHub не может прикрепить файл к PR -комментариям, поэтому в качестве обходного пути я решил загрузить отчет HTML в хранилище Blob Blob и ссылку на него в комментарии PR:

      - name: Upload DB Delta Report to Azure Blob
        uses: azure/powershell@v1
        with:
          inlineScript: |
            az storage blob upload --account-name storageaccountname --container-name '$web' --file "db-deploy/lease_extract/DbUpLeaseExtract/UpgradeReport.html" --name UpgradeReport.html
          azPSVersion: "latest"

      - name: Comment on PR
        uses: unsplash/comment-on-pr@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          msg: "Please review the [DB Delta Report](https://storageaccountname.z33.web.core.windows.net/)"

Как только это было рассмотрено нашими DBA, и запрос на притяжение объединяется. Происходит другой рабочий процесс:

name: DB Upgrade
on:
  push:
    branches:
      - develop
    paths:
      - 'db-deploy/**'

Вместо того, чтобы запустить отчет HTML, он запускает обновление в базе данных, вызывая другой сценарий:

      - name: Deploy DB Upgrade
        env: 
          LEASE_EXTRACT_DB_CONNECTION_STRING_DEV: ${{ secrets.LEASE_EXTRACT_DB_CONNECTION_STRING_DEV }}
        run: ./db-deploy/scripts/db-upgrade-dev.ps1
        shell: pwsh

Единственное в этом сценарии, это то, что PreviewReportPath Переключатель отсутствует:

$packagePath = "db-deploy/lease_extract/DbUpLeaseExtract"
$connectionString = $Env:LEASE_EXTRACT_DB_CONNECTION_STRING_DEV
$reportPath = "db-deploy/lease_extract/DbUpLeaseExtract"
$dllToRun = "$packagePath/DbUpLeaseExtract.dll"
$generatedReport = "$reportPath/UpgradeReport.html"

if ((test-path $reportPath) -eq $false){
    New-Item $reportPath -ItemType "directory"
}

dotnet $dllToRun --ConnectionString="$connectionString"

В качестве последнего шага мы отслеживаем все наши развертывания в Код климатической скорости — Если вы еще не используете его, я настоятельно рекомендую вам проверить:

      - name: Send Deployment to Code Climate
        run: curl -d "token=${{ secrets.VELOCITY_DEPLOYMENT_TOKEN }}" -d "revision=${GITHUB_SHA}" -d "repository_url=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" -d "branch=develop" -d "environment=db-dev" -d "version=${GITHUB_RUN_NUMBER}" https://velocity.codeclimate.com/deploys

Эта же конвенция будет выполнена для Dev, QA & Prod и т. Д. С помощью филиалов, выровненных по каждой среде.

Резюме

DBUP действительно помог Firstport создать надежный конвейер развертывания для баз данных. Теперь DBA (и другие) могут пересмотреть изменения с помощью действий GitHub, прежде чем они будут развернуты. Возможность пересмотреть изменения должна помочь укрепить доверие к процессу и помочь ускорить принятие.

В этом посте я продемонстрировал один метод для достижения автоматических развертываний базы данных с использованием действий GitHub. Существует множество других решений, и если вы используете структуру сущности или аналогично, эти инструменты имеют встроенную поддержку миграции, но основной подход будет таким же.

Я надеюсь, что смогу помочь вам узнать что -то новое сегодня и поделиться тем, как мы делаем что -то здесь, в Первый порт Анкет

Любые вопросы, свяжитесь с Twitter

Оригинал: «https://dev.to/ghostinthewire5/how-firstport-execute-a-database-as-code-strategy-using-dbup-terraform-github-actions-2b58»