Azure DevOps file transform with vault secrets - azure-devops

I need to deploy linux azure app service via azure devops. My configuration is stored in appsettings file and I need to substitute configuration values to the values stored in azure vault.
So I created variable group in artifacts, linked it to variables in pipeline and used FileTransform#2 to substitute appsettings values.
But it substitutes to null values. If I explicitly define variable value in pipeline by assigning some string value - it works fine.
Also I cannot use AzureRmWebAppDeployment#4 with JSONFiles, it does not work for linux deployment
What is the way of solving this?
here is pipeline code:
trigger:
branches:
include:
- master
- develop
- release/*
paths:
include:
- src/ConsumerBackEnd/*
- src/ConsumerShared/*
variables:
- name: poolName
value: 'Private-Windows10'
- name: azureRegisteredApp
value: 'portal-devops'
- name: workingDirectory
value: '$(System.DefaultWorkingDirectory)/src/ConsumerBackEnd'
- name: solutionDirectory
value: '$(System.DefaultWorkingDirectory)/src'
stages:
- stage: Build
displayName: Build stage
jobs:
- job: Build
displayName: Build
pool:
name: $(poolName)
variables:
- group: ConsumerDevVariableGroup
- name: 'Graph.GraphAppTenantId'
value: '**************' #works fine
- name: 'Graph.GraphAppClientId'
value: '$[variables.GraphAppClientId]' #should take value from vault but injects null
- task: FileTransform#2
inputs:
folderPath: '$(workingDirectory)'
xmlTransformationRules:
jsonTargetFiles: '**/appsettings.json'
- task: DotNetCoreCLI#2
displayName: Nuget Restore
inputs:
command: 'restore'
projects: '$(workingDirectory)/*.csproj'
feedsToUse: 'config'
nugetConfigPath: '$(solutionDirectory)/NuGet.config'
- task: DotNetCoreCLI#2
displayName: Build
inputs:
command: 'build'
projects: |
$(workingDirectory)/ConsumerBackEnd.csproj
arguments: --output $(System.DefaultWorkingDirectory)/output
- task: DotNetCoreCLI#2
displayName: Publish
inputs:
command: 'publish'
publishWebProjects: false
projects: '$(workingDirectory)/ConsumerBackEnd.csproj'
arguments: '-c Release -r linux-x64 --self-contained true --output $(System.DefaultWorkingDirectory)/publish_output'
#requires approval on pipeline
- stage: DeployDev
displayName: DeployDev
dependsOn: Build
condition: succeeded()
jobs:
- deployment: DeployConsumerBackendAPIDev
displayName: DeployConsumerBackendAPIDev
environment: ConsumerBackendAPIDev
pool:
name: $(poolName)
strategy:
runOnce:
deploy:
steps:
- task: AzureRmWebAppDeployment#4
inputs:
ConnectionType: 'AzureRM'
azureSubscription: '$(azureRegisteredApp)'
appType: 'webAppLinux'
WebAppName: 'my-backend-dev'
packageForLinux: '$(System.DefaultWorkingDirectory)/publish_output/**/*.zip'
RuntimeStack: 'DOTNETCORE|LTS --configuration Release'

it appears that referencing group variable using runtime expression does not work with file transform task but macro syntax works fine
Microsoft documentation does not describe it well
so here is how it should be defined:
variables:
#secrets
- group: ConsumerDevVariableGroup
- name: Graph.GraphAppTenantId
value: $(GraphAppTenantId) #works fine
- name: 'Graph.GraphAppClientId'
value: '$[variables.GraphAppClientId]' #does not work

Azure DevOps file transform with vault secrets
To achieve this, you could use the task Replace Tokens to update the key's values with Azure vault secrets.
As test, I created a vault secret LeoVar1 in the Azure portal with test value 123456:
Then connect Azure key vault into azure devops pipeline.
Now, I use the Replace Tokens to replace the value of key webpages:Version in the web.config file with the vault secret in the Azure vault #{LeoVar1}#:
My test web.config file:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="DefaultConnection"
connectionString="Data Source=(LocalDb)\\MSDB;DbFilename=aspcore-local.mdf;" />
</connectionStrings>
<appSettings>
<add key="webpages:Version" value="#{LeoVar1}#" />
<add key="webpages:Enabled" value="false" />
</appSettings>
<system.web>
<authentication mode="None" />
<compilation targetFramework="4.5" debug="true" />
</system.web>
</configuration>
You will see the replace are finished successfully after this task ended:
You could check this document Store the app secrets in Azure Key Vault and use during Azure Pipelines for some details.
Update:
I just wandering why FileTransform#2 does not work with secrets
To make it work, you should make sure following things:
The path of appsettings.json is correct. Makre sure it under the folder $(workingDirectory).
The important is that make sure the name of the vault secret is the combination of the attribute name and its parent node.
For example, if we want change the DebugMode from enabled to disable in following json file:
{
"Data": {
"DefaultConnection": {
"ConnectionString": "Data Source=(LocalDb)\\MSDB;AttachDbFilename=aspcore-local.mdf;"
},
"DebugMode": "enabled",
"DBAccess": {
"Administrators": ["Admin-1", "Admin-2"],
"Users": ["Vendor-1", "vendor-3"]
},
"FeatureFlags": {
"Preview": [
{
"newUI": "AllAccounts"
},
{
"NewWelcomeMessage": "Newusers"
}
]
}
}
}
We need defined the name of the vault secret to Data.DebugMode instead of DebugMode.
However, the point . is not supported by vault secret, it will give the error:
Please provide a valid secret name. Secret names can only contain
alphanumeric characters and dashes.
If we test it with Variables in the pipeline, it works without any error:
Since the point . is not supported by vault secret, we could NOT replace the properties with parent node. That the reason why I suggest you use replace token task at first.

Related

XML transformation using only IIS Web App Deployment On Machine Group task (YAML pipeline)

I have created a pipeline using only YAML.
I have defined the deployment part like this:
- stage: AzureDevOpsStaging
displayName: Deploy build artifacts to staging environment
dependsOn: BuildSolution
condition: succeeded('BuildSolution')
jobs:
- deployment: DeployArtifacts
displayName: Deploy artifacts
environment:
name: AzureDevOpsStaging
resourceType: VirtualMachine
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: drop
- task: IISWebAppDeploymentOnMachineGroup#0
displayName: Deploy artifacts to IIS
inputs:
webSiteName: 'mysite-staging'
package: '$(Pipeline.Workspace)\drop\*.zip'
xmlTransformation: true
When I run this I get:
##[warning]Unable to apply transformation for the given package. Verify the following.
##[warning]1. Whether the Transformation is already applied for the MSBuild generated package during build. If yes, remove the <DependentUpon> tag for each config in the csproj file and rebuild.
##[warning]2. Ensure that the config file and transformation files are present in the same folder inside the package.
Things that I've checked:
Both Web.config and Web.AzureDevOpsStaging.config files are in the zip/artifact
Name of stage - The docs say stage must have the same name as your transform config file; that is: Web.AzureDevOpsStaging.config.
Name of .config transform file - the name of the .config transform file is Web.AzureDevOpsStaging.config
Name of environment (the docs doesn't say the name has to be the same as Web.ThisPart.config but I still named the environment
AzureDevOpsStaging just in case.)
But again doing all of the above results in the Web.config not being transformed.
I got it to work with using the file transform task instead which is referenced in the docs from the IIS Web App Deploy task:
- stage: AzureDevOpsStaging
displayName: Deploy build artifacts to staging environment
dependsOn: BuildSolution
condition: succeeded('BuildSolution')
jobs:
- deployment: DeployArtifacts
displayName: Deploy artifacts
environment:
name: AzureDevOpsStaging
resourceType: VirtualMachine
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: drop
- task: FileTransform#1
inputs:
folderPath: '$(Pipeline.Workspace)\drop\*.zip'
enableXmlTransform: true
xmlTransformationRules: -transform **\*.AzureDevOpsStaging.config -xml **\*.config
- task: IISWebAppDeploymentOnMachineGroup#0
displayName: Deploy artifacts to IIS
inputs:
webSiteName: 'mysite-staging'
package: '$(Pipeline.Workspace)\drop\*.zip'
So can someone please explain to me how I am supposed to configure my YAML to get it to work using only the IISWebAppDeploymentOnMachineGroup#0 task?
And if this is not possible am I using the task FileTransform#1 properly?
Also, I saw there is a version FileTransform#2 as well. That task didn't have one of the properties that #1 has so I reverted to using v1 instead. But would be great if someone has a bit more info on this newer version and if it's going to deprecate #1 in the future?
Btw, I also got xmlTransformation: true to work with classic release pipeline under the Releases tab in Azure DevOps using the UI. But again I don't want to use the classic stuff, I want to do everything in YAML.
And if this is not possible am I using the task FileTransform#1 properly?
The answer is yes.
The task FileTransform is the one I am using and use frequently.
When I use it in the YAML pipeline as you set:
- task: FileTransform#1
displayName: 'File Transform'
inputs:
folderPath: '$(Pipeline.Workspace)\drop\*.zip'
enableXmlTransform: true
xmlTransformationRules: '-transform **\*.UAT.config -xml **\*.config'
fileType: xml
It works fine on my side:
In order to perform the conversion correctly, it is necessary to ensure that the syntax in config is correct, and the specified directory is correct

Transform appsettings.json in Release pipeline with another appsettings file

In my .NET Core project I have a appsettings.json file and a appsettings.Test.json file.
I have a release pipeline where I would like to override the settings in appsettings.json with those in appsettings.Test.json.
Is this possible?
I tried to do the following in my "IIS Web App Deploy" task:
But the content of appsettings.Test.json isn't transformed into appsettings.json.
It does however work for the variables that I define in the "Variables" tab. But that's not what I'm after.
Is there a way to tell the release pipeline to tranform the appsettings.json file with another file like apssettings.Test.json in my case?
You must use Transfrom File:
stages:
- stage: TransformFiles
displayName: TransformFiles
jobs:
- job: Transform_AppSettings
steps:
- bash: echo "===== Transforming appsettings.json for $(variables['Build.SourceBranchName']) environment ====="
- task: FileTransform#2
inputs:
folderPath: '$(System.DefaultWorkingDirectory)'
fileType: 'json'
targetFiles: 'appsettings.json'
You can use variables or variable groups. Here is an example of how you can replace the Connection string. I have variable group prod-secrets with key psql-conn-conn-str-prod.
- group: prod-secrets
- name: ConnectionStrings.DefaultConnection
value: $(psql-conn-str-prod)

Azure Static Web App: transform Blazor webassembly appsettings.json in Azure DevOps

I have a static blazor web app that has the following structure:
and the following setting in appsettings.json:
{
"ApiUrl": "http://localhost:7071/api/"
}
I also have a Azure DevOps pipeline with the following yaml:
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
submodules: true
- task: AzureStaticWebApp#0
inputs:
app_location: '/Atlas.Blazor'
api_location: '/Atlas.InternalHost'
output_location: '/wwwroot'
azure_static_web_apps_api_token: $(deployment_token)
How do I update the appsettings during deployment with a variable?
At the end this worked
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
submodules: true
- task: FileTransform#1
inputs:
folderPath: '$(System.DefaultWorkingDirectory)/Atlas.Blazor/wwwroot'
fileType: 'json'
targetFiles: '**/appsettings.json'
- task: AzureStaticWebApp#0
inputs:
app_location: '/Atlas.Blazor'
api_location: '/Atlas.InternalHost'
output_location: '/wwwroot'
azure_static_web_apps_api_token: $(deployment_token)
I don't think it's possible to manipulate the configuration file during the build/deployment that happens within the Oryx-container. What you could do is to manipulate the file during the pipeline run by, for example, tokenizing the environment-specific configuration values and replacing the tokens during the pipeline run. To achieve this with single json-file (ie. having just a file with the localhost-value stored in version control) you could use tokenizer- and replace tokens -task from Colin's ALM Corner extension collection (https://marketplace.visualstudio.com/items?itemName=colinsalmcorner.colinsalmcorner-buildtasks). Another option would be to have multiple environment-specific configuration files in version control and just copying a specific one over the appsettings.json during the pipeline run.
Not sure if Static Web App environment variables could be utilized here, but adding it here if someone using GitHub Actions is looking for this - they are not yet supported by Azure Pipelines.
https://learn.microsoft.com/en-us/azure/static-web-apps/build-configuration?tabs=github-actions#environment-variables

Azure DevOps: Populating secure file references with job matrix variables

For context, I am trying to use an Azure build pipeline to build multiple flavors of an Android app. Each flavor has its own separate signing keystore, and all of those keystores are stored in my 'secure files' in the library.
However, when I try to dereference the $(Keystore) variable during the 'android signing' task, it doesn't seem to recognize that that is a variable that exists, and tries instead to locate a file called '$(Keystore)'
Am I doing something wrong here? This seems like it should work.
A sanitized example looks like this:
# Android
# Build your Android project with Gradle.
# Add steps that test, sign, and distribute the APK, save build artifacts, and more:
# https://learn.microsoft.com/azure/devops/pipelines/languages/android
trigger:
- feat/ci-setup
pool:
vmImage: 'macos-latest'
variables:
${{ if startsWith(variables['build.sourceBranch'], 'refs/heads/feat/') }}:
Branch_Type: 'feature'
${{ if startsWith(variables['build.sourceBranch'], 'refs/heads/hotfix/') }}:
Branch_Type: 'hotfix'
${{ if startsWith(variables['build.sourceBranch'], 'refs/heads/release/') }}:
Branch_Type: 'release'
${{ if eq(variables['Branch_Type'], 'release') }}:
Configuration: 'release'
ConfigurationCC: 'Release'
${{ if ne(variables['Branch_Type'], 'release') }}:
Configuration: 'debug'
ConfigurationCC: 'Debug'
jobs:
- job: Build
variables:
- group: android_keystores
strategy:
maxParallel: 2
matrix:
Flavor_1:
AppFlavor: '1'
AppFlavorCC: '1'
Keystore: 'flavor1.keystore'
KeyAlias: 'flavor1'
KeystorePass: '$(flavor1_storepass)'
KeyPass: '$(flavor1_keypass)'
Flavor_2:
AppFlavor: '2'
AppFlavorCC: '2'
Keystore: 'flavor2.keystore'
KeyAlias: 'flavor2'
KeystorePass: '$(flavor2_storepass)'
KeyPass: '$(flavor2_keypass)'
steps:
- task: Gradle#2
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
publishJUnitResults: false
tasks: 'assemble$(AppFlavorCC)$(ConfigurationCC)'
- task: AndroidSigning#3
displayName: Signing .apk
inputs:
apkFiles: 'app/build/outputs/apk/$(AppFlavor)/$(Configuration)/*.apk'
apksign: true
apksignerKeystoreFile: '$(Keystore)'
apksignerKeystorePassword: '$(KeystorePass)'
apksignerKeystoreAlias: '$(KeyAlias)'
apksignerKeyPassword: '$(KeyPass)'
zipalign: true
- task: Bash#3
displayName: Move APK to Artifact Folder
continueOnError: true
inputs:
targetType: 'inline'
script: |
mv \
app/build/outputs/apk/$(AppFlavor)/$(Configuration)/*.apk \
$(Build.ArtifactStagingDirectory)/$(ArtifactName)/
- task: PublishBuildArtifacts#1
displayName: Publish Build Artifacts
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'Blueprint-Build'
publishLocation: 'Container'
But when the pipeline runs I am told this:
There was a resource authorization issue: "The pipeline is not valid. Job Build: Step AndroidSigning input keystoreFile references secure file $(Keystore) which could not be found. The secure file does not exist or has not been authorized for use. For authorization details, refer to https://aka.ms/yamlauthz."
Azure DevOps: Populating secure file references with job matrix variables
This is a limitation from the task itself.
When we test it with Classic mode, we could find out that the value of the option Keystore file could not be entered manually, we could only select a certain file through the drop-down menu:
That the reason why it doesn't seem to recognize that that is a variable that exists, and tries instead to locate a file called '$(Keystore)'.
To resolve this issue, you could change the task version from 3 to 1, which supports manual input:
And as another solution, you could also use the command line to sign the *.apk:
Android apk signing: sign an unsigned apk using command line
You're missing the step to download the Secure File. Unlike variable groups, you need to explicitly download them to have access via the secure file name.
You'll want to add something similar to the example task below to your steps to pull the secure file. Then, you'll access your secure file via NAME_PARAMETER.secureFilePath:
- task: DownloadSecureFile#1
displayName: "Download Keyfile 1"
name: "YOUR_SECUREFILE_NAME"
inputs:
secureFile: keyfile1
- task: AndroidSigning#3
displayName: Signing .apk
inputs:
apkFiles: 'app/build/outputs/apk/$(AppFlavor)/$(Configuration)/*.apk'
apksign: true
apksignerKeystoreFile: '$(YOUR_SECUREFILE_NAME.secureFilePath)'
apksignerKeystorePassword: '$(KeystorePass)'
apksignerKeystoreAlias: '$(KeyAlias)'
apksignerKeyPassword: '$(KeyPass)'
zipalign: true

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

I want to release an app to Azure and deploy migrations to a database before deploying the Web App. That sounds relatively simple, you can create a migrations.sql script with dotnet-ef in your Build pipeline and apply this script in your Release pipeline.
However, I cannot create a a migrations.sql script in the Build pipeline as I am using four different databases for a DTAP environment. Thus, I would need to generate a migrations.sql script per environment and perform these separately against each of the databases. (as I understand it)
In my Release pipeline I use an incremental ARM template to deploy resources and set the ConnectionString (which comes from an Azure Key Vault) in the Azure Web App application settings configuration.
How/where do I generate the migrations.sql script? Do I do this in a Release pipeline? Am I making a major mistake in my reasoning?
EDIT:
Thanks for Madej's answer that shows the environment doesn't matter. I tried implementing creating the migrations.sql script in my pipelines.
# 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://learn.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'
My pipeline doesn't work, in the task "Create migrations.sql" I run into the following error:
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
This is because in my Program.cs I add a keyvault and authenticate with the Azure.Identity DefaultAzureCredential as follows:
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>();
});
The Azure Pipelines cannot get a token from DefaultAzureCredential. How do I authenticate the Azure Pipelines?
I have figured out the solution to the problem in my edit. The primary way that the DefaultAzureCredential class gets credentials is via environment variables.
Thus, I had to define the environment variables somewhere. I didn't want to do this in the pipeline variables to avoid having to manage them as they should be available from the project in the form of a service connection to Azure.
I did the following:
In my pipelines added an AzureCLI task to read out the service principal id, key and tenant id and set them to job variables as follows:
- 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
In my "Create migrations.sql" task pass these variables as environment variables as follows:
- 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)
Added the service principal to the Azure Key Vault RBAC as a Key Vault Secrets User. I could only do this with 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>'
This absolutely solved my problems without having to manage any more secrets/variables as they are all contained in the pipeline itself and don't pose any security threats.
You can do this in a build pipeline because migration.sql script makes some checks if specific migration was already applied or not.
To create migration script when you use Azure Key Vault in you confiugration the easiest way is to run command from Azure Clit task:
- 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'
Before that you need to add get and list permissions to your serivde principal which is behind your connection service:
And then even if you need to deploy the same script to different environments/databases it is all fine until they haven't been drifted. So if you do all changes through ef core you are good to go with migration.sql done once and applied many times.
In database you should have:
which contains already applied migrations. ANd then in script you will find:
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
Thus you are safe to run it against multiple databases.
And then to deploy you can use
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'