Skip to content

Commit

Permalink
Added support for DAC publish profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
krzysztof-madej committed Jul 22, 2019
1 parent 197a3e5 commit 9420097
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 13 deletions.
19 changes: 19 additions & 0 deletions help/markdown/sql-dacpac.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
73 changes: 60 additions & 13 deletions src/app/Fake.Sql.DacPac/Sql.DacPac.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 " "
Expand All @@ -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."

Expand All @@ -104,16 +151,16 @@ 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")
(printfn "%s")

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"

0 comments on commit 9420097

Please sign in to comment.