仅在 ARM 模板部署后才知道 ConnectionString 时如何生成 EF Core 迁移脚本?

How to generate EF Core migrations script when ConnectionString is only known after ARM template deployment?

我想在部署 Web 应用程序之前将应用程序发布到 Azure 并将迁移部署到数据库。这听起来相对简单,您可以在构建管道中使用 dotnet-ef 创建一个 migrations.sql 脚本,然后在发布管道中应用该脚本。

但是,我无法在构建管道中创建 migrations.sql 脚本,因为我在 DTAP 环境中使用了四个不同的数据库。因此,我需要为每个环境生成一个 migrations.sql 脚本,并针对每个数据库分别执行这些脚本。 (据我了解)

在我的发布管道中,我使用增量 ARM 模板来部署资源并在 Azure Web App 应用程序设置配置中设置 ConnectionString(来自 Azure Key Vault)。

How/where 是否生成 migrations.sql 脚本?我是否在发布管道中执行此操作?我的推理是否犯了重大错误?


编辑:

感谢 Madej 的回答表明环境无关紧要。我尝试在我的管道中创建 migrations.sql 脚本。

# ASP.NET Core (.NET Framework)
# Build and test ASP.NET Core projects targeting the full .NET Framework.
# Add steps that publish symbols, save build artifacts, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core

trigger:
- master

pool:
  vmImage: 'windows-latest'

variables:
  projects: '**/*.csproj'
  buildPlatform: 'Any CPU'
  buildConfiguration: 'Release'

steps:
- task: DotNetCoreCLI@2
  displayName: "Install dotnet-ef"
  inputs:
    command: 'custom'
    custom: 'tool'
    arguments: 'install --global dotnet-ef'

- task: DotNetCoreCLI@2
  displayName: "Restore tools"
  inputs:
    command: 'custom'
    custom: 'tool'
    arguments: 'restore'

- task: DotNetCoreCLI@2
  displayName: "Restore"
  inputs:
    command: 'restore'
    projects: '$(projects)'
    feedsToUse: 'select'

- task: DotNetCoreCLI@2
  displayName: "Build"
  inputs:
    command: 'build'
    projects: '$(projects)'
    arguments: '--configuration $(BuildConfiguration)'

- task: DotNetCoreCLI@2
  displayName: "Create migrations.sql"
  inputs:
    command: 'custom'
    custom: 'ef'
    arguments: 'migrations script --configuration $(BuildConfiguration) --no-build --idempotent --output $(Build.ArtifactStagingDirectory)\migrations.sql'
    workingDirectory: 'WebApi.api'

- task: DotNetCoreCLI@2
  displayName: "Publish"
  inputs:
    command: 'publish'
    publishWebProjects: true
    arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
    zipAfterPublish: false

- task: PublishBuildArtifacts@1
  displayName: "Publish to Azure Pipelines"
  inputs:
    PathtoPublish: '$(Build.ArtifactStagingDirectory)'
    ArtifactName: 'drop'
    publishLocation: 'Container'

我的管道不工作,在任务"Create migrations.sql"中我运行进入以下错误:

An error occurred while accessing the Microsoft.Extensions.Hosting services. Continuing without the application service provider. Error: DefaultAzureCredential failed to retrieve a token from the included credentials.
- EnvironmentCredential authentication unavailable. Environment variables are not fully configured.
- ManagedIdentityCredential authentication unavailable. No Managed Identity endpoint found.
- Visual Studio Token provider can't be accessed at C:\Users\VssAdministrator\AppData\Local\.IdentityService\AzureServiceAuth\tokenprovider.json
- Stored credentials not found. Need to authenticate user in VSCode Azure Account.
- Please run 'az login' to set up account

这是因为在我的 Program.cs 中,我添加了一个密钥库并使用 Azure.Identity DefaultAzureCredential 进行身份验证,如下所示:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
            {
                var settings = config.Build();

                var credentials = new DefaultAzureCredential(
                    new DefaultAzureCredentialOptions() {
                        ExcludeSharedTokenCacheCredential = true,
                        VisualStudioTenantId = settings["VisualStudioTenantId"],
                    }
                );

                config.AddAzureKeyVault(new Uri(settings["KeyVault:Endpoint"]), credentials).Build();
            })
            .UseStartup<Startup>();
        });

Azure Pipelines 无法从 DefaultAzureCredential 获取令牌。如何对 Azure Pipelines 进行身份验证?

您可以在构建管道中执行此操作,因为 migration.sql 脚本会检查是否已应用特定迁移。

要在配置中使用 Azure Key Vault 时创建迁移脚本,最简单的方法是从 Azure Clit 任务中执行 运行 命令:

  - task: AzureCLI@2
    inputs:
      azureSubscription: 'rg-tcm-si'
      scriptType: 'pscore'
      scriptLocation: 'inlineScript'
      inlineScript: 'dotnet ef migrations script --configuration $(BuildConfiguration) --no-build --idempotent --output $(Build.ArtifactStagingDirectory)\migrations.sql'
      workingDirectory: 'Itan.Database'

在此之前,您需要向连接服务背后的服务主体添加 getlist 权限:

然后即使您需要将相同的脚本部署到不同的 environments/databases,在它们没有被转移之前一切都很好。因此,如果您通过 ef core 进行所有更改,您最好使用 migration.sql 完成一次并应用多次。

在数据库中你应该有:

其中包含已应用的迁移。然后在脚本中你会发现:

IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20200101111512_InitialCreate')
BEGIN
    CREATE TABLE [SomeTable] (
        [Id] uniqueidentifier NOT NULL,
        [StorageDate] datetime2 NOT NULL,
       .....
    );
END;

GO

因此您可以安全地 运行 它针对多个数据库。

然后部署你可以使用

steps:
- task: SqlAzureDacpacDeployment@1
  displayName: 'Azure SQL SqlTask'
  inputs:
    azureSubscription: 'YourSubscription'
    ServerName: 'YourServerName'
    DatabaseName: 'YourDatabaseName'
    SqlUsername: UserName
    SqlPassword: '$(SqlServerPassword)'
    deployType: SqlTask
    SqlFile: '$(System.DefaultWorkingDirectory)/staging/drop/migrations.sql'

我在编辑中找到了问题的解决方案。 The primary way that the DefaultAzureCredential class gets credentials is via environment variables.

因此,我不得不在某处定义环境变量。我不想在管道变量中执行此操作以避免必须管理它们,因为它们应该以与 Azure 的服务连接的形式从项目中可用。

我做了以下事情:

  1. 在我的管道中添加了一个 AzureCLI 任务来读取服务主体 ID、密钥和租户 ID,并将它们设置为作业变量,如下所示:
- task: AzureCLI@2
  inputs:
    azureSubscription: '<subscription>'
    scriptType: 'ps'
    scriptLocation: 'inlineScript'
    inlineScript: |
      Write-Host '##vso[task.setvariable variable=AZURE_CLIENT_ID]'$env:servicePrincipalId
      Write-Host '##vso[task.setvariable variable=AZURE_CLIENT_SECRET]'$env:servicePrincipalKey
      Write-Host '##vso[task.setvariable variable=AZURE_TENANT_ID]'$env:tenantId
    addSpnToEnvironment: true
  1. 在我的“创建 migrations.sql”任务中,将这些变量作为环境变量传递如下:
- task: DotNetCoreCLI@2
  displayName: "Create migrations.sql"
  inputs:
    command: 'custom'
    custom: 'ef'
    arguments: 'migrations script --configuration $(BuildConfiguration) --no-build --idempotent --output $(Build.ArtifactStagingDirectory)\migrations.sql'
    workingDirectory: 'WebApi.api'
  env:
    AZURE_CLIENT_ID: $(AZURE_CLIENT_ID)
    AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET)
    AZURE_TENANT_ID: $(AZURE_TENANT_ID)
  1. 将服务主体作为 Key Vault 机密用户添加到 Azure Key Vault RBAC。我只能用 az:
az role assignment create --role 'Key Vault Secrets User (preview)' --scope '/subscriptions/<subscription ID>/resourcegroups/<resource group name>/providers/Microsoft.KeyVault/vaults/<vault name>' --assignee '<service principal object id>'

这完全解决了我的问题,无需再进行管理 secrets/variables,因为它们都包含在管道本身中,不会构成任何安全威胁。