├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .vscode └── settings.json ├── Common └── PowerShell3 │ └── VstsAzureHelpers │ ├── FindSqlPackagePath.ps1 │ ├── HelperFunctions.ps1 │ ├── ImportFunctions.ps1 │ ├── InitializeFunctions.ps1 │ ├── VstsAzureHelpers.psm1 │ └── module.json ├── Extension ├── LICENSE.txt ├── Screenshots │ ├── Add-Tasks.png │ └── TestBuild.png ├── extension-icon.png ├── overview.md └── vss-extension.json ├── GitVersion.yml ├── LICENSE.md ├── README.md ├── Tasks ├── AzCopy │ ├── azCopy.ts │ ├── azureEndpointConnection.ts │ ├── icon.png │ ├── package.json │ ├── task.json │ └── yarn.lock ├── ExecuteSql │ ├── ExecuteSql.ps1 │ ├── SqlPredefinedScripts │ │ ├── AddRoleMember.sql │ │ ├── CreateDbOwner.sql │ │ └── CreateUser.sql │ ├── icon.png │ ├── package.json │ ├── task.json │ └── yarn.lock ├── RestoreSqlDatabaseToSqlDatabase │ ├── RestoreSqlDatabaseToSqlDatabase.ps1 │ ├── icon.png │ ├── package.json │ ├── task.json │ └── yarn.lock ├── SqlMultiDacpacDeployment │ ├── SqlMultiDacpacDeployment.ps1 │ ├── SqlScripts │ │ └── GetDatabaseVersion.sql │ ├── icon.png │ ├── package.json │ ├── task.json │ └── yarn.lock ├── StartWebApp │ ├── StartWebApp.ps1 │ ├── icon.png │ ├── package.json │ ├── task.json │ └── yarn.lock ├── StopWebApp │ ├── StopWebApp.ps1 │ ├── icon.png │ ├── package.json │ ├── task.json │ └── yarn.lock └── SwapSlots │ ├── SwapSlots.ps1 │ ├── icon.png │ ├── package.json │ ├── task.json │ └── yarn.lock ├── Tests ├── Node │ ├── AzCopy │ │ └── azCopy.spec.ts │ ├── chutzpah.json │ ├── jasmine.json │ └── settings.sample.json └── PowerShell3 │ ├── GeekLearning.VstsTasks.Azure.Tests.sln │ ├── GeekLearning.VstsTasks.Azure.Tests │ ├── GeekLearning.VstsTasks.Azure.Tests.pssproj │ ├── Helpers.ps1 │ ├── Run-Tests.ps1 │ ├── SampleDirectory │ │ └── SqlMultiDacpacDeployment │ │ │ ├── 1.0.0.0.dacpac │ │ │ ├── 1.0.0.1.dacpac │ │ │ └── GeekLearning.Tasks.Database.dacpac │ ├── SqlMultiDacpacDeployment.Tests.ps1 │ └── appsettings.json │ └── global.json ├── configuration.json ├── package.json ├── tsconfig.json ├── tslint.json ├── typings.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules/**/*.* 2 | **/*.d.ts -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | plugins: ["@typescript-eslint"], 4 | extends: ["plugin:@typescript-eslint/recommended"], 5 | rules: { 6 | "@typescript-eslint/indent": ["error", 2], 7 | "@typescript-eslint/explicit-function-return-type": [ 8 | "error", 9 | { 10 | allowExpressions: true, 11 | allowTypedFunctionExpressions: true 12 | } 13 | ] 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | # Shell scripts should always use line feed not crlf 7 | *.sh text eol=lf 8 | 9 | ############################################################################### 10 | # Set default behavior for command prompt diff. 11 | # 12 | # This is need for earlier builds of msysgit that does not have it on by 13 | # default for csharp files. 14 | # Note: This is only used by command line 15 | ############################################################################### 16 | #*.cs diff=csharp 17 | 18 | ############################################################################### 19 | # Set the merge driver for project and solution files 20 | # 21 | # Merging from the command prompt will add diff markers to the files if there 22 | # are conflicts (Merging from VS is not affected by the settings below, in VS 23 | # the diff markers are never inserted). Diff markers may cause the following 24 | # file extensions to fail to load in VS. An alternative would be to treat 25 | # these files as binary and thus will always conflict and require user 26 | # intervention with every merge. To do so, just uncomment the entries below 27 | ############################################################################### 28 | *.js text 29 | *.json text 30 | *.resjson text 31 | *.htm text 32 | *.html text 33 | *.xml text 34 | *.txt text 35 | *.ini text 36 | *.inc text 37 | #*.sln merge=binary 38 | #*.csproj merge=binary 39 | #*.vbproj merge=binary 40 | #*.vcxproj merge=binary 41 | #*.vcproj merge=binary 42 | #*.dbproj merge=binary 43 | #*.fsproj merge=binary 44 | #*.lsproj merge=binary 45 | #*.wixproj merge=binary 46 | #*.modelproj merge=binary 47 | #*.sqlproj merge=binary 48 | #*.wwaproj merge=binary 49 | 50 | ############################################################################### 51 | # behavior for image files 52 | # 53 | # image files are treated as binary by default. 54 | ############################################################################### 55 | *.png binary 56 | *.jpg binary 57 | *.jpeg binary 58 | *.gif binary 59 | *.ico binary 60 | *.mov binary 61 | *.mp4 binary 62 | *.mp3 binary 63 | *.flv binary 64 | *.fla binary 65 | *.swf binary 66 | *.gz binary 67 | *.zip binary 68 | *.7z binary 69 | *.ttf binary 70 | 71 | ############################################################################### 72 | # diff behavior for common document formats 73 | # 74 | # Convert binary document formats to text before diffing them. This feature 75 | # is only available from the command line. Turn it on by uncommenting the 76 | # entries below. 77 | ############################################################################### 78 | *.doc diff=astextplain 79 | *.DOC diff=astextplain 80 | *.docx diff=astextplain 81 | *.DOCX diff=astextplain 82 | *.dot diff=astextplain 83 | *.DOT diff=astextplain 84 | *.pdf diff=astextplain 85 | *.PDF diff=astextplain 86 | *.rtf diff=astextplain 87 | *.RTF diff=astextplain -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Microsoft Azure ApplicationInsights config file 170 | ApplicationInsights.config 171 | 172 | # Windows Store app package directory 173 | AppPackages/ 174 | BundleArtifacts/ 175 | 176 | # Visual Studio cache files 177 | # files ending in .cache can be ignored 178 | *.[Cc]ache 179 | # but keep track of directories ending in .cache 180 | !*.[Cc]ache/ 181 | 182 | # Others 183 | ClientBin/ 184 | [Ss]tyle[Cc]op.* 185 | ~$* 186 | *~ 187 | *.dbmdl 188 | *.dbproj.schemaview 189 | *.pfx 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # RIA/Silverlight projects 195 | Generated_Code/ 196 | 197 | # Backup & report files from converting an old project file 198 | # to a newer Visual Studio version. Backup files are not needed, 199 | # because we have git ;-) 200 | _UpgradeReport_Files/ 201 | Backup*/ 202 | UpgradeLog*.XML 203 | UpgradeLog*.htm 204 | 205 | # SQL Server files 206 | *.mdf 207 | *.ldf 208 | 209 | # Business Intelligence projects 210 | *.rdl.data 211 | *.bim.layout 212 | *.bim_*.settings 213 | 214 | # Microsoft Fakes 215 | FakesAssemblies/ 216 | 217 | # GhostDoc plugin setting file 218 | *.GhostDoc.xml 219 | 220 | # Node.js Tools for Visual Studio 221 | .ntvs_analysis.dat 222 | 223 | # Visual Studio 6 build log 224 | *.plg 225 | 226 | # Visual Studio 6 workspace options file 227 | *.opt 228 | 229 | # Visual Studio LightSwitch build output 230 | **/*.HTMLClient/GeneratedArtifacts 231 | **/*.DesktopClient/GeneratedArtifacts 232 | **/*.DesktopClient/ModelManifest.xml 233 | **/*.Server/GeneratedArtifacts 234 | **/*.Server/ModelManifest.xml 235 | _Pvt_Extensions 236 | 237 | # LightSwitch generated files 238 | GeneratedArtifacts/ 239 | ModelManifest.xml 240 | 241 | # Paket dependency manager 242 | .paket/paket.exe 243 | 244 | # FAKE - F# Make 245 | .fake/ 246 | 247 | # PowerShell dependencies 248 | ps_modules 249 | 250 | .BuildOutput 251 | 252 | appsettings.development.json 253 | 254 | Tests/**/TEST-*.xml 255 | Tests/**/dump.txt 256 | Tests/**/*.js 257 | Tests/**/*.js.map 258 | Tests/**/*.d.ts 259 | Tests/Node/settings.json 260 | 261 | Tasks/**/*.js 262 | Tasks/**/*.js.map 263 | Tasks/**/*.d.ts 264 | Tasks/*/common/**/*.* -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": {} 3 | } 4 | -------------------------------------------------------------------------------- /Common/PowerShell3/VstsAzureHelpers/FindSqlPackagePath.ps1: -------------------------------------------------------------------------------- 1 | function Get-SqlPackageOnTargetMachine { 2 | try { 3 | $sqlDacPath, $sqlVersion = Get-HighestVersionSqlPackageWithSqlLocation 4 | $sqlVersionNumber = [decimal] $sqlVersion 5 | } 6 | catch [System.Exception] { 7 | Write-VstsTaskVerbose -Message ("Failed to get Dac Framework (installed with SQL Server) location with exception: " + $_.Exception.Message) 8 | $sqlVersionNumber = 0 9 | } 10 | 11 | try { 12 | $sqlMsiDacPath, $sqlMsiVersion = Get-HighestVersionSqlPackageWithDacMsiLocation 13 | $sqlMsiVersionNumber = [decimal] $sqlMsiVersion 14 | } 15 | catch [System.Exception] { 16 | Write-VstsTaskVerbose -Message ("Failed to get Dac Framework (installed with DAC Framework) location with exception: " + $_.Exception.Message) 17 | $sqlMsiVersionNumber = 0 18 | } 19 | 20 | try { 21 | $vsDacPath, $vsVersion = Get-HighestVersionSqlPackageInVSLocation 22 | $vsVersionNumber = [decimal] $vsVersion 23 | } 24 | catch [System.Exception] { 25 | Write-VstsTaskVerbose -Message ("Failed to get Dac Framework (installed with Visual Studio) location with exception: " + $_.Exception.Message) 26 | $vsVersionNumber = 0 27 | } 28 | 29 | if (($vsVersionNumber -ge $sqlVersionNumber) -and ($vsVersionNumber -ge $sqlMsiVersionNumber)) { 30 | $dacPath = $vsDacPath 31 | } 32 | elseif ($sqlVersionNumber -ge $sqlMsiVersionNumber) { 33 | $dacPath = $sqlDacPath 34 | } 35 | else { 36 | $dacPath = $sqlMsiDacPath 37 | } 38 | 39 | if ($dacPath -eq $null) { 40 | $client = new-object System.Net.WebClient 41 | 42 | $archive = $env:BUILD_SOURCESDIRECTORY + "\dataTools.zip" 43 | $dataToolsRoot = $env:BUILD_SOURCESDIRECTORY + "\dataTools" 44 | 45 | $client.DownloadFile("https://www.nuget.org/api/v2/package/Microsoft.Data.Tools.Msbuild/10.0.61710.120", $archive); 46 | 47 | Expand-Archive $archive -DestinationPath $dataToolsRoot 48 | 49 | $dacPath = $dataToolsRoot + "\lib\net46\sqlpackage.exe" 50 | 51 | return $dacPath 52 | #throw "Unable to find the location of Dac Framework (SqlPackage.exe) from registry on machine $env:COMPUTERNAME" 53 | } 54 | else { 55 | return $dacPath 56 | } 57 | } 58 | 59 | function Get-RegistryValueIgnoreError { 60 | param 61 | ( 62 | [parameter(Mandatory = $true)] 63 | [Microsoft.Win32.RegistryHive] 64 | $RegistryHive, 65 | 66 | [parameter(Mandatory = $true)] 67 | [System.String] 68 | $Key, 69 | 70 | [parameter(Mandatory = $true)] 71 | [System.String] 72 | $Value, 73 | 74 | [parameter(Mandatory = $true)] 75 | [Microsoft.Win32.RegistryView] 76 | $RegistryView 77 | ) 78 | 79 | try { 80 | $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey($RegistryHive, $RegistryView) 81 | $subKey = $baseKey.OpenSubKey($Key) 82 | if ($subKey -ne $null) { 83 | return $subKey.GetValue($Value) 84 | } 85 | } 86 | catch { 87 | } 88 | 89 | return $null 90 | } 91 | 92 | function Get-SubKeysInFloatFormat($keys) { 93 | $targetKeys = @() 94 | foreach ($key in $keys) { 95 | try { 96 | $targetKeys += [decimal] $key 97 | } 98 | catch { 99 | } 100 | } 101 | 102 | $targetKeys 103 | } 104 | 105 | function Get-SqlPackageForSqlVersion([int] $majorVersion, [bool] $wow6432Node) { 106 | $sqlInstallRootRegKey = "SOFTWARE", "Microsoft", "Microsoft SQL Server", "$majorVersion" -join [System.IO.Path]::DirectorySeparatorChar 107 | 108 | if ($wow6432Node -eq $true) { 109 | $sqlInstallRootPath = Get-RegistryValueIgnoreError LocalMachine "$sqlInstallRootRegKey" "VerSpecificRootDir" Registry64 110 | } 111 | else { 112 | $sqlInstallRootPath = Get-RegistryValueIgnoreError LocalMachine "$sqlInstallRootRegKey" "VerSpecificRootDir" Registry32 113 | } 114 | 115 | if ($sqlInstallRootPath -eq $null) { 116 | return $null 117 | } 118 | 119 | Write-VstsTaskVerbose -Message "Sql Version Specific Root Dir for version $majorVersion as read from registry: $sqlInstallRootPath" 120 | 121 | $DacInstallPath = [System.IO.Path]::Combine($sqlInstallRootPath, "Dac", "bin", "SqlPackage.exe") 122 | 123 | if (Test-Path $DacInstallPath) { 124 | Write-VstsTaskVerbose -Message "Dac Framework installed with SQL Version $majorVersion found at $DacInstallPath on machine $env:COMPUTERNAME" 125 | return $DacInstallPath 126 | } 127 | 128 | return $null 129 | } 130 | 131 | function Get-HighestVersionSqlPackageWithSqlLocation() { 132 | $sqlRegKey = "HKLM:", "SOFTWARE", "Wow6432Node", "Microsoft", "Microsoft SQL Server" -join [System.IO.Path]::DirectorySeparatorChar 133 | $sqlRegKey64 = "HKLM:", "SOFTWARE", "Microsoft", "Microsoft SQL Server" -join [System.IO.Path]::DirectorySeparatorChar 134 | 135 | if (-not (Test-Path $sqlRegKey)) { 136 | $sqlRegKey = $sqlRegKey64 137 | } 138 | 139 | if (-not (Test-Path $sqlRegKey)) { 140 | return $null, 0 141 | } 142 | 143 | $keys = Get-Item $sqlRegKey | % {$_.GetSubKeyNames()} 144 | $versions = Get-SubKeysInFloatFormat $keys | Sort-Object -Descending 145 | 146 | Write-VstsTaskVerbose -Message "Sql Versions installed on machine $env:COMPUTERNAME as read from registry: $versions" 147 | 148 | foreach ($majorVersion in $versions) { 149 | $DacInstallPathWow6432Node = Get-SqlPackageForSqlVersion $majorVersion $true 150 | $DacInstallPath = Get-SqlPackageForSqlVersion $majorVersion $false 151 | 152 | if ($DacInstallPathWow6432Node -ne $null) { 153 | return $DacInstallPathWow6432Node, $majorVersion 154 | } 155 | elseif ($DacInstallPath -ne $null) { 156 | return $DacInstallPath, $majorVersion 157 | } 158 | } 159 | 160 | Write-VstsTaskVerbose -Message "Dac Framework (installed with SQL) not found on machine $env:COMPUTERNAME" 161 | 162 | return $null, 0 163 | } 164 | 165 | function Get-HighestVersionSqlPackageWithDacMsiLocation() { 166 | $sqlDataTierFrameworkRegKeyWow = "HKLM:", "SOFTWARE", "Wow6432Node", "Microsoft", "Microsoft SQL Server", "Data-Tier Application Framework" -join [System.IO.Path]::DirectorySeparatorChar 167 | $sqlDataTierFrameworkRegKey = "HKLM:", "SOFTWARE", "Microsoft", "Microsoft SQL Server", "Data-Tier Application Framework" -join [System.IO.Path]::DirectorySeparatorChar 168 | 169 | if (-not (Test-Path $sqlDataTierFrameworkRegKey)) { 170 | $sqlDataTierFrameworkRegKey = $sqlDataTierFrameworkRegKeyWow 171 | } 172 | 173 | if ((Test-Path $sqlDataTierFrameworkRegKey)) { 174 | $keys = Get-Item $sqlDataTierFrameworkRegKey | % {$_.GetSubKeyNames()} 175 | $versions = Get-SubKeysInFloatFormat $keys | Sort-Object -Descending 176 | 177 | foreach ($majorVersion in $versions) { 178 | $sqlInstallRootRegKey = "SOFTWARE", "Microsoft", "Microsoft SQL Server", "Data-Tier Application Framework", "$majorVersion" -join [System.IO.Path]::DirectorySeparatorChar 179 | $sqlInstallRootPath64 = Get-RegistryValueIgnoreError LocalMachine "$sqlInstallRootRegKey" "InstallDir" Registry64 180 | $sqlInstallRootPath32 = Get-RegistryValueIgnoreError LocalMachine "$sqlInstallRootRegKey" "InstallDir" Registry32 181 | if ($sqlInstallRootPath64 -ne $null) { 182 | $sqlInstallRootPath = $sqlInstallRootPath64 183 | break 184 | } 185 | if ($sqlInstallRootPath32 -ne $null) { 186 | $sqlInstallRootPath = $sqlInstallRootPath32 187 | break 188 | } 189 | } 190 | 191 | $DacInstallPath = [System.IO.Path]::Combine($sqlInstallRootPath, "SqlPackage.exe") 192 | 193 | if (Test-Path $DacInstallPath) { 194 | Write-Verbose "Dac Framework installed with SQL Version $majorVersion found at $DacInstallPath on machine $env:COMPUTERNAME" 195 | return $DacInstallPath, $majorVersion 196 | } 197 | } 198 | 199 | $sqlRegKeyWow = "HKLM:", "SOFTWARE", "Wow6432Node", "Microsoft", "Microsoft SQL Server", "DACFramework", "CurrentVersion" -join [System.IO.Path]::DirectorySeparatorChar 200 | $sqlRegKey = "HKLM:", "SOFTWARE", "Microsoft", "Microsoft SQL Server", "DACFramework", "CurrentVersion" -join [System.IO.Path]::DirectorySeparatorChar 201 | 202 | $sqlKey = "SOFTWARE", "Microsoft", "Microsoft SQL Server", "DACFramework", "CurrentVersion" -join [System.IO.Path]::DirectorySeparatorChar 203 | 204 | if (Test-Path $sqlRegKey) { 205 | $dacVersion = Get-RegistryValueIgnoreError LocalMachine "$sqlKey" "Version" Registry64 206 | $majorVersion = $dacVersion.Substring(0, $dacVersion.IndexOf(".")) + "0" 207 | } 208 | 209 | if (Test-Path $sqlRegKeyWow) { 210 | $dacVersionX86 = Get-RegistryValueIgnoreError LocalMachine "$sqlKey" "Version" Registry32 211 | $majorVersionX86 = $dacVersionX86.Substring(0, $dacVersionX86.IndexOf(".")) + "0" 212 | } 213 | 214 | if ((-not($dacVersion)) -and (-not($dacVersionX86))) { 215 | Write-VstsTaskVerbose -Message "Dac Framework (installed with DAC Framework) not found on machine $env:COMPUTERNAME" 216 | return $null, 0 217 | } 218 | 219 | if ($majorVersionX86 -gt $majorVersion) { 220 | $majorVersion = $majorVersionX86 221 | } 222 | 223 | $dacRelativePath = "Microsoft SQL Server", "$majorVersion", "DAC", "bin", "SqlPackage.exe" -join [System.IO.Path]::DirectorySeparatorChar 224 | $programFiles = $env:ProgramFiles 225 | $programFilesX86 = "${env:ProgramFiles(x86)}" 226 | 227 | if (-not ($programFilesX86 -eq $null)) { 228 | $dacPath = $programFilesX86, $dacRelativePath -join [System.IO.Path]::DirectorySeparatorChar 229 | 230 | if (Test-Path("$dacPath")) { 231 | Write-VstsTaskVerbose -Message "Dac Framework (installed with DAC Framework Msi) found on machine $env:COMPUTERNAME at $dacPath" 232 | return $dacPath, $majorVersion 233 | } 234 | } 235 | 236 | if (-not ($programFiles -eq $null)) { 237 | $dacPath = $programFiles, $dacRelativePath -join [System.IO.Path]::DirectorySeparatorChar 238 | 239 | if (Test-Path($dacPath)) { 240 | Write-VstsTaskVerbose -Message "Dac Framework (installed with DAC Framework Msi) found on machine $env:COMPUTERNAME at $dacPath" 241 | return $dacPath, $majorVersion 242 | } 243 | } 244 | 245 | return $null, 0 246 | } 247 | 248 | function Get-SqlPackageInVSLocation([string] $version) { 249 | $vsRegKeyForVersion = "SOFTWARE", "Microsoft", "VisualStudio", $version -join [System.IO.Path]::DirectorySeparatorChar 250 | 251 | $vsInstallDir = Get-RegistryValueIgnoreError LocalMachine "$vsRegKeyForVersion" "InstallDir" Registry64 252 | 253 | if ($vsInstallDir -eq $null) { 254 | $vsInstallDir = Get-RegistryValueIgnoreError LocalMachine "$vsRegKeyForVersion" "InstallDir" Registry32 255 | } 256 | 257 | if ($vsInstallDir) { 258 | Write-VstsTaskVerbose -Message "Visual Studio install location: $vsInstallDir" 259 | 260 | $dacExtensionPath = [System.IO.Path]::Combine("Extensions", "Microsoft", "SQLDB", "DAC") 261 | $dacParentDir = [System.IO.Path]::Combine($vsInstallDir, $dacExtensionPath) 262 | 263 | if (Test-Path $dacParentDir) { 264 | $dacVersionDirs = Get-ChildItem $dacParentDir | Sort-Object @{e = {$_.Name -as [int]}} -Descending 265 | 266 | foreach ($dacVersionDir in $dacVersionDirs) { 267 | $dacVersion = $dacVersionDir.Name 268 | $dacFullPath = [System.IO.Path]::Combine($dacVersionDir.FullName, "SqlPackage.exe") 269 | 270 | if (Test-Path $dacFullPath -pathtype leaf) { 271 | Write-VstsTaskVerbose -Message "Dac Framework installed with Visual Studio found at $dacFullPath on machine $env:COMPUTERNAME" 272 | return $dacFullPath, $dacVersion 273 | } 274 | else { 275 | Write-VstsTaskVerbose -Message "Unable to find Dac framework installed with Visual Studio at $($dacVersionDir.FullName) on machine $env:COMPUTERNAME" 276 | } 277 | } 278 | } 279 | } 280 | 281 | return $null, 0 282 | } 283 | 284 | function Get-HighestVersionSqlPackageInVSLocation() { 285 | $vsRegKey = "HKLM:", "SOFTWARE", "Wow6432Node", "Microsoft", "VisualStudio" -join [System.IO.Path]::DirectorySeparatorChar 286 | $vsRegKey64 = "HKLM:", "SOFTWARE", "Microsoft", "VisualStudio" -join [System.IO.Path]::DirectorySeparatorChar 287 | 288 | if (-not (Test-Path $vsRegKey)) { 289 | $vsRegKey = $vsRegKey64 290 | } 291 | 292 | if (-not (Test-Path $vsRegKey)) { 293 | Write-VstsTaskVerbose -Message "Visual Studio not found on machine $env:COMPUTERNAME" 294 | return $null, 0 295 | } 296 | 297 | $keys = Get-Item $vsRegKey | % {$_.GetSubKeyNames()} 298 | $versions = Get-SubKeysInFloatFormat $keys | Sort-Object -Descending 299 | 300 | Write-VstsTaskVerbose -Message "Visual Studio versions found on machine $env:COMPUTERNAME as read from registry: $versions" 301 | 302 | foreach ($majorVersion in $versions) { 303 | $dacFullPath, $dacVersion = Get-SqlPackageInVSLocation $majorVersion 304 | 305 | if ($dacFullPath -ne $null) { 306 | return $dacFullPath, $dacVersion 307 | } 308 | } 309 | 310 | Write-VstsTaskVerbose -Message "Dac Framework (installed with Visual Studio) not found on machine $env:COMPUTERNAME " 311 | 312 | return $null, 0 313 | } 314 | -------------------------------------------------------------------------------- /Common/PowerShell3/VstsAzureHelpers/HelperFunctions.ps1: -------------------------------------------------------------------------------- 1 | function Get-AgentStartIPAddress { 2 | $data = (Invoke-WebRequest -Uri "checkip.dyndns.org" -UseBasicParsing -Verbose).Content 3 | 4 | $ipRegex = "(?
((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))" 5 | if ($data -Match $ipRegex) { 6 | $startIP = $Matches.Address 7 | } 8 | else { 9 | throw (Get-VstsLocString -Key AZ_CannotRetrieveExternalIp0 -ArgumentList $data) 10 | } 11 | 12 | return $startIP 13 | } 14 | 15 | function Get-ResourceGroupName { 16 | param([String] [Parameter(Mandatory = $true)] $resourceName, 17 | [String] [Parameter(Mandatory = $true)] $resourceType) 18 | 19 | try { 20 | Write-VstsTaskVerbose -Message "[Azure Call] Getting resource details for resource: $resourceName with resource type: $resourceType" 21 | $resourceDetails = (Get-AzureRMResource -ErrorAction Stop) | Where-Object { ($_.ResourceName -eq $resourceName -or $_.Name -eq $resourceName) -and $_.ResourceType -eq $resourceType } -Verbose 22 | Write-VstsTaskVerbose -Message "[Azure Call] Retrieved resource details successfully for resource: $resourceName with resource type: $resourceType" 23 | $resourceGroupName = $resourceDetails.ResourceGroupName 24 | Write-VstsTaskVerbose -Message "Resource Group Name for '$resourceName' of type '$resourceType': '$resourceGroupName'." 25 | return $resourceGroupName 26 | } 27 | finally { 28 | if ([string]::IsNullOrEmpty($resourceGroupName)) { 29 | throw (Get-VstsLocString -Key AZ_ResourceNotFound0 -ArgumentList $resourceName) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Common/PowerShell3/VstsAzureHelpers/ImportFunctions.ps1: -------------------------------------------------------------------------------- 1 | function Import-AzureModule { 2 | [CmdletBinding()] 3 | param( 4 | [Parameter(Mandatory = $true)] 5 | [ValidateSet('Azure', 'AzureRM')] 6 | [string[]]$PreferredModule) 7 | 8 | Trace-VstsEnteringInvocation $MyInvocation 9 | try { 10 | Write-VstsTaskVerbose -Message "Env:PSModulePath: '$env:PSMODULEPATH'" 11 | if ($PreferredModule -contains 'Azure' -and $PreferredModule -contains 'AzureRM') { 12 | # Attempt to import Azure and AzureRM. 13 | $azure = (Import-FromModulePath -Classic:$true) -or (Import-FromSdkPath -Classic:$true) 14 | $azureRM = (Import-FromModulePath -Classic:$false) -or (Import-FromSdkPath -Classic:$false) 15 | if (!$azure -and !$azureRM) { 16 | throw (Get-VstsLocString -Key AZ_ModuleNotFound) 17 | } 18 | } elseif ($PreferredModule -contains 'Azure') { 19 | # Attempt to import Azure but fallback to AzureRM. 20 | if (!(Import-FromModulePath -Classic:$true) -and 21 | !(Import-FromSdkPath -Classic:$true) -and 22 | !(Import-FromModulePath -Classic:$false) -and 23 | !(Import-FromSdkPath -Classic:$false)) 24 | { 25 | throw (Get-VstsLocString -Key AZ_ModuleNotFound) 26 | } 27 | } else { 28 | # Attempt to import AzureRM but fallback to Azure. 29 | if (!(Import-FromModulePath -Classic:$false) -and 30 | !(Import-FromSdkPath -Classic:$false) -and 31 | !(Import-FromModulePath -Classic:$true) -and 32 | !(Import-FromSdkPath -Classic:$true)) 33 | { 34 | throw (Get-VstsLocString -Key AZ_ModuleNotFound) 35 | } 36 | } 37 | 38 | # Validate the Classic version. 39 | $minimumVersion = [version]'0.8.10.1' 40 | if ($script:azureModule -and $script:azureModule.Version -lt $minimumVersion) { 41 | throw (Get-VstsLocString -Key AZ_RequiresMinVersion0 -ArgumentList $minimumVersion) 42 | } 43 | } finally { 44 | Trace-VstsLeavingInvocation $MyInvocation 45 | } 46 | } 47 | 48 | function Import-FromModulePath { 49 | [CmdletBinding()] 50 | param( 51 | [switch]$Classic) 52 | 53 | Trace-VstsEnteringInvocation $MyInvocation 54 | try { 55 | # Determine which module to look for. 56 | if ($Classic) { 57 | $name = "Azure" 58 | } else { 59 | $name = "AzureRM" 60 | } 61 | 62 | # Attempt to resolve the module. 63 | Write-VstsTaskVerbose -Message "Attempting to find the module '$name' from the module path." 64 | $module = Get-Module -Name $name -ListAvailable | Select-Object -First 1 65 | if (!$module) { 66 | return $false 67 | } 68 | 69 | # Import the module. 70 | Write-Host "##[command]Import-Module -Name $($module.Path) -Global" 71 | $module = Import-Module -Name $module.Path -Global -PassThru 72 | Write-VstsTaskVerbose -Message "Imported module version: $($module.Version)" 73 | 74 | if ($Classic) { 75 | # Store the imported Azure module. 76 | $script:azureModule = $module 77 | } else { 78 | # The AzureRM module was imported. 79 | 80 | # Validate the AzureRM.profile module can be found. 81 | $profileModule = Get-Module -Name AzureRM.profile -ListAvailable | Select-Object -First 1 82 | if (!$profileModule) { 83 | throw (Get-VstsLocString -Key AZ_AzureRMProfileModuleNotFound) 84 | } 85 | 86 | # Import and then store the AzureRM.profile module. 87 | Write-Host "##[command]Import-Module -Name $($profileModule.Path) -Global" 88 | $script:azureRMProfileModule = Import-Module -Name $profileModule.Path -Global -PassThru 89 | Write-VstsTaskVerbose -Message "Imported module version: $($script:azureRMProfileModule.Version)" 90 | } 91 | 92 | return $true 93 | } finally { 94 | Trace-VstsLeavingInvocation $MyInvocation 95 | } 96 | } 97 | 98 | function Import-FromSdkPath { 99 | [CmdletBinding()] 100 | param([switch]$Classic) 101 | 102 | Trace-VstsEnteringInvocation $MyInvocation 103 | try { 104 | if ($Classic) { 105 | $partialPath = 'Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Azure.psd1' 106 | } else { 107 | $partialPath = 'Microsoft SDKs\Azure\PowerShell\ResourceManager\AzureResourceManager\AzureRM.Profile\AzureRM.Profile.psd1' 108 | } 109 | 110 | foreach ($programFiles in @(${env:ProgramFiles(x86)}, $env:ProgramFiles)) { 111 | if (!$programFiles) { 112 | continue 113 | } 114 | 115 | $path = [System.IO.Path]::Combine($programFiles, $partialPath) 116 | Write-VstsTaskVerbose -Message "Checking if path exists: $path" 117 | if (Test-Path -LiteralPath $path -PathType Leaf) { 118 | # Import the module. 119 | Write-Host "##[command]Import-Module -Name $path -Global" 120 | $module = Import-Module -Name $path -Global -PassThru 121 | Write-VstsTaskVerbose -Message "Imported module version: $($module.Version)" 122 | 123 | # Store the imported module. 124 | if ($Classic) { 125 | $script:azureModule = $module 126 | } else { 127 | $script:azureRMProfileModule = $module 128 | } 129 | 130 | return $true 131 | } 132 | } 133 | 134 | return $false 135 | } finally { 136 | Trace-VstsLeavingInvocation $MyInvocation 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Common/PowerShell3/VstsAzureHelpers/InitializeFunctions.ps1: -------------------------------------------------------------------------------- 1 | function Add-Certificate { 2 | [CmdletBinding()] 3 | param([Parameter(Mandatory=$true)]$Endpoint) 4 | 5 | # Add the certificate to the cert store. 6 | $bytes = [System.Convert]::FromBase64String($Endpoint.Auth.Parameters.Certificate) 7 | $certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 8 | $certificate.Import($bytes) 9 | $store = New-Object System.Security.Cryptography.X509Certificates.X509Store( 10 | ([System.Security.Cryptography.X509Certificates.StoreName]::My), 11 | ([System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser)) 12 | $store.Open(([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)) 13 | $store.Add($certificate) 14 | $store.Close() 15 | return $certificate 16 | } 17 | 18 | function Format-Splat { 19 | [CmdletBinding()] 20 | param([Parameter(Mandatory = $true)][hashtable]$Hashtable) 21 | 22 | # Collect the parameters (names and values) in an array. 23 | $parameters = foreach ($key in $Hashtable.Keys) { 24 | $value = $Hashtable[$key] 25 | # If the value is a bool, format the parameter as a switch (ending with ':'). 26 | if ($value -is [bool]) { "-$($key):" } else { "-$key" } 27 | $value 28 | } 29 | 30 | $OFS = " " 31 | "$parameters" # String join the array. 32 | } 33 | 34 | function Initialize-AzureSubscription { 35 | [CmdletBinding()] 36 | param( 37 | [Parameter(Mandatory=$true)] 38 | $Endpoint, 39 | [Parameter(Mandatory=$false)] 40 | [string]$StorageAccount) 41 | 42 | #Set UserAgent for Azure Calls 43 | Set-UserAgent 44 | 45 | if ($Endpoint.Auth.Scheme -eq 'Certificate') { 46 | # Certificate is only supported for the Azure module. 47 | if (!$script:azureModule) { 48 | throw (Get-VstsLocString -Key AZ_CertificateAuthNotSupported) 49 | } 50 | 51 | # Add the certificate to the cert store. 52 | $certificate = Add-Certificate -Endpoint $Endpoint 53 | 54 | # Setup the additional parameters. 55 | $additional = @{ } 56 | if ($StorageAccount) { 57 | $additional['CurrentStorageAccountName'] = $StorageAccount 58 | } 59 | 60 | $environmentName = "AzureCloud" 61 | if( $Endpoint.Data.Environment ) { 62 | $environmentName = $Endpoint.Data.Environment 63 | } 64 | 65 | # Set the subscription. 66 | Write-Host "##[command]Set-AzureSubscription -SubscriptionName $($Endpoint.Data.SubscriptionName) -SubscriptionId $($Endpoint.Data.SubscriptionId) -Certificate ******** -Environment $environmentName $(Format-Splat $additional)" 67 | Set-AzureSubscription -SubscriptionName $Endpoint.Data.SubscriptionName -SubscriptionId $Endpoint.Data.SubscriptionId -Certificate $certificate -Environment $environmentName @additional 68 | Set-CurrentAzureSubscription -SubscriptionId $Endpoint.Data.SubscriptionId -StorageAccount $StorageAccount 69 | } elseif ($Endpoint.Auth.Scheme -eq 'UserNamePassword') { 70 | $psCredential = New-Object System.Management.Automation.PSCredential( 71 | $Endpoint.Auth.Parameters.UserName, 72 | (ConvertTo-SecureString $Endpoint.Auth.Parameters.Password -AsPlainText -Force)) 73 | 74 | # Add account (Azure). 75 | if ($script:azureModule) { 76 | try { 77 | Write-Host "##[command]Add-AzureAccount -Credential $psCredential" 78 | $null = Add-AzureAccount -Credential $psCredential 79 | } catch { 80 | # Provide an additional, custom, credentials-related error message. 81 | Write-VstsTaskError -Message $_.Exception.Message 82 | throw (New-Object System.Exception((Get-VstsLocString -Key AZ_CredentialsError), $_.Exception)) 83 | } 84 | } 85 | 86 | # Add account (AzureRM). 87 | if ($script:azureRMProfileModule) { 88 | try { 89 | Write-Host "##[command]Add-AzureRMAccount -Credential $psCredential" 90 | $null = Add-AzureRMAccount -Credential $psCredential 91 | } catch { 92 | # Provide an additional, custom, credentials-related error message. 93 | Write-VstsTaskError -Message $_.Exception.Message 94 | throw (New-Object System.Exception((Get-VstsLocString -Key AZ_CredentialsError), $_.Exception)) 95 | } 96 | } 97 | 98 | # Select subscription (Azure). 99 | if ($script:azureModule) { 100 | Set-CurrentAzureSubscription -SubscriptionId $Endpoint.Data.SubscriptionId -StorageAccount $StorageAccount 101 | } 102 | 103 | # Select subscription (AzureRM). 104 | if ($script:azureRMProfileModule) { 105 | Set-CurrentAzureRMSubscription -SubscriptionId $Endpoint.Data.SubscriptionId 106 | } 107 | } elseif ($Endpoint.Auth.Scheme -eq 'ServicePrincipal') { 108 | $psCredential = New-Object System.Management.Automation.PSCredential( 109 | $Endpoint.Auth.Parameters.ServicePrincipalId, 110 | (ConvertTo-SecureString $Endpoint.Auth.Parameters.ServicePrincipalKey -AsPlainText -Force)) 111 | if ($script:azureModule -and $script:azureModule.Version -lt ([version]'0.9.9')) { 112 | # Service principals arent supported from 0.9.9 and greater in the Azure module. 113 | try { 114 | Write-Host "##[command]Add-AzureAccount -ServicePrincipal -Tenant $($Endpoint.Auth.Parameters.TenantId) -Credential $psCredential" 115 | $null = Add-AzureAccount -ServicePrincipal -Tenant $Endpoint.Auth.Parameters.TenantId -Credential $psCredential 116 | } catch { 117 | # Provide an additional, custom, credentials-related error message. 118 | Write-VstsTaskError -Message $_.Exception.Message 119 | throw (New-Object System.Exception((Get-VstsLocString -Key AZ_ServicePrincipalError), $_.Exception)) 120 | } 121 | 122 | Set-CurrentAzureSubscription -SubscriptionId $Endpoint.Data.SubscriptionId -StorageAccount $StorageAccount 123 | } elseif ($script:azureModule) { 124 | # Throw if >=0.9.9 Azure. 125 | throw (Get-VstsLocString -Key "AZ_ServicePrincipalAuthNotSupportedAzureVersion0" -ArgumentList $script:azureModule.Version) 126 | } else { 127 | # Else, this is AzureRM. 128 | try { 129 | Write-Host "##[command]Add-AzureRMAccount -ServicePrincipal -Tenant $($Endpoint.Auth.Parameters.TenantId) -Credential $psCredential" 130 | $null = Add-AzureRMAccount -ServicePrincipal -Tenant $Endpoint.Auth.Parameters.TenantId -Credential $psCredential 131 | } catch { 132 | # Provide an additional, custom, credentials-related error message. 133 | Write-VstsTaskError -Message $_.Exception.Message 134 | throw (New-Object System.Exception((Get-VstsLocString -Key AZ_ServicePrincipalError), $_.Exception)) 135 | } 136 | 137 | Set-CurrentAzureRMSubscription -SubscriptionId $Endpoint.Data.SubscriptionId -TenantId $Endpoint.Auth.Parameters.TenantId 138 | } 139 | } else { 140 | throw (Get-VstsLocString -Key AZ_UnsupportedAuthScheme0 -ArgumentList $Endpoint.Auth.Scheme) 141 | } 142 | } 143 | 144 | function Set-CurrentAzureSubscription { 145 | [CmdletBinding()] 146 | param( 147 | [Parameter(Mandatory=$true)] 148 | [string]$SubscriptionId, 149 | [string]$StorageAccount) 150 | 151 | $additional = @{ } 152 | if ($script:azureModule.Version -lt ([version]'0.8.15')) { 153 | $additional['Default'] = $true # The Default switch is required prior to 0.8.15. 154 | } 155 | 156 | Write-Host "##[command]Select-AzureSubscription -SubscriptionId $SubscriptionId $(Format-Splat $additional)" 157 | $null = Select-AzureSubscription -SubscriptionId $SubscriptionId @additional 158 | if ($StorageAccount) { 159 | Write-Host "##[command]Set-AzureSubscription -SubscriptionId $SubscriptionId -CurrentStorageAccountName $StorageAccount" 160 | Set-AzureSubscription -SubscriptionId $SubscriptionId -CurrentStorageAccountName $StorageAccount 161 | } 162 | } 163 | 164 | function Set-CurrentAzureRMSubscription { 165 | [CmdletBinding()] 166 | param( 167 | [Parameter(Mandatory=$true)] 168 | [string]$SubscriptionId, 169 | [string]$TenantId) 170 | 171 | $additional = @{ } 172 | if ($TenantId) { $additional['TenantId'] = $TenantId } 173 | Write-Host "##[command]Select-AzureRMSubscription -SubscriptionId $SubscriptionId $(Format-Splat $additional)" 174 | $null = Select-AzureRMSubscription -SubscriptionId $SubscriptionId @additional 175 | } 176 | 177 | function Set-UserAgent { 178 | [CmdletBinding()] 179 | param() 180 | 181 | $userAgent = Get-VstsTaskVariable -Name AZURE_HTTP_USER_AGENT 182 | if ($userAgent) { 183 | Set-UserAgent_Core -UserAgent $userAgent 184 | } 185 | } 186 | 187 | function Set-UserAgent_Core { 188 | [CmdletBinding()] 189 | param( 190 | [Parameter(Mandatory = $true)] 191 | [string]$UserAgent) 192 | 193 | Trace-VstsEnteringInvocation $MyInvocation 194 | try { 195 | [Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent($UserAgent) 196 | } catch { 197 | Write-VstsTaskVerbose -Message "Set-UserAgent failed with exception message: $_.Exception.Message" 198 | } finally { 199 | Trace-VstsLeavingInvocation $MyInvocation 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /Common/PowerShell3/VstsAzureHelpers/VstsAzureHelpers.psm1: -------------------------------------------------------------------------------- 1 | # Private module-scope variables. 2 | $script:azureModule = $null 3 | $script:azureRMProfileModule = $null 4 | 5 | # Override the DebugPreference. 6 | if ($global:DebugPreference -eq 'Continue') { 7 | Write-VstsTaskVerbose -Message '$OVERRIDING $global:DebugPreference from ''Continue'' to ''SilentlyContinue''.' 8 | $global:DebugPreference = 'SilentlyContinue' 9 | } 10 | 11 | # Import the loc strings. 12 | Import-VstsLocStrings -LiteralPath $PSScriptRoot/module.json 13 | 14 | # Dot source the private functions. 15 | . $PSScriptRoot/InitializeFunctions.ps1 16 | . $PSScriptRoot/ImportFunctions.ps1 17 | . $PSScriptRoot/HelperFunctions.ps1 18 | . $PSScriptRoot/FindSqlPackagePath.ps1 19 | 20 | function Initialize-Azure { 21 | [CmdletBinding()] 22 | param() 23 | Trace-VstsEnteringInvocation $MyInvocation 24 | try { 25 | # Get the inputs. 26 | $serviceNameInput = Get-VstsInput -Name ConnectedServiceNameSelector -Default 'ConnectedServiceName' 27 | $serviceName = Get-VstsInput -Name $serviceNameInput -Default (Get-VstsInput -Name DeploymentEnvironmentName) 28 | if (!$serviceName) { 29 | # Let the task SDK throw an error message if the input isn't defined. 30 | Get-VstsInput -Name $serviceNameInput -Require 31 | } 32 | 33 | $endpoint = Get-VstsEndpoint -Name $serviceName -Require 34 | $storageAccount = Get-VstsInput -Name StorageAccount 35 | 36 | # Determine which modules are preferred. 37 | $preferredModules = @( ) 38 | if ($endpoint.Auth.Scheme -eq 'ServicePrincipal') { 39 | $preferredModules += 'AzureRM' 40 | } elseif ($endpoint.Auth.Scheme -eq 'UserNamePassword') { 41 | $preferredModules += 'Azure' 42 | $preferredModules += 'AzureRM' 43 | } else { 44 | $preferredModules += 'Azure' 45 | } 46 | 47 | # Import/initialize the Azure module. 48 | Import-AzureModule -PreferredModule $preferredModules 49 | Initialize-AzureSubscription -Endpoint $endpoint -StorageAccount $storageAccount 50 | 51 | # Check the installed Azure Powershell version 52 | $currentVersion = (Get-Module -Name AzureRM.profile).Version 53 | Write-VstsTaskVerbose -Message "Installed Azure PowerShell version: $currentVersion" 54 | 55 | $minimumAzureVersion = New-Object System.Version(0, 9, 9) 56 | if (-not ($currentVersion -and $currentVersion -gt $minimumAzureVersion)) { 57 | throw (Get-VstsLocString -Key AZ_RequiresMinVersion0 -ArgumentList $minimumAzureVersion) 58 | } 59 | } finally { 60 | Trace-VstsLeavingInvocation $MyInvocation 61 | } 62 | } 63 | 64 | function Get-AgentIPAddress { 65 | param([String] $startIPAddress, 66 | [String] $endIPAddress, 67 | [String] [Parameter(Mandatory = $true)] $ipDetectionMethod) 68 | 69 | [HashTable]$iPAddress = @{} 70 | if($ipDetectionMethod -eq "IPAddressRange") { 71 | $iPAddress.StartIPAddress = $startIPAddress 72 | $iPAddress.EndIPAddress = $endIPAddress 73 | } 74 | elseif($ipDetectionMethod -eq "AutoDetect") { 75 | $iPAddress.StartIPAddress = Get-AgentStartIPAddress 76 | $iPAddress.EndIPAddress = $IPAddress.StartIPAddress 77 | } 78 | 79 | Write-VstsTaskVerbose -Message "Agent IP Addresses:" 80 | Write-VstsTaskVerbose -Message " Start IP: $($iPAddress.StartIPAddress)" 81 | Write-VstsTaskVerbose -Message " End IP: $($iPAddress.EndIPAddress)" 82 | 83 | return $iPAddress 84 | } 85 | 86 | function Add-AzureSqlDatabaseServerFirewallRule { 87 | param([String] [Parameter(Mandatory = $true)] $startIp, 88 | [String] [Parameter(Mandatory = $true)] $endIp, 89 | [String] [Parameter(Mandatory = $true)] $serverName) 90 | 91 | [HashTable]$firewallSettings = @{} 92 | $firewallRuleName = [System.Guid]::NewGuid().ToString() 93 | 94 | $azureResourceGroupName = Get-AzureSqlDatabaseServerResourceGroupName -serverName $serverName 95 | 96 | try { 97 | Write-VstsTaskVerbose -Message "[Azure Call] Creating firewall rule $firewallRuleName on Azure SQL Server: $serverName" 98 | $null = New-AzureRMSqlServerFirewallRule -ResourceGroupName $azureResourceGroupName -StartIPAddress $startIp -EndIPAddress $endIp -ServerName $serverName -FirewallRuleName $firewallRuleName -ErrorAction Stop 99 | Write-VstsTaskVerbose -Message "[Azure Call] Firewall rule $firewallRuleName created on Azure SQL Server: $serverName" 100 | } 101 | catch [Hyak.Common.CloudException] { 102 | $exceptionMessage = $_.Exception.Message.ToString() 103 | Write-VstsTaskVerbose -Message "ExceptionMessage: $exceptionMessage" 104 | throw (Get-VstsLocString -Key AZ_InvalidIpAddress) 105 | } 106 | 107 | $firewallSettings.IsConfigured = $true 108 | $firewallSettings.RuleName = $firewallRuleName 109 | 110 | Write-VstsTaskVerbose -Message "Add Azure SQL Database Server Firewall Rule:" 111 | Write-VstsTaskVerbose -Message " IsConfigured: $($firewallSettings.IsConfigured)" 112 | Write-VstsTaskVerbose -Message " RuleName: $($firewallSettings.RuleName)" 113 | 114 | return $firewallSettings 115 | } 116 | 117 | function Remove-AzureSqlDatabaseServerFirewallRule { 118 | param([String] [Parameter(Mandatory = $true)] $serverName, 119 | [String] $firewallRuleName, 120 | [String] $isFirewallConfigured, 121 | [String] [Parameter(Mandatory = $true)] $deleteFireWallRule) 122 | 123 | if ($deleteFireWallRule -eq "true" -and $isFirewallConfigured -eq "true") { 124 | $azureResourceGroupName = Get-AzureSqlDatabaseServerResourceGroupName -serverName $serverName 125 | Write-VstsTaskVerbose -Message "[Azure Call] Deleting firewall rule $firewallRuleName on Azure SQL Server: $serverName" 126 | $null = Remove-AzureRMSqlServerFirewallRule -ResourceGroupName $azureResourceGroupName -ServerName $serverName -FirewallRuleName $firewallRuleName -Force -ErrorAction Stop 127 | Write-VstsTaskVerbose -Message "[Azure Call] Firewall rule $firewallRuleName deleted on Azure SQL Server: $serverName" 128 | } 129 | } 130 | 131 | function Get-AzureSqlDatabaseServerResourceGroupName { 132 | param([String] [Parameter(Mandatory = $true)] $serverName) 133 | 134 | return Get-ResourceGroupName -resourceName $serverName -resourceType "Microsoft.Sql/servers" 135 | } 136 | 137 | function Get-WebAppResourceGroupName { 138 | param([String] [Parameter(Mandatory = $true)] $webAppName) 139 | 140 | return Get-ResourceGroupName -resourceName $webAppName -resourceType "Microsoft.Web/sites" 141 | } 142 | 143 | function Get-SqlPackagePath { 144 | $sqlPackage = Get-SqlPackageOnTargetMachine 145 | Write-VstsTaskVerbose -Message "SqlPackage Path: '$sqlPackage'" 146 | return $sqlPackage 147 | } 148 | 149 | function Get-DacpacVersions { 150 | param([System.Array] [Parameter(Mandatory = $true)] $dacpacFilePaths) 151 | 152 | $dotnetVersion = [Environment]::Version 153 | if (!($dotnetVersion.Major -ge 4 -and $dotnetversion.Build -ge 30319)) { 154 | throw (Get-VstsLocString -Key "You have not Microsoft .Net Framework 4.5 installed on build agent.") 155 | } 156 | 157 | Add-Type -As System.IO.Compression.FileSystem 158 | $dacpacFileExtension = ".dacpac" 159 | $dacFilesWithVersion = @{} 160 | 161 | foreach ($dacpacFilePath in $dacpacFilePaths) { 162 | if ([System.IO.Path]::GetExtension($dacpacFilePath) -ne $dacpacFileExtension) { 163 | throw (Get-VstsLocString -Key "Invalid Dacpac file '{0}' provided" -ArgumentList $dacpacFilePath) 164 | } 165 | 166 | $dacMetadataXml = $null 167 | $dacVersion = $null 168 | 169 | $zip = [IO.Compression.ZipFile]::OpenRead($dacpacFilePath) 170 | $zip.Entries | Where-Object { $_.Name.EndsWith("DacMetadata.xml") } | ForEach-Object { 171 | $memoryStream = New-Object System.IO.MemoryStream 172 | $file = $_.Open() 173 | $file.CopyTo($memoryStream) 174 | $file.Dispose() 175 | $memoryStream.Position = 0 176 | $reader = New-Object System.IO.StreamReader($memoryStream) 177 | $dacMetadataXml = $reader.ReadToEnd() 178 | $reader.Dispose() 179 | $memoryStream.Dispose() 180 | } 181 | 182 | $zip.Dispose() 183 | 184 | if ($dacMetadataXml -ne $null) { 185 | $dacVersion = Select-Xml -XPath "//dac:DacType/dac:Version" -Content $dacMetadataXml -Namespace @{ dac = "http://schemas.microsoft.com/sqlserver/dac/Serialization/2012/02" } 186 | $dacFilesWithVersion.Add([Version]($dacVersion.ToString()), $dacpacFilePath) 187 | } 188 | else { 189 | throw (Get-VstsLocString -Key "Invalid Dacpac file '{0}' provided" -ArgumentList $dacpacFile) 190 | } 191 | } 192 | 193 | $dacFilesWithVersion = $dacFilesWithVersion.GetEnumerator() | Sort-Object Name 194 | Write-VstsTaskVerbose -Message "DACPAC files with version:" 195 | foreach ($dacFileWithVersion in $dacFilesWithVersion) { 196 | Write-VstsTaskVerbose -Message " Version: $($dacFileWithVersion.Name) Path: $($dacFileWithVersion.Value)" 197 | } 198 | 199 | return $dacFilesWithVersion 200 | } 201 | 202 | function Send-ExecuteCommand { 203 | param([String][Parameter(Mandatory=$true)] $command, 204 | [String][Parameter(Mandatory=$true)] $arguments, 205 | [String][Parameter(Mandatory=$true)] $secureArguments) 206 | 207 | $errorActionPreferenceRestore = $ErrorActionPreference 208 | $ErrorActionPreference="SilentlyContinue" 209 | 210 | $errout = $stdout = "" 211 | 212 | $arguments = $arguments.Replace(';', '`;') 213 | 214 | Write-VstsTaskVerbose -Message "[CMD Call] Executing: & `"$command`" $secureArguments 2>''" 215 | $null = Invoke-Expression "& `"$command`" $arguments 2>''" -ErrorVariable errout -OutVariable stdout 216 | Write-VstsTaskVerbose -Message "[CMD Call] Executed" 217 | 218 | $ErrorActionPreference = $errorActionPreferenceRestore 219 | 220 | foreach ($out in $stdout) { 221 | Write-VstsTaskVerbose -Message $out 222 | } 223 | 224 | if ($LastExitCode -gt 0) { 225 | foreach ($out in $errout) { 226 | Write-VstsTaskError -Message $out 227 | } 228 | 229 | throw $errout[0].Exception 230 | } 231 | } 232 | 233 | function Get-SqlPackageCommandArguments { 234 | param([String] $dacpacFile, 235 | [String] $serverName, 236 | [String] $databaseName, 237 | [String] $sqlUsername, 238 | [String] $sqlPassword, 239 | [String] $connectionString, 240 | [String] $publishProfile, 241 | [String] $additionalArguments, 242 | [switch] $isOutputSecure) 243 | 244 | $ErrorActionPreference = 'Stop' 245 | $SqlPackageOptions = @{ 246 | SourceFile = "/SourceFile:"; 247 | Action = "/Action:"; 248 | TargetServerName = "/TargetServerName:"; 249 | TargetDatabaseName = "/TargetDatabaseName:"; 250 | TargetUser = "/TargetUser:"; 251 | TargetPassword = "/TargetPassword:"; 252 | TargetConnectionString = "/TargetConnectionString:"; 253 | Profile = "/Profile:"; 254 | } 255 | 256 | $dacpacFileExtension = ".dacpac" 257 | if ([System.IO.Path]::GetExtension($dacpacFile) -ne $dacpacFileExtension) { 258 | throw (Get-VstsLocString -Key "Invalid Dacpac file '{0}' provided" -ArgumentList $dacpacFile) 259 | } 260 | 261 | $sqlPackageArguments = @($SqlPackageOptions.SourceFile + "`"$dacpacFile`"") 262 | $sqlPackageArguments += @($SqlPackageOptions.Action + "Publish") 263 | $sqlPackageArguments += @($SqlPackageOptions.TargetServerName + "`"$serverName`"") 264 | if ($databaseName) { 265 | $sqlPackageArguments += @($SqlPackageOptions.TargetDatabaseName + "`"$databaseName`"") 266 | } 267 | 268 | if ($sqlUsername) { 269 | $sqlPackageArguments += @($SqlPackageOptions.TargetUser + "`"$sqlUsername`"") 270 | if (-not($sqlPassword)) { 271 | throw (Get-VstsLocString -Key "No password specified for the SQL User: '{0}'" -ArgumentList $sqlUserName) 272 | } 273 | 274 | if ($isOutputSecure) { 275 | $sqlPassword = "********" 276 | } 277 | else { 278 | $sqlPassword = $sqlPassword.Replace('"', '\"') 279 | } 280 | 281 | $sqlPackageArguments += @($SqlPackageOptions.TargetPassword + "`"$sqlPassword`"") 282 | } 283 | 284 | if ($publishProfile) { 285 | if([System.IO.Path]::GetExtension($publishProfile) -ne ".xml") { 286 | throw (Get-VstsLocString -Key "Invalid Publish Profile '{0}' provided" -ArgumentList $publishProfile) 287 | } 288 | 289 | $sqlPackageArguments += @($SqlPackageOptions.Profile + "`"$publishProfile`"") 290 | } 291 | 292 | $sqlPackageArguments += @("$additionalArguments") 293 | $scriptArgument = ($sqlPackageArguments -join " ") 294 | 295 | return $scriptArgument 296 | } 297 | 298 | function Initialize-Sqlps { 299 | [CmdletBinding()] 300 | param() 301 | 302 | Trace-VstsEnteringInvocation $MyInvocation 303 | 304 | try { 305 | # pushd and popd to avoid import from changing the current directory (ref: http://stackoverflow.com/questions/12915299/sql-server-2012-sqlps-module-changing-current-location-automatically) 306 | # 3>&1 puts warning stream to standard output stream (see https://connect.microsoft.com/PowerShell/feedback/details/297055/capture-warning-verbose-debug-and-host-output-via-alternate-streams) 307 | # out-null blocks that output, so we don't see the annoying warnings described here: https://www.codykonior.com/2015/05/30/whats-wrong-with-sqlps/ 308 | Push-Location 309 | 310 | $sqlModule = Get-Module -ListAvailable | where -Property Name -eq SqlServer 311 | $sqlps = Get-Module -ListAvailable | where -Property Name -eq sqlps 312 | 313 | if ($sqlps) { 314 | Import-Module -Name sqlps -Global -PassThru -Cmdlet Invoke-Sqlcmd 3>&1 | Out-Null 315 | Write-VstsTaskVerbose -Message "SQLPS Module Imported" 316 | } else { 317 | if(!$sqlModule) { 318 | Install-module -Name SqlServer -Scope CurrentUser -Force 319 | } 320 | 321 | Import-Module -Name SqlServer -Global -PassThru -Cmdlet Invoke-Sqlcmd 3>&1 | Out-Null 322 | Write-VstsTaskVerbose -Message "SqlServer Module Imported" 323 | } 324 | 325 | Pop-Location 326 | } 327 | finally { 328 | Trace-VstsLeavingInvocation $MyInvocation 329 | } 330 | } 331 | 332 | function Get-SqlServerFriendlyName { 333 | param([String] [Parameter(Mandatory = $true)] $serverName) 334 | 335 | $serverName = $serverName.ToLower() 336 | if (-not $serverName.Contains(".database.windows.net")){ 337 | Write-VstsTaskWarning -Message "Bad task configuration: the ServerName parameter should be given with an Azure SQL Server name, like FabrikamSQL.database.windows.net,1433 or FabrikamSQL.database.windows.net" 338 | } 339 | 340 | $serverFriendlyName = $serverName.split(".")[0] 341 | Write-VstsTaskVerbose -Message "Server friendly name is $serverFriendlyName" 342 | 343 | return $serverFriendlyName 344 | } 345 | 346 | Export-ModuleMember -Function Initialize-Azure 347 | Export-ModuleMember -Function Get-AgentIPAddress 348 | Export-ModuleMember -Function Add-AzureSqlDatabaseServerFirewallRule 349 | Export-ModuleMember -Function Remove-AzureSqlDatabaseServerFirewallRule 350 | Export-ModuleMember -Function Get-AzureSqlDatabaseServerResourceGroupName 351 | Export-ModuleMember -Function Get-WebAppResourceGroupName 352 | Export-ModuleMember -Function Get-SqlPackagePath 353 | Export-ModuleMember -Function Get-DacpacVersions 354 | Export-ModuleMember -Function Send-ExecuteCommand 355 | Export-ModuleMember -Function Get-SqlPackageCommandArguments 356 | Export-ModuleMember -Function Initialize-Sqlps 357 | Export-ModuleMember -Function Get-SqlServerFriendlyName 358 | -------------------------------------------------------------------------------- /Common/PowerShell3/VstsAzureHelpers/module.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages": { 3 | "AZ_AzureRMProfileModuleNotFound": "Module 'AzureRM.Profile' not found. The 'AzureRM' module may not be fully installed. Running the following PowerShell commands from an elevated session may resolve the issue: Import-Module AzureRM ; Install-AzureRM", 4 | "AZ_CertificateAuthNotSupported": "Certificate based authentication is not supported. Azure PowerShell module is not found.", 5 | "AZ_CredentialsError": "There was an error with the Azure credentials used for the deployment.", 6 | "AZ_ModuleNotFound": "Neither the Azure module nor the AzureRM module was found. If the module was recently installed, retry after restarting the VSTS task agent.", 7 | "AZ_RequiresMinVersion0": "The required minimum version ({0}) of the Azure PowerShell module is not installed.", 8 | "AZ_ServicePrincipalError": "There was an error with the service principal used for the deployment.", 9 | "AZ_ServicePrincipalAuthNotSupportedAzureVersion0": "Service principal authentication is not supported in version '{0}' of the Azure module.", 10 | "AZ_UnsupportedAuthScheme0": "Unsupported authentication scheme '{0}' for Azure endpoint.", 11 | "AZ_CannotRetrieveExternalIp0": "Cannot parse external IP returned from service: '{0}'.", 12 | "AZ_InvalidIpAddress": "IPAddress mentioned is not a valid IPv4 address.", 13 | "AZ_ResourceNotFound0": "Azure Resource: '{0}' not found." 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Extension/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © `2016` `Geek Learning, Adrien Siffermann, Cyprien Autexier` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /Extension/Screenshots/Add-Tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeklearningio/gl-vsts-tasks-azure/5da85062a3925111f3b87469220fe04ed3e3956c/Extension/Screenshots/Add-Tasks.png -------------------------------------------------------------------------------- /Extension/Screenshots/TestBuild.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeklearningio/gl-vsts-tasks-azure/5da85062a3925111f3b87469220fe04ed3e3956c/Extension/Screenshots/TestBuild.png -------------------------------------------------------------------------------- /Extension/extension-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeklearningio/gl-vsts-tasks-azure/5da85062a3925111f3b87469220fe04ed3e3956c/Extension/extension-icon.png -------------------------------------------------------------------------------- /Extension/overview.md: -------------------------------------------------------------------------------- 1 | # Build and release tasks for Microsoft Azure 2 | 3 | Visual Studio Team Services Build and Release Management extensions that help you to build and publish your applications on Microsoft Azure. 4 | 5 | [Learn more](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki) about this extension on the wiki! 6 | 7 | > Note that we new agents features hence TFS 2015 is not supported. 8 | 9 | ## Tasks included 10 | 11 | * **[Azure Web App Slots Swap](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-Web-App-Slots-Swap)**: Swap two deployment slots of an Azure Web App 12 | * **[Azure Web App Start](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-Web-App-Start)**: Start an Azure Web App, or one of its slot 13 | * **[Azure Web App Stop](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-Web-App-Stop)**: Stop an Azure Web App, or one of its slot 14 | * **[Azure SQL Execute Query](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-SQL-Execute-Query)**: Execute a SQL query on an Azure SQL Database 15 | * **[Azure SQL Database Restore](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-SQL-Database-Restore)**: Restore an Azure SQL Database to another Azure SQL Database on the same server using the latest point-in-time backup 16 | * **[Azure SQL Database Incremental Deployment](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-SQL-Database-Incremental-Deployment)**: Deploy an Azure SQL Database using multiple DACPAC and performing incremental deployments based on current Data-Tier Application version 17 | * **[AzCopy](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/AzCopy)**: Copy blobs across Azure Storage accounts using AzCopy 18 | 19 | ## Steps 20 | 21 | After installing the extension, you can add one (or more) of the tasks to a new or existing [build definition](https://www.visualstudio.com/en-us/docs/build/define/create) or [release definition](https://www.visualstudio.com/en-us/docs/release/author-release-definition/more-release-definition) 22 | 23 | ![add-task](Screenshots/Add-Tasks.png) 24 | 25 | ## Learn more 26 | 27 | The [source](https://github.com/geeklearningio/gl-vsts-tasks-azure) for this extension is on GitHub. Take, fork, and extend. 28 | 29 | ## Release Notes 30 | 31 | > **10-24-2016** 32 | > - Added: AzCopy Tool Task 33 | 34 | > **8-19-2016** 35 | > - Added: Azure SQL Database Incremental Deployment 36 | 37 | > **8-1-2016** 38 | > - Added: Azure SQL Execute Query 39 | 40 | > **7-31-2016** 41 | > - Added: Azure RM Support 42 | -------------------------------------------------------------------------------- /Extension/vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": 1, 3 | "id": "gl-vsts-tasks-azure", 4 | "name": "Microsoft Azure Build and Release Tasks", 5 | "version": "0.0.0", 6 | "publisher": "geeklearningio", 7 | "targets": [ 8 | { 9 | "id": "Microsoft.VisualStudio.Services" 10 | } 11 | ], 12 | "description": "Build and publish your applications on Microsoft Azure using these Build and Release Management tasks.", 13 | "categories": [ 14 | "Build and release" 15 | ], 16 | "icons": { 17 | "default": "extension-icon.png" 18 | }, 19 | "tags": [ 20 | "Azure", 21 | "Resource Manager", 22 | "WebApp", 23 | "Swap", 24 | "Start", 25 | "Stop", 26 | "SQL", 27 | "Restore", 28 | "DACPAC" 29 | ], 30 | "screenshots": [ 31 | { 32 | "path": "Screenshots/TestBuild.png" 33 | }, 34 | { 35 | "path": "Screenshots/Add-Tasks.png" 36 | } 37 | ], 38 | "content": { 39 | "details": { 40 | "path": "overview.md" 41 | }, 42 | "license": { 43 | "path": "LICENSE.txt" 44 | } 45 | }, 46 | "links": { 47 | "getstarted": { 48 | "uri": "https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki" 49 | }, 50 | "support": { 51 | "uri": "https://github.com/geeklearningio/gl-vsts-tasks-azure/issues" 52 | }, 53 | "repository": { 54 | "uri": "https://github.com/geeklearningio/gl-vsts-tasks-azure" 55 | }, 56 | "issues": { 57 | "uri": "https://github.com/geeklearningio/gl-vsts-tasks-azure/issues" 58 | } 59 | }, 60 | "branding": { 61 | "color": "#008B8B", 62 | "theme": "dark" 63 | }, 64 | "files": [ 65 | { 66 | "path": "Tasks" 67 | }, 68 | { 69 | "path": "Screenshots", 70 | "addressable": true 71 | } 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: ContinuousDeployment 2 | branches: 3 | dev(elop)?(ment)?$: 4 | tag: alpha 5 | features?[/-]: 6 | tag: alpha.{BranchName} 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © `2016` `Geek Learning, Adrien Siffermann, Cyprien Autexier` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Icon](https://github.com/geeklearningio/gl-vsts-tasks-azure/blob/master/Extension/extension-icon.png) 2 | 3 | # Microsoft Azure Build and Release Tasks 4 | 5 | [![Build status](https://geeklearning.visualstudio.com/gl-github/_apis/build/status/Azure%20Pipelines%20Tasks/gl-vsts-tasks-azure)](https://geeklearning.visualstudio.com/gl-github/_build/latest?definitionId=37) 6 | 7 | Visual Studio Team Services Build and Release Management extensions that help you to build and publish your applications on Microsoft Azure. 8 | 9 | [Learn more](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki) about this extension on the wiki! 10 | 11 | ## Tasks included 12 | 13 | * **[Azure Web App Slots Swap](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-Web-App-Slots-Swap)**: Swap two deployment slots of an Azure Web App 14 | * **[Azure Web App Start](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-Web-App-Start)**: Start an Azure Web App, or one of its slot 15 | * **[Azure Web App Stop](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-Web-App-Stop)**: Stop an Azure Web App, or one of its slot 16 | * **[Azure SQL Execute Query](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-SQL-Execute-Query)**: Execute a SQL query on an Azure SQL Database 17 | * **[Azure SQL Database Restore](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-SQL-Database-Restore)**: Restore an Azure SQL Database to another Azure SQL Database on the same server using the latest point-in-time backup 18 | * **[Azure SQL Database Incremental Deployment](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-SQL-Database-Incremental-Deployment)**: Deploy an Azure SQL Database using multiple DACPAC and performing incremental deployments based on current Data-Tier Application version 19 | * **[Azure Copy Files](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/AzCopy)**: Copy blobs across Azure Storage accounts using AzCopy 20 | 21 | ## To contribute 22 | 23 | 1. Globally install typescript and tfx-cli (to package VSTS extensions): `npm install -g typescript tfx-cli` 24 | 2. From the root of the repo run `npm install`. This will pull down the necessary modules for the different tasks and for the build tools. 25 | 3. Run `npm run build` to compile the build tasks. 26 | 4. Run `npm run package -- --version ` to create the .vsix extension packages (supports multiple environments) that includes the build tasks. 27 | 28 | ## Release Notes 29 | 30 | > **10-24-2016** 31 | > - Added: AzCopy Tool Task 32 | 33 | > **8-19-2016** 34 | > - Added: Azure SQL Database Incremental Deployment 35 | 36 | > **8-1-2016** 37 | > - Added: Azure SQL Execute Query 38 | > - New build tools for all GL tasks 39 | 40 | > **7-31-2016** 41 | > - Added: Azure RM Support 42 | 43 | ## Contributors 44 | 45 | This extension was created by [Geek Learning](http://geeklearning.io/), with help from the community. 46 | 47 | ## Attributions 48 | 49 | * [AzureWebPowerShellDeployment icon from the VSTS Tasks project](https://github.com/Microsoft/vsts-tasks) 50 | * [SqlAzureDacpacDeployment icon from the VSTS Tasks project](https://github.com/Microsoft/vsts-tasks) 51 | * [Lightning by Carla Dias from the Noun Project](https://thenounproject.com/search/?q=lightning&i=542899) 52 | * [Restore by Arthur Shlain from the Noun Project](https://thenounproject.com/search/?q=restore&i=52760) 53 | * [Trade by Michelle Fosse from the Noun Project](https://thenounproject.com/search/?q=swap&i=560173) 54 | * [Stop by NAS from the Noun Project](https://thenounproject.com/search/?q=stop&i=55668) 55 | * [Play by NAS from the Noun Project](https://thenounproject.com/search/?q=play&i=55667) 56 | * [Checkmarks by Matt Saling from the Noun Project](https://thenounproject.com/search/?q=Checkmarks&i=202337) 57 | -------------------------------------------------------------------------------- /Tasks/AzCopy/azCopy.ts: -------------------------------------------------------------------------------- 1 | import { StorageManagementClient } from "azure-arm-storage"; 2 | import fs = require("fs-extra"); 3 | import path = require("path"); 4 | import q = require("q"); 5 | import * as tl from "azure-pipelines-task-lib/task"; 6 | import XRegExp = require("xregexp"); 7 | import azureEndpointConnection = require("./azureEndpointConnection"); 8 | 9 | const recursive: boolean = tl.getBoolInput("Recursive"); 10 | const pattern: string = tl.getInput("Pattern"); 11 | const excludeNewer: boolean = tl.getBoolInput("ExcludeNewer"); 12 | const excludeOlder: boolean = tl.getBoolInput("ExcludeOlder"); 13 | const sourceKind: string = tl.getInput("SourceKind"); 14 | const sourcePath: string = tl.getInput("SourcePath"); 15 | const sourceConnectedServiceName: string = tl.getInput( 16 | "SourceConnectedServiceName" 17 | ); 18 | const sourceAccount: string = tl.getInput("SourceAccount"); 19 | const sourceObject: string = tl.getInput("SourceObject"); 20 | const destinationKind: string = tl.getInput("DestinationKind"); 21 | const destinationPath: string = tl.getInput("DestinationPath"); 22 | const destinationConnectedServiceName: string = tl.getInput( 23 | "DestinationConnectedServiceName" 24 | ); 25 | const destinationAccount: string = tl.getInput("DestinationAccount"); 26 | const destinationObject: string = tl.getInput("DestinationObject"); 27 | 28 | const programFiles: string = tl.getVariable("ProgramFiles(x86)"); 29 | const additionalArguments: string = tl.getVariable("Arguments"); 30 | 31 | const azCopyknownLocations: string[] = [ 32 | path.join( 33 | tl.getVariable("Agent.HomeDirectory"), 34 | "externals/azcopy/azcopy.exe" 35 | ), 36 | path.join(__dirname, "../../azcopy.exe"), 37 | path.join( 38 | programFiles ? programFiles : "C:\\ProgramFiles(x86)", 39 | "Microsoft SDKs/Azure/AzCopy/azcopy.exe" 40 | ), 41 | ]; 42 | 43 | const accountIdRegex: RegExp = XRegExp( 44 | "/subscriptions/(?.*?)" + 45 | "/resourceGroups/(?.*?)" + 46 | "/providers/Microsoft.Storage" + 47 | "/storageAccounts/(?.*?)" 48 | ); 49 | 50 | function getConnectedServiceCredentials( 51 | connectedService: string 52 | ): q.Promise { 53 | const endpointAuth: tl.EndpointAuthorization = tl.getEndpointAuthorization( 54 | connectedService, 55 | true 56 | ); 57 | const servicePrincipalId: string = endpointAuth.parameters.serviceprincipalid; 58 | const servicePrincipalKey: string = 59 | endpointAuth.parameters.serviceprincipalkey; 60 | const tenantId: string = endpointAuth.parameters.tenantid; 61 | const subscriptionName: string = tl.getEndpointDataParameter( 62 | connectedService, 63 | "SubscriptionName", 64 | true 65 | ); 66 | const subscriptionId: string = tl.getEndpointDataParameter( 67 | connectedService, 68 | "SubscriptionId", 69 | true 70 | ); 71 | 72 | return azureEndpointConnection.getConnectedServiceCredentials( 73 | connectedService, 74 | servicePrincipalId, 75 | servicePrincipalKey, 76 | tenantId, 77 | subscriptionName, 78 | subscriptionId 79 | ); 80 | } 81 | 82 | function getStorageAccount( 83 | credentials: ICachedSubscriptionCredentals, 84 | accountName: string 85 | ): q.Promise { 86 | const deferal: q.Deferred = q.defer(); 87 | 88 | const client: StorageManagementClient = new StorageManagementClient( 89 | credentials.creds, 90 | credentials.id 91 | ); 92 | client.storageAccounts.list((listError: any, result: any): void => { 93 | if (listError) { 94 | deferal.reject(listError); 95 | } 96 | 97 | const account: any = result.filter((x: any) => x.name === accountName)[0]; 98 | const parsedAccountId: any = XRegExp.exec( 99 | account.id, 100 | accountIdRegex 101 | ) as any; 102 | const resourceGroupName: any = parsedAccountId.resourceGroupName; 103 | 104 | client.storageAccounts.getProperties( 105 | resourceGroupName, 106 | accountName, 107 | (getPropertiesError: any, properties: any): void => { 108 | if (getPropertiesError) { 109 | deferal.reject(getPropertiesError); 110 | } else { 111 | client.storageAccounts.listKeys( 112 | resourceGroupName, 113 | accountName, 114 | (listKeysError: any, keys: any): void => { 115 | if (listKeysError) { 116 | deferal.reject(listKeysError); 117 | } else { 118 | deferal.resolve({ 119 | blobEndpoint: properties.primaryEndpoints.blob, 120 | key: keys.keys[0].value, 121 | resourceGroupName, 122 | tableEndpoint: properties.primaryEndpoints.table, 123 | }); 124 | } 125 | } 126 | ); 127 | } 128 | } 129 | ); 130 | }); 131 | 132 | return deferal.promise; 133 | } 134 | 135 | (async function execute(): Promise { 136 | try { 137 | const azCopy: string[] = azCopyknownLocations.filter((x) => 138 | fs.existsSync(x) 139 | ); 140 | if (azCopy.length) { 141 | tl.debug("AzCopy utility found at path : " + azCopy[0]); 142 | 143 | const toolRunner = tl.tool(azCopy[0]); 144 | 145 | if (sourceKind === "Storage") { 146 | tl.debug("retrieving source account details"); 147 | const sourceCredentials: any = await getConnectedServiceCredentials( 148 | sourceConnectedServiceName 149 | ); 150 | const sourceStorageAccount: IStorageAccount = await getStorageAccount( 151 | sourceCredentials, 152 | sourceAccount 153 | ); 154 | tl.debug(sourceStorageAccount.blobEndpoint + sourceObject); 155 | toolRunner.arg( 156 | "/Source:" + sourceStorageAccount.blobEndpoint + sourceObject 157 | ); 158 | toolRunner.arg("/SourceKey:" + sourceStorageAccount.key); 159 | } else { 160 | toolRunner.arg("/Source:" + sourcePath); 161 | } 162 | 163 | if (destinationKind === "Storage") { 164 | const destCredentials: any = await getConnectedServiceCredentials( 165 | destinationConnectedServiceName 166 | ); 167 | const destStorageAccount: IStorageAccount = await getStorageAccount( 168 | destCredentials, 169 | destinationAccount 170 | ); 171 | tl.debug(destStorageAccount.blobEndpoint + destinationObject); 172 | toolRunner.arg( 173 | "/Dest:" + destStorageAccount.blobEndpoint + destinationObject 174 | ); 175 | toolRunner.arg("/DestKey:" + destStorageAccount.key); 176 | } else { 177 | toolRunner.arg("/Dest:" + destinationPath); 178 | } 179 | 180 | if (recursive) { 181 | toolRunner.arg("/S"); 182 | } 183 | 184 | if (pattern) { 185 | toolRunner.arg("/Pattern:" + pattern); 186 | } 187 | 188 | if (excludeNewer) { 189 | toolRunner.arg("/XN"); 190 | } 191 | 192 | if (excludeOlder) { 193 | toolRunner.arg("/XO"); 194 | } 195 | 196 | if (additionalArguments) { 197 | toolRunner.line(additionalArguments); 198 | } 199 | 200 | toolRunner.arg("/Y"); 201 | 202 | const result: number = await toolRunner.exec(); 203 | if (result) { 204 | tl.setResult(tl.TaskResult.Failed, "An error occured during azcopy"); 205 | } else { 206 | tl.setResult(tl.TaskResult.Succeeded, "Files copied successfully"); 207 | } 208 | } else { 209 | tl.setResult( 210 | tl.TaskResult.Failed, 211 | "AzCopy utility was not found, please refer to documentation for installation instructions." 212 | ); 213 | } 214 | } catch (err) { 215 | tl.debug(err.stack); 216 | tl.setResult(tl.TaskResult.Failed, err); 217 | } 218 | })(); 219 | 220 | interface ICachedSubscriptionCredentals { 221 | name: string; 222 | id: string; 223 | creds: any; 224 | } 225 | 226 | interface IStorageAccount { 227 | resourceGroupName: string; 228 | blobEndpoint: string; 229 | tableEndpoint: string; 230 | key: string; 231 | } 232 | -------------------------------------------------------------------------------- /Tasks/AzCopy/azureEndpointConnection.ts: -------------------------------------------------------------------------------- 1 | import msRestAzure = require("ms-rest-azure"); 2 | import q = require("q"); 3 | 4 | const connectedServiceCredentialsCache: { 5 | [key: string]: ICachedSubscriptionCredentals; 6 | } = {}; 7 | 8 | export function getConnectedServiceCredentials( 9 | connectedService: string, 10 | servicePrincipalId: string, 11 | servicePrincipalKey: string, 12 | tenantId: string, 13 | subscriptionName: string, 14 | subscriptionId: string 15 | ): q.Promise { 16 | if (connectedServiceCredentialsCache[connectedService]) { 17 | return q.when(connectedServiceCredentialsCache[connectedService]); 18 | } else { 19 | const deferal = q.defer(); 20 | msRestAzure.loginWithServicePrincipalSecret( 21 | servicePrincipalId, 22 | servicePrincipalKey, 23 | tenantId, 24 | {}, 25 | (err: any, credentials: any) => { 26 | if (err) { 27 | deferal.reject(err); 28 | return; 29 | } 30 | 31 | const result = (connectedServiceCredentialsCache[connectedService] = { 32 | creds: credentials, 33 | id: subscriptionId, 34 | name: subscriptionName, 35 | }); 36 | 37 | deferal.resolve(result); 38 | } 39 | ); 40 | 41 | return deferal.promise; 42 | } 43 | } 44 | 45 | export interface ICachedSubscriptionCredentals { 46 | name: string; 47 | id: string; 48 | creds: any; 49 | } 50 | -------------------------------------------------------------------------------- /Tasks/AzCopy/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeklearningio/gl-vsts-tasks-azure/5da85062a3925111f3b87469220fe04ed3e3956c/Tasks/AzCopy/icon.png -------------------------------------------------------------------------------- /Tasks/AzCopy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "az-copy", 3 | "private": true, 4 | "version": "0.1.0", 5 | "dependencies": { 6 | "azure-devops-node-api": "^10.1.1", 7 | "azure-pipelines-task-lib": "^2.9.6", 8 | "azure-arm-storage": "^8.1.0", 9 | "fs-extra": "9.0.1", 10 | "ms-rest-azure": "^3.0.0", 11 | "q": "1.5.1", 12 | "xregexp": "4.3.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tasks/AzCopy/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "cf964a85-dfeb-4313-aa53-dad0d5e320e3", 3 | "name": "AzCopy", 4 | "friendlyName": "Azure File Copy Extended", 5 | "description": "Copy files to or from Azure Storage", 6 | "helpMarkDown": "[More Information](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/AzCopy) (Version #{Version}#)", 7 | "category": "Deploy", 8 | "visibility": ["Build", "Release"], 9 | "author": "Geek Learning", 10 | "version": { 11 | "Major": 0, 12 | "Minor": 0, 13 | "Patch": 0 14 | }, 15 | "demands": ["cmd"], 16 | "minimumAgentVersion": "1.91.0", 17 | "instanceNameFormat": "Copy files from or to Azure Storage", 18 | "groups": [ 19 | { 20 | "name": "source", 21 | "displayName": "Source", 22 | "isExpanded": true 23 | }, 24 | { 25 | "name": "destination", 26 | "displayName": "Destination", 27 | "isExpanded": true 28 | }, 29 | { 30 | "name": "containerCopyOptions", 31 | "displayName": "Container Copy Options", 32 | "isExpanded": true 33 | }, 34 | { 35 | "name": "advanced", 36 | "displayName": "Additional Options", 37 | "isExpanded": true 38 | } 39 | ], 40 | "inputs": [ 41 | { 42 | "name": "SourceKind", 43 | "type": "radio", 44 | "label": "Source Kind", 45 | "options": { 46 | "FileSystem": "File System", 47 | "Storage": "Azure Storage" 48 | }, 49 | "defaultValue": "Storage", 50 | "required": true, 51 | "groupName": "source", 52 | "helpMarkDown": "" 53 | }, 54 | { 55 | "name": "SourcePath", 56 | "type": "filePath", 57 | "label": "Source Directory", 58 | "defaultValue": "", 59 | "visibleRule": "SourceKind = FileSystem", 60 | "required": true, 61 | "groupName": "source", 62 | "helpMarkDown": "Source directory" 63 | }, 64 | { 65 | "name": "SourceConnectedServiceName", 66 | "type": "connectedService:AzureRM", 67 | "label": "Azure RM Subscription", 68 | "defaultValue": "", 69 | "required": true, 70 | "groupName": "source", 71 | "visibleRule": "SourceKind = Storage", 72 | "helpMarkDown": "Source Azure Resource Manager subscription" 73 | }, 74 | { 75 | "name": "SourceAccount", 76 | "type": "pickList", 77 | "label": "Source Account Name", 78 | "defaultValue": "", 79 | "required": true, 80 | "properties": { 81 | "EditableOptions": "True" 82 | }, 83 | "groupName": "source", 84 | "visibleRule": "SourceKind = Storage", 85 | "helpMarkDown": "Enter or Select the name of an existing AzureRM Storage Account" 86 | }, 87 | { 88 | "name": "SourceObject", 89 | "type": "string", 90 | "label": "Source Container", 91 | "defaultValue": "", 92 | "required": true, 93 | "visibleRule": "SourceKind = Storage", 94 | "groupName": "source", 95 | "helpMarkDown": "The name of the container to copy." 96 | }, 97 | { 98 | "name": "DestinationKind", 99 | "type": "radio", 100 | "label": "Destination Kind", 101 | "options": { 102 | "FileSystem": "File System", 103 | "Storage": "Azure Storage" 104 | }, 105 | "defaultValue": "Storage", 106 | "required": true, 107 | "groupName": "destination", 108 | "helpMarkDown": "" 109 | }, 110 | { 111 | "name": "DestinationPath", 112 | "type": "filePath", 113 | "label": "Destintion path", 114 | "defaultValue": "", 115 | "required": true, 116 | "groupName": "destination", 117 | "visibleRule": "DestinationKind = FileSystem", 118 | "helpMarkDown": "The name of the artifact to create." 119 | }, 120 | { 121 | "name": "DestinationConnectedServiceName", 122 | "type": "connectedService:AzureRM", 123 | "label": "Azure RM Subscription", 124 | "defaultValue": "", 125 | "required": true, 126 | "groupName": "destination", 127 | "visibleRule": "DestinationKind = Storage", 128 | "helpMarkDown": "Destination Azure Resource Manager subscription" 129 | }, 130 | { 131 | "name": "DestinationAccount", 132 | "type": "pickList", 133 | "label": "Destination Account Name", 134 | "defaultValue": "", 135 | "required": true, 136 | "properties": { 137 | "EditableOptions": "True" 138 | }, 139 | "groupName": "destination", 140 | "visibleRule": "DestinationKind = Storage", 141 | "helpMarkDown": "Enter or Select the name of an existing AzureRM Storage Account" 142 | }, 143 | { 144 | "name": "DestinationObject", 145 | "type": "string", 146 | "label": "Destination Container", 147 | "defaultValue": "", 148 | "required": true, 149 | "groupName": "destination", 150 | "visibleRule": "DestinationKind = Storage", 151 | "helpMarkDown": "Destination container" 152 | }, 153 | { 154 | "name": "Recursive", 155 | "type": "boolean", 156 | "label": "Recursive copy", 157 | "groupName": "containerCopyOptions", 158 | "helpMarkDown": "Copy files recursively." 159 | }, 160 | { 161 | "name": "Pattern", 162 | "type": "string", 163 | "label": "Pattern", 164 | "defaultValue": "", 165 | "required": false, 166 | "groupName": "containerCopyOptions", 167 | "helpMarkDown": "Specifies a file pattern that indicates which files to copy." 168 | }, 169 | { 170 | "name": "ExcludeNewer", 171 | "type": "boolean", 172 | "label": "Exclude Newer", 173 | "required": false, 174 | "groupName": "containerCopyOptions", 175 | "helpMarkDown": "Excludes a newer source resource. The resource will not be copied if the source is the same or newer than destination." 176 | }, 177 | { 178 | "name": "ExcludeOlder", 179 | "type": "boolean", 180 | "label": "Exclude Older", 181 | "required": false, 182 | "groupName": "containerCopyOptions", 183 | "helpMarkDown": "Excludes an older source resource. The resource will not be copied if the source resource is the same or older than destination." 184 | }, 185 | { 186 | "name": "Arguments", 187 | "type": "string", 188 | "label": "Additional Arguments", 189 | "required": false, 190 | "groupName": "advanced", 191 | "helpMarkDown": "Appends additional arguments to the generated AzCopy command" 192 | } 193 | ], 194 | "dataSourceBindings": [ 195 | { 196 | "target": "SourceAccount", 197 | "endpointId": "$(SourceConnectedServiceName)", 198 | "dataSourceName": "AzureStorageAccountRM" 199 | }, 200 | { 201 | "target": "DestinationAccount", 202 | "endpointId": "$(DestinationConnectedServiceName)", 203 | "dataSourceName": "AzureStorageAccountRM" 204 | } 205 | ], 206 | "execution": { 207 | "Node10": { 208 | "target": "azCopy.js", 209 | "argumentFormat": "" 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /Tasks/ExecuteSql/ExecuteSql.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param() 3 | 4 | Trace-VstsEnteringInvocation $MyInvocation 5 | 6 | try { 7 | $ScriptType = Get-VstsInput -Name ScriptType -Require 8 | $ScriptPath = Get-VstsInput -Name ScriptPath 9 | $PredefinedScript = Get-VstsInput -Name PredefinedScript 10 | $Variables = Get-VstsInput -Name Variables 11 | $InlineScript = Get-VstsInput -Name InlineScript 12 | $ServerName = Get-VstsInput -Name ServerName -Require 13 | $DatabaseName = Get-VstsInput -Name DatabaseName -Require 14 | $SqlUsername = Get-VstsInput -Name SqlUsername -Require 15 | $SqlPassword = Get-VstsInput -Name SqlPassword -Require 16 | $QueryTimeout = Get-VstsInput -Name QueryTimeout -Require 17 | $IpDetectionMethod = Get-VstsInput -Name IpDetectionMethod -Require 18 | $StartIpAddress = Get-VstsInput -Name StartIpAddress 19 | $EndIpAddress = Get-VstsInput -Name EndIpAddress 20 | $DeleteFirewallRule = Get-VstsInput -Name DeleteFirewallRule -Require 21 | 22 | Import-Module $PSScriptRoot\ps_modules\VstsAzureHelpers_ 23 | 24 | Initialize-Azure 25 | Initialize-Sqlps 26 | 27 | Import-VstsLocStrings -LiteralPath $PSScriptRoot/Task.json 28 | 29 | $serverFriendlyName = Get-SqlServerFriendlyName -serverName $ServerName 30 | 31 | $ipAddress = Get-AgentIPAddress -startIPAddress $StartIpAddress -endIPAddress $EndIpAddress -ipDetectionMethod $IpDetectionMethod 32 | $firewallSettings = Add-AzureSqlDatabaseServerFirewallRule -startIP $ipAddress.StartIPAddress -endIP $ipAddress.EndIPAddress -serverName $serverFriendlyName 33 | 34 | try { 35 | if ($ScriptType -eq "PredefinedScript") { 36 | $ScriptType = "FilePath" 37 | $ScriptPath = "$PSScriptRoot\SqlPredefinedScripts\$PredefinedScript.sql" 38 | } 39 | 40 | $variableParameter = @() 41 | if ($Variables) { 42 | $variableParameter = ($Variables -split '[\r\n]') | ? { $_ } 43 | } 44 | 45 | $workingFolder = Split-Path $ScriptPath 46 | $workingFolderVariable = @("WorkingFolder=$workingFolder") 47 | if ($variableParameter -isnot [system.array]) { 48 | $variableParameter = @($variableParameter) 49 | } 50 | 51 | $variableParameter = $variableParameter + $workingFolderVariable 52 | 53 | if ($ScriptType -eq "FilePath") { 54 | Write-VstsTaskVerbose -Message "[Azure Call] Executing SQL query $ScriptPath on $DatabaseName with variables $variableParameter" 55 | Invoke-Sqlcmd -InputFile "$ScriptPath" -Database $DatabaseName -ServerInstance $ServerName -EncryptConnection -Username $SqlUsername -Password $SqlPassword -Variable $variableParameter -ErrorAction Stop -Verbose -QueryTimeout $QueryTimeout 56 | } 57 | else { 58 | Write-VstsTaskVerbose -Message "[Azure Call] Executing inline SQL query on $DatabaseName with variables $variableParameter" 59 | Invoke-Sqlcmd -Query "$InlineScript" -Database $DatabaseName -ServerInstance $ServerName -EncryptConnection -Username $SqlUsername -Password $SqlPassword -Variable $variableParameter -ErrorAction Stop -Verbose -QueryTimeout $QueryTimeout 60 | } 61 | 62 | Write-VstsTaskVerbose -Message "[Azure Call] SQL query executed on $DatabaseName" 63 | 64 | } 65 | finally { 66 | Remove-AzureSqlDatabaseServerFirewallRule -serverName $serverFriendlyName -firewallRuleName $firewallSettings.RuleName -isFirewallConfigured $firewallSettings.IsConfigured -deleteFireWallRule $DeleteFirewallRule 67 | } 68 | } 69 | finally { 70 | Trace-VstsLeavingInvocation $MyInvocation 71 | } 72 | -------------------------------------------------------------------------------- /Tasks/ExecuteSql/SqlPredefinedScripts/AddRoleMember.sql: -------------------------------------------------------------------------------- 1 | if exists(select * from [sys].[sysusers] where name = '$(Login)') 2 | begin 3 | exec sp_addrolemember N'$(Role)', N'$(Login)' 4 | end 5 | -------------------------------------------------------------------------------- /Tasks/ExecuteSql/SqlPredefinedScripts/CreateDbOwner.sql: -------------------------------------------------------------------------------- 1 | :setvar Role "db_owner" 2 | 3 | :r $(WorkingFolder)\CreateUser.sql 4 | :r $(WorkingFolder)\AddRoleMember.sql -------------------------------------------------------------------------------- /Tasks/ExecuteSql/SqlPredefinedScripts/CreateUser.sql: -------------------------------------------------------------------------------- 1 | if not exists(select * from [sys].[sysusers] where name = '$(Login)') 2 | begin 3 | create user $(Login) 4 | for login $(Login) 5 | with default_schema = dbo 6 | end 7 | -------------------------------------------------------------------------------- /Tasks/ExecuteSql/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeklearningio/gl-vsts-tasks-azure/5da85062a3925111f3b87469220fe04ed3e3956c/Tasks/ExecuteSql/icon.png -------------------------------------------------------------------------------- /Tasks/ExecuteSql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ExecuteSql", 3 | "private": true, 4 | "version": "0.0.0", 5 | "dependencies": { 6 | "azure-pipelines-task-lib": "^2.9.6" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tasks/ExecuteSql/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "A93D571D-F32C-4FE7-A63A-3599DDDD5279", 3 | "name": "ExecuteSql", 4 | "friendlyName": "Azure SQL Execute Query", 5 | "description": "Execute a SQL query on an Azure SQL Database", 6 | "helpMarkDown": "[More Information](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-SQL-Execute-Query) (Version #{Version}#)", 7 | "category": "Deploy", 8 | "visibility": [ 9 | "Build", 10 | "Release" 11 | ], 12 | "author": "Geek Learning", 13 | "version": { 14 | "Major": 0, 15 | "Minor": 0, 16 | "Patch": 0 17 | }, 18 | "demands": [ 19 | "azureps", 20 | "sqlps" 21 | ], 22 | "minimumAgentVersion": "1.103.0", 23 | "instanceNameFormat": "Execute SQL on $(DatabaseName)", 24 | "groups": [ 25 | { 26 | "name": "target", 27 | "displayName": "Target", 28 | "isExpanded": true 29 | }, 30 | { 31 | "name": "firewall", 32 | "displayName": "Firewall", 33 | "isExpanded": false 34 | } 35 | ], 36 | "inputs": [ 37 | { 38 | "name": "ConnectedServiceName", 39 | "type": "connectedService:AzureRM", 40 | "label": "Azure RM Subscription", 41 | "defaultValue": "", 42 | "required": true, 43 | "helpMarkDown": "Azure Resource Manager subscription to target for executing SQL" 44 | }, 45 | { 46 | "name": "ScriptType", 47 | "type": "pickList", 48 | "label": "Type", 49 | "defaultValue": "FilePath", 50 | "required": true, 51 | "helpMarkDown": "Type of the script: File Path, Inline Script or Predefined Script.", 52 | "options": { 53 | "FilePath": "File Path", 54 | "InlineScript": "Inline Script", 55 | "PredefinedScript": "Predefined Script" 56 | } 57 | }, 58 | { 59 | "name": "ScriptPath", 60 | "type": "filePath", 61 | "label": "Script Path", 62 | "defaultValue": "", 63 | "required": true, 64 | "visibleRule": "ScriptType = FilePath", 65 | "helpMarkDown": "Path of the script to execute. Should be fully qualified path or relative to the default working directory." 66 | }, 67 | { 68 | "name": "PredefinedScript", 69 | "type": "pickList", 70 | "label": "Predefined Script", 71 | "defaultValue": "CreateUser", 72 | "required": true, 73 | "visibleRule": "ScriptType = PredefinedScript", 74 | "helpMarkDown": "Predefined script to execute. You can see the required variables for each predefined script on the [wiki](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-SQL-Execute-Query).", 75 | "options": { 76 | "CreateUser": "Create User", 77 | "AddRoleMember": "Add Role Member", 78 | "CreateDbOwner": "Create Database Owner" 79 | } 80 | }, 81 | { 82 | "name": "Variables", 83 | "type": "multiLine", 84 | "label": "Variables", 85 | "defaultValue": "", 86 | "required": false, 87 | "helpMarkDown": "Variables passed to the SQLCMD script with the -v option. Should be formatted like 'Var1=Value1', with one variable per line. It can then be used with the SQLCMD $(Var1) syntax in the script.", 88 | "properties": { 89 | "resizable": "true", 90 | "rows": "2" 91 | } 92 | }, 93 | { 94 | "name": "InlineScript", 95 | "type": "multiLine", 96 | "label": "Inline Script", 97 | "defaultValue": "", 98 | "required": true, 99 | "helpMarkDown": "", 100 | "visibleRule": "ScriptType = InlineScript", 101 | "properties": { 102 | "resizable": "true", 103 | "rows": "10", 104 | "maxLength": "500" 105 | } 106 | }, 107 | { 108 | "name": "ServerName", 109 | "type": "string", 110 | "label": "Azure SQL Server Name", 111 | "required": true, 112 | "groupName": "target", 113 | "defaultValue": "", 114 | "helpMarkDown": "Azure SQL Server name like, FabrikamSQL.database.windows.net,1433 or FabrikamSQL.database.windows.net." 115 | }, 116 | { 117 | "name": "DatabaseName", 118 | "type": "string", 119 | "label": "Database Name", 120 | "required": true, 121 | "groupName": "target", 122 | "defaultValue": "", 123 | "helpMarkDown": "Name of the Azure SQL Database." 124 | }, 125 | { 126 | "name": "SqlUsername", 127 | "type": "string", 128 | "label": "Server Admin Login", 129 | "required": false, 130 | "groupName": "target", 131 | "defaultValue": "", 132 | "helpMarkDown": "Specify the Azure SQL Server administrator login." 133 | }, 134 | { 135 | "name": "SqlPassword", 136 | "type": "string", 137 | "label": "Password", 138 | "required": false, 139 | "groupName": "target", 140 | "defaultValue": "", 141 | "helpMarkDown": "Password for the Azure SQL Server administrator.
It can accept variable defined in Build/Release Definitions as '$(passwordVariable').
You may mark variable type as 'secret' to secure it." 142 | }, 143 | { 144 | "name": "QueryTimeout", 145 | "type": "string", 146 | "label": "Query Timeout", 147 | "required": false, 148 | "groupName": "target", 149 | "defaultValue": "60", 150 | "helpMarkDown": "Number of seconds before the query will time out. Default is 60 seconds." 151 | }, 152 | { 153 | "name": "IpDetectionMethod", 154 | "type": "pickList", 155 | "label": "Specify Firewall Rules Using", 156 | "required": true, 157 | "groupName": "firewall", 158 | "defaultValue": "AutoDetect", 159 | "helpMarkDown": "For the task to run, the IP Address of the automation agent has to be added to the 'Allowed IP Addresses' in the Azure SQL Server's Firewall. Provide the IP Address range of the automation agents, or select to auto-detect in case of hosted automation agent." 160 | }, 161 | { 162 | "name": "StartIpAddress", 163 | "type": "string", 164 | "label": "Start IP Address", 165 | "required": true, 166 | "groupName": "firewall", 167 | "defaultValue": "", 168 | "visibleRule": "IpDetectionMethod = IPAddressRange", 169 | "helpMarkDown": "The starting IP Address of the automation agent machine pool like 196.21.30.50." 170 | }, 171 | { 172 | "name": "EndIpAddress", 173 | "type": "string", 174 | "label": "End IP Address", 175 | "required": true, 176 | "groupName": "firewall", 177 | "defaultValue": "", 178 | "visibleRule": "IpDetectionMethod = IPAddressRange", 179 | "helpMarkDown": "The ending IP Address of the automation agent machine pool like 196.21.30.65." 180 | }, 181 | { 182 | "name": "DeleteFirewallRule", 183 | "type": "boolean", 184 | "label": "Delete Rule After Task Ends", 185 | "required": false, 186 | "groupName": "firewall", 187 | "defaultValue": "true", 188 | "helpMarkDown": "If selected, then after the task ends, the IP Addresses specified here are deleted from the 'Allowed IP Addresses' list of the Azure SQL Server's Firewall." 189 | } 190 | ], 191 | "sourceDefinitions": [ 192 | { 193 | "target": "IpDetectionMethod", 194 | "endpoint": "/_apis/vslabs/ipAddress/ipDetectionMethods", 195 | "selector": "jsonpath:$.value[*]", 196 | "authKey": "tfs:DevTestLabs" 197 | } 198 | ], 199 | "execution": { 200 | "PowerShell3": { 201 | "target": "ExecuteSql.ps1" 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /Tasks/ExecuteSql/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | asap@~2.0.3: 6 | version "2.0.6" 7 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" 8 | integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= 9 | 10 | azure-pipelines-task-lib@^2.9.6: 11 | version "2.9.6" 12 | resolved "https://registry.yarnpkg.com/azure-pipelines-task-lib/-/azure-pipelines-task-lib-2.9.6.tgz#acae923dc070ce0524b37786b7fb195d843d3022" 13 | integrity sha512-KTJuFdMl/r1Y8Snh5lHyV2YOyTu9bKzltMlRlnod8YKLc9GfENDkxwm+z9cHH6NlDln+2YI3G82Ri9XcbhyuZg== 14 | dependencies: 15 | minimatch "3.0.4" 16 | mockery "^1.7.0" 17 | q "^1.1.2" 18 | semver "^5.1.0" 19 | shelljs "^0.3.0" 20 | sync-request "3.0.1" 21 | uuid "^3.0.1" 22 | 23 | balanced-match@^1.0.0: 24 | version "1.0.0" 25 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 26 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 27 | 28 | brace-expansion@^1.1.7: 29 | version "1.1.11" 30 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 31 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 32 | dependencies: 33 | balanced-match "^1.0.0" 34 | concat-map "0.0.1" 35 | 36 | buffer-from@^1.0.0: 37 | version "1.1.1" 38 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 39 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 40 | 41 | caseless@~0.11.0: 42 | version "0.11.0" 43 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" 44 | integrity sha1-cVuW6phBWTzDMGeSP17GDr2k99c= 45 | 46 | concat-map@0.0.1: 47 | version "0.0.1" 48 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 49 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 50 | 51 | concat-stream@^1.4.6, concat-stream@^1.4.7: 52 | version "1.6.2" 53 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" 54 | integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== 55 | dependencies: 56 | buffer-from "^1.0.0" 57 | inherits "^2.0.3" 58 | readable-stream "^2.2.2" 59 | typedarray "^0.0.6" 60 | 61 | core-util-is@~1.0.0: 62 | version "1.0.2" 63 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 64 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 65 | 66 | http-basic@^2.5.1: 67 | version "2.5.1" 68 | resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-2.5.1.tgz#8ce447bdb5b6c577f8a63e3fa78056ec4bb4dbfb" 69 | integrity sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s= 70 | dependencies: 71 | caseless "~0.11.0" 72 | concat-stream "^1.4.6" 73 | http-response-object "^1.0.0" 74 | 75 | http-response-object@^1.0.0, http-response-object@^1.0.1, http-response-object@^1.1.0: 76 | version "1.1.0" 77 | resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-1.1.0.tgz#a7c4e75aae82f3bb4904e4f43f615673b4d518c3" 78 | integrity sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM= 79 | 80 | inherits@^2.0.3, inherits@~2.0.3: 81 | version "2.0.4" 82 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 83 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 84 | 85 | isarray@~1.0.0: 86 | version "1.0.0" 87 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 88 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 89 | 90 | minimatch@3.0.4: 91 | version "3.0.4" 92 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 93 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 94 | dependencies: 95 | brace-expansion "^1.1.7" 96 | 97 | mockery@^1.7.0: 98 | version "1.7.0" 99 | resolved "https://registry.yarnpkg.com/mockery/-/mockery-1.7.0.tgz#f4ede0d8750c1c9727c272ea2c60629e2c9a1c4f" 100 | integrity sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8= 101 | 102 | process-nextick-args@~2.0.0: 103 | version "2.0.1" 104 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 105 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 106 | 107 | promise@^7.1.1: 108 | version "7.3.1" 109 | resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" 110 | integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== 111 | dependencies: 112 | asap "~2.0.3" 113 | 114 | q@^1.1.2: 115 | version "1.5.1" 116 | resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" 117 | integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= 118 | 119 | qs@^6.1.0: 120 | version "6.9.4" 121 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" 122 | integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== 123 | 124 | readable-stream@^2.2.2: 125 | version "2.3.7" 126 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 127 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 128 | dependencies: 129 | core-util-is "~1.0.0" 130 | inherits "~2.0.3" 131 | isarray "~1.0.0" 132 | process-nextick-args "~2.0.0" 133 | safe-buffer "~5.1.1" 134 | string_decoder "~1.1.1" 135 | util-deprecate "~1.0.1" 136 | 137 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 138 | version "5.1.2" 139 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 140 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 141 | 142 | semver@^5.1.0: 143 | version "5.7.1" 144 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 145 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 146 | 147 | shelljs@^0.3.0: 148 | version "0.3.0" 149 | resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1" 150 | integrity sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E= 151 | 152 | string_decoder@~1.1.1: 153 | version "1.1.1" 154 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 155 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 156 | dependencies: 157 | safe-buffer "~5.1.0" 158 | 159 | sync-request@3.0.1: 160 | version "3.0.1" 161 | resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-3.0.1.tgz#caa1235aaf889ba501076a1834c436830a82fb73" 162 | integrity sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M= 163 | dependencies: 164 | concat-stream "^1.4.7" 165 | http-response-object "^1.0.1" 166 | then-request "^2.0.1" 167 | 168 | then-request@^2.0.1: 169 | version "2.2.0" 170 | resolved "https://registry.yarnpkg.com/then-request/-/then-request-2.2.0.tgz#6678b32fa0ca218fe569981bbd8871b594060d81" 171 | integrity sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE= 172 | dependencies: 173 | caseless "~0.11.0" 174 | concat-stream "^1.4.7" 175 | http-basic "^2.5.1" 176 | http-response-object "^1.1.0" 177 | promise "^7.1.1" 178 | qs "^6.1.0" 179 | 180 | typedarray@^0.0.6: 181 | version "0.0.6" 182 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 183 | integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= 184 | 185 | util-deprecate@~1.0.1: 186 | version "1.0.2" 187 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 188 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 189 | 190 | uuid@^3.0.1: 191 | version "3.4.0" 192 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" 193 | integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== 194 | -------------------------------------------------------------------------------- /Tasks/RestoreSqlDatabaseToSqlDatabase/RestoreSqlDatabaseToSqlDatabase.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param() 3 | 4 | Trace-VstsEnteringInvocation $MyInvocation 5 | 6 | try { 7 | # Get inputs. 8 | $ServerName = Get-VstsInput -Name ServerName -Require 9 | $SourceDatabaseName = Get-VstsInput -Name SourceDatabaseName -Require 10 | $TargetDatabaseName = Get-VstsInput -Name TargetDatabaseName -Require 11 | $PointInTimeWindow = Get-VstsInput -Name PointInTimeWindow -Require 12 | 13 | # Initialize Azure. 14 | Import-Module $PSScriptRoot\ps_modules\VstsAzureHelpers_ 15 | Initialize-Azure 16 | 17 | # Import SQL Azure Powershell cmdlets. 18 | Import-Module AzureRM.Sql 19 | 20 | # Import the loc strings. 21 | Import-VstsLocStrings -LiteralPath $PSScriptRoot/Task.json 22 | 23 | $serverFriendlyName = Get-SqlServerFriendlyName -serverName $ServerName 24 | 25 | $resourceGroupName = Get-AzureSqlDatabaseServerResourceGroupName -serverName $serverFriendlyName 26 | 27 | Write-VstsTaskVerbose -Message "[Azure Call] Getting Azure SQL Database details for target $TargetDatabaseName" 28 | $targetDatabase = Get-AzureRmSqlDatabase -ResourceGroupName $resourceGroupName -ServerName $serverFriendlyName -DatabaseName $TargetDatabaseName -ErrorAction SilentlyContinue -Verbose 29 | 30 | if ($targetDatabase) { 31 | Write-VstsTaskVerbose -Message "[Azure Call] Azure SQL Database details got for target $TargetDatabaseName :" 32 | Write-VstsTaskVerbose -Message ($targetDatabase | Format-List | Out-String) 33 | 34 | Write-VstsTaskVerbose -Message "[Azure Call] Azure SQL Database $TargetDatabaseName exists: removing it!" 35 | Remove-AzureRmSqlDatabase -ResourceGroupName $resourceGroupName -ServerName $serverFriendlyName -DatabaseName $TargetDatabaseName -Force -ErrorAction Stop -Verbose 36 | Write-VstsTaskVerbose -Message "[Azure Call] Azure SQL Database $TargetDatabaseName removed" 37 | } 38 | else { 39 | Write-VstsTaskVerbose -Message "[Azure Call] Target Azure SQL Database $TargetDatabaseName does not exists. Continuing..." 40 | } 41 | 42 | Write-VstsTaskVerbose -Message "[Azure Call] Getting Azure SQL Database details for source $SourceDatabaseName" 43 | $sourceDatabase = Get-AzureRmSqlDatabase -ResourceGroupName $resourceGroupName -ServerName $serverFriendlyName -DatabaseName $SourceDatabaseName 44 | Write-VstsTaskVerbose -Message "[Azure Call] Azure SQL Database details got for source $SourceDatabaseName :" 45 | Write-VstsTaskVerbose -Message ($sourceDatabase | Format-List | Out-String) 46 | 47 | $date = (Get-Date).AddMinutes( - ($PointInTimeWindow -as [int])) 48 | 49 | if ([string]::IsNullOrEmpty($sourceDatabase.ElasticPoolName)) { 50 | Write-VstsTaskVerbose -Message "[Azure Call] Restoring Azure SQL Database $SourceDatabaseName to $TargetDatabaseName (Edition $($sourceDatabase.Edition) $($sourceDatabase.CurrentServiceObjectiveName))" 51 | 52 | Restore-AzureRmSqlDatabase -FromPointInTimeBackup -PointInTime $date -ResourceGroupName $sourceDatabase.ResourceGroupName ` 53 | -ServerName $serverFriendlyName -TargetDatabaseName $TargetDatabaseName -ResourceId $sourceDatabase.ResourceID ` 54 | -Edition $sourceDatabase.Edition -ServiceObjectiveName $sourceDatabase.CurrentServiceObjectiveName -ErrorAction Stop -Verbose 55 | 56 | Write-VstsTaskVerbose -Message "[Azure Call] Azure SQL Database $SourceDatabaseName restored to $TargetDatabaseName (Edition $($sourceDatabase.Edition) $($sourceDatabase.CurrentServiceObjectiveName))" 57 | } 58 | else { 59 | Write-VstsTaskVerbose -Message "[Azure Call] Restoring Azure SQL Database $SourceDatabaseName to $TargetDatabaseName (ElasticPool $($sourceDatabase.ElasticPoolName))" 60 | 61 | Restore-AzureRmSqlDatabase -FromPointInTimeBackup -PointInTime $date -ResourceGroupName $sourceDatabase.ResourceGroupName ` 62 | -ServerName $serverFriendlyName -TargetDatabaseName $TargetDatabaseName -ResourceId $sourceDatabase.ResourceID ` 63 | -ElasticPoolName $sourceDatabase.ElasticPoolName -ErrorAction Stop -Verbose 64 | 65 | Write-VstsTaskVerbose -Message "[Azure Call] Azure SQL Database $SourceDatabaseName restored to $TargetDatabaseName (ElasticPool $($sourceDatabase.ElasticPoolName))" 66 | } 67 | 68 | } 69 | finally { 70 | Trace-VstsLeavingInvocation $MyInvocation 71 | } 72 | -------------------------------------------------------------------------------- /Tasks/RestoreSqlDatabaseToSqlDatabase/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeklearningio/gl-vsts-tasks-azure/5da85062a3925111f3b87469220fe04ed3e3956c/Tasks/RestoreSqlDatabaseToSqlDatabase/icon.png -------------------------------------------------------------------------------- /Tasks/RestoreSqlDatabaseToSqlDatabase/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RestoreSqlDatabaseToSqlDatabase", 3 | "private": true, 4 | "version": "0.0.0", 5 | "dependencies": { 6 | "azure-pipelines-task-lib": "^2.9.6" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tasks/RestoreSqlDatabaseToSqlDatabase/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "56EFEADB-7DF6-4DAC-8163-AF8E07298C8B", 3 | "name": "RestoreSqlDatabaseToSqlDatabase", 4 | "friendlyName": "Azure SQL Database Restore", 5 | "description": "Restore an Azure SQL Database to another Azure SQL Database on the same server using the latest point-in-time backup", 6 | "helpMarkDown": "[More Information](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-SQL-Database-Restore) (Version #{Version}#)", 7 | "category": "Deploy", 8 | "visibility": ["Build", "Release"], 9 | "author": "Geek Learning", 10 | "version": { 11 | "Major": 0, 12 | "Minor": 0, 13 | "Patch": 0 14 | }, 15 | "demands": ["azureps"], 16 | "minimumAgentVersion": "1.103.0", 17 | "instanceNameFormat": "Restore $(SourceDatabaseName) on $(TargetDatabaseName)", 18 | "inputs": [ 19 | { 20 | "name": "ConnectedServiceName", 21 | "type": "connectedService:AzureRM", 22 | "label": "Azure RM Subscription", 23 | "defaultValue": "", 24 | "required": true, 25 | "helpMarkDown": "Azure Resource Manager subscription to target for executing SQL" 26 | }, 27 | { 28 | "name": "ServerName", 29 | "type": "string", 30 | "label": "Azure SQL Server Name", 31 | "required": true, 32 | "defaultValue": "", 33 | "helpMarkDown": "Azure SQL Server name like, FabrikamSQL.database.windows.net,1433 or FabrikamSQL.database.windows.net." 34 | }, 35 | { 36 | "name": "SourceDatabaseName", 37 | "type": "string", 38 | "label": "Source Database Name", 39 | "required": true, 40 | "defaultValue": "", 41 | "helpMarkDown": "Name of the source Azure SQL Database from which to restore." 42 | }, 43 | { 44 | "name": "TargetDatabaseName", 45 | "type": "string", 46 | "label": "Target Database Name", 47 | "defaultValue": "", 48 | "required": true, 49 | "helpMarkDown": "Name of the target Azure SQL Database. If exists, will be deleted prior to restore!" 50 | }, 51 | { 52 | "name": "PointInTimeWindow", 53 | "type": "string", 54 | "label": "Point In Time Window", 55 | "defaultValue": "6", 56 | "required": true, 57 | "helpMarkDown": "How many minutes into the past to look for a backup to restore from.", 58 | "validation": { 59 | "expression": "isMatch(value,'^\\d+$','IgnoreCase')", 60 | "message": "Must be an integer." 61 | } 62 | } 63 | ], 64 | "sourceDefinitions": [ 65 | { 66 | "target": "IpDetectionMethod", 67 | "endpoint": "/_apis/vslabs/ipAddress/ipDetectionMethods", 68 | "selector": "jsonpath:$.value[*]", 69 | "authKey": "tfs:DevTestLabs" 70 | } 71 | ], 72 | "execution": { 73 | "PowerShell3": { 74 | "target": "RestoreSqlDatabaseToSqlDatabase.ps1" 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Tasks/RestoreSqlDatabaseToSqlDatabase/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | asap@~2.0.3: 6 | version "2.0.6" 7 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" 8 | integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= 9 | 10 | azure-pipelines-task-lib@^2.9.6: 11 | version "2.9.6" 12 | resolved "https://registry.yarnpkg.com/azure-pipelines-task-lib/-/azure-pipelines-task-lib-2.9.6.tgz#acae923dc070ce0524b37786b7fb195d843d3022" 13 | integrity sha512-KTJuFdMl/r1Y8Snh5lHyV2YOyTu9bKzltMlRlnod8YKLc9GfENDkxwm+z9cHH6NlDln+2YI3G82Ri9XcbhyuZg== 14 | dependencies: 15 | minimatch "3.0.4" 16 | mockery "^1.7.0" 17 | q "^1.1.2" 18 | semver "^5.1.0" 19 | shelljs "^0.3.0" 20 | sync-request "3.0.1" 21 | uuid "^3.0.1" 22 | 23 | balanced-match@^1.0.0: 24 | version "1.0.0" 25 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 26 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 27 | 28 | brace-expansion@^1.1.7: 29 | version "1.1.11" 30 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 31 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 32 | dependencies: 33 | balanced-match "^1.0.0" 34 | concat-map "0.0.1" 35 | 36 | buffer-from@^1.0.0: 37 | version "1.1.1" 38 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 39 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 40 | 41 | caseless@~0.11.0: 42 | version "0.11.0" 43 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" 44 | integrity sha1-cVuW6phBWTzDMGeSP17GDr2k99c= 45 | 46 | concat-map@0.0.1: 47 | version "0.0.1" 48 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 49 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 50 | 51 | concat-stream@^1.4.6, concat-stream@^1.4.7: 52 | version "1.6.2" 53 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" 54 | integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== 55 | dependencies: 56 | buffer-from "^1.0.0" 57 | inherits "^2.0.3" 58 | readable-stream "^2.2.2" 59 | typedarray "^0.0.6" 60 | 61 | core-util-is@~1.0.0: 62 | version "1.0.2" 63 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 64 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 65 | 66 | http-basic@^2.5.1: 67 | version "2.5.1" 68 | resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-2.5.1.tgz#8ce447bdb5b6c577f8a63e3fa78056ec4bb4dbfb" 69 | integrity sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s= 70 | dependencies: 71 | caseless "~0.11.0" 72 | concat-stream "^1.4.6" 73 | http-response-object "^1.0.0" 74 | 75 | http-response-object@^1.0.0, http-response-object@^1.0.1, http-response-object@^1.1.0: 76 | version "1.1.0" 77 | resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-1.1.0.tgz#a7c4e75aae82f3bb4904e4f43f615673b4d518c3" 78 | integrity sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM= 79 | 80 | inherits@^2.0.3, inherits@~2.0.3: 81 | version "2.0.4" 82 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 83 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 84 | 85 | isarray@~1.0.0: 86 | version "1.0.0" 87 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 88 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 89 | 90 | minimatch@3.0.4: 91 | version "3.0.4" 92 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 93 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 94 | dependencies: 95 | brace-expansion "^1.1.7" 96 | 97 | mockery@^1.7.0: 98 | version "1.7.0" 99 | resolved "https://registry.yarnpkg.com/mockery/-/mockery-1.7.0.tgz#f4ede0d8750c1c9727c272ea2c60629e2c9a1c4f" 100 | integrity sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8= 101 | 102 | process-nextick-args@~2.0.0: 103 | version "2.0.1" 104 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 105 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 106 | 107 | promise@^7.1.1: 108 | version "7.3.1" 109 | resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" 110 | integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== 111 | dependencies: 112 | asap "~2.0.3" 113 | 114 | q@^1.1.2: 115 | version "1.5.1" 116 | resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" 117 | integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= 118 | 119 | qs@^6.1.0: 120 | version "6.9.4" 121 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" 122 | integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== 123 | 124 | readable-stream@^2.2.2: 125 | version "2.3.7" 126 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 127 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 128 | dependencies: 129 | core-util-is "~1.0.0" 130 | inherits "~2.0.3" 131 | isarray "~1.0.0" 132 | process-nextick-args "~2.0.0" 133 | safe-buffer "~5.1.1" 134 | string_decoder "~1.1.1" 135 | util-deprecate "~1.0.1" 136 | 137 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 138 | version "5.1.2" 139 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 140 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 141 | 142 | semver@^5.1.0: 143 | version "5.7.1" 144 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 145 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 146 | 147 | shelljs@^0.3.0: 148 | version "0.3.0" 149 | resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1" 150 | integrity sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E= 151 | 152 | string_decoder@~1.1.1: 153 | version "1.1.1" 154 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 155 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 156 | dependencies: 157 | safe-buffer "~5.1.0" 158 | 159 | sync-request@3.0.1: 160 | version "3.0.1" 161 | resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-3.0.1.tgz#caa1235aaf889ba501076a1834c436830a82fb73" 162 | integrity sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M= 163 | dependencies: 164 | concat-stream "^1.4.7" 165 | http-response-object "^1.0.1" 166 | then-request "^2.0.1" 167 | 168 | then-request@^2.0.1: 169 | version "2.2.0" 170 | resolved "https://registry.yarnpkg.com/then-request/-/then-request-2.2.0.tgz#6678b32fa0ca218fe569981bbd8871b594060d81" 171 | integrity sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE= 172 | dependencies: 173 | caseless "~0.11.0" 174 | concat-stream "^1.4.7" 175 | http-basic "^2.5.1" 176 | http-response-object "^1.1.0" 177 | promise "^7.1.1" 178 | qs "^6.1.0" 179 | 180 | typedarray@^0.0.6: 181 | version "0.0.6" 182 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 183 | integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= 184 | 185 | util-deprecate@~1.0.1: 186 | version "1.0.2" 187 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 188 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 189 | 190 | uuid@^3.0.1: 191 | version "3.4.0" 192 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" 193 | integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== 194 | -------------------------------------------------------------------------------- /Tasks/SqlMultiDacpacDeployment/SqlMultiDacpacDeployment.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param() 3 | 4 | Trace-VstsEnteringInvocation $MyInvocation 5 | 6 | try { 7 | $DacpacFiles = Get-VstsInput -Name DacpacFiles -Require 8 | $AdditionalArguments = Get-VstsInput -Name AdditionalArguments 9 | $ServerName = Get-VstsInput -Name ServerName -Require 10 | $DatabaseName = Get-VstsInput -Name DatabaseName -Require 11 | $SqlUsername = Get-VstsInput -Name SqlUsername 12 | $SqlPassword = Get-VstsInput -Name SqlPassword 13 | $PublishProfile = Get-VstsInput -Name PublishProfile 14 | $IpDetectionMethod = Get-VstsInput -Name IpDetectionMethod -Require 15 | $StartIpAddress = Get-VstsInput -Name StartIpAddress 16 | $EndIpAddress = Get-VstsInput -Name EndIpAddress 17 | $DeleteFirewallRule = Get-VstsInput -Name DeleteFirewallRule -Require 18 | 19 | Import-Module $PSScriptRoot\ps_modules\VstsAzureHelpers_ 20 | 21 | Initialize-Azure 22 | Initialize-Sqlps 23 | 24 | Import-VstsLocStrings -LiteralPath $PSScriptRoot/Task.json 25 | 26 | $serverFriendlyName = Get-SqlServerFriendlyName -serverName $ServerName 27 | 28 | $dacpacFilePaths = Find-VstsFiles -LegacyPattern $DacpacFiles -Verbose 29 | 30 | if (!$dacpacFilePaths) { 31 | throw (Get-VstsLocString -Key "No files were found to deploy with search pattern {0}" -ArgumentList $DacpacFiles) 32 | } 33 | 34 | $publishProfilePath = "" 35 | if ([string]::IsNullOrWhitespace($PublishProfile) -eq $false -and $PublishProfile -ne $env:SYSTEM_DEFAULTWORKINGDIRECTORY -and $PublishProfile -ne [String]::Concat($env:SYSTEM_DEFAULTWORKINGDIRECTORY, "\")) { 36 | $publishProfilePath = Find-VstsFiles -LegacyPattern $PublishProfile 37 | if ($publishProfilePath -is [system.array]) { 38 | throw (Get-VstsLocString -Key "Found more than one file to deploy with search pattern {0}. There can be only one." -ArgumentList $PublishProfile) 39 | } 40 | elseif (!$publishProfilePath) { 41 | throw (Get-VstsLocString -Key "No files were found to deploy with search pattern {0}" -ArgumentList $PublishProfile) 42 | } 43 | } 44 | 45 | if ($dacpacFilePaths -isnot [System.Array]) { 46 | $dacpacFilePaths = @($dacpacFilePaths) 47 | } 48 | 49 | $dacFilesWithVersion = [Array](Get-DacpacVersions -dacpacFilePaths $dacpacFilePaths) 50 | 51 | $ipAddress = Get-AgentIPAddress -startIPAddress $StartIpAddress -endIPAddress $EndIpAddress -ipDetectionMethod $IpDetectionMethod 52 | $firewallSettings = Add-AzureSqlDatabaseServerFirewallRule -startIP $ipAddress.StartIPAddress -endIP $ipAddress.EndIPAddress -serverName $serverFriendlyName 53 | 54 | try { 55 | $variableParameter = @("DatabaseName='$DatabaseName'") 56 | Write-VstsTaskVerbose -Message "[SQL Call] Retrieving $DatabaseName DAC Version Number..." 57 | $databaseVersion = [Version]((Invoke-Sqlcmd -InputFile "$PSScriptRoot\SqlScripts\GetDatabaseVersion.sql" -Database "master" -ServerInstance $ServerName -EncryptConnection -Username $SqlUsername -Password $SqlPassword -Variable $variableParameter -ErrorAction Stop -Verbose).DatabaseVersion) 58 | Write-VstsTaskVerbose -Message "[SQL Call] $DatabaseName DAC Version Number retrieved: $databaseVersion" 59 | 60 | $dacFilesToDeploy = $dacFilesWithVersion.GetEnumerator() | Where-Object { $_.Name -gt $databaseVersion } 61 | if ($dacFilesToDeploy.Count -eq 0) { 62 | Write-VstsTaskWarning -Message "Nothing to deploy, the database version ($databaseVersion) is up to date" 63 | } 64 | else { 65 | $sqlPackagePath = Get-SqlPackagePath 66 | 67 | # Always register Data-Tier Application (as this task needs to retrieve later the database version number) 68 | if (-not ($AdditionalArguments -like "*RegisterDataTierApplication*")) { 69 | $AdditionalArguments += " /p:RegisterDataTierApplication=True" 70 | } 71 | 72 | foreach ($dacFileToDeploy in $dacFilesToDeploy) { 73 | Write-Host "Deploying Version: $($dacFileToDeploy.Name)" 74 | 75 | $scriptArguments = Get-SqlPackageCommandArguments -dacpacFile $dacFileToDeploy.Value -serverName $ServerName -databaseName $DatabaseName ` 76 | -sqlUsername $SqlUsername -sqlPassword $SqlPassword -publishProfile $publishProfilePath -additionalArguments $AdditionalArguments 77 | 78 | $scriptArgumentsToBeLogged = Get-SqlPackageCommandArguments -dacpacFile $dacFileToDeploy.Value -serverName $ServerName -databaseName $DatabaseName ` 79 | -sqlUsername $SqlUsername -sqlPassword $SqlPassword -publishProfile $publishProfilePath -additionalArguments $AdditionalArguments -isOutputSecure 80 | 81 | Send-ExecuteCommand -command $sqlPackagePath -arguments $scriptArguments -secureArguments $scriptArgumentsToBeLogged 82 | 83 | Write-Host "Version $($dacFileToDeploy.Name) deployed" 84 | } 85 | } 86 | } 87 | finally { 88 | Remove-AzureSqlDatabaseServerFirewallRule -serverName $serverFriendlyName -firewallRuleName $firewallSettings.RuleName -isFirewallConfigured $firewallSettings.IsConfigured -deleteFireWallRule $DeleteFirewallRule 89 | } 90 | } 91 | finally { 92 | Trace-VstsLeavingInvocation $MyInvocation 93 | } 94 | -------------------------------------------------------------------------------- /Tasks/SqlMultiDacpacDeployment/SqlScripts/GetDatabaseVersion.sql: -------------------------------------------------------------------------------- 1 | declare @databaseVersion nvarchar(50) 2 | 3 | select @databaseVersion = type_version 4 | from master.dbo.sysdac_instances 5 | where instance_name = $(DatabaseName) 6 | 7 | select isnull(@databaseVersion, '0.0.0.0') as DatabaseVersion -------------------------------------------------------------------------------- /Tasks/SqlMultiDacpacDeployment/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeklearningio/gl-vsts-tasks-azure/5da85062a3925111f3b87469220fe04ed3e3956c/Tasks/SqlMultiDacpacDeployment/icon.png -------------------------------------------------------------------------------- /Tasks/SqlMultiDacpacDeployment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SqlMultiDacpacDeployment", 3 | "private": true, 4 | "version": "0.0.0", 5 | "dependencies": { 6 | "azure-pipelines-task-lib": "^2.9.6" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tasks/SqlMultiDacpacDeployment/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "fc7c81bb-92a2-4750-a0c9-d0d88c749ac0", 3 | "name": "SqlMultiDacpacDeployment", 4 | "friendlyName": "Azure SQL Database Incremental Deployment", 5 | "description": "Deploy an Azure SQL Database using multiple DACPAC and performing incremental deployments based on current Data-Tier Application version", 6 | "helpMarkDown": "[More Information](https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki/Azure-SQL-Database-Incremental-Deployment) (Version #{Version}#)", 7 | "category": "Deploy", 8 | "visibility": [ 9 | "Build", 10 | "Release" 11 | ], 12 | "author": "Geek Learning", 13 | "version": { 14 | "Major": 0, 15 | "Minor": 0, 16 | "Patch": 0 17 | }, 18 | "demands": [ 19 | "azureps" 20 | ], 21 | "minimumAgentVersion": "1.103.0", 22 | "instanceNameFormat": "Deploy Azure SQL DACPAC: $(DacpacFiles)", 23 | "groups": [ 24 | { 25 | "name": "target", 26 | "displayName": "Target", 27 | "isExpanded": true 28 | }, 29 | { 30 | "name": "firewall", 31 | "displayName": "Firewall", 32 | "isExpanded": false 33 | } 34 | ], 35 | "inputs": [ 36 | { 37 | "name": "ConnectedServiceName", 38 | "type": "connectedService:AzureRM", 39 | "label": "Azure RM Subscription", 40 | "defaultValue": "", 41 | "required": true, 42 | "helpMarkDown": "Azure Resource Manager subscription to target for executing SQL" 43 | }, 44 | { 45 | "name": "DacpacFiles", 46 | "type": "filePath", 47 | "label": "DACPAC Files", 48 | "required": true, 49 | "defaultValue": "", 50 | "helpMarkDown": "Location of the DACPAC files on the automation agent or on a UNC path accessible to the automation agent like, \\\\\\\\BudgetIT\\Web\\Deploy\\*.dacpac. Predefined system variables like, $(Agent.ReleaseDirectory) can be also used here." 51 | }, 52 | { 53 | "name": "ServerName", 54 | "type": "string", 55 | "label": "Azure SQL Server Name", 56 | "required": true, 57 | "groupName": "target", 58 | "defaultValue": "", 59 | "helpMarkDown": "Azure SQL Server name like, FabrikamSQL.database.windows.net,1433 or FabrikamSQL.database.windows.net." 60 | }, 61 | { 62 | "name": "DatabaseName", 63 | "type": "string", 64 | "label": "Database Name", 65 | "required": true, 66 | "groupName": "target", 67 | "defaultValue": "", 68 | "helpMarkDown": "Name of the Azure SQL Database." 69 | }, 70 | { 71 | "name": "SqlUsername", 72 | "type": "string", 73 | "label": "Server Admin Login", 74 | "required": false, 75 | "groupName": "target", 76 | "defaultValue": "", 77 | "helpMarkDown": "Specify the Azure SQL Server administrator login." 78 | }, 79 | { 80 | "name": "SqlPassword", 81 | "type": "string", 82 | "label": "Password", 83 | "required": false, 84 | "groupName": "target", 85 | "defaultValue": "", 86 | "helpMarkDown": "Password for the Azure SQL Server administrator.
It can accept variable defined in Build/Release Definitions as '$(PasswordVariable').
You may mark variable type as 'secret' to secure it." 87 | }, 88 | { 89 | "name": "PublishProfile", 90 | "type": "filePath", 91 | "label": "Publish Profile", 92 | "required": false, 93 | "groupName": "target", 94 | "defaultValue": "", 95 | "helpMarkDown": "Publish profile provides fine-grained control over Azure SQL Database creation or upgrades. Specify the path to the Publish profile XML file on the automation agent or on a UNC share. Predefined system variables like, $(Agent.BuildDirectory) or $(Agent.ReleaseDirectory) can be also used here." 96 | }, 97 | { 98 | "name": "AdditionalArguments", 99 | "type": "string", 100 | "label": "Additional SqlPackage.exe Arguments", 101 | "required": false, 102 | "groupName": "target", 103 | "defaultValue": "", 104 | "helpMarkDown": "Additional SqlPackage.exe arguments that will be applied when creating or updating the Azure SQL Database like, /p:IgnoreAnsiNulls=True /p:IgnoreComments=True.​" 105 | }, 106 | { 107 | "name": "IpDetectionMethod", 108 | "type": "pickList", 109 | "label": "Specify Firewall Rules Using", 110 | "required": true, 111 | "groupName": "firewall", 112 | "defaultValue": "AutoDetect", 113 | "helpMarkDown": "For the task to run, the IP Address of the automation agent has to be added to the 'Allowed IP Addresses' in the Azure SQL Server's Firewall. Provide the IP Address range of the automation agents, or select to auto-detect in case of hosted automation agent." 114 | }, 115 | { 116 | "name": "StartIpAddress", 117 | "type": "string", 118 | "label": "Start IP Address", 119 | "required": true, 120 | "groupName": "firewall", 121 | "defaultValue": "", 122 | "visibleRule": "IpDetectionMethod = IPAddressRange", 123 | "helpMarkDown": "The starting IP Address of the automation agent machine pool like 196.21.30.50." 124 | }, 125 | { 126 | "name": "EndIpAddress", 127 | "type": "string", 128 | "label": "End IP Address", 129 | "required": true, 130 | "groupName": "firewall", 131 | "defaultValue": "", 132 | "visibleRule": "IpDetectionMethod = IPAddressRange", 133 | "helpMarkDown": "The ending IP Address of the automation agent machine pool like 196.21.30.65." 134 | }, 135 | { 136 | "name": "DeleteFirewallRule", 137 | "type": "boolean", 138 | "label": "Delete Rule After Task Ends", 139 | "required": false, 140 | "groupName": "firewall", 141 | "defaultValue": "true", 142 | "helpMarkDown": "If selected, then after the task ends, the IP Addresses specified here are deleted from the 'Allowed IP Addresses' list of the Azure SQL Server's Firewall." 143 | } 144 | ], 145 | "sourceDefinitions": [ 146 | { 147 | "target": "IpDetectionMethod", 148 | "endpoint": "/_apis/vslabs/ipAddress/ipDetectionMethods", 149 | "selector": "jsonpath:$.value[*]", 150 | "authKey": "tfs:DevTestLabs" 151 | } 152 | ], 153 | "execution": { 154 | "PowerShell3": { 155 | "target": "SqlMultiDacpacDeployment.ps1" 156 | } 157 | } 158 | } -------------------------------------------------------------------------------- /Tasks/SqlMultiDacpacDeployment/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | asap@~2.0.3: 6 | version "2.0.6" 7 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" 8 | integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= 9 | 10 | azure-pipelines-task-lib@^2.9.6: 11 | version "2.9.6" 12 | resolved "https://registry.yarnpkg.com/azure-pipelines-task-lib/-/azure-pipelines-task-lib-2.9.6.tgz#acae923dc070ce0524b37786b7fb195d843d3022" 13 | integrity sha512-KTJuFdMl/r1Y8Snh5lHyV2YOyTu9bKzltMlRlnod8YKLc9GfENDkxwm+z9cHH6NlDln+2YI3G82Ri9XcbhyuZg== 14 | dependencies: 15 | minimatch "3.0.4" 16 | mockery "^1.7.0" 17 | q "^1.1.2" 18 | semver "^5.1.0" 19 | shelljs "^0.3.0" 20 | sync-request "3.0.1" 21 | uuid "^3.0.1" 22 | 23 | balanced-match@^1.0.0: 24 | version "1.0.0" 25 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 26 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 27 | 28 | brace-expansion@^1.1.7: 29 | version "1.1.11" 30 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 31 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 32 | dependencies: 33 | balanced-match "^1.0.0" 34 | concat-map "0.0.1" 35 | 36 | buffer-from@^1.0.0: 37 | version "1.1.1" 38 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 39 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 40 | 41 | caseless@~0.11.0: 42 | version "0.11.0" 43 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" 44 | integrity sha1-cVuW6phBWTzDMGeSP17GDr2k99c= 45 | 46 | concat-map@0.0.1: 47 | version "0.0.1" 48 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 49 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 50 | 51 | concat-stream@^1.4.6, concat-stream@^1.4.7: 52 | version "1.6.2" 53 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" 54 | integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== 55 | dependencies: 56 | buffer-from "^1.0.0" 57 | inherits "^2.0.3" 58 | readable-stream "^2.2.2" 59 | typedarray "^0.0.6" 60 | 61 | core-util-is@~1.0.0: 62 | version "1.0.2" 63 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 64 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 65 | 66 | http-basic@^2.5.1: 67 | version "2.5.1" 68 | resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-2.5.1.tgz#8ce447bdb5b6c577f8a63e3fa78056ec4bb4dbfb" 69 | integrity sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s= 70 | dependencies: 71 | caseless "~0.11.0" 72 | concat-stream "^1.4.6" 73 | http-response-object "^1.0.0" 74 | 75 | http-response-object@^1.0.0, http-response-object@^1.0.1, http-response-object@^1.1.0: 76 | version "1.1.0" 77 | resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-1.1.0.tgz#a7c4e75aae82f3bb4904e4f43f615673b4d518c3" 78 | integrity sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM= 79 | 80 | inherits@^2.0.3, inherits@~2.0.3: 81 | version "2.0.4" 82 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 83 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 84 | 85 | isarray@~1.0.0: 86 | version "1.0.0" 87 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 88 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 89 | 90 | minimatch@3.0.4: 91 | version "3.0.4" 92 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 93 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 94 | dependencies: 95 | brace-expansion "^1.1.7" 96 | 97 | mockery@^1.7.0: 98 | version "1.7.0" 99 | resolved "https://registry.yarnpkg.com/mockery/-/mockery-1.7.0.tgz#f4ede0d8750c1c9727c272ea2c60629e2c9a1c4f" 100 | integrity sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8= 101 | 102 | process-nextick-args@~2.0.0: 103 | version "2.0.1" 104 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 105 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 106 | 107 | promise@^7.1.1: 108 | version "7.3.1" 109 | resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" 110 | integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== 111 | dependencies: 112 | asap "~2.0.3" 113 | 114 | q@^1.1.2: 115 | version "1.5.1" 116 | resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" 117 | integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= 118 | 119 | qs@^6.1.0: 120 | version "6.9.4" 121 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" 122 | integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== 123 | 124 | readable-stream@^2.2.2: 125 | version "2.3.7" 126 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 127 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 128 | dependencies: 129 | core-util-is "~1.0.0" 130 | inherits "~2.0.3" 131 | isarray "~1.0.0" 132 | process-nextick-args "~2.0.0" 133 | safe-buffer "~5.1.1" 134 | string_decoder "~1.1.1" 135 | util-deprecate "~1.0.1" 136 | 137 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 138 | version "5.1.2" 139 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 140 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 141 | 142 | semver@^5.1.0: 143 | version "5.7.1" 144 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 145 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 146 | 147 | shelljs@^0.3.0: 148 | version "0.3.0" 149 | resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1" 150 | integrity sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E= 151 | 152 | string_decoder@~1.1.1: 153 | version "1.1.1" 154 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 155 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 156 | dependencies: 157 | safe-buffer "~5.1.0" 158 | 159 | sync-request@3.0.1: 160 | version "3.0.1" 161 | resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-3.0.1.tgz#caa1235aaf889ba501076a1834c436830a82fb73" 162 | integrity sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M= 163 | dependencies: 164 | concat-stream "^1.4.7" 165 | http-response-object "^1.0.1" 166 | then-request "^2.0.1" 167 | 168 | then-request@^2.0.1: 169 | version "2.2.0" 170 | resolved "https://registry.yarnpkg.com/then-request/-/then-request-2.2.0.tgz#6678b32fa0ca218fe569981bbd8871b594060d81" 171 | integrity sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE= 172 | dependencies: 173 | caseless "~0.11.0" 174 | concat-stream "^1.4.7" 175 | http-basic "^2.5.1" 176 | http-response-object "^1.1.0" 177 | promise "^7.1.1" 178 | qs "^6.1.0" 179 | 180 | typedarray@^0.0.6: 181 | version "0.0.6" 182 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 183 | integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= 184 | 185 | util-deprecate@~1.0.1: 186 | version "1.0.2" 187 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 188 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 189 | 190 | uuid@^3.0.1: 191 | version "3.4.0" 192 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" 193 | integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== 194 | -------------------------------------------------------------------------------- /Tasks/StartWebApp/StartWebApp.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param() 3 | 4 | Trace-VstsEnteringInvocation $MyInvocation 5 | 6 | try { 7 | # Get inputs. 8 | $WebAppName = Get-VstsInput -Name WebAppName -Require 9 | $Slot = Get-VstsInput -Name Slot 10 | $StartedUrl = Get-VstsInput -Name StartedUrl 11 | 12 | # Initialize Azure. 13 | Import-Module $PSScriptRoot\ps_modules\VstsAzureHelpers_ 14 | Initialize-Azure 15 | 16 | # Import the loc strings. 17 | Import-VstsLocStrings -LiteralPath $PSScriptRoot/Task.json 18 | 19 | $resourceGroupName = Get-WebAppResourceGroupName -webAppName $WebAppName 20 | 21 | if ($Slot) { 22 | Write-VstsTaskVerbose -Message "[Azure Call] Starting slot: $WebAppName / $Slot" 23 | $result = Start-AzureRmWebAppSlot -ResourceGroupName $resourceGroupName -Name $WebAppName -Slot $Slot -Verbose 24 | Write-VstsTaskVerbose -Message "[Azure Call] Slot started: $WebAppName / $Slot" 25 | } 26 | else { 27 | Write-VstsTaskVerbose -Message "[Azure Call] Starting Web App: $WebAppName" 28 | $result = Start-AzureRmWebApp -ResourceGroupName $resourceGroupName -Name $WebAppName -Verbose 29 | Write-VstsTaskVerbose -Message "[Azure Call] Web App started: $WebAppName" 30 | } 31 | 32 | $scheme = "http" 33 | $hostName = $result.HostNames[0] 34 | foreach ($hostNameSslState in $result.HostNameSslStates) { 35 | if ($hostName -eq $hostNameSslState.Name) { 36 | if (-not $hostNameSslState.SslState -eq 0) { 37 | $scheme = "https" 38 | } 39 | 40 | break 41 | } 42 | } 43 | 44 | $urlValue = "${scheme}://$hostName" 45 | 46 | Write-Host (Get-VstsLocString -Key "WebappsuccessfullystartedatUrl0" -ArgumentList $urlValue) 47 | 48 | # Set ouput variable with $destinationUrl 49 | if (-not [string]::IsNullOrEmpty($StartedUrl)) { 50 | if ([string]::IsNullOrEmpty($StartedUrl)) { 51 | Throw (Get-VstsLocString -Key "Unabletoretrievewebappurlforwebapp0" -ArgumentList $webAppName) 52 | } 53 | 54 | Set-VstsTaskVariable -Name $StartedUrl -Value $urlValue 55 | } 56 | } 57 | finally { 58 | Trace-VstsLeavingInvocation $MyInvocation 59 | } 60 | -------------------------------------------------------------------------------- /Tasks/StartWebApp/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeklearningio/gl-vsts-tasks-azure/5da85062a3925111f3b87469220fe04ed3e3956c/Tasks/StartWebApp/icon.png -------------------------------------------------------------------------------- /Tasks/StartWebApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "StartWebApp", 3 | "private": true, 4 | "version": "0.0.0", 5 | "dependencies": { 6 | "azure-pipelines-task-lib": "^2.9.6" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tasks/StartWebApp/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "DF1611D6-6AEE-40B5-8AF6-143AF94D9F3E", 3 | "name": "StartWebApp", 4 | "friendlyName": "Azure Web App Start", 5 | "description": "Deprecated: use the official “Azure App Service Manage” task from Microsoft instead. You can then set the “Action” parameter to 'Start Azure App Service'.", 6 | "helpMarkDown": "", 7 | "category": "Deploy", 8 | "deprecated": true, 9 | "visibility": [ 10 | "Build", 11 | "Release" 12 | ], 13 | "author": "Geek Learning", 14 | "version": { 15 | "Major": 0, 16 | "Minor": 0, 17 | "Patch": 0 18 | }, 19 | "demands": [ 20 | "azureps" 21 | ], 22 | "minimumAgentVersion": "1.103.0", 23 | "instanceNameFormat": "Start Azure Web App: $(WebAppName) $(Slot)", 24 | "groups": [{ 25 | "name": "output", 26 | "displayName": "Output", 27 | "isExpanded": false 28 | }], 29 | "inputs": [{ 30 | "name": "ConnectedServiceName", 31 | "type": "connectedService:AzureRM", 32 | "label": "Azure RM Subscription", 33 | "defaultValue": "", 34 | "required": true, 35 | "helpMarkDown": "Azure Resource Manager subscription to target for starting the Web App" 36 | }, 37 | { 38 | "name": "WebAppName", 39 | "type": "pickList", 40 | "label": "Web App Name", 41 | "defaultValue": "", 42 | "required": true, 43 | "properties": { 44 | "EditableOptions": "True" 45 | }, 46 | "helpMarkDown": "Enter or Select the name of an existing AzureRM Web Application" 47 | }, 48 | { 49 | "name": "Slot", 50 | "type": "string", 51 | "label": "Slot", 52 | "defaultValue": "", 53 | "required": false, 54 | "helpMarkDown": "If provided, will start the specified slot instead of the Web App itself" 55 | }, 56 | { 57 | "name": "StartedUrl", 58 | "type": "string", 59 | "label": "Started Url", 60 | "required": false, 61 | "defaultValue": "", 62 | "groupName": "output", 63 | "helpMarkDown": "Provide a name for the variable for the started url. The variable can be used as $(variableName) to refer to the hosted url of Web App in subsequent tasks like in the PowerShell on Target Machines task." 64 | } 65 | ], 66 | "dataSourceBindings": [{ 67 | "target": "WebAppName", 68 | "endpointId": "$(ConnectedServiceName)", 69 | "dataSourceName": "AzureRMWebAppNames" 70 | }], 71 | "execution": { 72 | "PowerShell3": { 73 | "target": "StartWebApp.ps1" 74 | } 75 | }, 76 | "messages": { 77 | "WebappsuccessfullystartedatUrl0": "Web App successfully started at Url: {0}", 78 | "Unabletoretrievewebappurlforwebapp0": "Unable to retrieve Web App url for Web App: {0}" 79 | } 80 | } -------------------------------------------------------------------------------- /Tasks/StartWebApp/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | asap@~2.0.3: 6 | version "2.0.6" 7 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" 8 | integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= 9 | 10 | azure-pipelines-task-lib@^2.9.6: 11 | version "2.9.6" 12 | resolved "https://registry.yarnpkg.com/azure-pipelines-task-lib/-/azure-pipelines-task-lib-2.9.6.tgz#acae923dc070ce0524b37786b7fb195d843d3022" 13 | integrity sha512-KTJuFdMl/r1Y8Snh5lHyV2YOyTu9bKzltMlRlnod8YKLc9GfENDkxwm+z9cHH6NlDln+2YI3G82Ri9XcbhyuZg== 14 | dependencies: 15 | minimatch "3.0.4" 16 | mockery "^1.7.0" 17 | q "^1.1.2" 18 | semver "^5.1.0" 19 | shelljs "^0.3.0" 20 | sync-request "3.0.1" 21 | uuid "^3.0.1" 22 | 23 | balanced-match@^1.0.0: 24 | version "1.0.0" 25 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 26 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 27 | 28 | brace-expansion@^1.1.7: 29 | version "1.1.11" 30 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 31 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 32 | dependencies: 33 | balanced-match "^1.0.0" 34 | concat-map "0.0.1" 35 | 36 | buffer-from@^1.0.0: 37 | version "1.1.1" 38 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 39 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 40 | 41 | caseless@~0.11.0: 42 | version "0.11.0" 43 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" 44 | integrity sha1-cVuW6phBWTzDMGeSP17GDr2k99c= 45 | 46 | concat-map@0.0.1: 47 | version "0.0.1" 48 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 49 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 50 | 51 | concat-stream@^1.4.6, concat-stream@^1.4.7: 52 | version "1.6.2" 53 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" 54 | integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== 55 | dependencies: 56 | buffer-from "^1.0.0" 57 | inherits "^2.0.3" 58 | readable-stream "^2.2.2" 59 | typedarray "^0.0.6" 60 | 61 | core-util-is@~1.0.0: 62 | version "1.0.2" 63 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 64 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 65 | 66 | http-basic@^2.5.1: 67 | version "2.5.1" 68 | resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-2.5.1.tgz#8ce447bdb5b6c577f8a63e3fa78056ec4bb4dbfb" 69 | integrity sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s= 70 | dependencies: 71 | caseless "~0.11.0" 72 | concat-stream "^1.4.6" 73 | http-response-object "^1.0.0" 74 | 75 | http-response-object@^1.0.0, http-response-object@^1.0.1, http-response-object@^1.1.0: 76 | version "1.1.0" 77 | resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-1.1.0.tgz#a7c4e75aae82f3bb4904e4f43f615673b4d518c3" 78 | integrity sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM= 79 | 80 | inherits@^2.0.3, inherits@~2.0.3: 81 | version "2.0.4" 82 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 83 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 84 | 85 | isarray@~1.0.0: 86 | version "1.0.0" 87 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 88 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 89 | 90 | minimatch@3.0.4: 91 | version "3.0.4" 92 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 93 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 94 | dependencies: 95 | brace-expansion "^1.1.7" 96 | 97 | mockery@^1.7.0: 98 | version "1.7.0" 99 | resolved "https://registry.yarnpkg.com/mockery/-/mockery-1.7.0.tgz#f4ede0d8750c1c9727c272ea2c60629e2c9a1c4f" 100 | integrity sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8= 101 | 102 | process-nextick-args@~2.0.0: 103 | version "2.0.1" 104 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 105 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 106 | 107 | promise@^7.1.1: 108 | version "7.3.1" 109 | resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" 110 | integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== 111 | dependencies: 112 | asap "~2.0.3" 113 | 114 | q@^1.1.2: 115 | version "1.5.1" 116 | resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" 117 | integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= 118 | 119 | qs@^6.1.0: 120 | version "6.9.4" 121 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" 122 | integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== 123 | 124 | readable-stream@^2.2.2: 125 | version "2.3.7" 126 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 127 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 128 | dependencies: 129 | core-util-is "~1.0.0" 130 | inherits "~2.0.3" 131 | isarray "~1.0.0" 132 | process-nextick-args "~2.0.0" 133 | safe-buffer "~5.1.1" 134 | string_decoder "~1.1.1" 135 | util-deprecate "~1.0.1" 136 | 137 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 138 | version "5.1.2" 139 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 140 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 141 | 142 | semver@^5.1.0: 143 | version "5.7.1" 144 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 145 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 146 | 147 | shelljs@^0.3.0: 148 | version "0.3.0" 149 | resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1" 150 | integrity sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E= 151 | 152 | string_decoder@~1.1.1: 153 | version "1.1.1" 154 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 155 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 156 | dependencies: 157 | safe-buffer "~5.1.0" 158 | 159 | sync-request@3.0.1: 160 | version "3.0.1" 161 | resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-3.0.1.tgz#caa1235aaf889ba501076a1834c436830a82fb73" 162 | integrity sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M= 163 | dependencies: 164 | concat-stream "^1.4.7" 165 | http-response-object "^1.0.1" 166 | then-request "^2.0.1" 167 | 168 | then-request@^2.0.1: 169 | version "2.2.0" 170 | resolved "https://registry.yarnpkg.com/then-request/-/then-request-2.2.0.tgz#6678b32fa0ca218fe569981bbd8871b594060d81" 171 | integrity sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE= 172 | dependencies: 173 | caseless "~0.11.0" 174 | concat-stream "^1.4.7" 175 | http-basic "^2.5.1" 176 | http-response-object "^1.1.0" 177 | promise "^7.1.1" 178 | qs "^6.1.0" 179 | 180 | typedarray@^0.0.6: 181 | version "0.0.6" 182 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 183 | integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= 184 | 185 | util-deprecate@~1.0.1: 186 | version "1.0.2" 187 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 188 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 189 | 190 | uuid@^3.0.1: 191 | version "3.4.0" 192 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" 193 | integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== 194 | -------------------------------------------------------------------------------- /Tasks/StopWebApp/StopWebApp.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param() 3 | 4 | Trace-VstsEnteringInvocation $MyInvocation 5 | 6 | try { 7 | # Get inputs. 8 | $WebAppName = Get-VstsInput -Name WebAppName -Require 9 | $Slot = Get-VstsInput -Name Slot 10 | $StoppedUrl = Get-VstsInput -Name StoppedUrl 11 | 12 | # Initialize Azure. 13 | Import-Module $PSScriptRoot\ps_modules\VstsAzureHelpers_ 14 | Initialize-Azure 15 | 16 | # Import the loc strings. 17 | Import-VstsLocStrings -LiteralPath $PSScriptRoot/Task.json 18 | 19 | $resourceGroupName = Get-WebAppResourceGroupName -webAppName $WebAppName 20 | 21 | if ($Slot) { 22 | Write-VstsTaskVerbose -Message "[Azure Call] Stopping slot: $WebAppName / $Slot" 23 | $result = Stop-AzureRmWebAppSlot -ResourceGroupName $resourceGroupName -Name $WebAppName -Slot $Slot -Verbose 24 | Write-VstsTaskVerbose -Message "[Azure Call] Slot stopped: $WebAppName / $Slot" 25 | } 26 | else { 27 | Write-VstsTaskVerbose -Message "[Azure Call] Stopping Web App: $WebAppName" 28 | $result = Stop-AzureRmWebApp -ResourceGroupName $resourceGroupName -Name $WebAppName -Verbose 29 | Write-VstsTaskVerbose -Message "[Azure Call] Web App stopped: $WebAppName" 30 | } 31 | 32 | $scheme = "http" 33 | $hostName = $result.HostNames[0] 34 | foreach ($hostNameSslState in $result.HostNameSslStates) { 35 | if ($hostName -eq $hostNameSslState.Name) { 36 | if (-not $hostNameSslState.SslState -eq 0) { 37 | $scheme = "https" 38 | } 39 | 40 | break 41 | } 42 | } 43 | 44 | $urlValue = "${scheme}://$hostName" 45 | 46 | Write-Host (Get-VstsLocString -Key "WebappsuccessfullystoppedatUrl0" -ArgumentList $urlValue) 47 | 48 | # Set ouput variable with $destinationUrl 49 | if (-not [string]::IsNullOrEmpty($StoppedUrl)) { 50 | if ([string]::IsNullOrEmpty($StoppedUrl)) { 51 | Throw (Get-VstsLocString -Key "Unabletoretrievewebappurlforwebapp0" -ArgumentList $webAppName) 52 | } 53 | 54 | Set-VstsTaskVariable -Name $StoppedUrl -Value $urlValue 55 | } 56 | } 57 | finally { 58 | Trace-VstsLeavingInvocation $MyInvocation 59 | } 60 | -------------------------------------------------------------------------------- /Tasks/StopWebApp/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeklearningio/gl-vsts-tasks-azure/5da85062a3925111f3b87469220fe04ed3e3956c/Tasks/StopWebApp/icon.png -------------------------------------------------------------------------------- /Tasks/StopWebApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "StopWebApp", 3 | "private": true, 4 | "version": "0.0.0", 5 | "dependencies": { 6 | "azure-pipelines-task-lib": "^2.9.6" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tasks/StopWebApp/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "A3F35BB6-5341-413A-8091-10F93D390BB5", 3 | "name": "StopWebApp", 4 | "friendlyName": "Azure Web App Stop", 5 | "description": "Deprecated: use the official “Azure App Service Manage” task from Microsoft instead. You can then set the “Action” parameter to 'Stop Azure App Service'.", 6 | "helpMarkDown": "", 7 | "category": "Deploy", 8 | "deprecated": true, 9 | "visibility": [ 10 | "Build", 11 | "Release" 12 | ], 13 | "author": "Geek Learning", 14 | "version": { 15 | "Major": 0, 16 | "Minor": 0, 17 | "Patch": 0 18 | }, 19 | "demands": [ 20 | "azureps" 21 | ], 22 | "minimumAgentVersion": "1.103.0", 23 | "instanceNameFormat": "Stop Azure Web App: $(WebAppName) $(Slot)", 24 | "groups": [ 25 | { 26 | "name": "output", 27 | "displayName": "Output", 28 | "isExpanded": false 29 | } 30 | ], 31 | "inputs": [ 32 | { 33 | "name": "ConnectedServiceName", 34 | "type": "connectedService:AzureRM", 35 | "label": "Azure RM Subscription", 36 | "defaultValue": "", 37 | "required": true, 38 | "helpMarkDown": "Azure Resource Manager subscription to target for stopping the Web App" 39 | }, 40 | { 41 | "name": "WebAppName", 42 | "type": "pickList", 43 | "label": "Web App Name", 44 | "defaultValue": "", 45 | "required": true, 46 | "properties": { 47 | "EditableOptions": "True" 48 | }, 49 | "helpMarkDown": "Enter or Select the name of an existing AzureRM Web Application" 50 | }, 51 | { 52 | "name": "Slot", 53 | "type": "string", 54 | "label": "Slot", 55 | "defaultValue": "", 56 | "required": false, 57 | "helpMarkDown": "If provided, will stop the specified slot instead of the Web App itself" 58 | }, 59 | { 60 | "name": "StoppedUrl", 61 | "type": "string", 62 | "label": "Stopped Url", 63 | "required": false, 64 | "defaultValue": "", 65 | "groupName": "output", 66 | "helpMarkDown": "Provide a name for the variable for the stopped url. The variable can be used as $(variableName) to refer to the hosted url of Web App in subsequent tasks like in the PowerShell on Target Machines task." 67 | } 68 | ], 69 | "dataSourceBindings": [ 70 | { 71 | "target": "WebAppName", 72 | "endpointId": "$(ConnectedServiceName)", 73 | "dataSourceName": "AzureRMWebAppNames" 74 | } 75 | ], 76 | "execution": { 77 | "PowerShell3": { 78 | "target": "StopWebApp.ps1" 79 | } 80 | }, 81 | "messages": { 82 | "WebappsuccessfullystoppedatUrl0": "Web App successfully stopped at Url: {0}", 83 | "Unabletoretrievewebappurlforwebapp0": "Unable to retrieve Web App url for Web App: {0}" 84 | } 85 | } -------------------------------------------------------------------------------- /Tasks/StopWebApp/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | asap@~2.0.3: 6 | version "2.0.6" 7 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" 8 | integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= 9 | 10 | azure-pipelines-task-lib@^2.9.6: 11 | version "2.9.6" 12 | resolved "https://registry.yarnpkg.com/azure-pipelines-task-lib/-/azure-pipelines-task-lib-2.9.6.tgz#acae923dc070ce0524b37786b7fb195d843d3022" 13 | integrity sha512-KTJuFdMl/r1Y8Snh5lHyV2YOyTu9bKzltMlRlnod8YKLc9GfENDkxwm+z9cHH6NlDln+2YI3G82Ri9XcbhyuZg== 14 | dependencies: 15 | minimatch "3.0.4" 16 | mockery "^1.7.0" 17 | q "^1.1.2" 18 | semver "^5.1.0" 19 | shelljs "^0.3.0" 20 | sync-request "3.0.1" 21 | uuid "^3.0.1" 22 | 23 | balanced-match@^1.0.0: 24 | version "1.0.0" 25 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 26 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 27 | 28 | brace-expansion@^1.1.7: 29 | version "1.1.11" 30 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 31 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 32 | dependencies: 33 | balanced-match "^1.0.0" 34 | concat-map "0.0.1" 35 | 36 | buffer-from@^1.0.0: 37 | version "1.1.1" 38 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 39 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 40 | 41 | caseless@~0.11.0: 42 | version "0.11.0" 43 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" 44 | integrity sha1-cVuW6phBWTzDMGeSP17GDr2k99c= 45 | 46 | concat-map@0.0.1: 47 | version "0.0.1" 48 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 49 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 50 | 51 | concat-stream@^1.4.6, concat-stream@^1.4.7: 52 | version "1.6.2" 53 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" 54 | integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== 55 | dependencies: 56 | buffer-from "^1.0.0" 57 | inherits "^2.0.3" 58 | readable-stream "^2.2.2" 59 | typedarray "^0.0.6" 60 | 61 | core-util-is@~1.0.0: 62 | version "1.0.2" 63 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 64 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 65 | 66 | http-basic@^2.5.1: 67 | version "2.5.1" 68 | resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-2.5.1.tgz#8ce447bdb5b6c577f8a63e3fa78056ec4bb4dbfb" 69 | integrity sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s= 70 | dependencies: 71 | caseless "~0.11.0" 72 | concat-stream "^1.4.6" 73 | http-response-object "^1.0.0" 74 | 75 | http-response-object@^1.0.0, http-response-object@^1.0.1, http-response-object@^1.1.0: 76 | version "1.1.0" 77 | resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-1.1.0.tgz#a7c4e75aae82f3bb4904e4f43f615673b4d518c3" 78 | integrity sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM= 79 | 80 | inherits@^2.0.3, inherits@~2.0.3: 81 | version "2.0.4" 82 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 83 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 84 | 85 | isarray@~1.0.0: 86 | version "1.0.0" 87 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 88 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 89 | 90 | minimatch@3.0.4: 91 | version "3.0.4" 92 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 93 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 94 | dependencies: 95 | brace-expansion "^1.1.7" 96 | 97 | mockery@^1.7.0: 98 | version "1.7.0" 99 | resolved "https://registry.yarnpkg.com/mockery/-/mockery-1.7.0.tgz#f4ede0d8750c1c9727c272ea2c60629e2c9a1c4f" 100 | integrity sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8= 101 | 102 | process-nextick-args@~2.0.0: 103 | version "2.0.1" 104 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 105 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 106 | 107 | promise@^7.1.1: 108 | version "7.3.1" 109 | resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" 110 | integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== 111 | dependencies: 112 | asap "~2.0.3" 113 | 114 | q@^1.1.2: 115 | version "1.5.1" 116 | resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" 117 | integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= 118 | 119 | qs@^6.1.0: 120 | version "6.9.4" 121 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" 122 | integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== 123 | 124 | readable-stream@^2.2.2: 125 | version "2.3.7" 126 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 127 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 128 | dependencies: 129 | core-util-is "~1.0.0" 130 | inherits "~2.0.3" 131 | isarray "~1.0.0" 132 | process-nextick-args "~2.0.0" 133 | safe-buffer "~5.1.1" 134 | string_decoder "~1.1.1" 135 | util-deprecate "~1.0.1" 136 | 137 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 138 | version "5.1.2" 139 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 140 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 141 | 142 | semver@^5.1.0: 143 | version "5.7.1" 144 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 145 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 146 | 147 | shelljs@^0.3.0: 148 | version "0.3.0" 149 | resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1" 150 | integrity sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E= 151 | 152 | string_decoder@~1.1.1: 153 | version "1.1.1" 154 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 155 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 156 | dependencies: 157 | safe-buffer "~5.1.0" 158 | 159 | sync-request@3.0.1: 160 | version "3.0.1" 161 | resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-3.0.1.tgz#caa1235aaf889ba501076a1834c436830a82fb73" 162 | integrity sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M= 163 | dependencies: 164 | concat-stream "^1.4.7" 165 | http-response-object "^1.0.1" 166 | then-request "^2.0.1" 167 | 168 | then-request@^2.0.1: 169 | version "2.2.0" 170 | resolved "https://registry.yarnpkg.com/then-request/-/then-request-2.2.0.tgz#6678b32fa0ca218fe569981bbd8871b594060d81" 171 | integrity sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE= 172 | dependencies: 173 | caseless "~0.11.0" 174 | concat-stream "^1.4.7" 175 | http-basic "^2.5.1" 176 | http-response-object "^1.1.0" 177 | promise "^7.1.1" 178 | qs "^6.1.0" 179 | 180 | typedarray@^0.0.6: 181 | version "0.0.6" 182 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 183 | integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= 184 | 185 | util-deprecate@~1.0.1: 186 | version "1.0.2" 187 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 188 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 189 | 190 | uuid@^3.0.1: 191 | version "3.4.0" 192 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" 193 | integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== 194 | -------------------------------------------------------------------------------- /Tasks/SwapSlots/SwapSlots.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param() 3 | 4 | Trace-VstsEnteringInvocation $MyInvocation 5 | 6 | try { 7 | 8 | # Get inputs. 9 | $WebAppName = Get-VstsInput -Name WebAppName -Require 10 | $SourceSlot = Get-VstsInput -Name SourceSlot -Require 11 | $DestinationSlot = Get-VstsInput -Name DestinationSlot -Require 12 | $DestinationUrl = Get-VstsInput -Name DestinationUrl 13 | 14 | # Initialize Azure. 15 | Import-Module $PSScriptRoot\ps_modules\VstsAzureHelpers_ 16 | Initialize-Azure 17 | 18 | # Import the loc strings. 19 | Import-VstsLocStrings -LiteralPath $PSScriptRoot/Task.json 20 | 21 | $resourceGroupName = Get-WebAppResourceGroupName -webAppName $WebAppName 22 | $parametersObject = @{targetSlot = "$DestinationSlot" } 23 | if ($SourceSlot -eq "production") { 24 | $resourceName = "$WebAppName" 25 | $resourceType = "Microsoft.Web/sites" 26 | } 27 | else { 28 | $resourceName = "$WebAppName/$SourceSlot" 29 | $resourceType = "Microsoft.Web/sites/slots" 30 | } 31 | 32 | Write-VstsTaskVerbose -Message "[Azure Call] Swapping slot: $resourceName with resource type: $resourceType to $DestinationSlot" 33 | $result = Invoke-AzureRmResourceAction -ResourceGroupName $resourceGroupName -ResourceType $resourceType -ResourceName $resourceName -Action slotsswap -Parameters $parametersObject -ApiVersion 2015-07-01 -Force -Verbose 34 | Write-VstsTaskVerbose -Message "[Azure Call] Slot swapped: $resourceName with resource type: $resourceType to $DestinationSlot" 35 | 36 | $scheme = "http" 37 | $hostName = $result.Properties.HostNames[0] 38 | foreach ($hostNameSslState in $result.Properties.HostNameSslStates) { 39 | if ($hostName -eq $hostNameSslState.Name) { 40 | if (-not $hostNameSslState.SslState -eq 0) { 41 | $scheme = "https" 42 | } 43 | 44 | break 45 | } 46 | } 47 | 48 | $destinationUrlValue = "${scheme}://$hostName" 49 | 50 | Write-Host (Get-VstsLocString -Key "WebappslotsuccessfullyswappedatUrl0" -ArgumentList $destinationUrlValue) 51 | 52 | # Set ouput variable with $destinationUrl 53 | if (-not [string]::IsNullOrEmpty($DestinationUrl)) { 54 | if ([string]::IsNullOrEmpty($destinationUrl)) { 55 | Throw (Get-VstsLocString -Key "Unabletoretrievewebapppublishurlforwebapp0" -ArgumentList $webAppName) 56 | } 57 | 58 | Set-VstsTaskVariable -Name $DestinationUrl -Value $destinationUrlValue 59 | } 60 | } 61 | finally { 62 | Trace-VstsLeavingInvocation $MyInvocation 63 | } 64 | -------------------------------------------------------------------------------- /Tasks/SwapSlots/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeklearningio/gl-vsts-tasks-azure/5da85062a3925111f3b87469220fe04ed3e3956c/Tasks/SwapSlots/icon.png -------------------------------------------------------------------------------- /Tasks/SwapSlots/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SwapSlots", 3 | "private": true, 4 | "version": "0.0.0", 5 | "dependencies": { 6 | "azure-pipelines-task-lib": "^2.9.6" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tasks/SwapSlots/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "8698E535-D070-4A41-8C0E-4624D6283F05", 3 | "name": "SwapSlots", 4 | "friendlyName": "Azure Web App Slots Swap", 5 | "description": "Deprecated: use the official “Azure App Service Manage” task from Microsoft instead. You can then set the “Action” parameter to 'Swap Slots'.", 6 | "helpMarkDown": "", 7 | "category": "Deploy", 8 | "deprecated": true, 9 | "visibility": [ 10 | "Build", 11 | "Release" 12 | ], 13 | "author": "Geek Learning", 14 | "version": { 15 | "Major": 0, 16 | "Minor": 0, 17 | "Patch": 0 18 | }, 19 | "demands": [ 20 | "azureps" 21 | ], 22 | "minimumAgentVersion": "1.103.0", 23 | "instanceNameFormat": "Swap Azure Web App slots: $(SourceSlot) to $(DestinationSlot)", 24 | "groups": [ 25 | { 26 | "name": "output", 27 | "displayName": "Output", 28 | "isExpanded": false 29 | } 30 | ], 31 | "inputs": [ 32 | { 33 | "name": "ConnectedServiceName", 34 | "type": "connectedService:AzureRM", 35 | "label": "Azure RM Subscription", 36 | "defaultValue": "", 37 | "required": true, 38 | "helpMarkDown": "Azure Resource Manager subscription to target for swapping Web App deployment slots" 39 | }, 40 | { 41 | "name": "WebAppName", 42 | "type": "pickList", 43 | "label": "Web App Name", 44 | "defaultValue": "", 45 | "required": true, 46 | "properties": { 47 | "EditableOptions": "True" 48 | }, 49 | "helpMarkDown": "Enter or Select the name of an existing AzureRM Web Application" 50 | }, 51 | { 52 | "name": "SourceSlot", 53 | "type": "string", 54 | "label": "Source Slot", 55 | "defaultValue": "", 56 | "required": true, 57 | "helpMarkDown": "" 58 | }, 59 | { 60 | "name": "DestinationSlot", 61 | "type": "string", 62 | "label": "Destination Slot", 63 | "defaultValue": "", 64 | "required": true, 65 | "helpMarkDown": "" 66 | }, 67 | { 68 | "name": "DestinationUrl", 69 | "type": "string", 70 | "label": "Destination Slot Url", 71 | "required": false, 72 | "defaultValue": "", 73 | "groupName": "output", 74 | "helpMarkDown": "Provide a name for the variable for the destination slot url. The variable can be used as $(variableName) to refer to the hosted url of Web App in subsequent tasks like in the PowerShell on Target Machines task." 75 | } 76 | ], 77 | "dataSourceBindings": [ 78 | { 79 | "target": "WebAppName", 80 | "endpointId": "$(ConnectedServiceName)", 81 | "dataSourceName": "AzureRMWebAppNames" 82 | } 83 | ], 84 | "execution": { 85 | "PowerShell3": { 86 | "target": "SwapSlots.ps1" 87 | } 88 | }, 89 | "messages": { 90 | "WebappslotsuccessfullyswappedatUrl0": "Web App slot successfully swapped at Url : {0}", 91 | "Unabletoretrievewebapppublishurlforwebapp0": "Unable to retrieve Web App slot url for webapp : '{0}'." 92 | } 93 | } -------------------------------------------------------------------------------- /Tasks/SwapSlots/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | asap@~2.0.3: 6 | version "2.0.6" 7 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" 8 | integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= 9 | 10 | azure-pipelines-task-lib@^2.9.6: 11 | version "2.9.6" 12 | resolved "https://registry.yarnpkg.com/azure-pipelines-task-lib/-/azure-pipelines-task-lib-2.9.6.tgz#acae923dc070ce0524b37786b7fb195d843d3022" 13 | integrity sha512-KTJuFdMl/r1Y8Snh5lHyV2YOyTu9bKzltMlRlnod8YKLc9GfENDkxwm+z9cHH6NlDln+2YI3G82Ri9XcbhyuZg== 14 | dependencies: 15 | minimatch "3.0.4" 16 | mockery "^1.7.0" 17 | q "^1.1.2" 18 | semver "^5.1.0" 19 | shelljs "^0.3.0" 20 | sync-request "3.0.1" 21 | uuid "^3.0.1" 22 | 23 | balanced-match@^1.0.0: 24 | version "1.0.0" 25 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 26 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 27 | 28 | brace-expansion@^1.1.7: 29 | version "1.1.11" 30 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 31 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 32 | dependencies: 33 | balanced-match "^1.0.0" 34 | concat-map "0.0.1" 35 | 36 | buffer-from@^1.0.0: 37 | version "1.1.1" 38 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 39 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 40 | 41 | caseless@~0.11.0: 42 | version "0.11.0" 43 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" 44 | integrity sha1-cVuW6phBWTzDMGeSP17GDr2k99c= 45 | 46 | concat-map@0.0.1: 47 | version "0.0.1" 48 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 49 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 50 | 51 | concat-stream@^1.4.6, concat-stream@^1.4.7: 52 | version "1.6.2" 53 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" 54 | integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== 55 | dependencies: 56 | buffer-from "^1.0.0" 57 | inherits "^2.0.3" 58 | readable-stream "^2.2.2" 59 | typedarray "^0.0.6" 60 | 61 | core-util-is@~1.0.0: 62 | version "1.0.2" 63 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 64 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 65 | 66 | http-basic@^2.5.1: 67 | version "2.5.1" 68 | resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-2.5.1.tgz#8ce447bdb5b6c577f8a63e3fa78056ec4bb4dbfb" 69 | integrity sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s= 70 | dependencies: 71 | caseless "~0.11.0" 72 | concat-stream "^1.4.6" 73 | http-response-object "^1.0.0" 74 | 75 | http-response-object@^1.0.0, http-response-object@^1.0.1, http-response-object@^1.1.0: 76 | version "1.1.0" 77 | resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-1.1.0.tgz#a7c4e75aae82f3bb4904e4f43f615673b4d518c3" 78 | integrity sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM= 79 | 80 | inherits@^2.0.3, inherits@~2.0.3: 81 | version "2.0.4" 82 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 83 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 84 | 85 | isarray@~1.0.0: 86 | version "1.0.0" 87 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 88 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 89 | 90 | minimatch@3.0.4: 91 | version "3.0.4" 92 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 93 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 94 | dependencies: 95 | brace-expansion "^1.1.7" 96 | 97 | mockery@^1.7.0: 98 | version "1.7.0" 99 | resolved "https://registry.yarnpkg.com/mockery/-/mockery-1.7.0.tgz#f4ede0d8750c1c9727c272ea2c60629e2c9a1c4f" 100 | integrity sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8= 101 | 102 | process-nextick-args@~2.0.0: 103 | version "2.0.1" 104 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 105 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 106 | 107 | promise@^7.1.1: 108 | version "7.3.1" 109 | resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" 110 | integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== 111 | dependencies: 112 | asap "~2.0.3" 113 | 114 | q@^1.1.2: 115 | version "1.5.1" 116 | resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" 117 | integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= 118 | 119 | qs@^6.1.0: 120 | version "6.9.4" 121 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" 122 | integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== 123 | 124 | readable-stream@^2.2.2: 125 | version "2.3.7" 126 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 127 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 128 | dependencies: 129 | core-util-is "~1.0.0" 130 | inherits "~2.0.3" 131 | isarray "~1.0.0" 132 | process-nextick-args "~2.0.0" 133 | safe-buffer "~5.1.1" 134 | string_decoder "~1.1.1" 135 | util-deprecate "~1.0.1" 136 | 137 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 138 | version "5.1.2" 139 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 140 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 141 | 142 | semver@^5.1.0: 143 | version "5.7.1" 144 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 145 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 146 | 147 | shelljs@^0.3.0: 148 | version "0.3.0" 149 | resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1" 150 | integrity sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E= 151 | 152 | string_decoder@~1.1.1: 153 | version "1.1.1" 154 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 155 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 156 | dependencies: 157 | safe-buffer "~5.1.0" 158 | 159 | sync-request@3.0.1: 160 | version "3.0.1" 161 | resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-3.0.1.tgz#caa1235aaf889ba501076a1834c436830a82fb73" 162 | integrity sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M= 163 | dependencies: 164 | concat-stream "^1.4.7" 165 | http-response-object "^1.0.1" 166 | then-request "^2.0.1" 167 | 168 | then-request@^2.0.1: 169 | version "2.2.0" 170 | resolved "https://registry.yarnpkg.com/then-request/-/then-request-2.2.0.tgz#6678b32fa0ca218fe569981bbd8871b594060d81" 171 | integrity sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE= 172 | dependencies: 173 | caseless "~0.11.0" 174 | concat-stream "^1.4.7" 175 | http-basic "^2.5.1" 176 | http-response-object "^1.1.0" 177 | promise "^7.1.1" 178 | qs "^6.1.0" 179 | 180 | typedarray@^0.0.6: 181 | version "0.0.6" 182 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 183 | integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= 184 | 185 | util-deprecate@~1.0.1: 186 | version "1.0.2" 187 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 188 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 189 | 190 | uuid@^3.0.1: 191 | version "3.4.0" 192 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" 193 | integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== 194 | -------------------------------------------------------------------------------- /Tests/Node/AzCopy/azCopy.spec.ts: -------------------------------------------------------------------------------- 1 | import fs = require('fs'); 2 | import path = require('path'); 3 | import azureEndpointConnection = require('../../../Tasks/AzCopy/azureEndpointConnection'); 4 | 5 | var settings = JSON.parse(fs.readFileSync(path.join(__dirname, '../settings.json'), 'utf-8')); 6 | 7 | var computeDigest = new Buffer("pat:" + settings.vsts.pat).toString('base64'); 8 | var auhtorizationHeader = "Basic " + computeDigest; 9 | 10 | describe("AzureEndpointConnection", () => { 11 | 12 | it(": should support basic add.", (done: any) => { 13 | try { 14 | azureEndpointConnection.getConnectedServiceCredentials( 15 | settings.azure.connectionName, 16 | settings.azure.servicePrincipalId, 17 | settings.azure.servicePrincipalkey, 18 | settings.azure.tenantId, 19 | settings.azure.subscriptionName, 20 | settings.azure.subscriptionId) 21 | .then((result) => { 22 | console.log(result); 23 | expect(result).toBeDefined(); 24 | expect(result.creds).toBeTruthy(); 25 | expect(result.id).toBeTruthy(); 26 | expect(result.name).toBeTruthy(); 27 | done(); 28 | }) 29 | .catch((err) => { 30 | expect(err).toBeNull(); 31 | done(); 32 | }); 33 | } 34 | catch (err) { 35 | expect(err).toBeUndefined(); 36 | } 37 | }, 60000); 38 | }); -------------------------------------------------------------------------------- /Tests/Node/chutzpah.json: -------------------------------------------------------------------------------- 1 | { 2 | "Framework": "jasmine", 3 | "Tests": [ 4 | { "Includes": ["**/*.spec.js"], "Excludes": [] } 5 | ] 6 | } -------------------------------------------------------------------------------- /Tests/Node/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "tests", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ] 6 | } -------------------------------------------------------------------------------- /Tests/Node/settings.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "vsts" : { 3 | "url" : "https://accountname.visualstudio.com", 4 | "pat" : "somepat" 5 | }, 6 | "azure": { 7 | "ConnectionName": "A Connection Name", 8 | "SubscriptionId": "aguid0000-0000-0000-0000-00000000000", 9 | "SubscriptionName": "A Subscription Name", 10 | "ServicePrincipalId": "aguid0000-0000-0000-0000-00000000000", 11 | "ServicePrincipalkey": "asecretkey", 12 | "TenantId": "aguid0000-0000-0000-0000-00000000000" 13 | } 14 | } -------------------------------------------------------------------------------- /Tests/PowerShell3/GeekLearning.VstsTasks.Azure.Tests.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F5034706-568F-408A-B7B3-4D38C6DB8A32}") = "GeekLearning.VstsTasks.Azure.Tests", "GeekLearning.VstsTasks.Azure.Tests\GeekLearning.VstsTasks.Azure.Tests.pssproj", "{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /Tests/PowerShell3/GeekLearning.VstsTasks.Azure.Tests/GeekLearning.VstsTasks.Azure.Tests.pssproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | 2.0 6 | 6CAFC0C6-A428-4d30-A9F9-700E829FEA51 7 | Exe 8 | MyApplication 9 | MyApplication 10 | GeekLearning.VstsTasks.Azure.Tests 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug\ 17 | DEBUG;TRACE 18 | prompt 19 | 4 20 | 21 | 22 | pdbonly 23 | true 24 | bin\Release\ 25 | TRACE 26 | prompt 27 | 4 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Tests/PowerShell3/GeekLearning.VstsTasks.Azure.Tests/Helpers.ps1: -------------------------------------------------------------------------------- 1 | function Get-TestsConfiguration() { 2 | param( 3 | [Parameter(Mandatory=$true)][string]$invocationCommandPath 4 | ) 5 | 6 | function ExtendJSON($base, $ext) { 7 | $propNames = $($ext | Get-Member -MemberType *Property).Name 8 | foreach ($propName in $propNames) { 9 | if ($base.PSObject.Properties.Match($propName).Count) { 10 | if ($base.$propName.GetType().Name -eq "PSCustomObject") { 11 | $base.$propName = ExtendJSON $base.$propName $ext.$propName 12 | } 13 | else { 14 | $base.$propName = $ext.$propName 15 | } 16 | } 17 | else { 18 | $base | Add-Member -MemberType NoteProperty -Name $propName -Value $ext.$propName 19 | } 20 | } 21 | 22 | return $base 23 | } 24 | 25 | $here = Split-Path -Parent $invocationCommandPath 26 | $appSettings = Get-Content (Join-Path $here "appsettings.json") | ConvertFrom-Json 27 | $appDevelopmentSettingsPath = Join-Path $here "appsettings.development.json" 28 | 29 | if (Test-Path $appDevelopmentSettingsPath) { 30 | $appDevelopmentSettings = Get-Content $appDevelopmentSettingsPath | ConvertFrom-Json 31 | $settings = ExtendJSON $appSettings $appDevelopmentSettings 32 | } 33 | else { 34 | $settings = $appSettings 35 | } 36 | 37 | $tasksFolderPath = Resolve-Path "$here/$($settings.VstsTasksPath)" 38 | $scriptName = (Split-Path -Leaf $invocationCommandPath) -replace '\.Tests\.', '.' 39 | $scriptFolderName = [System.IO.Path]::GetFileNameWithoutExtension($scriptName) 40 | $scriptPath = Resolve-Path "$tasksFolderPath/$scriptFolderName/$scriptName" 41 | $taskLibPath = Resolve-Path "$tasksFolderPath/$scriptFolderName/$($settings.VstsTaskSdkPath)" 42 | 43 | return @{ 44 | settings = $settings 45 | tasksFolderPath = $tasksFolderPath 46 | targetScriptName = $scriptFolderName 47 | targetScriptPath = $scriptPath 48 | taskLibPath = $taskLibPath 49 | } 50 | } 51 | 52 | function Add-AzureEndpoint() { 53 | param( 54 | [Parameter(Mandatory=$true)]$config, 55 | [Parameter(Mandatory=$false)][string]$inputName 56 | ) 57 | 58 | if (!($inputName)) { 59 | $inputName = "ConnectedServiceName" 60 | } 61 | 62 | $endPointName = [System.Guid]::NewGuid().ToString() 63 | 64 | $auth = @{ 65 | scheme = "ServicePrincipal" 66 | parameters = @{ 67 | servicePrincipalId = $config.settings.AzureAccount.ServicePrincipalClientId 68 | servicePrincipalKey = $config.settings.AzureAccount.ServicePrincipalKey 69 | tenantId = $config.settings.AzureAccount.TenantId 70 | } 71 | } | ConvertTo-Json 72 | 73 | $data = @{ 74 | subscriptionId = $config.settings.AzureAccount.SubscriptionId 75 | subscriptionName = $config.settings.AzureAccount.SubscriptionName 76 | azureSpnRoleAssignmentId = "" 77 | spnObjectId = "" 78 | appObjectId = "" 79 | creationMode = "Manual" 80 | } | ConvertTo-Json 81 | 82 | Add-VstsInput -name $inputName -value $endPointName 83 | Add-VstsEndPoint -name $endPointName -url "https://management.core.windows.net/" ` 84 | -auth $auth -data $data 85 | } 86 | 87 | function Add-EnvironmentVariable() { 88 | param( 89 | [Parameter(Mandatory=$true)][string]$name, 90 | [Parameter(Mandatory=$false)][string]$value 91 | ) 92 | 93 | $addEnvironmentVariable = "`${env:$name} = '$value'" 94 | Invoke-Expression -Command $addEnvironmentVariable 95 | } 96 | 97 | function Add-VstsInput() { 98 | param( 99 | [Parameter(Mandatory=$true)][string]$name, 100 | [Parameter(Mandatory=$false)][string]$value 101 | ) 102 | 103 | Add-EnvironmentVariable -name "INPUT_$name" -value $value 104 | } 105 | 106 | function Add-VstsEndPoint() { 107 | param( 108 | [Parameter(Mandatory=$true)][string]$name, 109 | [Parameter(Mandatory=$true)][string]$url, 110 | [Parameter(Mandatory=$true)][string]$auth, 111 | [Parameter(Mandatory=$true)][string]$data 112 | ) 113 | 114 | Add-EnvironmentVariable -name "ENDPOINT_URL_$name" -value $url 115 | Add-EnvironmentVariable -name "ENDPOINT_AUTH_$name" -value $auth 116 | Add-EnvironmentVariable -name "ENDPOINT_DATA_$name" -value $data 117 | } -------------------------------------------------------------------------------- /Tests/PowerShell3/GeekLearning.VstsTasks.Azure.Tests/Run-Tests.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string]$SourceDir = $env:BUILD_SOURCESDIRECTORY, 3 | [string]$TempDir = $env:TEMP 4 | ) 5 | 6 | $ErrorActionPreference = "Stop" 7 | 8 | $modulePath = Join-Path $TempDir Pester-master\Pester.psm1 9 | 10 | if (-not(Test-Path $modulePath)) { 11 | 12 | # Note: PSGet and chocolatey are not supported in hosted vsts build agent 13 | $tempFile = Join-Path $TempDir pester.zip 14 | Invoke-WebRequest https://github.com/pester/Pester/archive/master.zip -OutFile $tempFile 15 | 16 | [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') | Out-Null 17 | [System.IO.Compression.ZipFile]::ExtractToDirectory($tempFile, $tempDir) 18 | 19 | Remove-Item $tempFile 20 | } 21 | 22 | Import-Module $modulePath -DisableNameChecking 23 | 24 | $outputFile = Join-Path $SourceDir "TEST-pester.xml" 25 | 26 | Invoke-Pester -Path $SourceDir -PassThru -OutputFile $outputFile -OutputFormat NUnitXml -EnableExit -------------------------------------------------------------------------------- /Tests/PowerShell3/GeekLearning.VstsTasks.Azure.Tests/SampleDirectory/SqlMultiDacpacDeployment/1.0.0.0.dacpac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeklearningio/gl-vsts-tasks-azure/5da85062a3925111f3b87469220fe04ed3e3956c/Tests/PowerShell3/GeekLearning.VstsTasks.Azure.Tests/SampleDirectory/SqlMultiDacpacDeployment/1.0.0.0.dacpac -------------------------------------------------------------------------------- /Tests/PowerShell3/GeekLearning.VstsTasks.Azure.Tests/SampleDirectory/SqlMultiDacpacDeployment/1.0.0.1.dacpac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeklearningio/gl-vsts-tasks-azure/5da85062a3925111f3b87469220fe04ed3e3956c/Tests/PowerShell3/GeekLearning.VstsTasks.Azure.Tests/SampleDirectory/SqlMultiDacpacDeployment/1.0.0.1.dacpac -------------------------------------------------------------------------------- /Tests/PowerShell3/GeekLearning.VstsTasks.Azure.Tests/SampleDirectory/SqlMultiDacpacDeployment/GeekLearning.Tasks.Database.dacpac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeklearningio/gl-vsts-tasks-azure/5da85062a3925111f3b87469220fe04ed3e3956c/Tests/PowerShell3/GeekLearning.VstsTasks.Azure.Tests/SampleDirectory/SqlMultiDacpacDeployment/GeekLearning.Tasks.Database.dacpac -------------------------------------------------------------------------------- /Tests/PowerShell3/GeekLearning.VstsTasks.Azure.Tests/SqlMultiDacpacDeployment.Tests.ps1: -------------------------------------------------------------------------------- 1 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | . "$here/Helpers.ps1" 3 | 4 | $config = Get-TestsConfiguration -invocationCommandPath $MyInvocation.MyCommand.Path 5 | 6 | Import-Module $config.taskLibPath -ArgumentList @{ NonInteractive = $true } 7 | 8 | Describe "SqlMultiDacpacDeployment" { 9 | 10 | Context "With correctly configured Azure Endpoint" { 11 | 12 | Add-AzureEndpoint -config $config 13 | Add-EnvironmentVariable -name "BUILD_SOURCESDIRECTORY" -value $here 14 | 15 | It "Runs" { 16 | Add-VstsInput -name "DacpacFiles" -value $config.settings.SqlMultiDacpacDeployment.DacpacFiles.Replace("`$(Build.SourcesDirectory)", $here) 17 | Add-VstsInput -name "AdditionalArguments" -value $config.settings.SqlMultiDacpacDeployment.AdditionalArguments 18 | Add-VstsInput -name "ServerName" -value $config.settings.SqlMultiDacpacDeployment.ServerName 19 | Add-VstsInput -name "DatabaseName" -value $config.settings.SqlMultiDacpacDeployment.DatabaseName 20 | Add-VstsInput -name "SqlUsername" -value $config.settings.SqlMultiDacpacDeployment.SqlUsername 21 | Add-VstsInput -name "SqlPassword" -value $config.settings.SqlMultiDacpacDeployment.SqlPassword 22 | Add-VstsInput -name "IpDetectionMethod" -value $config.settings.SqlMultiDacpacDeployment.IpDetectionMethod 23 | Add-VstsInput -name "DeleteFirewallRule" -value $config.settings.SqlMultiDacpacDeployment.DeleteFirewallRule.ToString() 24 | 25 | $outputVariable = Invoke-VstsTaskScript -ScriptBlock ([scriptblock]::Create(". $($config.targetScriptPath)")) -Verbose 2>&1 3>&1 4>&1 5>&1 6>&1 | Out-String 26 | $outputVariable | Out-File -FilePath "$here/dump.txt" -Force 27 | Write-VstsAddAttachment -Type "TestLog" -Name "SqlMultiDacpacDeployment-Runs" -Path "$here/dump.txt" 28 | 29 | $outputVariable | Should Not BeLike "*task.logissue type=error*" 30 | } 31 | 32 | It "Runs with only one DACPAC" { 33 | Add-VstsInput -name "DacpacFiles" -value $config.settings.SqlMultiDacpacDeployment.OneDacpacFile.Replace("`$(Build.SourcesDirectory)", $here) 34 | Add-VstsInput -name "AdditionalArguments" -value $config.settings.SqlMultiDacpacDeployment.AdditionalArguments 35 | Add-VstsInput -name "ServerName" -value $config.settings.SqlMultiDacpacDeployment.ServerName 36 | Add-VstsInput -name "DatabaseName" -value $config.settings.SqlMultiDacpacDeployment.DatabaseName 37 | Add-VstsInput -name "SqlUsername" -value $config.settings.SqlMultiDacpacDeployment.SqlUsername 38 | Add-VstsInput -name "SqlPassword" -value $config.settings.SqlMultiDacpacDeployment.SqlPassword 39 | Add-VstsInput -name "IpDetectionMethod" -value $config.settings.SqlMultiDacpacDeployment.IpDetectionMethod 40 | Add-VstsInput -name "DeleteFirewallRule" -value $config.settings.SqlMultiDacpacDeployment.DeleteFirewallRule.ToString() 41 | 42 | $outputVariable = Invoke-VstsTaskScript -ScriptBlock ([scriptblock]::Create(". $($config.targetScriptPath)")) -Verbose 2>&1 3>&1 4>&1 5>&1 6>&1 | Out-String 43 | $outputVariable | Out-File -FilePath "$here/dump.txt" -Force 44 | Write-VstsAddAttachment -Type "TestLog" -Name "SqlMultiDacpacDeployment-Runs" -Path "$here/dump.txt" 45 | 46 | $outputVariable | Should Not BeLike "*task.logissue type=error*" 47 | } 48 | 49 | It "Fails because no DACPAC is found" { 50 | Add-VstsInput -name "DacpacFiles" -value "whatever\\*.any" 51 | Add-VstsInput -name "AdditionalArguments" -value $config.settings.SqlMultiDacpacDeployment.AdditionalArguments 52 | Add-VstsInput -name "ServerName" -value $config.settings.SqlMultiDacpacDeployment.ServerName 53 | Add-VstsInput -name "DatabaseName" -value $config.settings.SqlMultiDacpacDeployment.DatabaseName 54 | Add-VstsInput -name "SqlUsername" -value $config.settings.SqlMultiDacpacDeployment.SqlUsername 55 | Add-VstsInput -name "SqlPassword" -value $config.settings.SqlMultiDacpacDeployment.SqlPassword 56 | Add-VstsInput -name "IpDetectionMethod" -value $config.settings.SqlMultiDacpacDeployment.IpDetectionMethod 57 | Add-VstsInput -name "DeleteFirewallRule" -value $config.settings.SqlMultiDacpacDeployment.DeleteFirewallRule.ToString() 58 | 59 | $outputVariable = Invoke-VstsTaskScript -ScriptBlock ([scriptblock]::Create(". $($config.targetScriptPath)")) -Verbose 2>&1 3>&1 4>&1 5>&1 6>&1 | Out-String 60 | $outputVariable | Out-File -FilePath "$here/dump.txt" -Force 61 | Write-VstsAddAttachment -Type "TestLog" -Name "SqlMultiDacpacDeployment-Fails because no DACPAC is found" -Path "$here/dump.txt" 62 | 63 | $outputVariable | Should BeLike "*task.logissue type=error*" 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /Tests/PowerShell3/GeekLearning.VstsTasks.Azure.Tests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "VstsTaskSdkPath": "node_modules/vsts-task-sdk/VstsTaskSdk", 3 | "VstsTasksPath": "../../../Tasks", 4 | "AzureAccount": { 5 | "ConnectionName": "", 6 | "SubscriptionId": "", 7 | "SubscriptionName": "", 8 | "ServicePrincipalClientId": "", 9 | "ServicePrincipalKey": "", 10 | "TenantId": "" 11 | }, 12 | "SqlMultiDacpacDeployment": { 13 | "DacpacFiles": "$(Build.SourcesDirectory)\\SampleDirectory\\SqlMultiDacpacDeployment\\*.dacpac", 14 | "OneDacpacFile": "$(Build.SourcesDirectory)\\SampleDirectory\\SqlMultiDacpacDeployment\\GeekLearning.Tasks.Database.dacpac", 15 | "AdditionalArguments": "/p:DropObjectsNotInSource=True /p:DoNotDropObjectTypes=users /p:ExcludeObjectTypes=RoleMembership;users /p:BlockOnPossibleDataLoss=False", 16 | "ServerName": "", 17 | "DatabaseName": "", 18 | "SqlUsername": "", 19 | "SqlPassword": "", 20 | "IpDetectionMethod": "AutoDetect", 21 | "DeleteFirewallRule": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/PowerShell3/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ "src", "tests" ], 3 | "sdk": { 4 | "version": "1.0.0-preview2-003121" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "environments": [ 3 | { 4 | "Name": "production", 5 | "VssExtensionIdSuffix": "", 6 | "VssExtensionGalleryFlags": [ 7 | "Public" 8 | ], 9 | "DisplayNamesSuffix": "", 10 | "TaskIds": { 11 | "AzCopy" : "d66b2794-5008-46fc-ad96-77d90c2e5615", 12 | "RestoreSqlDatabaseToSqlDatabase": "56EFEADB-7DF6-4DAC-8163-AF8E07298C8B", 13 | "StartWebApp": "DF1611D6-6AEE-40B5-8AF6-143AF94D9F3E", 14 | "StopWebApp": "A3F35BB6-5341-413A-8091-10F93D390BB5", 15 | "SwapSlots": "8698E535-D070-4A41-8C0E-4624D6283F05", 16 | "ExecuteSql": "A93D571D-F32C-4FE7-A63A-3599DDDD5279", 17 | "SqlMultiDacpacDeployment": "fc7c81bb-92a2-4750-a0c9-d0d88c749ac0" 18 | } 19 | }, 20 | { 21 | "Name": "preview", 22 | "VssExtensionIdSuffix": "-preview", 23 | "VssExtensionGalleryFlags": [], 24 | "DisplayNamesSuffix": " (Preview)", 25 | "TaskIds": { 26 | "AzCopy" : "0c29237d-1bbb-4172-9d54-8ee559104a82", 27 | "RestoreSqlDatabaseToSqlDatabase": "9FF8A021-58DA-4386-91DB-728B064D11FB", 28 | "StartWebApp": "3543A9E7-FF08-47C5-A8F4-D9FBB5D5F345", 29 | "StopWebApp": "4F59C1AE-AA86-4FAA-8E0C-D632C8CC07A7", 30 | "SwapSlots": "714D2CD7-B76B-4E1D-BE59-B66C4001A89E", 31 | "ExecuteSql": "CF2009AD-2816-4A2B-BBCF-2004C769555B", 32 | "SqlMultiDacpacDeployment": "9b319c14-18ab-4e76-84e9-443f6efd5722" 33 | } 34 | }, 35 | { 36 | "Name": "dev", 37 | "VssExtensionIdSuffix": "-dev", 38 | "VssExtensionGalleryFlags": [], 39 | "DisplayNamesSuffix": " (Development)", 40 | "TaskIds": { 41 | "AzCopy" : "c76ec66c-c6ae-4687-a978-b02440bcc8e6", 42 | "RestoreSqlDatabaseToSqlDatabase": "AEE6A733-4997-40A0-AA39-D23EFC10BBBC", 43 | "StartWebApp": "14118FFB-528E-44CC-9F3C-E9078400AC6F", 44 | "StopWebApp": "8E2A112A-F09C-4C9D-A73B-41D94B2389E4", 45 | "SwapSlots": "D5DABE39-CC68-4B5A-B068-A248DF24A754", 46 | "ExecuteSql": "01450B61-71C8-4AE1-B38D-FB7444C5A87F", 47 | "SqlMultiDacpacDeployment": "14d05d66-7359-4996-b54b-0b870c86d22b" 48 | } 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gl-vsts-tasks-azure", 3 | "version": "0.1.0", 4 | "description": "Visual Studio Team Services Build and Release Management extensions that help you to build and publish your applications on Microsoft Azure", 5 | "main": "gulpfile.js", 6 | "scripts": { 7 | "clean": "vsts-build-tools-clean", 8 | "postinstall": "vsts-build-tools-install", 9 | "prebuild": "vsts-build-tools-prebuild", 10 | "build": "tsc", 11 | "package": "vsts-build-tools-package", 12 | "build:test": "run-s build test", 13 | "test": "jasmine JASMINE_CONFIG_PATH=./Tests/Node/jasmine.json", 14 | "test:chutzpah": "chutzpah.console ./Tests", 15 | "fix:prettier": "prettier Tasks/**/*.ts --write" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/geeklearningio/gl-vsts-tasks-azure" 20 | }, 21 | "keywords": [ 22 | "VSTS", 23 | "build", 24 | "tasks", 25 | "Azure" 26 | ], 27 | "author": "Geek Learning, Adrien Siffermann, Cyprien Autexier", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/geeklearningio/gl-vsts-tasks-azure/issues" 31 | }, 32 | "homepage": "https://github.com/geeklearningio/gl-vsts-tasks-azure/wiki", 33 | "devDependencies": { 34 | "@types/fs-extra": "^5.0.5", 35 | "@types/ini": "^1.3.30", 36 | "@types/jasmine": "2.8.4", 37 | "@types/node": "^6.14.3", 38 | "@types/q": "^1.5.1", 39 | "@types/xregexp": "^3.0.29", 40 | "@typescript-eslint/eslint-plugin": "^1.9.0", 41 | "@typescript-eslint/parser": "^1.9.0", 42 | "async": "^3.2.0", 43 | "fs-extra": "9.0.1", 44 | "gl-vsts-tasks-build-scripts": "^0.6.0-alpha.0", 45 | "jasmine": "3.5.0", 46 | "lodash": "^4.17.15", 47 | "npm-run-all": "4.1.5", 48 | "eslint": "^5.16.0", 49 | "typescript": "^3.9.5", 50 | "prettier": "2.0.5" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "noImplicitAny": true, 6 | "removeComments": true, 7 | "preserveConstEnums": true, 8 | "target": "es6", 9 | "sourceMap": false, 10 | "inlineSourceMap": true, 11 | "declaration": true 12 | }, 13 | "filesGlob": [ 14 | "./Tasks/*.ts", 15 | "./Tests/*.ts", 16 | "./typings/index.d.ts", 17 | "./Commom/Node/*.d.ts" 18 | ], 19 | "exclude": [ 20 | "node_modules", 21 | "Tasks/AzCopy/node_modules", 22 | "Common", 23 | "BuildScripts/node_modules", 24 | ".BuildOutput" 25 | ] 26 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended" 3 | } -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gl-vsts-tasks-azure", 3 | "dependencies": {}, 4 | "globalDependencies": { 5 | "fs-extra": "registry:dt/fs-extra#0.0.0+20160907105211", 6 | "jasmine": "registry:dt/jasmine#2.2.0+20160621224255", 7 | "minimatch": "registry:dt/minimatch#2.0.8+20160317120654", 8 | "node": "registry:dt/node#6.0.0+20160907103612", 9 | "q": "registry:dt/q#0.0.0+20160613154756", 10 | "xregexp": "registry:dt/xregexp#3.0.0+20160317120654" 11 | } 12 | } --------------------------------------------------------------------------------