0

We have a business requirement where we have to create the Database and collection(s) as part of our CD step inside AzureDevOps. As far as my research shows this is not possible to do via ARM.

A support ticket to MS confirms this is not possible in ARM and has to be done inside your application. As mentioned a business requirement prevents us to do so.

The basic options are programmatically or the REST API of Cosmos Db.

1 Answer 1

2

I eventually got this working using a PowerShell script! With the help of this and this posts I was able to get started - They especially helped with the authentication token.

Then I referenced the MS docs on the Cosmos Db SQL REST API: Create Database & Create Collection

Here is my PS Script (working in AzureDevOps/VSTS at time of post):

param($endpoint, $masterKey, $databaseName, $collectionName, $collectionRUs)
Add-Type -AssemblyName System.Web

Add-Type -TypeDefinition @"
public enum CosmosResourceType
{
  Database,
  Collection
}
"@

Function Create-Resource
{
[CmdletBinding()]
Param
(
    [Parameter(Mandatory=$true)][String]$endPoint,
    [Parameter(Mandatory=$true)][String]$masterKey,
    [Parameter(Mandatory=$true)][String]$dataBaseName,
    [Parameter(Mandatory=$true)][String]$collectionName,
    [Parameter(Mandatory=$true)][CosmosResourceType]$cosmosResourceType
)

$verb = "POST";
$resourceType = "dbs";
$resourceLink = "dbs";
$header = "";
$idValue = "";
$contentType = "application/json";
$queryUri = "$endPoint$resourceLink"

if($cosmosResourceType -eq "Database")
{
    $header = Generate-Headers -verb $verb -resourceType $resourceType -key $masterKey
    $idValue = $dataBaseName        
}
elseif($cosmosResourceType -eq "Collection")
{
    $resourceType = "colls";
    $resourceLink = "dbs/$dataBaseName" 
    $header = Generate-Headers -verb $verb -resourceType $resourceType -resourceLink $resourceLink -key $masterKey

    # Not sure why but at this moment Cosmos Db ignores this setting
    $header | Add-Member -Name 'x-ms-offer-throughput' -Type NoteProperty -Value $collectionRUs

    $idValue = $collectionName
    $queryUri = "$endPoint$resourceLink/colls"
}
else
{
    Write-Host "Invalid Cosmos Resource Type:"$cosmosResourceType -ForeGroundColor Red 
}

$jsonDoc = [pscustomobject]@{ id = $idValue }
$jsonDoc = $jsonDoc | ConvertTo-Json

$response = InvokeRest -verb $Verb -ContentType $contentType -uri $queryUri -headers $header -body $jsonDoc

if($response.code.ToLowerInvariant() -eq "Conflict".ToLowerInvariant())
{
    Write-Host "Warning: $cosmosResourceType already existing" -ForeGroundColor Yellow  
}
elseif($response.code.ToLowerInvariant() -eq "Ok".ToLowerInvariant())
{
    Write-Host "$cosmosResourceType Created" -ForeGroundColor Green 
}
else
{
    Write-Host "response:"$response -ForeGroundColor Red 
}    
}

Function InvokeRest
{
[CmdletBinding()]
Param
(
    [Parameter(Mandatory=$true)]$verb,
    [Parameter(Mandatory=$true)]$contentType,
    [Parameter(Mandatory=$true)]$uri,    
    [Parameter(Mandatory=$true)]$headers,
    [Parameter(Mandatory=$false)]$body    
)

Try {
    $result = Invoke-RestMethod -Method $Verb -ContentType $contentType -Uri $uri -Headers $headers -Body $body
    Write-Host "InvokeRest: "$result -ForeGroundColor Green 

    $response = [pscustomobject]@{
        code = "OK"
        body = $result
    }

    return $response
}
Catch {        
    # Check if there is a response.
    if ($_.Exception.Response -eq $null) {
        $expMessage = $_.Exception.Message
        $failedItem = $_.Exception.Source
        $line = $_.InvocationInfo.ScriptLineNumber
        Write-Host "At $($line):`r`n$expMessage `r`n$failedItem" -ForeGroundColor Red   
        throw $_.Exception         
    }
    else {
        # Get the response body with more error detail.
        $respStream = $_.Exception.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($respStream)
        $response = $reader.ReadToEnd() | ConvertFrom-Json
        return $response
    } 
}
}

Function Generate-Headers
{
[CmdletBinding()]
Param
(
    [Parameter(Mandatory=$true)][String]$verb,
    [Parameter(Mandatory=$false)][String]$resourceLink,
    [Parameter(Mandatory=$true)][String]$resourceType,    
    [Parameter(Mandatory=$true)][String]$key    
)

$dateTime = [DateTime]::UtcNow.ToString("r")
$authHeader = Generate-MasterKeyAuthorizationSignature -verb $verb -resourceLink $resourceLink -resourceType $resourceType -key $masterKey -keyType "master" -tokenVersion "1.0" -dateTime $dateTime
Write-Host $authHeader
$header = @{authorization=$authHeader;"x-ms-version"="2017-02-22";"x-ms-date"=$dateTime}

return $header
}

Function Generate-MasterKeyAuthorizationSignature
{
[CmdletBinding()]
Param
(
    [Parameter(Mandatory=$true)][String]$verb,
    [Parameter(Mandatory=$false)][String]$resourceLink,
    [Parameter(Mandatory=$true)][String]$resourceType,    
    [Parameter(Mandatory=$true)][String]$key,
    [Parameter(Mandatory=$true)][String]$keyType,
    [Parameter(Mandatory=$true)][String]$tokenVersion,
    [Parameter(Mandatory=$true)][String]$dateTime
)

$hmacSha256 = New-Object System.Security.Cryptography.HMACSHA256
$hmacSha256.Key = [System.Convert]::FromBase64String($key)

$payLoad = Generate-Payload -verb $verb -resourceLink $resourceLink -resourceType $resourceType -dateTime $dateTime
$hashPayLoad = $hmacSha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($payLoad))
$signature = [System.Convert]::ToBase64String($hashPayLoad);

[System.Web.HttpUtility]::UrlEncode("type=$keyType&ver=$tokenVersion&sig=$signature")
}

Function Generate-Payload
{
[CmdletBinding()]
Param
(
    [Parameter(Mandatory=$true)][String]$verb,
    [Parameter(Mandatory=$false)][String]$resourceLink,
    [Parameter(Mandatory=$true)][String]$resourceType,
    [Parameter(Mandatory=$true)][String]$dateTime
)

$payLoad = ""

if ( [string]::IsNullOrEmpty($resourceLink) ) 
{ 
    $payLoad = "$($verb.ToLowerInvariant())`n$($resourceType.ToLowerInvariant())`n`n$($dateTime.ToLowerInvariant())`n`n"
}    
else 
{
    $payLoad = "$($verb.ToLowerInvariant())`n$($resourceType.ToLowerInvariant())`n$resourceLink`n$($dateTime.ToLowerInvariant())`n`n"
} 


return $payLoad
}

# Create Database
$cosmosResourceType =  [CosmosResourceType]::Database
Create-Resource -endPoint $endpoint -masterKey $masterKey -dataBaseName $databaseName -collectionName $collectionName -cosmosResourceType $cosmosResourceType

# Create Collection
$cosmosResourceType =  [CosmosResourceType]::Collection
Create-Resource -endPoint $endpoint -masterKey $masterKey -dataBaseName $databaseName -collectionName $collectionName -cosmosResourceType $cosmosResourceType

I hope this helps someone else.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.