1
0

Program.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. using Microsoft.Azure.Batch.Conventions.Files;
  2. using Microsoft.WindowsAzure.Storage.Blob;
  3. using System;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. namespace SqlPackageWrapper
  9. {
  10. public static class Program
  11. {
  12. private static string dataDirectory = "F:\\data";
  13. private static string tempDirectory = "F:\\temp";
  14. private static string[] directories = { dataDirectory, tempDirectory };
  15. private static readonly TimeSpan stdoutFlushDelay = TimeSpan.FromSeconds(3);
  16. private static void WriteLine(string message) => WriteLineInternal(Console.Out, message);
  17. private static void WriteErrorLine(string message) => WriteLineInternal(Console.Error, message);
  18. private static void WriteLineInternal(TextWriter writer, string message)
  19. {
  20. var lines = message?.Split('\n') ?? new string[0];
  21. foreach (var line in lines)
  22. {
  23. writer.WriteLine($"[{DateTime.UtcNow:u}] {line?.TrimEnd()}");
  24. }
  25. }
  26. public static async Task<int> Main(string[] args)
  27. {
  28. var assembly = typeof(Program).Assembly;
  29. WriteLine($"{assembly.ManifestModule.Name} v{assembly.GetName().Version.ToString(3)}");
  30. // Get the command payload
  31. var payload = new Payload();
  32. if (args.Length > 0)
  33. {
  34. payload.Action = (ActionType)Enum.Parse(typeof(ActionType), args[0]);
  35. payload.LogicalServerName = args[1] + ".database.windows.net";
  36. payload.DatabaseName = args[2];
  37. payload.Username = args[3];
  38. payload.Password = args[4];
  39. payload.SqlPackageVersion = args[5];
  40. }
  41. // Cleanup folders
  42. foreach (string dir in directories)
  43. {
  44. if (Directory.Exists(dir))
  45. {
  46. Directory.Delete(dir, true);
  47. }
  48. Directory.CreateDirectory(dir);
  49. }
  50. string sqlPackageDataPath = Path.Combine(dataDirectory, payload.DatabaseName + ".bacpac");
  51. string sqlPackageLogPath = Path.Combine(dataDirectory, payload.DatabaseName + ".log");
  52. var targetDir = Environment.GetEnvironmentVariable($"{Constants.EnvironmentVariableNames.SqlPackageLocation}#{payload.SqlPackageVersion}");
  53. var workingDir = Environment.GetEnvironmentVariable(Constants.EnvironmentVariableNames.TaskWorkingDir);
  54. string taskId = Environment.GetEnvironmentVariable(Constants.EnvironmentVariableNames.AzBatchTaskId);
  55. string jobContainerUrl = Environment.GetEnvironmentVariable(Constants.EnvironmentVariableNames.JobContainerUrl);
  56. // Build the import/export command
  57. var cmdBuilder = new StringBuilder();
  58. cmdBuilder.Append($"/Action:{payload.Action}");
  59. cmdBuilder.Append(" /MaxParallelism:16");
  60. cmdBuilder.Append(String.Format(" /DiagnosticsFile:{0}", sqlPackageLogPath));
  61. cmdBuilder.Append(" /p:CommandTimeout=86400");
  62. switch (payload.Action)
  63. {
  64. case ActionType.Export:
  65. cmdBuilder.Append($" /SourceServerName:{payload.LogicalServerName}");
  66. cmdBuilder.Append($" /SourceDatabaseName:{payload.DatabaseName}");
  67. cmdBuilder.Append($" /SourceUser:{payload.Username}");
  68. cmdBuilder.Append($" /SourcePassword:{payload.Password}");
  69. cmdBuilder.Append($" /TargetFile:{sqlPackageDataPath}");
  70. cmdBuilder.Append(String.Format(" /p:TempDirectoryForTableData=\"{0}\"", tempDirectory));
  71. cmdBuilder.Append(" /p:VerifyFullTextDocumentTypesSupported=false");
  72. break;
  73. case ActionType.Import:
  74. cmdBuilder.Append($" /TargetServerName:{payload.LogicalServerName}");
  75. cmdBuilder.Append($" /TargetDatabaseName:{payload.DatabaseName}");
  76. cmdBuilder.Append($" /TargetUser:{payload.Username}");
  77. cmdBuilder.Append($" /TargetPassword:{payload.Password}");
  78. cmdBuilder.Append($" /SourceFile:{sqlPackageDataPath}");
  79. break;
  80. default:
  81. throw new ArgumentException($"Invalid action type: {payload.Action}");
  82. }
  83. if (payload.Action == ActionType.Import)
  84. {
  85. WriteLine(string.Format("Downloading {0} bacpac file to {1}", payload.DatabaseName, sqlPackageDataPath));
  86. CloudBlobContainer container = new CloudBlobContainer(new Uri(jobContainerUrl));
  87. CloudBlockBlob blob = container.GetBlockBlobReference(String.Format("$JobOutput/{0}.bacpac", payload.DatabaseName));
  88. await blob.DownloadToFileAsync(sqlPackageDataPath, FileMode.CreateNew);
  89. WriteLine(string.Format("Downloaded {0} bacpac file to {1}", payload.DatabaseName, sqlPackageDataPath));
  90. await Task.Delay(stdoutFlushDelay);
  91. }
  92. // Perform the import/export process
  93. var startTime = DateTimeOffset.UtcNow;
  94. var process = new Process
  95. {
  96. StartInfo = new ProcessStartInfo
  97. {
  98. WorkingDirectory = workingDir,
  99. FileName = Path.Combine(targetDir, "sqlpackage.exe"),
  100. Arguments = cmdBuilder.ToString(),
  101. CreateNoWindow = true,
  102. UseShellExecute = false,
  103. RedirectStandardOutput = true,
  104. RedirectStandardError = true
  105. }
  106. };
  107. process.OutputDataReceived += (s, e) => WriteLine(e.Data);
  108. process.ErrorDataReceived += (s, e) => WriteErrorLine(e.Data);
  109. process.Start();
  110. process.BeginOutputReadLine();
  111. process.BeginErrorReadLine();
  112. process.WaitForExit();
  113. WriteLine(String.Format("SqlPackage.exe exited with code: {0}", process.ExitCode));
  114. if (payload.Action == ActionType.Export)
  115. {
  116. // Persist the Job Output
  117. JobOutputStorage jobOutputStorage = new JobOutputStorage(new Uri(jobContainerUrl));
  118. await jobOutputStorage.SaveAsync(JobOutputKind.JobOutput, sqlPackageLogPath, payload.DatabaseName + ".log");
  119. WriteLine(String.Format("Uploaded {0} to job account", sqlPackageLogPath));
  120. await jobOutputStorage.SaveAsync(JobOutputKind.JobOutput, sqlPackageDataPath, payload.DatabaseName + ".bacpac");
  121. WriteLine(String.Format("Uploaded {0} to job account", sqlPackageDataPath));
  122. // We are tracking the disk file to save our standard output, but the node agent may take
  123. // up to 3 seconds to flush the stdout stream to disk. So give the file a moment to catch up.
  124. await Task.Delay(stdoutFlushDelay);
  125. }
  126. // Cleanup folders
  127. foreach (string dir in directories)
  128. {
  129. if (Directory.Exists(dir))
  130. {
  131. Directory.Delete(dir, true);
  132. }
  133. }
  134. return process.ExitCode;
  135. }
  136. }
  137. }