仅在 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'
在此之前,您需要向连接服务背后的服务主体添加 get
和 list
权限:
然后即使您需要将相同的脚本部署到不同的 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 的服务连接的形式从项目中可用。
我做了以下事情:
- 在我的管道中添加了一个 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
- 在我的“创建 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)
- 将服务主体作为 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,因为它们都包含在管道本身中,不会构成任何安全威胁。
我想在部署 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'
在此之前,您需要向连接服务背后的服务主体添加 get
和 list
权限:
然后即使您需要将相同的脚本部署到不同的 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 的服务连接的形式从项目中可用。
我做了以下事情:
- 在我的管道中添加了一个 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
- 在我的“创建 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)
- 将服务主体作为 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,因为它们都包含在管道本身中,不会构成任何安全威胁。