背景と目的
Cosmos DB にアクセスする API アプリを App Service や Functions で用意しおき、その API を API Management から使用するのが一般的だと思います。でもよくよく考えると Cosmos DB にはデータプレーン用の REST API が備わっており JSON がレスポンスされます。だったら、API Management から直接 Cosmos DB と連携した方が汎用的に使い回せて良いのではないかと思い、実際に試してみました。
前提条件
検証コマンドの実施環境は、Mac + Azure CLI です。
zsh
% sw_vers
ProductName: macOS
ProductVersion: 11.4
BuildVersion: 20F71
% az version
{
"azure-cli": "2.25.0",
"azure-cli-core": "2.25.0",
"azure-cli-telemetry": "1.0.6",
"extensions": {}
}
実施内容
リソースグループと Cosmos DB を作成
zsh
# 環境変数とセットします
region=japaneast
prefix=mnrapimcdb
# リソースグループを作成します
az group create \
--name ${prefix}-rg \
--location $region
# Cosmos DB を作成します
az cosmosdb create \
--name ${prefix}-cdb \
--resource-group ${prefix}-rg
Cosmos DB のサンプル Python アプリで動作確認
zsh
git clone https://github.com/Azure-Samples/azure-cosmos-db-python-getting-started.git
cd azure-cosmos-db-python-getting-started/
cdbendpoint=$(az cosmosdb show \
--name ${prefix}-cdb \
--resource-group ${prefix}-rg \
--query documentEndpoint \
--output tsv)
cdbprimarykey=$(az cosmosdb keys list \
--name ${prefix}-cdb \
--resource-group ${prefix}-rg \
--query primaryMasterKey \
--output tsv)
sed -ie "s#\"endpoint\"#\"$cdbendpoint\"#" cosmos_get_started.py
sed -ie "s#\'primary_key'#'$cdbprimarykey'#" cosmos_get_started.py
python3 -m venv .venv
. .venv/bin/activate
pip install azure-cosmos
python cosmos_get_started.py
deactivate
cd ..
API Management を作成
zsh
# API Management を作成します
az apim create \
--name ${prefix}-apim \
--resource-group ${prefix}-rg \
--sku-name Consumption \
--publisher-email email@mydomain.com \
--publisher-name Microsoft
# Blank API を作成します
az apim api create \
--service-name ${prefix}-apim \
--resource-group ${prefix}-rg \
--api-id MyApi \
--path '/myapi' \
--display-name 'My API'
# API 操作を作成します
az apim api operation create \
--service-name ${prefix}-apim \
--resource-group ${prefix}-rg \
--api-id MyApi \
--url-template "/getitem/{container}/{lastname}" \
--method "GET" \
--display-name GetContainerItem \
--template-parameters name=container required="true" type="string" \
--template-parameters name=lastname required="true" type="string"
# 名前付きの値を作成します:cdbendpoint
az apim nv create \
--service-name ${prefix}-apim \
--resource-group ${prefix}-rg \
--named-value-id cdbendpoint \
--display-name 'CosmosDB-Endpoint' \
--value $cdbendpoint
# 名前付きの値を作成します:cosmosdbkey
az apim nv create \
--service-name ${prefix}-apim \
--resource-group ${prefix}-rg \
--named-value-id cosmosdbkey \
--display-name 'CosmosDB-Key' \
--value $cdbprimarykey
作成した MyApi の Policy を登録
下記の XML を管理ポータル上で MyApi の Policy に登録する。
xml
<policies>
<inbound>
<base />
<set-variable name="requestDateString" value="@(DateTime.UtcNow.ToString("r"))" />
<set-variable name="container" value="@(context.Request.MatchedParameters["container"])" />
<set-variable name="lastname" value="@(context.Request.MatchedParameters["lastname"])" />
<send-request mode="new" response-variable-name="response" timeout="10" ignore-error="false">
<set-url>@("{{CosmosDB-Endpoint}}/dbs/AzureSampleFamilyDatabase/colls/" + (string)context.Variables["container"] + "/docs")</set-url>
<set-method>POST</set-method>
<set-header name="Authorization" exists-action="override">
<value>@{
var verb = "post";
var resourceType = "docs";
var resourceLink = "dbs/AzureSampleFamilyDatabase/colls/" + (string)context.Variables["container"];
var key = "{{CosmosDB-Key}}";
var keyType = "master";
var tokenVersion = "1.0";
var date = context.Variables.GetValueOrDefault<string>("requestDateString");
var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };
verb = verb ?? "";
resourceType = resourceType ?? "";
resourceLink = resourceLink ?? "";
string payLoad = string.Format("{0}\n{1}\n{2}\n{3}\n{4}\n",
verb.ToLowerInvariant(),
resourceType.ToLowerInvariant(),
resourceLink,
date.ToLowerInvariant(),
""
);
byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad));
string signature = Convert.ToBase64String(hashPayLoad);
return System.Uri.EscapeDataString(String.Format("type={0}&ver={1}&sig={2}",
keyType,
tokenVersion,
signature));
}</value>
</set-header>
<set-header name="Content-Type" exists-action="override">
<value>application/query+json</value>
</set-header>
<set-header name="x-ms-documentdb-isquery" exists-action="override">
<value>True</value>
</set-header>
<set-header name="x-ms-documentdb-query-enablecrosspartition" exists-action="override">
<value>True</value>
</set-header>
<set-header name="x-ms-date" exists-action="override">
<value>@(context.Variables.GetValueOrDefault<string>("requestDateString"))</value>
</set-header>
<set-header name="x-ms-version" exists-action="override">
<value>2018-12-31</value>
</set-header>
<set-header name="x-ms-query-enable-crosspartition" exists-action="override">
<value>true</value>
</set-header>
<set-body>@("{\"query\": \"SELECT * FROM c WHERE c.lastName = @lastName\", " +
"\"parameters\": [{ \"name\": \"@lastName\", \"value\": \"" + (string)context.Variables["lastname"] + "\"}]}")</set-body>
</send-request>
<return-response response-variable-name="existing response variable">
<set-status code="200" reason="OK" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>@(new JObject(new JProperty("response",((IResponse)context.Variables["response"]).Body.As<JObject>())).ToString())</set-body>
</return-response>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
実施結果
API Management 経由で Cosmos DB の JSON を取得できるか試してみます。
zsh
# API Management のシークレットキーを取得します
apimkey=$(az rest \
--method post \
--uri "https://management.azure.com/subscriptions/$subid/resourceGroups/${prefix}-rg/providers/Microsoft.ApiManagement/service/${prefix}-apim/subscriptions/master/listSecrets?api-version=2019-12-01" \
--query primaryKey \
--output tsv)
# FamilyContainer の lastName = Smith をリクエストします
curl -s https://${prefix}-apim.azure-api.net/myapi/getitem/FamilyContainer/Smith \
-H "Ocp-Apim-Subscription-Key: $apimkey"
# lastName = Smith の Item を取得できました
{
"response": {
"_rid": "c8Y7AKAgb44=",
"Documents": [
{
"id": "Smith_0961f8ce-93a9-41ea-b610-1cf0b3dfb496",
"lastName": "Smith",
"parents": null,
"children": null,
"address": {
"state": "WA",
"city": "Redmond"
},
"registered": true,
"_rid": "c8Y7AKAgb44CAAAAAAAAAA==",
"_self": "dbs/c8Y7AA==/colls/c8Y7AKAgb44=/docs/c8Y7AKAgb44CAAAAAAAAAA==/",
"_etag": "\"020033b8-0000-2300-0000-60def6660000\"",
"_attachments": "attachments/",
"_ts": 1625224806
}
],
"_count": 1
}
}
# FamilyContainer の lastName = Andersen をリクエストします
curl -s https://${prefix}-apim.azure-api.net/myapi/getitem/FamilyContainer/Andersen \
-H "Ocp-Apim-Subscription-Key: $apimkey"
# lastName = Andersen の Item を取得できました
{
"response": {
"_rid": "c8Y7AKAgb44=",
"Documents": [
{
"id": "Andersen_5feb3d2c-f103-43f9-925f-8a97995e2aff",
"lastName": "Andersen",
"district": "WA5",
"parents": [
{
"familyName": null,
"firstName": "Thomas"
},
{
"familyName": null,
"firstName": "Mary Kay"
}
],
"children": null,
"address": {
"state": "WA",
"county": "King",
"city": "Seattle"
},
"registered": true,
"_rid": "c8Y7AKAgb44BAAAAAAAAAA==",
"_self": "dbs/c8Y7AA==/colls/c8Y7AKAgb44=/docs/c8Y7AKAgb44BAAAAAAAAAA==/",
"_etag": "\"020032b8-0000-2300-0000-60def6660000\"",
"_attachments": "attachments/",
"_ts": 1625224806
}
],
"_count": 1
}
}
参考
検証が済んだら後片付け。
zsh
# リソースグループを削除します
az group delete \
--name ${prefix}-rg
Use bash, Azure CLI and REST API to access CosmosDB - how to get token and hash right?