OCIの操作をPL/SQL SDKを使って行うアプリケーションを作り始めたのですが、DBMS_CLOUD.CREATE_CREDENTIALを呼び出してクリデンシャルを登録するのが思いのほか面倒でした。
エラーが発生したときに登録手順のミスなのか、その他に問題があるのか、色々と切り分けをするために条件の違うクリデンシャルを多数作ったり、削除と再作成を簡単に繰り返せるよう、アプリケーションを作ることにしました。
以下から、その作業ログを記述します。
最初にAPEXのワークスペース・スキーマに以下の権限を与えます。今のところPL/SQL SDKが使えるのはAutonomous Databaseのみ(もしかしたらDBCSも使えるかも - 未確認)だったはずなので、SQL Developer WebにADMINで接続してSQLを実行するのが早いでしょう。
grant execute on dbms_cloud to APEXワークスペース・スキーマ;
grant execute on dbms_cloud_oci_obs_object_storage to APEXワークスペース・スキーマ;
grant execute on dbms_cloud_oci_obs_object_storage_get_namespace_response_t to APEXワークスペース・スキーマ;
APEXのワークスペース・スキーマがAPEXDEVである場合の実行例は以下になります。
APEXのアプリケーション・ビルダーにサインインし、空のアプケーションを作成します。名前は任意ですが、今回はクリデンシャル管理としました。今のところPL/SQL SDKはNLS_LANGUAGEがAmerican以外では正常に動作しないようなので、言語は英語(en)を選択します。以上の設定で、アプリケーションの作成を行います。
*) エラーについてはMOS NoteのList Compartments Through DBMS_CLOUD.SEND_REQUEST With HTTP GET Method Failed With Error ORA-20401: Authorization failed (Doc ID 2680726.1)に該当している模様です。
Content Body以下でリージョンの作成を行い、新規に作成されたリージョンの識別のタイトルに登録済みクリデンシャルと入力し、タイプとして対話モード・レポートを選択します。ソースの位置はローカル・データベース、タイプにSQL問合せを選択し、SQL問合せとして、以下を指定します。
select * from user_credentials
作成したページを実行してみます。すでに登録済みのクリデンシャルがあれば、対話モード・レポートに表示されます。
次にフォームのページを作成します。ページ・デザイナに戻り、作成メニューからページを実行します。
ページ・タイプにコンポーネントを選択し、その中のフォームをクリックします。
フォームをクリックします。
ページ・タイプはモーダル・ダイアログなので、左メニューにナビゲーションとして現れる必要はありません。ナビゲーションのプリファレンスはこのページとナビゲーション・メニュー・エントリを関連付けないを選択し、次に進みます。
データ・ソースとしてローカル・データベースを選択し、ソース・タイプをSQL問合せとします。SQL SELECT文を入力として、クリデンシャルの登録時に引数となる列の検索に限定したSELECT文を指定します。これ以外の引数は、フォームの作成後にページ・アイテムとして追加します。次に進みます。
select credential_name, username from user_credentials
主キー列としてCREDENTIAL_NAME(Varchar2)を選択し、作成を実行します。
ファンクションDBMS_CLOUD.CREATE_CREDENTIALの引数には、credential_name、user_ocid、tenancy_ocid、private_key、fingerprintがあります。この内、credential_nameとuser_ocidはそれぞれP2_CREDENTIAL_NAME、P2_USERNAMEとして、値の受け皿となるページ・アイテムが作成されています。それ以外のtenancy_ocid、private_key、fingerprintを指定するためのページ・アイテムを作成します。
それぞれ、P2_TENANCY、P2_PRIVATE_KEY、P2_FINGERPRINTとします。
最初にページ・アイテムP2_TENANCYを作成します。リージョンでページ・アイテムの作成を実行し、識別の名前をP2_TENANCY、タイプはテキスト・フィールドを選択します。ラベルはTenancy OCIDを設定します。ソースのセッション・ステートの保持はリクエストごと(メモリーのみ)を選択します。できるだけ隠しておきたい値はセッション・ステート(つまりデータベース)に保存すべきではないし、保存する場合でも、セッション・ステートに暗号化された値を保存をONにすべきです。
次にページ・アイテムP2_FINGERPRINTを作成します。設定内容はP2_TENANCYと同じなので、P2_TENANCY上でコンテキスト・メニューを表示し、重複を実行します。作成されたページ・アイテムP2_TENANCY_1の名前をP2_FINGERPRINT、ラベルをFingerprintに変更します。
また、すでに作成されているページ・アイテムも調整します。
ページ・アイテムP2_CREDENTIAL_NAMEは主キー列として定義されているため、タイプが非表示になっています。これを入力が可能なテキスト・フィールドに変更し、ラベルにCredential Nameを指定します。
ページ・アイテムP2_USERNAMEは、タイプをテキスト・フィールドに変更し、ラベルをより適切なUser OCIDとします。
以上でクリデンシャルの登録に必要な引数を保持するページ・アイテムが作成されました。
これらのページ・アイテムを受け取って、実際にクリデンシャルの作成と削除を行うプロセスの設定を行います。リージョン・ソースはビューUSER_CREDENTIALSを参照していて、これは更新できません。そのため、フォームを処理するプロセスを選択し、設定のターゲット・タイプをPL/SQL Codeに変更します。挿入/更新/削除するPL/SQLコードとして以下を記述します。秘密鍵を1行にする部分以外は、受け取ったページ・アイテムを引数としてプロシージャを呼び出しているだけの、簡単な処理です。
declare
l_private_key varchar2(4000);
l_end integer;
begin
case
when :APEX$ROW_STATUS = 'C' then
-- 以下より、秘密鍵を一行にする。
l_private_key := :P2_PRIVATE_KEY;
-- MIIより前の文字を削除する。
l_private_key := substr(l_private_key, instr(l_private_key, 'MII'));
-- '-----END'以降の文字を削除する。見つからない場合は変更なし。
l_end := instr(l_private_key, '-----END');
if l_end > 0 then
l_private_key := substr(l_private_key, 1, (l_end-1));
end if;
-- 文字列から CR(13) と LF(10) を取り除く。
l_private_key := replace(l_private_key, CHR(10));
l_private_key := replace(l_private_key, CHR(13));
-- クリデンシャルを登録する。
dbms_cloud.create_credential(
credential_name => :P2_CREDENTIAL_NAME
, user_ocid => :P2_USERNAME
, tenancy_ocid => :P2_TENANCY
, private_key => :P2_PRIVATE_KEY
, fingerprint => :P2_FINGERPRINT
);
when :APEX$ROW_STATUS = 'D' then
-- クリデンシャルを削除する。
dbms_cloud.drop_credential(
credential_name => :P2_CREDENTIAL_NAME
);
end case;
end;
その他、失われた更新の防止をOFF、行のロックもOFFとします。
以上でクリデンシャルの作成と削除を行うフォームのページが完成しました。
対話モード・レポートから、作成したフォームを呼び出すリンクを追加します。
対話モード・レポートのページを開き、登録済みクリデンシャルのリージョンでボタンの作成を実行します。識別のボタン名はB_CREATEとし、ラベルを作成とします。レイアウトのボタン位置は対話モード・レポートの検索バーの右とします。外観のホットをONにします。動作のアクションとしてこのアプリケーションのページにリダイレクトを選択します。
ターゲットのタイプはこのアプリケーションのページ、ページはフォームのページである2を指定します。キャッシュのクリアにも2を設定し、OKをクリックしターゲットの設定を完了します。
クリデンシャルの削除を行うため、対話モード・レポートにリンク列を追加します。Attributesを開き、リンク列としてカスタム・ターゲットへのリンクを選択します。 趣味の問題なので変更しなくても良いのですが、リンク・アイコンを写実的な鉛筆アイコンから、フラットなアイコンへ変更するため、<span class="fa fa-edit"></span>に変更しています。
ターゲットは作成と同様に、タイプはこのアプリケーションのページ、ページはフォームのページ番号の2を指定します。アイテムの設定として、名前P2_CREDENTIAL_NAMEに値#CREDENTIAL_NAME#を渡すようにします。CREDENTIAL_NAMEが主キーの扱いなので、それ以外の値はフォームのリージョン・ソースから取得されます。キャッシュのクリアとして2を設定し、OKをクリックします。
以上で、登録されているクリデンシャルの一覧表示、クリデンシャルの新規作成および削除は実行できるようになりました。ページの保存して、アプリケーションを実行して確認します。
作成をクリックします。
ここで設定する情報は秘密鍵を除いて、OCIのコンソールのアイデンティティ/ユーザー/ユーザーの詳細/APIキーの構成ファイルの表示から参照することができます。
構成ファイルの表示を行うと、構成ファイルがプレビューされます。user、tenancy、fingerprintの情報を参照できます。
さて、フォームを閉じてレポートに戻っても、登録したクリデンシャルがリストされません。ページをリロードすると表示されます。これはレポートとフォームをそれぞれ独立で作成したときには、ページ作成ウィザードで同時に作成される場合とは異なり、フォームが閉じられたときにレポートをリフレッシュする動的アクションが作成されないためです。
レポートをリフレッシュする動的アクションを追加します。
対話モード・レポートのページをページ・デザイナで開き、左ペインで動的アクション・ビューを表示させます。ダイアログのクローズ上でコンテキスト・メニューを表示させ、動的アクションの作成を実行します。
作成した動的アクションの名前をクリデンシャルの編集とし、タイミングのイベントはダイアログのクローズ、選択タイプはリージョン、リージョンとして登録済みクリデンシャルを指定します。
Trueアクションを選択し、アクションはリフレッシュ(最初からリフレッシュで作成されているはずです)、影響を受ける要素の選択タイプはリージョン、実際にリフレッシュされるリージョンとして登録済みクリデンシャルを指定します。
クリデンシャルを作成、削除してフォームを閉じると対話モード・レポートが更新されるようになりました。クリデンシャルの作成、削除を実際に行ってアプリケーションの動作を確認してみてください。
作成したクリデンシャルを使ってオブジェクト・ストレージのネームスペースを取得することで、クリデンシャルが正しく登録されていかどうか確認する機能を追加します。
クリデンシャルを選択して開くフォームから、OCIのPL/SQL SDKを呼び出すようにします。フォームをページ・デザイナで開きます。
静的コンテンツのリージョンをクリデンシャルの下に作成します。コンポーネント・ギャラリでリージョンを選択し、静的コンテンツをレイアウト・ビューにドラッグ&ドロップします。
テストで発行するのはオブジェクト・ストレージのネームスペースを取得するファンクションDBMS_CLOUD_OCI_OBS_OBJECT_STORAGE.GET_NAMESPACEです。このファンクションは引数としてクリデンシャル名とリージョンを受け取り、ネームスペースを返します。クリデンシャル名はP2_CREDENTIAL_NAMEから取得できるので、残りのリージョンの入力と、戻り値であるネームスペースを表示するページ・アイテムを作成します。
リージョンを入力するページ・アイテムP2_REGIONを作成します。リージョンのクリデンシャルのテスト上でコンテキスト・メニューを開き、ページ・アイテムの作成を実行します。新規に作成されたページ・アイテムの名前をP2_REGIONとし、タイプはテキスト・フィールド、ソースのセッション・ステートの保持はリクエストごと(メモリーのみ)を選択します。
続いてネームスペースを表示するページ・アイテムP2_NAMESPACEを作成します。 名前をP2_NAMESPACE、タイプは表示のみを選びます。ラベルをNamespaceとし、設定のページの送信時に送信はOFFにします。ソースのセッション・ステートの保持はリクエスト(メモリーのみ)を選択します。
PL/SQL SDKの呼び出しは動的アクションから行います。SAVEは使用することのないボタンなので、リージョンクリデンシャルのテストに移動し、設定を変更します。ボタン名をB_GET_NAMESPACEに変更し、ラベルはGet Namespaceとします。レイアウトのボタン位置はリージョンの下部とし、動作のアクションに動的アクションで定義を指定します。
ボタンをクリックしたときに、PL/SQLコードを実行しネームスペースを取得する動的アクションを作成します。ボタンB_GET_NAMESPACE上でコンテキスト・メニューを表示し、動的アクションの作成を実行します。作成した動的アクションの名前をネームスペースの取得とします。ボタン上で作成した動的アクションのタイミングはデフォルトで、イベントがクリック、選択タイプがボタン、ボタンとしてB_GET_NAMESPACEとなりますが、一応、確認します。
続いてTrueアクションの設定を行います。識別のアクションとしてサーバー側のコードを実行を選択します。設定の言語はPL/SQLで、PL/SQLコードとして以下を記述します。
declare
response dbms_cloud_oci_obs_object_storage_get_namespace_response_t;
begin
response := dbms_cloud_oci_obs_object_storage.get_namespace(
compartment_id => null
, region => :P2_REGION
, credential_name => :P2_CREDENTIAL_NAME
);
if response.status_code != 200 then
:P2_NAMESPACE := to_char(response.status_code, '9999');
else
:P2_NAMESPACE := response.response_body;
end if;
end;
単にPL/SQL SDKを呼び出しているだけですが、呼び出しに失敗している場合はネームスペースの代わりにHTTPのステータスコードを返すようにしています。
送信するアイテムとしてP2_CREDENTIAL_NAMEとP2_REGION、戻すアイテムとしてP2_NAMESPACEを設定します。
以上でクリデンシャルのテストとして、オブジェクト・ストレージのネームスペースを取得する機能が追加されました。
アプリケーションを実行し、作成したクリデンシャルの編集アイコンをクリックしてフォームを開き、Get Namespaceのボタンをクリックします。ネームスペースが返されればクリデンシャルが正しく登録できています。
今回作成したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/ocicredentialmanager.sql
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完