From 9420097156ee2fa77f9896b8b7683a5ff21bcc6f Mon Sep 17 00:00:00 2001 From: Krzysztof Madej Date: Mon, 22 Jul 2019 14:05:12 +0200 Subject: [PATCH] Added support for DAC publish profiles --- help/markdown/sql-dacpac.md | 19 +++++++ src/app/Fake.Sql.DacPac/Sql.DacPac.fs | 73 ++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 13 deletions(-) diff --git a/help/markdown/sql-dacpac.md b/help/markdown/sql-dacpac.md index a82feef2ed7..34666b747b0 100644 --- a/help/markdown/sql-dacpac.md +++ b/help/markdown/sql-dacpac.md @@ -33,3 +33,22 @@ You can optionally specify the type of command to use (again, refer to the docum * Report - XML report of diff In addition, you can also elect to deploy to Dacpac files rather than SQL databases - simply pass in the pass to the dacpac that you wish to generate. + + +## Arguments + +You can provide following arguments (in brackets are given sqlpackage.exe parameters name): + +* SqlPackageToolPath - path to sqlpackage.exe +* Action - deployment option (/a) +* Source - specifies a source file to be used as the source of action instead of a database (/SourceFile) +* Destination - specifies a valid SQL Server/Azure connection string to the target database (/TargetConnectionString) +* Timeout - specifies the command timeout in seconds when executing queries against SQL Server (/p:CommandTimeout) +* BlockOnPossibleDataLoss - Specifies that the publish episode should be terminated if there is a possibility of data loss resulting from the publish.operation (/p:BlockOnPossibleDataLoss) +* DropObjectsNotInSource - specifies whether objects that do not exist in the database snapshot (.dacpac) file will be dropped from the target database when you publish to a database (/p:DropObjectsNotInSource) +* RecreateDb - specifies whether the target database should be updated or whether it should be dropped and re-created when you publish to a database (/p:CreateNewDatabase) +* AdditionalSqlPackageProperties - specifies a name value pair for an properties;{PropertyName}={Value} +* Variables - specifies a name value pair for an action-specific variable;{VariableName}={Value}. The DACPAC file contains the list of valid SQLCMD variables (/v) +* Profile - specifies the file path to a DAC Publish Profile (/pr) + +If both DAC Publish Profile file and command line parameters provides the same argument, then the one from command line overrites Publish Profile value. An example: if Profile File has BlockOnPossibleDataLoss set to true and command line set it to false, sqlpackage.exe set BlockOnPossibleDataLoss to false. diff --git a/src/app/Fake.Sql.DacPac/Sql.DacPac.fs b/src/app/Fake.Sql.DacPac/Sql.DacPac.fs index e0539552048..b80b10d7279 100644 --- a/src/app/Fake.Sql.DacPac/Sql.DacPac.fs +++ b/src/app/Fake.Sql.DacPac/Sql.DacPac.fs @@ -30,17 +30,19 @@ module DacPac = /// Path to destination (path to DACPAC or Connection String). Destination : string /// Timeout for deploy (defaults to 120 seconds). - Timeout : int + Timeout : int option /// Block deployment if data loss can occur. Defaults to true. - BlockOnPossibleDataLoss : bool + BlockOnPossibleDataLoss : bool option /// Drops objects in the destination that do not exist in the source. Defaults to false. - DropObjectsNotInSource : bool + DropObjectsNotInSource : bool option /// Recreates the database from scratch on publish (rather than an in-place update). Defaults to false. - RecreateDb : bool + RecreateDb : bool option /// Additional configuration parameters required by sqlpackage.exe AdditionalSqlPackageProperties : (string * string) list /// SQLCMD variables - Variables : (string * string) list } + Variables : (string * string) list + ///Specifies the file path to a DAC Publish Profile. The profile defines a collection of properties and variables to use when generating outputs. + Profile : string } let validPaths = let getSqlVersion (path:string) = path.Split '\\' |> Array.item 3 |> int @@ -63,12 +65,33 @@ module DacPac = Action = Deploy Source = "" Destination = "" - Timeout = 120 - BlockOnPossibleDataLoss = true - DropObjectsNotInSource = false - RecreateDb = false + Timeout = None + BlockOnPossibleDataLoss = None + DropObjectsNotInSource = None + RecreateDb = None AdditionalSqlPackageProperties = [] - Variables = [] } + Variables = [] + Profile = "" } + + let private formatArgument (args:DeployDbArgs) action outputPath additionalParameters variables argumentName = + + match argumentName with + | "Action" -> sprintf "/Action:%s" action + | "Source" -> sprintf """/SourceFile:"%s" """ args.Source + | "Destination" when not(String.isNullOrEmpty(args.Destination)) -> sprintf """/TargetConnectionString:"%s" """ args.Destination + | "OutputPath" -> sprintf "%s" outputPath + | "BlockOnPossibleDataLoss" when args.BlockOnPossibleDataLoss.IsSome -> sprintf "/p:BlockOnPossibleDataLoss=%b" args.BlockOnPossibleDataLoss.Value + | "BlockOnPossibleDataLoss" when String.isNullOrEmpty(args.Profile) && args.BlockOnPossibleDataLoss.IsNone -> sprintf "/p:BlockOnPossibleDataLoss=%b" false + | "DropObjectsNotInSource" when args.DropObjectsNotInSource.IsSome -> sprintf "/p:DropObjectsNotInSource=%b" args.DropObjectsNotInSource.Value + | "DropObjectsNotInSource" when String.isNullOrEmpty(args.Profile) && args.DropObjectsNotInSource.IsNone -> sprintf "/p:DropObjectsNotInSource=%b" false + | "Timeout" when args.Timeout.IsSome -> sprintf "/p:CommandTimeout=%d" args.Timeout.Value + | "Timeout" when String.isNullOrEmpty(args.Profile) && args.Timeout.IsNone -> sprintf "/p:CommandTimeout=%d" args.Timeout.Value + | "RecreateDb" when args.RecreateDb.IsSome -> sprintf "/p:CreateNewDatabase=%b" args.RecreateDb.Value + | "RecreateDb" when String.isNullOrEmpty(args.Profile) && args.RecreateDb.IsNone -> sprintf "/p:CreateNewDatabase=%b" false + | "AdditionalSqlPackageProperties" when not(String.isNullOrEmpty(additionalParameters)) -> sprintf "%s" additionalParameters + | "Variables" when not(String.isNullOrEmpty(variables)) -> sprintf "%s" variables + | "Profile" when not(System.String.IsNullOrEmpty(args.Profile)) -> sprintf "/pr:%s" args.Profile + | _ -> "" module PropertyKeys = /// When creating a new SQL Azure database, specifies the database service tier to use e.g. S2, P1 @@ -83,11 +106,33 @@ module DacPac = let outputPath = defaultArg(outputPath |> Option.map(sprintf """/OutputPath:"%s" """)) "" action, outputPath + let private formatDacPacArguments args action outputPath additionalParameters variables = + + let format = formatArgument args action outputPath additionalParameters variables + + let actionParameter = format "Action" + let sourceParameter = format "Source" + let destinationParameter = format "Destination" + let outputPathParameter = format "OutputPath" + let blockOnPossibleDataLossParameter = format "BlockOnPossibleDataLoss" + let dropObjectsNotInSourceParameter = format "DropObjectsNotInSource" + let timeoutParameter = format "Timeout" + let recreateDbParameter = format "RecreateDb" + let additionalSqlPackagePropertiesParameter = format "AdditionalSqlPackageProperties" + let variablesParameter = format "Variables" + let profileParameter = format "Profile" + + [ actionParameter; sourceParameter; destinationParameter; outputPathParameter; blockOnPossibleDataLossParameter; dropObjectsNotInSourceParameter; timeoutParameter; recreateDbParameter; additionalSqlPackagePropertiesParameter; variablesParameter; profileParameter ] + |> List.filter (fun item -> item <> "") + |> String.concat " " + /// Deploys a SQL DacPac or database to another database or DacPac. let deployDb setParams = let args = setParams DefaultDeploymentArgs let action, outputPath = generateCommandLine args.Action + printfn "%A" args + let concat parameter = List.map (fun (key, value) -> sprintf "/%s:%s=%s" parameter key value) >> String.concat " " @@ -96,6 +141,8 @@ module DacPac = let variables = args.Variables |> concat "v" + let arguments = formatDacPacArguments args action outputPath additionalParameters variables + if System.String.IsNullOrWhiteSpace args.SqlPackageToolPath then failwith "No SqlPackage.exe filename was given." @@ -104,10 +151,10 @@ module DacPac = if validPaths |> List.contains args.SqlPackageToolPath then validPaths else [ args.SqlPackageToolPath ] failwithf "Unable to find a valid instance of SqlPackage.exe. Paths checked were: %A." paths - + let result = Process.execRaw - (fun psi -> { psi with Arguments = sprintf """/Action:%s /SourceFile:"%s" /TargetConnectionString:"%s" %s /p:BlockOnPossibleDataLoss=%b /p:DropObjectsNotInSource=%b /p:CommandTimeout=%d /p:CreateNewDatabase=%b %s %s""" action args.Source args.Destination outputPath args.BlockOnPossibleDataLoss args.DropObjectsNotInSource args.Timeout args.RecreateDb additionalParameters variables; FileName = args.SqlPackageToolPath }) + (fun psi -> { psi with Arguments = arguments; FileName = args.SqlPackageToolPath }) TimeSpan.MaxValue true (printfn "SqlPackage error: %s") @@ -115,5 +162,5 @@ module DacPac = match result with | 0 -> () - | _ -> failwith "Error executing DACPAC deployment. Please see output for error details." + | _ -> failwith "Error executing DACPAC deployment. Please see output for error details"