はじめに
この記事は SAP Advent Calendar 2020 の12月6日分の記事として執筆しています。
やりたいこと
SAP Could PlatformのワークフローをABAPから起動します。参考にしたのは以下のブログです。
Call SAP Cloud Platform Workflow from ABAP using an OAuth access token
ブログではNeo環境のワークフローを呼んでいますが、今回はCloud Foundryのワークフローを呼びます。Cloud FoundryではAPIが変わりますが、基本的な流れは同じです。
認証の流れ
SCP上のサービスのAPIを呼ぶ際の認証の流れは以下のようになります。
①トークンを発行するためのエンドポイントにアクセス(クライアントID、シークレットを渡す)
②トークンが返される(権限のスコープなどが渡される)
③トークンを使用してAPIにアクセス(APIエンドポイント側で権限チェック)
UIを伴うアプリの場合はこちらの記事で紹介したようにユーザID、パスワードの入力が求められますが、今回はテクニカルな認証ということで、ユーザによる入力は不要です。
認証の手順はワークフローに限らずSCP上のサービスのAPIを呼ぶときに共通なので、一度やり方を押さえれば他のサービスに接続する際にも応用することができます。
ステップ
SCP側の設定
1.1. 事前準備
1.2. ワークフロー作成
1.3. ワークフローのテスト
1.4. PostmanでワークフローのAPIを叩いてみるABAP側の設定
2.1. Destinationの設定
2.2. SSLの設定
2.3. プロファイルパラメータの更新
2.4. ABAPコード
1. SCP側の設定
1.1. 事前準備
このステップではSCPのワークフローを使えるようにします。前提として、SCPのトライアルアカウントが必要です。
以下のページを参考に必要な設定を行います。
Setting up for Workflow on SAP Cloud Platform
やるとことは大きく2つです。
1. Boosterを使ってワークフローを使用するための設定を自動で実施
2. Business Application Studioにワークフロー開発用のスペースを作成
私の場合はすでにFiori用に作ったスペースがあったので、Workflow Managementのエクステンションを追加しました。
1.2. ワークフロー作成
以下のチュートリアルグループを参考に、Business Application Studioでワークフローを作成します。
Get Started with SAP Cloud Platform Workflow
チュートリアルグループには3つのチュートリアルがありますが、最初の2つまででOKです。
チュートリアルから変更した点が2つあります。
①ユーザタスクの受信者 (チュートリアル2のステップ1)
チュートリアルではタスクの受信者にワークフローを起動したユーザを指定していますが、ABAPから実行した場合は起動したユーザがSCPのユーザにならないので、固定値でSCPに登録した自分のメールアドレスを指定しました。
②ワークフローのサービスインスタス (チュートリアル2のステップ4)
Boosterを使ったことにより、使用するサービスインスタンスに変更が必要です。Boosterはdefault_workflow
というサービスインスタンスを作っているのでそれを使うようにします。
mta.yaml
または、以下のように新しいサービスインスタンスを作成するようにしてもよいです。resourcesのtypeがorg.cloudfoundry.existing-service
となっていると既存のサービスインスタンスが使われ、org.cloudfoundry.managed-service
となっていると新規のサービスインスタンスが作られます。
1.3. ワークフローのテスト
デプロイができたらチュートリアル2のステップ5の要領で、ワークフローインスタンスを開始してみます。うまくいけばMy Inboxアプリで以下のようにタスクを確認できます。
1.4. PostmanでワークフローのAPIを叩いてみる
ABAPからワークフローのAPIを呼ぶにあたり、Postmanを使ってリクエストに必要なパラメータを確認しておきましょう。APIを呼ぶのに必要な情報はワークフローのサービスキーから確認できます。
①トークンを取得
以下のパラメータを設定してリクエストを実行します。
パラメータ | 設定値 | |
---|---|---|
HTTPメソッド | POST | |
URL | <サービスキーのuaa.url>/oauth/token | |
Headers | Content-Type | application/x-www-form-urlencoded |
Accept | application/json | |
Body | client_id | <サービスキーのuaa.clientid> |
client_secret | <サービスキーのuaa.clientsecret> | |
grant_type | client_credentials | |
response_type | token |
②APIでワークフローを起動
ワークフローのAPIはAPI Hubで確認できます。ここではワークフローのインスタンスを生成する/v1/workflow-instances
を使用します。
以下のパラメータを設定してリクエストを実行します。
パラメータ | 設定値 | |
---|---|---|
HTTPメソッド | POST | |
URL | <サービスキーのendpoints.workflow_rest_url>/v1/workflow-instances | |
Headers | Authorization | Bearer <①で取得したaccess_token> |
application/json | application/json |
Bodyには以下のJSONオブジェクトを設定します。definitionIdにワークフローのIDを、contextにワークフローに渡すパラメータを設定します。
{
"definitionId": "onboard",
"context": {
"managerId": "john.edrich@sapdemo.com",
"buddyId": "kevin.hart@saptest.com",
"userId": "cgrant1",
"empData": {
"firstName": "Postman",
"lastName": "Grant",
"city": "San Mateo",
"country": "United States",
"hireDate": "2020-07-11",
"jobTitle": "General Manager, Industries"
}
}
}
実行すると以下のような権限なしエラーが返ってきます。
実はAPIごとに必要な権限がScopeとして定義されおり、サービスインスタンスにこのScopeを追加する必要があります。
以下のコマンドでサービスインスタンスにScopeを追加します。(ここではワークフロー起動の権限とワークフロー定義の表示の権限を追加しています)
cf update-service default_workflow -c '{"authorities": ["WORKFLOW_DEFINITION_GET", "WORKFLOW_INSTANCE_START"]}'
※Windowで実行の場合はシングルクォートが使えないので、以下のように指定する必要があります。
cf update-service default_workflow -c "{\""authorities\"": [\""WORKFLOW_DEFINITION_GET\"", \""WORKFLOW_INSTANCE_START\""]}"
もう一度ワークフロー起動のリクエストを実行すると、今度は以下のような結果が返されます。
My Inboxアプリからタスクを確認することができます。
以上でSCP側の設定は完了です。
2. ABAP側の設定
2.1. Destinationの設定
トークン取得用の宛先とAPI呼び出し用の宛先をtr-cd:SM59
で登録します。
①トークン取得用の宛先
以下のパラメータを設定します。
タブ | パラメータ | 設定値 |
---|---|---|
Technical Settings | Host | <サービスキーのuaa.url> ※https://はつけない |
Logon & Security | Basic Authentication | 選択 |
User | <サービスキーのuaa.clientid> | |
Password | <サービスキーのuaa.cliensecret> | |
SSL | Active | |
SSL Certificate | ANONYM SSL Client(Anonymous) |
②API用の宛先
以下のパラメータを設定します。Logon & Securityタブは設定不要です。
タブ | パラメータ | 設定値 |
---|---|---|
Technical Settings | Host | <サービスキーのendpoints.workflow_rest_url> ※https://はつけない |
Path Prefix | /workflow-service/rest |
2022/1/20更新
OAuth 2.0 Client Profileの設定をすることで、トークン取得用の宛先を作らなくてもよくなります。(詳細は以下のブログ参照)
Configure SAP Event Mesh for SAP S/4HANA On-Premise
2.2. SSLの設定
各宛先へはHTTPSで接続するので、SSLの設定が必要です。具体的には、サーバ証明書をダウンロードしてtr-cd:STRUST
でインポートします。
①サーバ証明書のダウンロード
<サービスキーのuaa.url>
にブラウザからアクセスしてCertificateをクリック
証明書タブを開き、「ファイルにコピー」をクリック
任意の名前を付けて保存
これで1つ目の証明書のダウンロードが完了です。
「証明書のパス」タブを開き、上位レベルの証明書についても同様にダウンロードします。
①サーバ証明書のインストール
tr-cd:STRUST
を実行し、変更モードにします。以下の手順で①でダウンロードしたすべての証明書を取り込みます。
③接続テスト
この時点でDestinationの接続テストをしてみます。
トークン取得用の宛先へは接続できています。
API用の宛先はSSSLERR_SERVER_CERT_MISMATCH
というエラーになりました。
ICMのトレースを見ると、証明書が対象にしているsubjectと接続しようとしているホスト名が違うようです。API用のエンドポイントについても証明書をインストールしてみましたが、結果は変わりませんでした。(結果として、API用のエンドポイントのサーバ証明書はなくても動きました)
2.3. プロファイルパラメータの更新
以下のスレッドに類似の質問が投稿されていました。ここで提示されている解決策を試してみたところ、うまくいきました。
https://answers.sap.com/questions/599994/ssslerrservercertmismatch.html
tr-cd:RZ11
でパラメータicm/HTTPS/client_sni_enabled
の値を変更します。デフォルトはFALSEになっています。
"Change Value"で値を変更します。
※RZ11での変更はサーバを再起動するとリセットされてしまうので、パラメータを固定する場合はRZ10で変更します。
プロファイルパラメータの変更後、API用の宛先をテストしてみるとログインを求めるポップアップが出ました。ここまで行けば接続はOKです。
2.4. ABAPコード
いよいよコーディングです。ADTで新規のクラスを作成します。
ここではワークフローのインスタンスを作成してADTのコンソールに結果を表示するというシンプルな処理にします。メソッドif_oo_adt_classrun~main
に実装していきます。
CLASS zcl_scp_workflow DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun .
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_scp_workflow IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
ENDMETHOD.
ENDCLASS.
①トークンの取得
まずは、トークン取得のリクエストを投げて取得したトークンをコンソールに表示します。リクエストのヘッダとボディに設定しているパラメータはPostmanで指定したのと同じです。クライアントID、シークレットはDestinationで設定しているのでここで指定する必要はありません。
data lo_client_oauth type ref to if_http_client.
data lo_client_wfapi TYPE REF TO if_http_client.
data content type string.
data lr_json type ref to data.
data token type string.
data body type string.
"1. トークンの取得
"1.1 Destinationを指定してHTTPクライアントを取得
cl_http_client=>create_by_destination(
EXPORTING
destination = 'WORKFLOW_OAUTH' " Logical destination (specified in function call)
IMPORTING
client = lo_client_oauth " HTTP Client Abstraction
EXCEPTIONS
argument_not_found = 1 " Connection Parameter (Destination) Not Available
destination_not_found = 2 " Destination not found
destination_no_authority = 3 " No Authorization to Use HTTP Destination
plugin_not_active = 4 " HTTP/HTTPS communication not available
internal_error = 5 " Internal error (e.g. name too long)
others = 6
).
IF sy-subrc <> 0.
"error handling
ENDIF.
"1.2. リクエストヘッダ、ボディの設定
"Set header
lo_client_oauth->request->set_header_field( name = '~request_method' value = 'POST' ).
lo_client_oauth->request->set_header_field( name = '~request_uri' value = '/oauth/token' ).
lo_client_oauth->request->set_header_field( name = 'Accept' value = 'application/json' ).
"Set body
lo_client_oauth->request->set_form_field( name = 'grant_type' value = 'client_credentials' ).
lo_client_oauth->request->set_form_field( name = 'response_type' value = 'token' ).
"1.3. リクエストを投げる(send, receiveの2ステップ)
"Get token
lo_client_oauth->send(
EXCEPTIONS
http_communication_failure = 1 " Communication Error
http_invalid_state = 2 " Invalid state
http_processing_failed = 3 " Error When Processing Method
http_invalid_timeout = 4 " Invalid Time Entry
others = 5
).
IF sy-subrc <> 0.
"error handling
ENDIF.
lo_client_oauth->receive(
EXCEPTIONS
http_communication_failure = 1 " Communication Error
http_invalid_state = 2 " Invalid state
http_processing_failed = 3 " Error When Processing Method
others = 4
).
IF sy-subrc <> 0.
"error handling
ENDIF.
"1.4. HTTPステータスを取得(リクエスト成功かどうか)
lo_client_oauth->response->get_status( IMPORTING code = data(lv_code) ).
"1.5. レスポンスをJSONオブジェクトに変換し、"access_token"を取得
content = lo_client_oauth->response->get_cdata( ).
lr_json = /ui2/cl_json=>generate( json = content ).
/ui2/cl_data_access=>create( ir_data = lr_json iv_component = 'access_token' )->value( IMPORTING ev_data = token ).
"1.6. ステータスとトークンをコンソールに出力
out->write( | Response status for Token: { lv_code }, Token: { token } | ).
F9キーを押して実行すると、コンソールに以下のようにステータスとトークンが出力されます。
②APIを呼んでワークフローを起動
続いて、①で取得したトークンを使用してワークフローのAPIにアクセスします。
"2. APIにアクセス
"2.1. Destinationを指定してAPI接続用のHTTPクライアントを取得
cl_http_client=>create_by_destination(
EXPORTING
destination = 'WORKFLOW_API' " Logical destination (specified in function call)
IMPORTING
client = lo_client_wfapi " HTTP Client Abstraction
EXCEPTIONS
argument_not_found = 1 " Connection Parameter (Destination) Not Available
destination_not_found = 2 " Destination not found
destination_no_authority = 3 " No Authorization to Use HTTP Destination
plugin_not_active = 4 " HTTP/HTTPS communication not available
internal_error = 5 " Internal error (e.g. name too long)
others = 6
).
IF sy-subrc <> 0.
"error handling
ENDIF.
"2.2. リクエストヘッダ、ボディの設定
"Set header
lo_client_wfapi->request->set_header_field( name = '~request_method' value = 'POST' ).
lo_client_wfapi->request->set_header_field( name = '~request_uri' value = '/v1/workflow-instances' ).
lo_client_wfapi->request->set_header_field( name = 'Content-Type' value = 'application/json' ).
lo_client_wfapi->request->set_header_field( name = 'Authorization' value = |Bearer { token }| ).
"Set body
concatenate '{ "definitionId": "onboard", "context": { "managerId": "john.edrich@sapdemo.com", "buddyId": "kevin.hart@saptest.com"'
', "userId": "cgrant1", "empData": { "firstName": "ABAP", "lastName": "Grant", "city": "San Mateo", "country": "United States", "hireDate": "2020-07-11", "jobTitle": "General Manager, Industries" } } }'
into body.
lo_client_wfapi->request->set_cdata( data = body ).
"2.3. リクエストを投げる(send, receiveの2ステップ)
lo_client_wfapi->send(
EXCEPTIONS
http_communication_failure = 1 " Communication Error
http_invalid_state = 2 " Invalid state
http_processing_failed = 3 " Error When Processing Method
http_invalid_timeout = 4 " Invalid Time Entry
others = 5
).
IF sy-subrc <> 0.
ENDIF.
lo_client_wfapi->receive(
EXCEPTIONS
http_communication_failure = 1 " Communication Error
http_invalid_state = 2 " Invalid state
http_processing_failed = 3 " Error When Processing Method
others = 4
).
IF sy-subrc <> 0.
ENDIF.
"2.4. HTTPステータスを取得(リクエスト成功かどうか)
lo_client_wfapi->response->get_status( IMPORTING code = lv_code ).
"2.5. レスポンスを取得
content = lo_client_wfapi->response->get_cdata( ).
"2.6. ステータスとレスポンスをコンソールに出力
out->write( | Response status for WF: { lv_code }, Content: { content } | ).
以下のようにステータス201が返ってくれば成功です。
My Inboxアプリからタスクを確認することができます。
おわりに
ABAPからSCPへの接続ルートがめでたく開通しました。これができるとSCP上の他のサービスも同じ方法で呼ぶことができ、できることが広がりそうです。
参考
ワークフロー
- Setting up for Workflow on SAP Cloud Platform
- Get Started with SAP Cloud Platform Workflow
- Enable Technical Authentication
APIの呼び方
- Calling the Workflow API from Postman
- Note 2760424 - API Access to xsuaa Configuration Data
- Call SAP Cloud Platform Workflow from ABAP using an OAuth access token
トラブルシューティング