9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【SCP】ABAPからSCPのWorkflowを起動する

Last updated at Posted at 2020-12-05

##はじめに
この記事は 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が変わりますが、基本的な流れは同じです。
image.png

###認証の流れ
SCP上のサービスのAPIを呼ぶ際の認証の流れは以下のようになります。

①トークンを発行するためのエンドポイントにアクセス(クライアントID、シークレットを渡す)
②トークンが返される(権限のスコープなどが渡される)
③トークンを使用してAPIにアクセス(APIエンドポイント側で権限チェック)
image.png

UIを伴うアプリの場合はこちらの記事で紹介したようにユーザID、パスワードの入力が求められますが、今回はテクニカルな認証ということで、ユーザによる入力は不要です。

認証の手順はワークフローに限らずSCP上のサービスのAPIを呼ぶときに共通なので、一度やり方を押さえれば他のサービスに接続する際にも応用することができます。

##ステップ

  1. SCP側の設定
    1.1. 事前準備
    1.2. ワークフロー作成
    1.3. ワークフローのテスト
    1.4. PostmanでワークフローのAPIを叩いてみる

  2. 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のエクステンションを追加しました。
image.png

####1.2. ワークフロー作成
以下のチュートリアルグループを参考に、Business Application Studioでワークフローを作成します。
Get Started with SAP Cloud Platform Workflow 
チュートリアルグループには3つのチュートリアルがありますが、最初の2つまででOKです。
image.png

チュートリアルから変更した点が2つあります。
①ユーザタスクの受信者 (チュートリアル2のステップ1
チュートリアルではタスクの受信者にワークフローを起動したユーザを指定していますが、ABAPから実行した場合は起動したユーザがSCPのユーザにならないので、固定値でSCPに登録した自分のメールアドレスを指定しました。
image.png

②ワークフローのサービスインスタス (チュートリアル2のステップ4
Boosterを使ったことにより、使用するサービスインスタンスに変更が必要です。Boosterはdefault_workflowというサービスインスタンスを作っているのでそれを使うようにします。
image.png
mta.yaml
image.png

または、以下のように新しいサービスインスタンスを作成するようにしてもよいです。resourcesのtypeがorg.cloudfoundry.existing-serviceとなっていると既存のサービスインスタンスが使われ、org.cloudfoundry.managed-serviceとなっていると新規のサービスインスタンスが作られます。
image.png

####1.3. ワークフローのテスト
デプロイができたらチュートリアル2のステップ5の要領で、ワークフローインスタンスを開始してみます。うまくいけばMy Inboxアプリで以下のようにタスクを確認できます。
image.png

####1.4. PostmanでワークフローのAPIを叩いてみる
ABAPからワークフローのAPIを呼ぶにあたり、Postmanを使ってリクエストに必要なパラメータを確認しておきましょう。APIを呼ぶのに必要な情報はワークフローのサービスキーから確認できます。
image.png
image.png

①トークンを取得
以下のパラメータを設定してリクエストを実行します。

パラメータ 設定値
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

image.png
image.png
アクセストークンが返ってきます。
image.png

②APIでワークフローを起動
ワークフローのAPIはAPI Hubで確認できます。ここではワークフローのインスタンスを生成する/v1/workflow-instancesを使用します。
image.png

以下のパラメータを設定してリクエストを実行します。

パラメータ 設定値
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"
        }
    }
}

image.png
image.png
実行すると以下のような権限なしエラーが返ってきます。
image.png
実はAPIごとに必要な権限がScopeとして定義されおり、サービスインスタンスにこのScopeを追加する必要があります。
image.png
以下のコマンドでサービスインスタンスに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\""]}"

もう一度ワークフロー起動のリクエストを実行すると、今度は以下のような結果が返されます。
image.png
My Inboxアプリからタスクを確認することができます。
image.png

以上で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)

image.png
image.png

②API用の宛先
以下のパラメータを設定します。Logon & Securityタブは設定不要です。

タブ パラメータ 設定値
Technical Settings Host <サービスキーのendpoints.workflow_rest_url> ※https://はつけない
Path Prefix /workflow-service/rest
image.png

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をクリック
image.png
証明書タブを開き、「ファイルにコピー」をクリック
image.png
image.png
任意の名前を付けて保存
image.png
これで1つ目の証明書のダウンロードが完了です。
image.png
「証明書のパス」タブを開き、上位レベルの証明書についても同様にダウンロードします。
image.png

①サーバ証明書のインストール
tr-cd:STRUSTを実行し、変更モードにします。以下の手順で①でダウンロードしたすべての証明書を取り込みます。

  • "SSL client SSL Client (Anonymous)フォルダをクリック
  • ①のボタンで証明書をインポート
  • ②のボタンで証明書リストに追加
    image.png

③接続テスト
この時点でDestinationの接続テストをしてみます。
image.png
トークン取得用の宛先へは接続できています。
image.png
API用の宛先はSSSLERR_SERVER_CERT_MISMATCHというエラーになりました。
image.png
ICMのトレースを見ると、証明書が対象にしているsubjectと接続しようとしているホスト名が違うようです。API用のエンドポイントについても証明書をインストールしてみましたが、結果は変わりませんでした。(結果として、API用のエンドポイントのサーバ証明書はなくても動きました)
image.png

####2.3. プロファイルパラメータの更新
以下のスレッドに類似の質問が投稿されていました。ここで提示されている解決策を試してみたところ、うまくいきました。
https://answers.sap.com/questions/599994/ssslerrservercertmismatch.html

tr-cd:RZ11でパラメータicm/HTTPS/client_sni_enabledの値を変更します。デフォルトはFALSEになっています。
image.png
"Change Value"で値を変更します。
image.png
image.png

※RZ11での変更はサーバを再起動するとリセットされてしまうので、パラメータを固定する場合はRZ10で変更します。

プロファイルパラメータの変更後、API用の宛先をテストしてみるとログインを求めるポップアップが出ました。ここまで行けば接続はOKです。
image.png

####2.4. ABAPコード
いよいよコーディングです。ADTで新規のクラスを作成します。
image.png
ここではワークフローのインスタンスを作成して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キーを押して実行すると、コンソールに以下のようにステータスとトークンが出力されます。
image.png

②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が返ってくれば成功です。
image.png
My Inboxアプリからタスクを確認することができます。
image.png

##おわりに
ABAPからSCPへの接続ルートがめでたく開通しました。これができるとSCP上の他のサービスも同じ方法で呼ぶことができ、できることが広がりそうです。

##参考

ワークフロー

APIの呼び方

トラブルシューティング

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?