Skip to content

Commit

Permalink
feat: Added "Set Entities" script for Azure Table storage (#222)
Browse files Browse the repository at this point in the history
Co-authored-by: Stijn Moreels <[email protected]>
Co-authored-by: Pim Simons <[email protected]>
Co-authored-by: Pim Simons <[email protected]>
  • Loading branch information
4 people authored May 31, 2023
1 parent 12902f5 commit 1cb37f2
Show file tree
Hide file tree
Showing 15 changed files with 482 additions and 2 deletions.
8 changes: 7 additions & 1 deletion build/templates/run-pester-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ parameters:
projectName: ''

steps:
- pwsh: 'wget -O - https://aka.ms/install-powershell.sh | sudo bash'
displayName: 'Update Powershell on Linux'
condition: eq( variables['Agent.OS'], 'Linux' )
- powershell: 'Invoke-Expression -Command "& { $(Invoke-RestMethod -Uri ''https://aka.ms/install-powershell.ps1'') } -UseMSI -quiet" '
displayName: 'Update Powershell on Windows'
condition: eq( variables['Agent.OS'], 'Windows_NT' )
- bash: |
if [ -z "$PROJECT_NAME" ]; then
echo "##vso[task.logissue type=error;]Missing template parameter \"projectName\""
Expand Down Expand Up @@ -37,7 +43,7 @@ steps:
Install-Module -Name Microsoft.Graph.Applications -Force -SkipPublisherCheck -MaximumVersion 1.15.0
Write-Host "Done installing, start importing modules"
displayName: 'Install Pester test framework and Az required modules'
- powershell: |
- pwsh: |
Import-Module ./src/Arcus.Scripting.Security
Get-ChildItem -Path ./src -Filter *.psm1 -Recurse -Exclude "*Arcus.Scripting.Security*", "*.All.psm1" |
% { Write-Host "Import $($_.DirectoryName) module"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,81 @@ PS> Create-AzStorageTable `
# Failed to re-create the Azure storage table 'products' in Azure storage account 'admin', retrying in 5 seconds...
# Azure storage table 'products' created in Azure storage account 'admin'
```


## Set the entities in a table of an Azure Storage Account

Deletes all entities of a specified table in an Azure Storage Account and creates new entities based on a configuration file.

| Parameter | Mandatory | Description |
| ---------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `ResourceGroupName` | yes | The resource group where the Azure Storage Account is located |
| `StorageAccountName` | yes | The name of the Azure Storage Account that contains the table |
| `TableName` | yes | The name of the table in which the entities should be set |
| `ConfigurationFile` | yes | Path to the JSON Configuration file containing all the entities to be set |

**Configuration File**

The configuration file is a simple JSON file that contains all of the entities that should be set on the specified table, the JSON file consists of an array of JSON objects (= your entities). Each object contains simple name-value pairs (string-string).

Defining the PartitionKey and/or RowKey are optional, if not provided a random GUID will be set for these.

The file needs to adhere to the following JSON schema:

``` json
{
"definitions": {},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://scripting.arcus-azure.net/Features/powershell/azure-storage/azure-storage-table/config.json",
"type": "array",
"title": "The configuration JSON schema",
"items": [{
"type": "object",
"patternProperties": {
"^.*$": {
"anyOf": [{
"type": "string"
}, {
"type": "null"
}
]
}
},
"additionalProperties": false
}
]
}
```

**Example Configuration File**

```json
[
{
"PartitionKey": "SystemA",
"RowKey": "100",
"ReadPath": "/home/in",
"ReadIntervalInSeconds": "30"
},
{
"PartitionKey": "SystemA",
"RowKey": "200",
"ReadPath": "/data/in",
"ReadIntervalInSeconds": "10",
"HasSubdirectories": "true"
}
]
```

**Example**

```powershell
PS> Set-AzTableStorageEntities `
-ResourceGroupName "someresourcegroup" `
-StorageAccountName "somestorageaccount" `
-TableName "sometable" `
-ConfigurationFile ".\config.json"
# Deleting all existing entities in Azure storage table 'sometable' for Azure storage account 'somestorageaccount' in resource group 'someresourcegroup'...
# Successfully deleted all existing entities in Azure storage table 'sometable' for Azure storage account 'somestorageaccount' in resource group 'someresourcegroup'
# Successfully added all entities in Azure storage table 'sometable' for Azure storage account 'somestorageaccount' in resource group 'someresourcegroup'
```
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,48 @@ function Create-AzStorageTable {
}
}

Export-ModuleMember -Function Create-AzStorageTable
Export-ModuleMember -Function Create-AzStorageTable



<#
.Synopsis
Sets the entities in a specified table storage.
.Description
Sets (Delete & Add) the entities in a specified table storage to the values specified in a (JSON) configuration file.
.Parameter ResourceGroupName
The resource group where the Azure Storage Account is located.
.Parameter StorageAccountName
The name of the Azure Storage Account which holds the Azure table storage.
.Parameter TableName
The name of the table on the Azure Storage Account.
.Parameter ConfigurationFile
The Configuration File (JSON) specifying the entities that have to be set in the table storage. The JSON file has a single property "entities" which is an array of objects.
.Parameter RetryIntervalSeconds
The optional amount of seconds to wait each retry-run when a failure occures during the re-creating process.
.Parameter MaxRetryCount
The optional maximum amount of retry-runs should happen when a failure occurs during the re-creating process.
#>
function Set-AzTableStorageEntities {
param(
[Parameter(Mandatory = $true)][string] $ResourceGroupName = $(throw "Name of resource group is required"),
[Parameter(Mandatory = $true)][string] $StorageAccountName = $(throw "Name of Azure storage account is required"),
[Parameter(Mandatory = $true)][string] $TableName = $(throw "Name of Azure table is required"),
[Parameter(Mandatory = $true)][string] $ConfigurationFile = $(throw "Path to the configuration file is required")
)

. $PSScriptRoot\Scripts\Set-AzTableStorageEntities.ps1 `
-ResourceGroupName $ResourceGroupName `
-StorageAccountName $StorageAccountName `
-TableName $TableName `
-ConfigurationFile $ConfigurationFile
}

Export-ModuleMember -Function Set-AzTableStorageEntities
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
param(
[Parameter(Mandatory = $true)][string] $ResourceGroupName = $(throw "Name of resource group is required"),
[Parameter(Mandatory = $true)][string] $StorageAccountName = $(throw "Name of Azure storage account is required"),
[Parameter(Mandatory = $true)][string] $TableName = $(throw "Name of Azure table is required"),
[Parameter(Mandatory = $true)][string] $ConfigurationFile = $(throw "Path to the configuration file is required")
)

if (-not (Test-Path -Path $ConfigurationFile)) {
throw "Cannot re-create entities based on JSON configuration file because no file was found at: '$ConfigurationFile'"
}
if ((Get-Content -Path $ConfigurationFile -Raw) -eq $null) {
throw "Cannot re-create entities based on JSON configuration file because the file is empty."
}

$schema = @'
{
"definitions": {},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://scripting.arcus-azure.net/Features/powershell/azure-storage/azure-storage-table/config.json",
"type": "array",
"title": "The configuration JSON schema",
"items": [{
"type": "object",
"patternProperties": {
"^.*$": {
"anyOf": [{
"type": "string"
}, {
"type": "null"
}
]
}
},
"additionalProperties": false
}
]
}
'@

if (-not (Get-Content -Path $ConfigurationFile -Raw | Test-Json -Schema $schema -ErrorAction SilentlyContinue)) {
throw "Cannot re-create entities based on JSON configuration file because the file does not contain a valid JSON configuration file."
}

Write-Verbose "Retrieving Azure storage account context for Azure storage account '$StorageAccountName' in resource group '$ResourceGroupName'..."
$storageAccount = Get-AzStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName
if ($storageAccount -eq $null) {
throw "Retrieving Azure storage account context for Azure storage account '$StorageAccountName' in resource group '$ResourceGroupName' failed."
}
$ctx = $storageAccount.Context
Write-Verbose "Azure storage account context has been retrieved for Azure storage account '$StorageAccountName' in resource group '$ResourceGroupName'"

Write-Verbose "Retrieving Azure storage table '$TableName' for Azure storage account '$StorageAccountName' in resource group '$ResourceGroupName'..."
$storageTable = Get-AzStorageTable -Name $TableName -Context $ctx
if ($storageTable -eq $null) {
throw "Retrieving Azure storage table '$TableName' for Azure storage account '$StorageAccountName' in resource group '$ResourceGroupName' failed."
}
$cloudTable = ($storageTable).CloudTable
Write-Verbose "Azure storage table '$TableName' has been retrieved for Azure storage account '$StorageAccountName' in resource group '$ResourceGroupName'"

Write-Host "Deleting all existing entities in Azure storage table '$TableName' for Azure storage account '$StorageAccountName' in resource group '$ResourceGroupName'..."
$entitiesToDelete = Get-AzTableRow -table $cloudTable
$deletedEntities = $entitiesToDelete | Remove-AzTableRow -table $cloudTable
Write-Host "Successfully deleted all existing entities in Azure storage table '$TableName' for Azure storage account '$StorageAccountName' in resource group '$ResourceGroupName'"

$configFile = Get-Content -Path $ConfigurationFile | ConvertFrom-Json
foreach ($entityToAdd in $configFile) {
if ($entityToAdd.PartitionKey) {
$partitionKey = $entityToAdd.PartitionKey
$entityToAdd.PSObject.Properties.Remove('PartitionKey')
} else {
$partitionKey = New-Guid
}

if ($entityToAdd.RowKey) {
$rowKey = $entityToAdd.RowKey
$entityToAdd.PSObject.Properties.Remove('RowKey')
} else {
$rowKey = New-Guid
}

$entityHash = @{}
$entityToAdd.PSObject.Properties | foreach { $entityHash[$_.Name] = $_.Value }

$addedRow = Add-AzTableRow `
-table $cloudTable `
-partitionKey $partitionKey `
-rowKey $rowKey `
-property $entityHash

Write-Verbose "Successfully added row with PartitionKey '$partitionKey' and RowKey '$rowKey' to Azure storage table '$TableName' for Azure storage account '$StorageAccountName' in resource group '$ResourceGroupName'"
}

Write-Host "Successfully added all entities in Azure storage table '$TableName' for Azure storage account '$StorageAccountName' in resource group '$ResourceGroupName'"
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,57 @@ InModuleScope Arcus.Scripting.Storage.Table {
}
}
}
Context "Setting Azure Table Storage Entities" {
It "Setting entities in an Azure Table Storage account" {
# Arrange
$resourceGroup = $config.Arcus.ResourceGroupName
$storageAccountName = $config.Arcus.Storage.StorageAccount.Name
$tableName = "SetEntityTable"
$configFile = "$PSScriptRoot\Files\TableStorage\set-aztablestorageentities-config.json"
$storageAccount = Get-AzStorageAccount -ResourceGroupName $resourceGroup -Name $storageAccountName

try {
# Act
Create-AzStorageTable `
-ResourceGroupName $config.Arcus.ResourceGroupName `
-StorageAccountName $config.Arcus.Storage.StorageAccount.Name `
-Table $tableName

Set-AzTableStorageEntities -ResourceGroupName $resourceGroup -StorageAccountName $storageAccountName -TableName $tableName -ConfigurationFile $configFile

# Assert
$storageTable = Get-AzStorageTable –Name $tableName –Context $storageAccount.Context
(Get-AzTableRow -table $storageTable.CloudTable | measure).Count |
Should -Be 2
} finally {
Remove-AzStorageTable -Name $tableName -Context $storageAccount.Context -Force -ErrorAction SilentlyContinue
}
}
It "Setting entities in an Azure Table Storage account without specifying PartitionKey and RowKey" {
# Arrange
$resourceGroup = $config.Arcus.ResourceGroupName
$storageAccountName = $config.Arcus.Storage.StorageAccount.Name
$tableName = "SetEntityTableNoKeys"
$configFile = "$PSScriptRoot\Files\TableStorage\set-aztablestorageentities-config-nokeys.json"
$storageAccount = Get-AzStorageAccount -ResourceGroupName $resourceGroup -Name $storageAccountName

try {
# Act
Create-AzStorageTable `
-ResourceGroupName $config.Arcus.ResourceGroupName `
-StorageAccountName $config.Arcus.Storage.StorageAccount.Name `
-Table $tableName

Set-AzTableStorageEntities -ResourceGroupName $resourceGroup -StorageAccountName $storageAccountName -TableName $tableName -ConfigurationFile $configFile

# Assert
$storageTable = Get-AzStorageTable –Name $tableName –Context $storageAccount.Context
(Get-AzTableRow -table $storageTable.CloudTable | measure).Count |
Should -Be 2
} finally {
Remove-AzStorageTable -Name $tableName -Context $storageAccount.Context -Force -ErrorAction SilentlyContinue
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
<Folder Include="Files\IntegrationAccount\Partners\" />
<Folder Include="Files\IntegrationAccount\Schemas\" />
<Folder Include="Files\IntegrationAccount\Schemas\Common\" />
<Folder Include="Files\TableStorage\" />
<Folder Include="SqlScripts\" />
<Folder Include="SqlScripts\MigrationScriptsAreSuccessfullyExecuted\" />
<Folder Include="SqlScripts\MigrationStopsOnError\" />
Expand Down Expand Up @@ -94,6 +95,8 @@
<Content Include="Files\IntegrationAccount\Schemas\NestedSchema.xsd" />
<Content Include="Files\IntegrationAccount\Schemas\Acknowledgement.xsd" />
<Content Include="Files\IntegrationAccount\Schemas\Common\NestedSchemaRoot.xsd" />
<Content Include="Files\TableStorage\set-aztablestorageentities-config-nokeys.json" />
<Content Include="Files\TableStorage\set-aztablestorageentities-config.json" />
<Content Include="SqlScripts\1_EmptyMigration.sql" />
<Content Include="SqlScripts\MigrationScriptsAreSuccessfullyExecuted\0.0.1_baseline.sql" />
<Content Include="SqlScripts\MigrationScriptsAreSuccessfullyExecuted\0.1.0_AddCustomerTable.sql" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
{
"ReadPath": "/home/in",
"ReadIntervalInSeconds": "30"
},
{
"ReadPath": "/data/in",
"ReadIntervalInSeconds": "10",
"HasSubdirectories": "true"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[
{
"PartitionKey": "SystemA",
"RowKey": "100",
"ReadPath": "/home/in",
"ReadIntervalInSeconds": "30"
},
{
"PartitionKey": "SystemA",
"RowKey": "200",
"ReadPath": "/data/in",
"ReadIntervalInSeconds": "10",
"HasSubdirectories": "true"
}
]
Loading

0 comments on commit 1cb37f2

Please sign in to comment.