Synapse/src/bicep-deployment/Pattern2/PowerShellScripts/SynapseDeployArtifacts.ps1 (441 lines of code) (raw):
<#
.NOTES
=========================================================================================================
Created by: Author: Analytics Fundamentals - FTA Toolkit Team
Created on: 09/13/2022
=========================================================================================================
.DESCRIPTION
You can run the script one of two ways:
1. Using Inline Parameters
.\SynapseDeployArtifacts.ps1 -SubscriptionID xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -ResourceGroupName P2-AnalyticsFundamentals-RG -ResourceGroupLocation eastus -KeyVaultName ftatoolkit-keyvault-xxx -KeyVaultID /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/P2-AnalyticsFundamentals-RG/providers/Microsoft.KeyVault/vaults/ftatoolkit-keyvault-xxx -SynapseWorkspaceName ftatoolkit-synapse-xxx -SynapseWorkspaceID /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/P2-AnalyticsFundamentals-RG/providers/Microsoft.Synapse/workspaces/ftatoolkit-synapse-xxx -DataLakeAccountName ftatoolkitadlsxxx -DataLakeAccountResourceID /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/P2-AnalyticsFundamentals-RG/providers/Microsoft.Storage/storageAccounts/ftatoolkitadlsxxx -AzureSQLServerName ftatoolkit-sql-xxx -UAMIPrincipalID xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -CtrlDeploySampleArtifacts $True -SampleArtifactCollectioName OpenDatasets
#>
#---------------------------------------------------------[Parameters]-----------------------------------------------------
#region Parameters
param(
[string] $SubscriptionID,
[string] $ResourceGroupName,
[string] $ResourceGroupLocation,
[string] $SynapseWorkspaceName,
[string] $SynapseWorkspaceID,
[string] $DataLakeAccountName,
[string] $DataLakeAccountResourceID,
[string] $KeyVaultName,
[string] $KeyVaultID,
[string] $AzureSQLServerName,
[string] $UAMIPrincipalID,
[Parameter(Mandatory=$false)]
[bool] $CtrlDeploySampleArtifacts,
[AllowEmptyString()]
[Parameter(Mandatory=$false)]
[string] $SampleArtifactCollectioName
)
#>
#endregion Parameters
Clear-Host
#------------------------------------------------------------------------------------------------------------
# FUNCTION DEFINITIONS
#------------------------------------------------------------------------------------------------------------
function Set-SynapseControlPlaneOperation{
param (
[string] $SynapseWorkspaceID,
[string] $HttpRequestBody
)
$uri = "https://management.azure.com$SynapseWorkspaceID`?api-version=2021-06-01"
$token = (Get-AzAccessToken -Resource "https://management.azure.com").Token
$headers = @{ Authorization = "Bearer $token" }
$retrycount = 1
$completed = $false
$secondsDelay = 60
while (-not $completed) {
try {
Invoke-RestMethod -Method Patch -ContentType "application/json" -Uri $uri -Headers $headers -Body $HttpRequestBody -ErrorAction Stop
Write-Host "Control plane operation completed successfully."
$completed = $true
}
catch {
if ($retrycount -ge $retries) {
Write-Host "Control plane operation failed the maximum number of $retryCount times."
Write-Warning $Error[0]
throw
} else {
Write-Host "Control plane operation failed $retryCount time(s). Retrying in $secondsDelay seconds."
Write-Warning $Error[0]
Start-Sleep $secondsDelay
$retrycount++
}
}
}
}
function Save-SynapseLinkedService{
param (
[string] $SynapseWorkspaceName,
[string] $LinkedServiceName,
[string] $LinkedServiceRequestBody
)
[string] $uri = "https://$SynapseWorkspaceName.dev.azuresynapse.net/linkedservices/$LinkedServiceName"
$uri += "?api-version=2019-06-01-preview"
Write-Host "Creating Linked Service [$LinkedServiceName]..."
$retrycount = 1
$completed = $false
$secondsDelay = 60
while (-not $completed) {
try {
Write-Host "URI: $uri"
Write-Host "Body: $LinkedServiceRequestBody"
Write-Host "Headers: $headers"
Invoke-RestMethod -Method Put -ContentType "application/json" -Uri $uri -Headers $headers -Body $LinkedServiceRequestBody -ErrorAction Stop
Write-Host "Linked service [$LinkedServiceName] created successfully."
$completed = $true
}
catch {
if ($retrycount -ge $retries) {
Write-Host "Linked service [$LinkedServiceName] creation failed the maximum number of $retryCount times."
Write-Warning $Error[0]
throw
} else {
Write-Host "Linked service [$LinkedServiceName] creation failed $retryCount time(s). Retrying in $secondsDelay seconds."
Write-Warning $Error[0]
Start-Sleep $secondsDelay
$retrycount++
}
}
}
}
function Save-SynapseSampleArtifacts{
param (
[string] $SynapseWorkspaceName,
[string] $SampleArtifactCollectionName
)
#Install Synapse PowerShell Module
#You need to install latest Az.Synapse module and make sure that Az.Account Module is at least at Version 2.10
if (Get-Module -ListAvailable -Name "Az.Synapse") {
Write-Host "PowerShell Module Az.Synapse already installed."
}
else {
Install-Module Az.Synapse -Force
Import-Module Az.Synapse
}
#Add System.Web type to encode/decode URL
Add-Type -AssemblyName System.Web
#Authenticate for REST API calls
$token = (Get-AzAccessToken -Resource "https://dev.azuresynapse.net").Token
$headers = @{ Authorization = "Bearer $token" }
$synapseTokens = @{"`#`#azsynapsewks`#`#" = $SynapseWorkspaceName; }
$indexFileUrl = "https://raw.githubusercontent.com/AndresPad/FTAToolkitArtifacts/main/Sample/index.json"
#$indexFileUrl = "index.json"
#$sampleCodeIndex = Get-Content -Raw -Path index.json | ConvertFrom-Json
$sampleCodeIndex = Invoke-WebRequest $indexFileUrl | ConvertFrom-Json
Write-Host "Deploying SynapseSampleArtifacts:"
foreach($sampleArtifactCollection in $sampleCodeIndex)
{
if ($sampleArtifactCollection.template -eq $SampleArtifactCollectionName) {
Write-Host "Deploying Sample Artifact Collection: $($sampleArtifactCollection.template)"
Write-Host "-----------------------------------------------------------------------"
#Create SQL Script artifacts.
#----------------------------------------------------------------------------------
Write-Host "Deploying SQL Scripts:"
Write-Host "-----------------------------------------------------------------------"
foreach($sqlScript in $sampleArtifactCollection.artifacts.sqlScripts)
{
$fileContent = Invoke-WebRequest $sqlScript.definitionFilePath
if ($sqlScript.tokens.length -gt 0) {
foreach($token in $sqlScript.tokens)
{
$fileContent = $fileContent -replace $token, $synapseTokens.Get_Item($token)
}
}
if ($sqlScript.interface.ToLower() -eq "powershell") {
Write-Host "Creating SQL Script: $($sqlScript.name) via PowerShell"
$definitionFilePath = [guid]::NewGuid()
Set-Content -Path $definitionFilePath $fileContent
Set-AzSynapseSqlScript -WorkspaceName $SynapseWorkspaceName -Name $sqlScript.name -DefinitionFile $definitionFilePath -FolderPath $sqlScript.workspaceFolderPath
Remove-Item -Path $definitionFilePath
}
elseif ($sqlScript.interface.ToLower() -eq "rest")
{
Write-Host "Creating SQL Script: $($sqlScript.name) via REST API"
$subresource = "sqlScripts"
$uri = "https://$SynapseWorkspaceName.dev.azuresynapse.net/$subresource/$($sqlScript.name)?api-version=2020-02-01"
#Assign Synapse Workspace Administrator Role to UAMI
$body = $fileContent
Invoke-RestMethod -Method Put -ContentType "application/json" -Uri $uri -Headers $headers -Body $body
}
}
#Create Linked Service artifacts.
#----------------------------------------------------------------------------------
Write-Host "-----------------------------------------------------------------------"
Write-Host "Deploying Linked Service:"
Write-Host "-----------------------------------------------------------------------"
foreach($linkedService in $sampleArtifactCollection.artifacts.linkedServices)
{
$fileContent = Invoke-WebRequest $linkedService.definitionFilePath
if ($linkedService.tokens.length -gt 0) {
foreach($token in $linkedService.tokens)
{
$fileContent = $fileContent -replace $token, $synapseTokens.Get_Item($token)
}
}
$definitionFilePath = [guid]::NewGuid()
Set-Content -Path $definitionFilePath $fileContent
Set-AzSynapseLinkedService -WorkspaceName $SynapseWorkspaceName -Name $linkedService.name -DefinitionFile $definitionFilePath
Remove-Item -Path $definitionFilePath
}
#Create Dataset artifacts.
#----------------------------------------------------------------------------------
Write-Host "-----------------------------------------------------------------------"
Write-Host "Deploying Datasets:"
Write-Host "-----------------------------------------------------------------------"
foreach($dataset in $sampleArtifactCollection.artifacts.datasets)
{
$fileContent = Invoke-WebRequest $dataset.definitionFilePath
if ($dataset.tokens.length -gt 0) {
foreach($token in $dataset.tokens)
{
$fileContent = $fileContent -replace $token, $synapseTokens.Get_Item($token)
}
}
$definitionFilePath = [guid]::NewGuid()
Set-Content -Path $definitionFilePath $fileContent
Set-AzSynapseDataset -WorkspaceName $SynapseWorkspaceName -Name $dataset.name -DefinitionFile $definitionFilePath
Remove-Item -Path $definitionFilePath
}
#Create Dataflows artifacts.
#----------------------------------------------------------------------------------
Write-Host "-----------------------------------------------------------------------"
Write-Host "Deploying Dataflows:"
Write-Host "-----------------------------------------------------------------------"
foreach($dataflow in $sampleArtifactCollection.artifacts.dataflows)
{
$fileContent = Invoke-WebRequest $dataflow.definitionFilePath
if ($dataflow.tokens.length -gt 0) {
foreach($token in $dataflow.tokens)
{
$fileContent = $fileContent -replace $token, $synapseTokens.Get_Item($token)
}
}
$definitionFilePath = [guid]::NewGuid()
Set-Content -Path $definitionFilePath $fileContent
Set-AzSynapseDataFlow -WorkspaceName $SynapseWorkspaceName -Name $dataflow.name -DefinitionFile $definitionFilePath
Remove-Item -Path $definitionFilePath
}
#Create Pipeline artifacts.
#----------------------------------------------------------------------------------
Write-Host "-----------------------------------------------------------------------"
Write-Host "Deploying Pipelines:"
Write-Host "-----------------------------------------------------------------------"
foreach($pipeline in $sampleArtifactCollection.artifacts.pipelines)
{
$fileContent = Invoke-WebRequest $pipeline.definitionFilePath
if ($pipeline.tokens.length -gt 0) {
foreach($token in $pipeline.tokens)
{
$fileContent = $fileContent -replace $token, $synapseTokens.Get_Item($token)
}
}
$definitionFilePath = [guid]::NewGuid()
Set-Content -Path $definitionFilePath $fileContent
Set-AzSynapsePipeline -WorkspaceName $SynapseWorkspaceName -Name $pipeline.name -DefinitionFile $definitionFilePath
Remove-Item -Path $definitionFilePath
}
#Create Notebook artifacts.
#----------------------------------------------------------------------------------
Write-Host "-----------------------------------------------------------------------"
Write-Host "Deploying Notebooks:"
Write-Host "-----------------------------------------------------------------------"
foreach($notebook in $sampleArtifactCollection.artifacts.notebooks)
{
$fileContent = Invoke-WebRequest $notebook.definitionFilePath
if ($notebook.tokens.length -gt 0) {
foreach($token in $notebook.tokens)
{
$fileContent = $fileContent -replace $token, $synapseTokens.Get_Item($token)
}
}
if ($notebook.interface.ToLower() -eq "powershell") {
$definitionFilePath = [guid]::NewGuid()
Set-Content -Path $definitionFilePath $fileContent
Set-AzSynapseNotebook -WorkspaceName $SynapseWorkspaceName -Name $notebook.name -DefinitionFile $definitionFilePath -FolderPath $notebook.workspaceFolderPath
Remove-Item -Path $definitionFilePath
}
elseif ($notebook.interface.ToLower() -eq "rest") {
## Action to perform if the condition is true #>
}
}
break
}
}
}
$retries = 10
$secondsDelay = 60
#---------------------------------------------------------[Entry Point - Execution of Script Starts Here]-----------------------------------------------------
#region Entry Point - MAIN SCRIPT BODY
Write-Host "#--------------------------------------------------------------------------------------------------------";
Write-Host " Analytics Fundamentals - Synapse Analytics Workspace Artifacts Creation" -ForegroundColor Gray
Write-Output " Display Params"
$output = 'Your Params {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}' -f $SubscriptionID, $ResourceGroupName, $ResourceGroupLocation, $SynapseWorkspaceName, $SynapseWorkspaceID, $DataLakeAccountName, $DataLakeAccountResourceID, $KeyVaultName, $KeyVaultID, $AzureSQLServerName, $UAMIPrincipalID
Write-Host " SubscriptionID: $SubscriptionID "
Write-Host " ResourceGroupName: $ResourceGroupName "
Write-Host " ResourceGroupLocation: $ResourceGroupLocation "
Write-Host " SynapseWorkspaceName: $SynapseWorkspaceName "
Write-Host " SynapseWorkspaceID: $SynapseWorkspaceID "
Write-Host " DataLakeAccountName: $DataLakeAccountName "
Write-Host " DataLakeAccountResourceID: $DataLakeAccountResourceID "
Write-Host " KeyVaultName: $KeyVaultName "
Write-Host " KeyVaultID: $KeyVaultID "
Write-Host " AzureSQLServerName: $AzureSQLServerName "
Write-Host " UAMIPrincipalID: $UAMIPrincipalID "
$DeploymentScriptOutputs = @{}
$DeploymentScriptOutputs['text'] = $output
#------------------------------------------------------------------------------------------------------------
# CONTROL PLANE OPERATION: ASSIGN SYNAPSE WORKSPACE ADMINISTRATOR TO USER-ASSIGNED MANAGED IDENTITY
# UAMI needs Synapse Admin rights before it can make calls to the Data Plane APIs to create Synapse objects
#------------------------------------------------------------------------------------------------------------
$token = (Get-AzAccessToken -Resource "https://dev.azuresynapse.net").Token
$headers = @{ Authorization = "Bearer $token" }
$uri = "https://$SynapseWorkspaceName.dev.azuresynapse.net/rbac/roleAssignments?api-version=2020-02-01-preview"
#Assign Synapse Workspace Administrator Role to UAMI
$body = "{
roleId: ""6e4bf58a-b8e1-4cc3-bbf9-d73143322b78"",
principalId: ""$UAMIPrincipalID""
}"
Write-Host "Assign Synapse Administrator Role to UAMI..."
Invoke-RestMethod -Method Post -ContentType "application/json" -Uri $uri -Headers $headers -Body $body
#------------------------------------------------------------------------------------------------------------
# DATA PLANE OPERATION: CREATE AZURE KEY VAULT LINKED SERVICE
#------------------------------------------------------------------------------------------------------------
#Create AKV Linked Service. Linked Service name same as Key Vault's.
$body = "{
name: ""$KeyVaultName"",
properties: {
annotations: [],
type: ""AzureKeyVault"",
typeProperties: {
baseUrl: ""https://$KeyVaultName.vault.azure.net/""
}
}
}"
Write-Host "Create AKV Linked Service..."
Save-SynapseLinkedService $SynapseWorkspaceName $KeyVaultName $body
#Create a Second AKV Linked Service that uses parameters to connect to the Key Vault created through the bicep
$body = "{
name: ""$KeyVaultName-with-params"",
properties: {
parameters: {
keyVaultName: {
type: ""string"",
defaultValue: ""$KeyVaultName""
}
},
annotations: [],
type: ""AzureKeyVault"",
typeProperties: {
baseUrl: ""@{concat('https://',linkedService().keyVaultName,'.vault.azure.net/')}""
}
}
}"
Write-Host "Create AKV Linked Service with parameters..."
Save-SynapseLinkedService $SynapseWorkspaceName "$KeyVaultName-with-params" $body
#------------------------------------------------------------------------------------------------------------
# DATA PLANE OPERATION: CREATE ADLS LINKED SERVICE
#------------------------------------------------------------------------------------------------------------
$dataLakeAccountNames = @($DataLakeAccountName)
$dataLakeDFSEndpoints = @("https://$DataLakeAccountName.dfs.core.windows.net")
for ($i = 0; $i -lt $dataLakeAccountNames.Length; $i++) {
$body = "{
name: ""$($dataLakeAccountNames[$i])"",
properties: {
annotations: [],
type: ""AzureBlobFS"",
typeProperties: {
url: ""$($dataLakeDFSEndpoints[$i])""
},
connectVia: {
referenceName: ""AutoResolveIntegrationRuntime"",
type: ""IntegrationRuntimeReference""
}
}
}"
Write-Host "Create DataLake Linked Service..."
Save-SynapseLinkedService $SynapseWorkspaceName $DataLakeAccountName $body
}
$body = "{
name: ""TripFaresDataLakeStorageLS"",
properties: {
parameters: {
keyVaultName: {
type: ""string"",
defaultValue: ""$KeyVaultName""
},
datalakeAccountName: {
type: ""string"",
defaultValue: ""$DataLakeAccountName""
}
},
annotations: [],
type: ""AzureBlobFS"",
typeProperties: {
url: ""@{concat('https://',linkedService().datalakeAccountName,'.dfs.core.windows.net')}"",
accountKey: {
type: ""AzureKeyVaultSecret"",
store: {
referenceName: ""keyVaultLinkedservice"",
type: ""LinkedServiceReference"",
parameters: {
keyVaultName: {
value: ""@linkedService().keyVaultName"",
type: ""Expression""
}
}
},
secretName: ""ADLS--AccountKey""
}
},
connectVia: {
referenceName: ""AutoResolveIntegrationRuntime"",
type: ""IntegrationRuntimeReference""
}
}
}"
Write-Host "Create DataLake Linked Service..."
Save-SynapseLinkedService $SynapseWorkspaceName "TripFaresDataLakeStorageLS" $body
#upload sample csv to public container of newly created storage account
$secret = Get-AzKeyVaultSecret -VaultName $KeyVaultName -Name "ADLS--AccountKey" -AsPlainText
$context = New-AzStorageContext -StorageAccountName $DataLakeAccountName -StorageAccountKey $secret
Start-AzStorageBlobCopy -AbsoluteUri "https://raw.githubusercontent.com/Azure/Test-Drive-Azure-Synapse-with-a-1-click-POC/main/tripDataAndFaresCSV/trip-data.csv" -DestContainer "public" -DestBlob "trip-data.csv" -DestContext $context -Force
Start-AzStorageBlobCopy -AbsoluteUri "https://raw.githubusercontent.com/Azure/Test-Drive-Azure-Synapse-with-a-1-click-POC/main/tripDataAndFaresCSV/fares-data.csv" -DestContainer "public" -DestBlob "fares-data.csv" -DestContext $context -Force
#------------------------------------------------------------------------------------------------------------
# DATA PLANE OPERATION: CREATE SQL SERVER LINKED SERVICE
#------------------------------------------------------------------------------------------------------------
$sqlAccountName = $AzureSQLServerName -replace ("-","_")
Write-Host "AzureSQLServerName: $AzureSQLServerName"
$body = "{
name: ""$($AzureSQLServerName)"",
properties: {
annotations: [],
type: ""AzureSqlDatabase"",
typeProperties: {
connectionString: {
type: ""AzureKeyVaultSecret"",
store: {
referenceName: ""$KeyVaultName"",
type: ""LinkedServiceReference""
},
secretName: ""ConnectionStrings--CnxDB""
}
},
connectVia: {
referenceName: ""AutoResolveIntegrationRuntime"",
type: ""IntegrationRuntimeReference""
},
description: ""This link service connects to the sql server sampldb database...""
}
}"
Save-SynapseLinkedService $SynapseWorkspaceName $AzureSQLServerName $body
#------------------------------------------------------------------------------------------------------------
# DATA PLANE OPERATION: DEPLOY SAMPLE ARTIFACTS
# Deploy sample artifcats (SQL Scripts, Datasets, Linked Services, Pipelines and Notebooks) based on chosen template.
#------------------------------------------------------------------------------------------------------------
if ($CtrlDeploySampleArtifacts) {
Save-SynapseSampleArtifacts $SynapseWorkspaceName $SampleArtifactCollectioName
}
#endregion Entry Point