2022年12月5日月曜日

GitHubにAPEXアプリをエクスポートする(2) - データベース側の準備

 GitHubでパーソナル・アクセス・トークンを生成したので、それを使ってクリデンシャルを作成します。

また、今回はAPEXのエクスポート処理をPL/SQLのプロシージャとして実装します。APEXアプリケーションからはAPEX 22.2の新機能であるAPI呼び出しを使って、プロシージャを呼び出します。


クリデンシャルの作成


GitHubの操作に使用するクリデンシャルは、DBMS_CLOUD.CREATE_CREDENTIALを呼び出して作成します。そのため、APEXのワークスペース・スキーマにパッケージDBMS_CLOUDの実行権限を与えます。また、後で作成するプロシージャが使用するパッケージDBMS_CLOUD_REPOの実行権限も与えておきます。

grant execute on dbms_cloud to <ワークスペース・スキーマ名>;
grant execute on dbms_cloud_repo to <ワークスペース・スキーマ名>;

ワークスペース名がAPEXDEVの場合、ワークスペース・スキーマ名は通常、先頭にWKSP_が付加されWKSP_APEXDEVのようになります。

grant execute on dbms_cloud to wksp_apexdev;
grant execute on dbms_cloud_repo to wksp_apexdev;

データベース・アクションに管理者ユーザーADMINで接続し、SQLより実行します。


クリデンシャルの作成は、SQLワークショップSQLコマンドより実施します。

以下のコマンドを実行します。
begin
    dbms_cloud.create_credential(
        credential_name => 'GITHUB_CRED'
        , username => 'GitHubのユーザー名'
        , password => 'GitHubのPersonal access token'
    );
end;
作成するクリデンシャルの名前はGITHUB_CREDとしました。



プロシージャEXPORT_APEX_APP_TO_GITHUBの作成




APEXアプリケーションをGitHubへエクスポートするプロシージャEXPORT_APEX_APP_TO_GITHUBを作成します。

以下のSQLを実行します。

create or replace procedure export_apex_app_to_github(
/* GitHubリポジトリの指定 */
p_repo_name in varchar2
,p_credential_name in varchar2
,p_owner in varchar2
,p_directory in varchar2
/* DBMS_CLOUD_REPOへの引数 */
,p_branch_name in varchar2 default null
,p_commit_details in varchar2 default null
/* 上書きの許可 */
,p_allow_overwrite in boolean default false
/* ZIPのエクスポートのみ */
,p_is_zip_export_only in boolean default true
/* APEXアプリケーションのエクスポートへ与える引数 */
,p_alias in varchar2 -- アプリケーション別名は引数はなく、エクスポートするファイル名として使用する。
,p_application_id in number
,p_with_date in boolean default false
,p_with_ir_public_reports in boolean default true
,p_with_ir_private_reports in boolean default false
,p_with_ir_notifications in boolean default false
,p_with_translations in boolean default true
,p_with_original_ids in boolean default false
,p_with_no_subscriptions in boolean default false
,p_with_comments in boolean default false
,p_with_supporting_objects in varchar2 default 'Y'
,p_with_acl_assignments in boolean default false
)
is
l_repo clob;
l_file_name varchar2(800);
l_zip_export blob;
l_app_export apex_t_export_files;
l_export_files apex_t_varchar2 := apex_t_varchar2();
l_delete_candidates apex_t_varchar2 := apex_t_varchar2();
l_is_exist integer;
e_export_exist exception;
/*
* YAML形式のファイルは"f + アプリケーションID"ディレクトリ以下に
* 保存されるファイル名になる。
* 先頭のf101といったディレクトリ名をアプリケーション別名に置き換える。
*/
function replace_applicaton_id_by_alias(
p_file_name in varchar2
,p_alias in varchar2
,p_directory in varchar2 default null
)
return varchar2
is
l_file_name varchar2(4000);
begin
l_file_name := p_alias || substr(p_file_name, instr(p_file_name,'/'));
if p_directory is not null then
l_file_name := p_directory || '/' || l_file_name;
end if;
-- '//'という形式で/が連続する場合があるので、それを排除する。
l_file_name := replace(l_file_name,'//','/');
l_file_name := utl_url.escape(l_file_name, false, 'AL32UTF8');
return l_file_name;
end replace_applicaton_id_by_alias;
/*
* ディレクトリ名を先頭に付ける。
*/
function prepend_directory(
p_file_name in varchar2
,p_directory in varchar2
)
return varchar2
is
l_file_name varchar2(4000);
begin
l_file_name := p_file_name;
if p_directory is not null then
l_file_name := p_directory || '/' || l_file_name;
end if;
l_file_name := utl_url.escape(l_file_name, false, 'AL32UTF8');
return l_file_name;
end prepend_directory;
begin
/*
* GitHub上の保存先となるリポジトリのハンドルを取得する。
*/
l_repo := dbms_cloud_repo.init_repo(
params => JSON_OBJECT(
'provider' value 'github'
,'repo_name' value p_repo_name
,'credential_name' value p_credential_name
,'owner' value p_owner
)
);
/*
* ZIP形式でエクスポートする。
*/
l_app_export := apex_export.get_application(
p_application_id => p_application_id
,p_split => TRUE
,p_with_date => p_with_date
,p_with_ir_public_reports => p_with_ir_public_reports
,p_with_ir_private_reports => p_with_ir_private_reports
,p_with_ir_notifications => p_with_ir_notifications
,p_with_translations => p_with_translations
,p_with_original_ids => p_with_original_ids
,p_with_no_subscriptions => p_with_no_subscriptions
,p_with_comments => p_with_comments
,p_with_supporting_objects => p_with_supporting_objects
,p_with_acl_assignments => p_with_acl_assignments
);
l_zip_export := apex_export.zip(
p_source_files => l_app_export
);
apex_debug.info(
'apex_application_export_to_github: %s files exported and compressed in ZIP'
,l_app_export.count
);
/*
* YAML形式でエクスポートする。
*/
if not p_is_zip_export_only then
l_app_export := apex_export.get_application(
p_application_id => p_application_id
,p_split => TRUE
,p_type => 'READABLE_YAML'
,p_with_date => p_with_date
,p_with_ir_public_reports => p_with_ir_public_reports
,p_with_ir_private_reports => p_with_ir_private_reports
,p_with_ir_notifications => p_with_ir_notifications
,p_with_translations => p_with_translations
,p_with_original_ids => p_with_original_ids
,p_with_no_subscriptions => p_with_no_subscriptions
,p_with_comments => p_with_comments
,p_with_supporting_objects => p_with_supporting_objects
,p_with_acl_assignments => p_with_acl_assignments
);
apex_debug.info(
'apex_application_export_to_github: %s files exported in YAML'
, l_app_export.count
);
/*
   * エクスポートされたファイル名をl_app_exportに入れ直す。
* 削除されたファイルの確認に使用する。
*/
for i in l_app_export.first .. l_app_export.last
loop
apex_string.push(
l_export_files
,replace_applicaton_id_by_alias(l_app_export(i).name, p_alias, p_directory)
);
end loop;
/*
* リポジトリのファイル一覧で、READABLE_YAMLのファイル一覧l_export_filesに
* 含まれていないファイルは削除の対象になる。
*/
l_file_name := p_alias || '/';
l_file_name := prepend_directory(l_file_name, p_directory);
for c in (
select name from dbms_cloud_repo.list_files(
repo => l_repo
,path => l_file_name
)
where name not in (select column_value from table(l_export_files))
)
loop
apex_string.push(l_delete_candidates, c.name);
end loop;
/* 削除対象のファイルを印刷する */
if l_delete_candidates.count > 0 then
for i in l_delete_candidates.first..l_delete_candidates.last
loop
apex_debug.info('apex_application_export_to_github: to be removed %s', l_delete_candidates(i));
end loop;
else
apex_debug.info('apex_application_export_to_github: no YAML file to be removed');
end if;
end if;
/*
* ZIPファイルがすでに存在していないかどうか確認した後、ZIP形式のエクスポートを
* GitHubに保存する。
* utl_url.escapeで対応されているはずだが、アプリケーション別名に日本語は使わない方がよい。
*/
l_file_name := p_alias || '.zip';
l_file_name := prepend_directory(l_file_name, p_directory);
select count(*) into l_is_exist from dbms_cloud_repo.list_files(
repo => l_repo
) where name = l_file_name;
/* 上書きが禁止されているときは例外を上げる */
if (p_allow_overwrite or l_is_exist = 0) then
dbms_cloud_repo.put_file(
repo => l_repo
,file_path => l_file_name
,contents => l_zip_export
,branch_name => p_branch_name
,commit_details => p_commit_details
);
else
raise e_export_exist;
end if;
apex_debug.info('apex_application_export_to_github: %s exported to GitHub', l_file_name);
/*
* YAML形式のファイルをGitHubに保存する。
* YAMLについては常に上書き。
*/
if not p_is_zip_export_only then
for i in l_app_export.first..l_app_export.last
loop
dbms_cloud_repo.put_file(
repo => l_repo
,file_path => replace_applicaton_id_by_alias(l_app_export(i).name, p_alias, p_directory)
,contents => apex_util.clob_to_blob(l_app_export(i).contents)
,branch_name => p_branch_name
,commit_details => p_commit_details
);
end loop;
apex_debug.info('apex_application_export_to_github: %s YAML files exported to GitHub', l_app_export.count);
/*
* GitHubには存在するがエクスポートに含まれていないファイルを削除する。
*/
if (l_delete_candidates.count > 0) then
for i in l_delete_candidates.first..l_delete_candidates.last
loop
dbms_cloud_repo.delete_file(
repo => l_repo
,file_path => l_delete_candidates(i)
,branch_name => p_branch_name
,commit_details => p_commit_details
);
end loop;
apex_debug.info('apex_application_export_to_github: %s YAML files deleted from GitHub', l_delete_candidates.count);
end if;
end if;
end export_apex_app_to_github;
SQLワークショップSQLコマンドより実行します。


APEXアプリケーションのエクスポートは、2回実行しています。最初にAPEXアプリケーションとしてインポート可能なZIP形式でのエクスポート、2回目は人間が読めるYAML形式でエクスポートしています。

プロシージャの引数のほとんどは、DBMS_CLOUD_REPO.PUT_FILE、APEX_EXPORT.GET_APPLICATIONの引数として、そのまま渡されます。

一旦、プロシージャを作成した後は、オブジェクト・ブラウザプロシージャよりEXPORT_APEX_APP_TO_GITHUBを選択し、コードを開いて変更できます。

APEXアプリケーションでは、このプロシージャをプロセスとして呼び出します。

続く