2024年1月17日水曜日

Autonomous DatabaseでAzureのリソース・プリンシパルを作成しAzure Blob Storageにアクセスする

 Microsoft AzureのBlobストレージを操作するAPEXアプリケーションを作ってみました。Azure Blob Storageにアクセスするために、パッケージDBMS_CLOUDに含まれるプロシージャを呼び出します。

認証に必要なAzureのサービス・プリンシパルの作成は、オラクルの公式ブログに、Product ManagerのCan Tuzlaさんが公開している記事の手順に沿って実施します。

How to Easily Access Azure Resources from Your Autonomous Database

作成したアプリケーションは以下のように動作します。実際にはこちらの記事で作成したGoogle Cloud Storageにアクセスするアプリケーションとほとんど同じです。ファイルのダウンロードを行なうボタンを追加してみました。


Oracle APEXよりMicrosoft Azureのストレージにアクセスするにあたって、Azure側で実施した作業を以下より記述します。

最初にAzureのポータルよりMicrosoft Entra ID概要ページを開き、テナントIDをコピーしておきます。この値は、Autonomous Database側でPrincipal Authenticationを有効にする際の引数として使用します。


ストレージアカウントを作成するにあたって、ストレージアカウントに割り当てるリソースグループを作成します。既存のリソースグループを使用する場合、および、ストレージアカウントの作成時にもリソースグループは作成できるため、この作業は必須ではありません。

Azureのポータルにてリソースグループを開き、作成をクリックします。


リソースグループ名前を設定します。今回の作業ではapex-storage-resourceとしました。

サブスクリプションおよびリージョンについては、それぞれの構成に合わせて選択します。

確認および作成をクリックします。タグはスキップします。


検証が成功し確認画面が表示されます。

作成をクリックします。


リソースグループが作成されます。


Azureのポータルからストレージアカウントを開き、作成をクリックします。


リソースグループには、先ほど作成したリソースグループを選択します。

ストレージアカウント名は、Azure Blob Storageにアクセスする際のURLに含まれる値になります。今回の例ではoracleapex2024としました。

アクセスの確認を行なうだけなので、パフォーマンスStandard冗長性は一番低いローカル冗長ストレージ(LRS)を選択します。

その他の詳細設定などはデフォルトの値を採用します。

レビューをクリックします。


レビューが完了したら、作成ボタンが押せるようになります。

作成をクリックします。


作成をクリックするとデプロイが進行します。デプロイが完了すると、ストレージアカウントの作成は完了です。

リソースに移動します。


ストレージアカウントの作成直後には、Blobを保存するためのコンテナーがありません。ナビゲーションメニューのコンテナーまたはプロパティのBlob serviceをクリックして、ストレージアカウントのコンテナー一覧の画面を開きます。


コンテナーをクリックして、コンテナーを新規作成します。


新しいコンテナーの名前を指定します。今回の例ではmy-apex-filesとしました。

作成をクリックします。


新しくコンテナーが作成されました。


Autonomous Database側の作業を行います。

 AzureのPrincipal Authenticationを有効にします。

APEXのワークスペース・スキーマWKSP_APEXDEVを対象とします。Azureのポータルより取得したテナントIDをparamsazure_tenantidに指定します。

ユーザーADMINにて、DBMS_CLOUD_ADMIN.ENABLE_PRINCIPAL_AUTHを実行します。
begin
    dbms_cloud_admin.enable_principal_auth(
        provider => 'AZURE'
        ,params => json_object('azure_tenantid' value '********-****-****-****-************')
        ,username => 'WKSP_APEXDEV'
    );
end;

有効になったAzureのプリンシパルに関する情報を確認します。

以下のSELECT文を実行します。
select param_name, param_value from cloud_integrations where param_name like 'azure%'

Azureのポータルを開いているブラウザより、azure_consent_urlとして得られたURLにアクセスします。

アクセス許可を要求する画面が開きます。承諾をクリックすると(なぜかOracle Cloudのサインイン画面に移動しますが)Microsoft Entra IDにazure_app_nameの値がアプリケーション名である、エンタープライズアプリケーションが作成されます。


Microsoft Entra IDの画面からエンタープライズアプリケーションを一覧します。

Autonomous Databaseでazure_app_nameとして得られた値を名前とした、エンタープライズアプリケーションがあることが確認できます。


ストレージアカウントにロールを割り当てます。

作成済みのストレージアカウントを開き、メニューからアクセス制御(IAM)を開きます。


ロールの割り当ての追加をクリックします。


割り当てるロールとしてストレージBLOBデータ所有者を選択します。カテゴリストレージを選ぶと、見つけやすいでしょう。

次へ進みます。


サービスプリンシパルをメンバーとするので、アクセスの割り当て先ユーザー、グループ、またはサービスプリンシパルです。

メンバーを選択するをクリックします。


メンバーを選択するの画面には、最初はユーザーがリストされています。

選択azure_app_nameの値(作成されたエンタープライズアプリケーションの名前)を入力すると、そのアプリケーションが選択可能になります。

選択可能になったアプリケーションをクリックし、選択します。


選択したメンバーを確定します。

選択をクリックします。


レビューと割り当てをクリックします。


確認画面が開くので再度、レビューと割り当てをクリックします。


ロール割り当てのタブを開き、ストレージBLOBデータ所有者としてエンタープライズアプリケーションが追加されていることを確認します。



以上でAzure Blob Storageの準備は完了です。

Azure Blob Storageを操作するAPEXアプリケーションについて、Google Cloud Storageのアプリケーションとの差異について説明します。

アプリケーション定義置換では、置換文字列としてG_STORAGE_ACCOUNT_NAMEG_CONTAINER_NAMEを定義しています。Azure Blob StorageにアクセスするAPIエンドポイントは、これらの値から構成されます。


対話モード・レポートソースSQL問合せは、以下に置き換えています。
select object_name id, object_name, bytes, checksum, created, last_modified
from dbms_cloud.list_objects(
    credential_name => 'AZURE$PA'
    ,location_uri => 'https://' || :G_STORAGE_ACCOUNT_NAME || '.blob.core.windows.net/' || :G_CONTAINER_NAME || '/'
)


フォームソースSQL問合せも、同じSELECT文に置き換えています。


プロセスUpload Filesコードは、以下に置き換えています。Blobファイルのアップロードに必須であるヘッダーx-ms-blob-typeを指定しています。
declare
    l_uri varchar2(32767);
begin
    for r in (
        select * from apex_application_temp_files
        where name in (
            select column_value from apex_string.split(:P3_FILES,':')
        )
    )
    loop
        l_uri := 'https://' || :G_STORAGE_ACCOUNT_NAME || '.blob.core.windows.net/' || :G_CONTAINER_NAME || '/'
            || utl_url.escape(r.filename, false, 'AL32UTF8');
        dbms_cloud.send_request(
            credential_name => 'AZURE$PA'
            ,uri => l_uri
            ,headers => json_object(
                'Content-Type' value r.mime_type
                ,'x-ms-blob-type' value 'BlockBlob'
            )
            ,method => DBMS_CLOUD.METHOD_PUT
            ,body => r.blob_content
        );
    end loop;
end;

プロセスDelete Fileコードは、以下に置き換えています。
declare
    l_uri varchar2(32767);
begin
    l_uri := 'https://' || :G_STORAGE_ACCOUNT_NAME || '.blob.core.windows.net/' || :G_CONTAINER_NAME || '/'
            || utl_url.escape(:P3_ID, false, 'AL32UTF8');
    dbms_cloud.send_request(
        credential_name => 'AZURE$PA'
        ,uri => l_uri
        ,method => DBMS_CLOUD.METHOD_DELETE
    );
end;

ファイルのダウンロードは、新規に空白のページを作成して実装しています。

ファイル名を保持するページ・アイテムP4_FILE_NAMEと、ヘッダーの前にダウンロード処理を実行するプロセスDownloadを作成しています。

ソースPL/SQLコードとして以下を記述しています。
declare
    l_object_uri varchar2(400);
    l_blob blob;
    l_download apex_data_export.t_export;
begin
    l_object_uri := 'https://' || :G_STORAGE_ACCOUNT_NAME || '.blob.core.windows.net/' || :G_CONTAINER_NAME 
        || '/' || utl_url.escape(:P4_FILE_NAME, false, 'AL32UTF8');
    l_blob := dbms_cloud.get_object(
        credential_name => 'AZURE$PA'
        ,object_uri => l_object_uri
    );
    l_download.file_name := :P4_FILE_NAME;
    l_download.mime_type := 'application/octet-stream';
    l_download.as_clob := FALSE;
    l_download.content_blob := l_blob;
    apex_data_export.download( p_export => l_download );
    apex_application.stop_apex_engine;
end;

ファイルのダウンロードを呼び出すボタンをフォームのページに作成しています。

ボタンDownloadをクリックすると、ダウンロード処理を実装したページにリダイレクトします。

ファイルが選択されているときだけボタンDownloadが表示されるよう、サーバー側の条件タイプアイテムはNULLではないアイテムとしてP3_IDを指定します。


ターゲットタイプこのアプリケーションのページページはダウンロード処理が実装されているページ番号4を指定しています。ページ・アイテムP4_FILE_NAMEには&P3_OBJECT_NAME.を渡しています。


以上でAzure Blob StorageにアクセスするAPEXアプリケーションは完成です。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-azure-storage-access.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。