Skip to content

Commit 8389e3c

Browse files
authored
Add readonly key support (#17)
* Add readonly key support * Update get content test * Fork the pester tester
1 parent a5a8053 commit 8389e3c

File tree

7 files changed

+274
-79
lines changed

7 files changed

+274
-79
lines changed

Diff for: .github/workflows/ci.yml

+49-49
Original file line numberDiff line numberDiff line change
@@ -2,61 +2,61 @@ name: CI
22

33
on:
44
push:
5-
branches: [ main ]
5+
branches: [main]
66
pull_request:
7-
branches: [ main ]
7+
branches: [main]
88

99
workflow_dispatch:
1010

1111
jobs:
1212
run-tests:
1313
runs-on: ubuntu-latest
1414
steps:
15-
- name: checkout
16-
uses: actions/checkout@v1
17-
18-
- name: test module
19-
id: test_module
20-
uses: zyborg/pester-tests-report@v1
21-
with:
22-
include_paths: tests
23-
report_name: cosmos-db tests
24-
report_title: cosmos-db tests
25-
gist_name: pester-tests-report.md
26-
github_token: ${{ secrets.GITHUB_TOKEN }}
27-
gist_token: ${{ secrets.GIST_TOKEN }}
28-
gist_badge_label: Tests %ExecutedAt%
15+
- name: checkout
16+
uses: actions/checkout@v1
2917

30-
- name: dump results
31-
shell: pwsh
32-
run: |
33-
ipmo GitHubActions
34-
$test_results_path = '${{ steps.test_module.outputs.test_results_path }}'
35-
$error_message = '${{ steps.test_module.outputs.error_message }}'
36-
$error_clixml_path = '${{ steps.test_module.outputs.error_clixml_path }}'
37-
$result_clixml_path = '${{ steps.test_module.outputs.result_clixml_path }}'
38-
$result_value = '${{ steps.test_module.outputs.result_value }}'
39-
$total_count = '${{ steps.test_module.outputs.total_count }}'
40-
$passed_count = '${{ steps.test_module.outputs.passed_count }}'
41-
$failed_count = '${{ steps.test_module.outputs.failed_count }}'
42-
Write-ActionInfo "Found these outputs from [test_module]:"
43-
Write-ActionInfo " * test_results_path = $test_results_path"
44-
Write-ActionInfo " * error_message = $error_message"
45-
Write-ActionInfo " * error_clixml_path = $error_clixml_path"
46-
Write-ActionInfo " * result_clixml_path = $result_clixml_path"
47-
Write-ActionInfo " * result_value = $result_value"
48-
Write-ActionInfo " * total_count = $total_count"
49-
Write-ActionInfo " * passed_count = $passed_count"
50-
Write-ActionInfo " * failed_count = $failed_count"
51-
if ($error_clixml_path) {
52-
$er = Import-Clixml -Path $error_clixml_path
53-
Write-ActionInfo "Loaded up the ErrorRecord:"
54-
$er
55-
$er.Exception
56-
}
57-
if ($result_clixml_path) {
58-
$pr = Import-Clixml -Path $result_clixml_path
59-
Write-ActionInfo "Loaded up the Pester Result:"
60-
$pr
61-
}
62-
exit $failed_count
18+
- name: test module
19+
id: test_module
20+
uses: cmbrose/pester-tests-report@v1
21+
with:
22+
include_paths: tests
23+
report_name: cosmos-db tests
24+
report_title: cosmos-db tests
25+
gist_name: pester-tests-report.md
26+
github_token: ${{ secrets.GITHUB_TOKEN }}
27+
gist_token: ${{ secrets.GIST_TOKEN }}
28+
gist_badge_label: Tests %ExecutedAt%
29+
30+
- name: dump results
31+
shell: pwsh
32+
run: |
33+
ipmo GitHubActions
34+
$test_results_path = '${{ steps.test_module.outputs.test_results_path }}'
35+
$error_message = '${{ steps.test_module.outputs.error_message }}'
36+
$error_clixml_path = '${{ steps.test_module.outputs.error_clixml_path }}'
37+
$result_clixml_path = '${{ steps.test_module.outputs.result_clixml_path }}'
38+
$result_value = '${{ steps.test_module.outputs.result_value }}'
39+
$total_count = '${{ steps.test_module.outputs.total_count }}'
40+
$passed_count = '${{ steps.test_module.outputs.passed_count }}'
41+
$failed_count = '${{ steps.test_module.outputs.failed_count }}'
42+
Write-ActionInfo "Found these outputs from [test_module]:"
43+
Write-ActionInfo " * test_results_path = $test_results_path"
44+
Write-ActionInfo " * error_message = $error_message"
45+
Write-ActionInfo " * error_clixml_path = $error_clixml_path"
46+
Write-ActionInfo " * result_clixml_path = $result_clixml_path"
47+
Write-ActionInfo " * result_value = $result_value"
48+
Write-ActionInfo " * total_count = $total_count"
49+
Write-ActionInfo " * passed_count = $passed_count"
50+
Write-ActionInfo " * failed_count = $failed_count"
51+
if ($error_clixml_path) {
52+
$er = Import-Clixml -Path $error_clixml_path
53+
Write-ActionInfo "Loaded up the ErrorRecord:"
54+
$er
55+
$er.Exception
56+
}
57+
if ($result_clixml_path) {
58+
$pr = Import-Clixml -Path $result_clixml_path
59+
Write-ActionInfo "Loaded up the Pester Result:"
60+
$pr
61+
}
62+
exit $failed_count

Diff for: .vscode/launch.json

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "PowerShell: Run Pester Tests",
9+
"type": "PowerShell",
10+
"request": "launch",
11+
"script": "Invoke-Pester",
12+
"createTemporaryIntegratedConsole": true,
13+
"attachDotnetDebugger": true
14+
}
15+
]
16+
}

Diff for: README.md

+20
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,26 @@ Remove-CosmosDbRecord ...
228228
| PartitionKey | The partition key of the resource | No - defaults to `Id`<br/>Must be set if the collection uses a different parition scheme |
229229
| GetPartitionKeyBlock | Callback to get the `PartitionKey` from `Object` - useful in pipelines | No - used only if `PartitionKey` is not set |
230230

231+
### Use-CosmosDbReadonlyKeys
232+
233+
Enables or disables using readonly DB keys in commands. By default this is disabled (i.e. writable keys are used).
234+
235+
#### Examples
236+
237+
```powershell
238+
# Enable using readonly keys
239+
Use-CosmosDbReadonlyKeys
240+
241+
# Disable using readonly keys (use writable keys)
242+
Use-CosmosDbReadonlyKeys -Disable
243+
```
244+
245+
#### Parameters
246+
247+
| Name | Usage | Required |
248+
| - | - | - |
249+
| Disable | Disables readonly keys if set (enables writable keys) | No - default is `$false` (which will enable readonly keys) |
250+
231251
### Use-CosmosDbInternalFlag
232252

233253
Enables or disables internal flags in the module, normally should only be used for debugging or dogfooding

Diff for: cosmos-db/cosmos-db.psd1

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# RootModule = ''
1212

1313
# Version number of this module.
14-
ModuleVersion = '1.15'
14+
ModuleVersion = '1.16'
1515

1616
# Supported PSEditions
1717
# CompatiblePSEditions = @()
@@ -71,7 +71,7 @@
7171
FunctionsToExport = 'Get-CosmosDbRecord', 'Get-AllCosmosDbRecords',
7272
'Search-CosmosDbRecords', 'New-CosmosDbRecord',
7373
'Update-CosmosDbRecord', 'Remove-CosmosDbRecord',
74-
'Get-CosmosDbRecordContent', 'Use-CosmosDbInternalFlag'
74+
'Get-CosmosDbRecordContent', 'Use-CosmosDbReadonlyKeys', 'Use-CosmosDbInternalFlag'
7575

7676
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
7777
CmdletsToExport = '*'

Diff for: cosmos-db/cosmos-db.psm1

+56-19
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Function Get-DocumentsUrl([string]$Container, [string]$Collection, [string]$Reco
3030
$encodedRecordId = [uri]::EscapeDataString($RecordId)
3131

3232
return @{
33-
ApiUrl = "$collectionsUrl/$DOCS_TYPE/$encodedRecordId";
33+
ApiUrl = "$collectionsUrl/$DOCS_TYPE/$encodedRecordId";
3434
ResourceUrl = "$collectionsUrl/$DOCS_TYPE/$RecordId";
3535
}
3636
}
@@ -61,20 +61,36 @@ Function Set-CacheValue([string]$key, $value, [hashtable]$cache, [int]$expiratio
6161
}
6262

6363
Function Get-Base64Masterkey([string]$ResourceGroup, [string]$Database, [string]$SubscriptionId) {
64-
$cacheKey = "$SubscriptionId/$ResourceGroup/$Database"
64+
$readonly = $env:COSMOS_DB_FLAG_ENABLE_READONLY_KEYS -eq 1
65+
66+
$cacheKey = "$SubscriptionId/$ResourceGroup/$Database/$readonly"
6567
$cacheResult = Get-CacheValue -Key $cacheKey -Cache $MASTER_KEY_CACHE
6668
if ($cacheResult) {
6769
return $cacheResult
6870
}
6971

70-
if ($SubscriptionId) {
71-
$masterKey = az cosmosdb keys list --name $Database --query primaryMasterKey --output tsv --resource-group $ResourceGroup --subscription $SubscriptionId
72+
$masterKey = Get-Base64MasterkeyWithoutCaching -ResourceGroup $ResourceGroup -Database $Database -SubscriptionId $SubscriptionId -Readonly $readonly
73+
74+
Set-CacheValue -Key $cacheKey -Value $masterKey -Cache $MASTER_KEY_CACHE -ExpirationHours 6
75+
76+
$masterKey
77+
}
78+
79+
# This is just to support testing caching with Get-Base64Masterkey and isn't meant to be used directly
80+
Function Get-Base64MasterkeyWithoutCaching([string]$ResourceGroup, [string]$Database, [string]$SubscriptionId, [bool]$Readonly) {
81+
$query = if ($readonly) {
82+
"primaryReadonlyMasterKey"
7283
}
7384
else {
74-
$masterKey = az cosmosdb keys list --name $Database --query primaryMasterKey --output tsv --resource-group $ResourceGroup
85+
"primaryMasterKey"
7586
}
7687

77-
Set-CacheValue -Key $cacheKey -Value $masterKey -Cache $MASTER_KEY_CACHE -ExpirationHours 6
88+
if ($SubscriptionId) {
89+
$masterKey = az cosmosdb keys list --name $Database --query $query --output tsv --resource-group $ResourceGroup --subscription $SubscriptionId
90+
}
91+
else {
92+
$masterKey = az cosmosdb keys list --name $Database --query $query --output tsv --resource-group $ResourceGroup
93+
}
7894

7995
$masterKey
8096
}
@@ -140,9 +156,9 @@ Function Get-CommonHeaders([string]$now, [string]$encodedAuthString, [string]$co
140156
$headers["x-ms-documentdb-partitionkey"] = "[`"$PartitionKey`"]"
141157
}
142158

143-
if ($Etag) {
144-
$headers["If-Match"] = $Etag
145-
}
159+
if ($Etag) {
160+
$headers["If-Match"] = $Etag
161+
}
146162

147163
$headers
148164
}
@@ -172,15 +188,16 @@ Function Get-ExceptionResponseOrThrow($err) {
172188

173189
if ($err.Exception.Response) {
174190
$msg = @{
175-
StatusCode = $err.Exception.Response.StatusCode;
191+
StatusCode = $err.Exception.Response.StatusCode;
176192
RawResponse = $err.Exception.Response;
177193
}
178194

179195
if ($PSVersionTable.PSEdition -eq "Core") {
180196
# In PS Core, the body is eaten and put into this message
181197
# See: https://stackoverflow.com/questions/18771424/how-to-get-powershell-invoke-restmethod-to-return-body-of-http-500-code-response
182198
$msg.Content = $err.ErrorDetails.Message
183-
} else {
199+
}
200+
else {
184201
# In Desktop we can re-read the content stream
185202
$result = $err.Exception.Response.GetResponseStream()
186203
$reader = New-Object System.IO.StreamReader($result)
@@ -191,7 +208,8 @@ Function Get-ExceptionResponseOrThrow($err) {
191208
}
192209

193210
return [PSCustomObject]$msg
194-
} else {
211+
}
212+
else {
195213
throw $err.Exception
196214
}
197215
}
@@ -802,7 +820,8 @@ Function Update-CosmosDbRecord {
802820

803821
if ($EnforceOptimisticConcurrency) {
804822
$headers = Get-CommonHeaders -now $now -encodedAuthString $encodedAuthString -PartitionKey $requestPartitionKey -Etag $Object._etag
805-
} else {
823+
}
824+
else {
806825
$headers = Get-CommonHeaders -now $now -encodedAuthString $encodedAuthString -PartitionKey $requestPartitionKey
807826
}
808827

@@ -911,11 +930,12 @@ Function Get-CosmosDbRecordContent([parameter(ValueFromPipeline)]$RecordResponse
911930
process {
912931
$code = [int]$RecordResponse.StatusCode
913932
$content =
914-
if ($RecordResponse.Content) {
915-
$RecordResponse.Content | ConvertFrom-Json
916-
} else {
917-
$null
918-
}
933+
if ($RecordResponse.Content) {
934+
$RecordResponse.Content | ConvertFrom-Json
935+
}
936+
else {
937+
$null
938+
}
919939

920940
if ($code -lt 300) {
921941
if ($RecordResponse.Content) {
@@ -925,6 +945,12 @@ Function Get-CosmosDbRecordContent([parameter(ValueFromPipeline)]$RecordResponse
925945
$null
926946
}
927947
}
948+
elseif ($code -eq 401) {
949+
if ($env:COSMOS_DB_FLAG_ENABLE_READONLY_KEYS -eq 1) {
950+
throw "Unauthorized (used a readonly key)"
951+
}
952+
throw "Unauthorized"
953+
}
928954
elseif ($code -eq 404) {
929955
if ($content.Message -like "*Owner resource does not exist*") {
930956
throw "Database does not exist"
@@ -935,6 +961,7 @@ Function Get-CosmosDbRecordContent([parameter(ValueFromPipeline)]$RecordResponse
935961
elseif ($code -eq 429) {
936962
throw "Request rate limited"
937963
}
964+
938965
else {
939966
$message = $content.Message
940967
throw "Request failed with status code $code with message`n`n$message"
@@ -961,6 +988,14 @@ Function Use-CosmosDbInternalFlag
961988
}
962989
}
963990

991+
Function Use-CosmosDbReadonlyKeys
992+
(
993+
[switch]$Disable
994+
) {
995+
$env:COSMOS_DB_FLAG_ENABLE_READONLY_KEYS = if ($Disable) { 0 } else { 1 }
996+
}
997+
998+
964999
Export-ModuleMember -Function "Get-CosmosDbRecord"
9651000
Export-ModuleMember -Function "Get-AllCosmosDbRecords"
9661001

@@ -974,4 +1009,6 @@ Export-ModuleMember -Function "Remove-CosmosDbRecord"
9741009

9751010
Export-ModuleMember -Function "Get-CosmosDbRecordContent"
9761011

977-
Export-ModuleMember -Function "Use-CosmosDbInternalFlag"
1012+
Export-ModuleMember -Function "Use-CosmosDbReadonlyKeys"
1013+
1014+
Export-ModuleMember -Function "Use-CosmosDbInternalFlag"

0 commit comments

Comments
 (0)