【问题标题】:How to generate EF Core migrations script when ConnectionString is only known after ARM template deployment?仅在 ARM 模板部署后才知道 ConnectionString 时如何生成 EF Core 迁移脚本?
【发布时间】:2020-10-21 11:56:57
【问题描述】:

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

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

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

如何/在哪里生成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 进行身份验证?

【问题讨论】:

    标签: azure-devops entity-framework-core azure-sql-database entity-framework-migrations arm-template


    【解决方案1】:

    我已经在我的编辑中找到了解决问题的方法。 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>'
    

    这绝对解决了我的问题,而无需管理任何更多的秘密/变量,因为它们都包含在管道本身中并且不会构成任何安全威胁。

    【讨论】:

    • 这对我来说真的很有效。我遇到的唯一问题是作业变量会在 Env 变量前面加上一个空格,因此在 dotnet 应用程序中,我手动检索了变量并对其进行了修剪。
    • 非常感谢!我最初让我的 dotnet ef migrations script 命令在 Azure CLI 任务中工作,但它上周突然开始失败。这解决了它 - 我也遇到了间距问题,但将 .Trim() 添加到 PowerShell 行修复了该问题。
    • 我还将作业变量标记为机密,因此它们将在任何控制台/日志输出中被抑制。
    【解决方案2】:

    您可以在构建管道中执行此操作,因为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 权限添加到连接服务背后的服务主体:

    然后,即使您需要将相同的脚本部署到不同的环境/数据库,这一切都很好,直到它们没有被漂移。因此,如果您通过 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'
    

    【讨论】:

    • 这给我带来了一个后续问题。由于 ConnectionString 来自 Azure Key Vault,因此应用程序会在 Program.cs 中添加此 Key Vault,并使用 Azure.Identity 中的 DefaultAzureCredential 类对自身进行身份验证并从 Key Vault 构建配置。在本地和部署到 Azure 之后,一切正常。然而,在 Azure Pipelines 中,它无法获得令牌。如何以这种方式对 Azure Pipelines 进行身份验证?我是否在管道中登录?这似乎是无意的。
    • 也许这将有助于解决这个问题stackoverflow.com/questions/62817337/…
    • 我不这么认为。在本地和部署后,这工作正常。没有配置 EnvironmentCredential。在本地我使用 VisualStudioTokenProvider,部署后我使用 ManagedIdentityCredential.. 密钥保管库使用 RBAC。
    • 你在哪个项目中配置了这个?
    • 因此,当您设置上述主题中提到的这些环境变量时,它会起作用。超级酷!
    猜你喜欢
    • 2019-02-07
    • 2021-03-19
    • 1970-01-01
    • 1970-01-01
    • 2020-12-13
    • 1970-01-01
    • 2020-04-25
    • 1970-01-01
    • 2016-10-08
    相关资源
    最近更新 更多