SAP Build Process AutomationでBTPのユーザ登録を自動化する

## はじめに
SAP BTPを使うプロジェクトにおいては、開発者やベーシスのメンバーが新たに参画したり、メンバーに新たなロールコレクションの割当が必要になったりする場面があります。[SAP Build Process Automation](https://help.sap.com/docs/PROCESS_AUTOMATION/a331c4ef0a9d48a89c779fd449c022e7/c20b4e77201b4cde9ce4227e21850deb.html)を使い、そのような作業をできるだけ自動化してみることにしました。
※この記事で紹介するフローはPoC的に作成したもので、実際に運用で使われているものではありません

作成したプロジェクトは以下のリポジトリからダウンロード可能です。
https://github.com/miyasuta/btp-user-role-request

## フロー
1. 申請者がフォームに必要事項（メールアドレス、必要なロール等）を記入して申請を行う
1. 所属チームのリーダーが承認する
1. BTPサブアカウントにユーザが作成され、ロールコレクションが割り当たる
1. 登録対象者がS-Userをまだ持っていない場合、またはCloud Foundryのスペースにユーザ追加が必要な場合はシステム管理者に依頼が届く。システム管理者はマニュアルでこれらを登録する
1. 処理が完了すると、申請者にメールで通知する 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/371eaa27-791d-a730-2669-2af259754efd.png)

## 前提
- 承認者となる各チームのリーダーは、事前にBTPサブアカウントへの登録されていること
- Process Automationのフォームを使うにはBTPサブアカウントのユーザが必要なため、申請者は共通ユーザを使用すること
- Identity ProviderにはSAP ID Serviceを使用する。他のIdPを利用する場合はプロセスの設定変更が必要

## 動作
- 共通ユーザでログインし、フォームから申請を行います。リクエストカテゴリは「新規ユーザの登録」とし、スペースにユーザを追加してもらいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/fc25338a-94b8-f341-dd1e-d3742c8b261f.png)

- 承認者が受信ボックスから承認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/5699f2d0-c358-16b3-6f42-0736c62420c9.png)

- システム管理者にタスクが届きます。
システム管理者は依頼された作業（ここではスペースへのユーザ追加）を実施し、"Submit"を押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/0d05438d-324b-b798-593d-cd728cadaf09.png)

- 登録されたユーザにメールが届きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/4cb5c512-1928-dc23-a89a-dfb8d8c718c2.png)

- サブアカウントにユーザが登録され、ロールコレクションが割り当たった状態になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/570bd0b0-6de0-ec62-94fc-ea0bdddf62c7.png)

## 事前準備
### 1. Authorization and Trust Management ServiceのDestination登録
BTPサブアカウントへのユーザ登録、およびロールコレクション割当には、[Authorization and Trust Management ServiceのAPI](https://api.sap.com/api/PlatformAPI/overview)を使用します。このAPIを使用するためのDestinationをサブアカウントに登録しておきます。

- XSUAAのサービスインスタンスを登録
プラン"apiaccess"で登録し、サービスキーを登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/aaffa11b-ed27-91c1-37e3-b5c38b84bff6.png)

- Destinationを登録
サービスキーを参照してDestinationを登録します。Process Automationから使うために、以下のプロパティが必要です。
`sap.applicationdevelopment.actions.enabled: true`
`sap.processautomation.enabled: true`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/89dfe01f-1c49-16d1-9a98-de9bafb9f3ed.png)
※サービスキーとDestinationの項目マッピングは、[こちら](https://qiita.com/tami/items/3ada6f414055fe59250b#12-destination%E3%82%92%E7%99%BB%E9%8C%B2)のブログをご参照ください

- Process AutomationにDestination追加
Process AutomationのSettings > Destinationsのメニューで、"New Destination"ボタンから上で作成したDestinationを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/e0d4b57a-fac5-d9b4-5e12-69c8380b4798.png)

### 2. メール送信用のDestination登録
[ヘルプ](https://help.sap.com/docs/PROCESS_AUTOMATION/a331c4ef0a9d48a89c779fd449c022e7/4f2d36db614241c9850b9ec80f9e0c1b.html)を参考に、Process Automaionのフローからメールを送信するためのDestinationを登録します。ここではGmailを使用しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/512a34a8-7dad-deb6-061d-6b8672adcdae.png)

Gmailで2要素認証を有効化している場合、以下の手順でApp password（アプリごとのパスワード）を作成する必要があります。作成したパスワードをDestinationのPasswordに設定します。
https://help.warmupinbox.com/en/articles/4934806-configure-for-google-workplace-with-two-factor-authentication-2fa

## 作成したプロセス
作成したプロセスは以下になります。番号を振ったステップについて説明します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/1578217b-8369-119f-4580-98d1c7545755.png)

### 1. 承認者決定
リクエストフォームで選択したチームによって承認者を振り分ける処理です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/5ba3d86e-ddb8-2555-7e7c-67439f08817d.png)
ここでは承認者のメールアドレスを直接指定しています。承認可能な人が複数いる場合は、ロールコレクションを設定してもよいでしょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/39bae1e4-58cb-7ad7-160a-64dda38f5750.png)

### 2. サブアカウントへユーザ登録
新規ユーザの場合、サブアカウントへユーザ登録を行います。プロセスから直接APIを呼ぶために、Actionのプロジェクトを事前に登録しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/a147bf1f-47ac-485c-b40c-3871bfe4dcdd.png)

#### 2.1. Actionプロジェクト登録
Process Automationのロビーより、Create > Build an Automated Process > Actionsを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/e07f2ff7-c4d0-70f7-25d6-cafca9eabd70.png)
SAP API Business HubからダウンロードしたAPI Specification (JSON)をアップロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/39384e19-2438-97c2-c08e-ab1cd90ee2f7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/e784096a-a953-707d-3d38-d60c24af38c6.png)
使用するエンドポイントにチェックを入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/f3f24ea4-54b2-504c-3318-4c26b2d5e65b.png)
今回は、"Creates a user"のエンドポイントを使用します。Bodyを使用する項目だけに絞り、決まった値を入れる項目には固定値を設定しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/62c44fc8-7a09-6250-5b13-b50a99031944.png)

#### 2.2. アクションを使用
プロセスでアクションを使用します。まず、Destination variableを定義します。これはこのプロセスの環境変数となり、実行時にDestinationを指定できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/38deb1f7-c8e0-2254-2fea-70bc48518078.png)
Inputsタブでフォームで入力された名前やメールアドレスなどをAPIの項目にマッピングします。アクション側で項目を絞ったおかげで、ここでは最小限の設定で済みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/dd6c67fc-32cd-5662-8b43-9815e1c40fd3.png)

### 3. ロールコレクションをリスト型の変数にマッピング
リクエストフォームは必要なロールコレクションにチェックを入れる形式になっています。後続のロールコレクションの割り当て処理は、ロールコレクションをリストで受け取るため、マッピングが必要です。そのため、Automationのスクリプトを使ってマッピングを行いました。
※Business Application Studioのワークフローエディタには「スクリプトタスク」が用意されているのですが、Process Automationにはそのようなものがないため、こちらの方法を使用しました

インプットはロールコレクションにチェックがついているかをBoolean型で受け取り、アウトプットはString型のリストです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/791c9a74-772f-4eae-4ad3-5c05ef0f4201.png)

スクリプトは以下のようになっています。

```javascript
let roleCollections = [];

if(Subaccount_Viewer){
    roleCollections.push('Subaccount Viewer');
}

if(Business_Application_Studio_Developer){
    roleCollections.push('Business_Application_Studio_Developer');
}

if(Launchpad_Admin){
    roleCollections.push('Launchpad_Admin');
}

return roleCollections;
```

### 4. ロールコレクションの割り当て
ロールコレクションの割り当ては、対象のメールアドレスとロールコレクションのリストをインプットとして受け取り、ロールコレクションの件数分、割り当て処理のサブフローを実行します。

#### 4.1. メインのフロー
メインのフローの最初の3ステップは、ユーザのメールアドレスをもとにIDを取得する処理です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/3059c2b0-764b-a375-430f-7426b8bf51ad.png)
BTPサブアカウントのユーザを特定するキーはメールアドレスではなくIDなので、以下のリクエストを発行してIDを取得します。以下の画面ショットはPostmanでのリクエストのイメージです。
`/Users?filter=origin eq 'sap.default' and userName eq '<メールアドレス>'`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/d314e5ed-75f3-723d-16d2-c6b4be98cede.png)

##### 4.1.1. リクエストオブジェクトの編集
スクリプトを使ってAPIに渡すためのリクエストオブジェクトの編集を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/ee4b9fc8-afe6-ed80-cf4a-3c1116a37016.png)

スクリプトの中身は以下のようになっています。

```javascript
let url = "/Users?filter=origin eq 'sap.default' and userName eq '" + email + "'";

return {
  'method': 'GET',
  'url': url ,
  'responseType':'json', // parse the body of the result as a JSON object
  'resolveBodyOnly':true // get only the body of the response
};
```

##### 4.1.2. API呼び出し
"Call Web Service with Destination"ステップに対し、前のステップで編集したリクエストオブジェクトを渡してAPIを呼び出します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/4e1903b3-f960-da28-f85c-e64c571b5983.png)

##### 4.1.3. IDを変数に設定
"Create Variable"ステップで、取得したIDを変数に設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b0a86b3c-c2a0-400e-f556-09b075d16707.png)

##### 4.1.4 ロールコレクション割当のループ
"For Each"ステップでインプットのロールコレクションをループし、サブフローを呼び出します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/1ba11d01-d64e-8373-64a7-0d48361bac59.png)
サブフローに対しては、IDとロールコレクションを渡します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/da5b8218-1bae-37c4-95ec-f1226ac3b7e1.png)

#### 4.2. サブフロー
サブフローでは、ユーザをロールコレクションに割り当てます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/bb21dc22-8042-bae6-f87c-d883204b5fa3.png)
ロールコレクションの割り当ては、Patchリクエストにより行うため、Etag（If-Matchヘッダ）が必要です。そのため、最初にGetリクエストによりEtagを取得しておく必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/beb9be67-cd57-9c74-0911-205a5f9fb072.png)

##### 4.2.1. Etag取得用リクエストオブジェクトの編集
Etag取得用のGetリクエストを送るためのリクエストオブジェクトを編集します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/5e136e8b-69e5-eaf1-2ea7-986a1aafb1cc.png)

Etagはレスポンスヘッダに設定されるため、`resolveBodyOnly`をfalseにします。
```javascript
let url = '/Groups/' + roleCollection;

return {
  'method': 'GET',
  'url': url ,
  'responseType':'json', // parse the body of the result as a JSON object
  'resolveBodyOnly':false // get only the body of the response
};
```
##### 4.2.2. Etag取得
"Call Web Service with Destination"ステップに対し、前のステップで編集したリクエストオブジェクトを渡してAPIを呼び出します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/11bdfebb-b397-7ba0-0652-1a5e4c87d436.png)

##### 4.2.3. Etagを変数に設定
"Create Variable"ステップで、取得したEtagを変数に設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/a865a8ff-243c-b623-8267-4c0c5e86c6ac.png)

##### 4.2.4. Role Collection割り当て用リクエストオブジェクトの編集
Role Collection割り当て用てのPatchリクエストを送るためのリクエストオブジェクトを編集します。ロールコレクション、ユーザのID、Etagをインプットにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b5637a9a-cc99-74a4-6252-5d5be72f7a1b.png)

```javascript
let url = '/Groups/' + roleCollection;
let headers = { 
    'Content-Type': 'application/json',
    'If-Match': etag
};
let body = {
    'members': [{
      'origin': 'sap.default',
      'type': 'USER',
      'value': id
    }]
};
return {
  'method': 'PATCH',
  'url': url ,
  'headers': headers,
  'body': JSON.stringify(body),
  'responseType':'json', // parse the body of the result as a JSON object
  'resolveBodyOnly':true // get only the body of the response
};
```

##### 4.2.5. Role Collection割り当て
"Call Web Service with Destination"ステップに対し、前のステップで編集したリクエストオブジェクトを渡してAPIを呼び出します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/0ab82a02-b3c3-fb01-5d1a-20861084657b.png)

### 5. システム管理者のステップが必要かどうか判断
S-Userの登録が必要な場合、またはCloud Foundryのスペースにユーザ追加が必要な場合にシステム管理者のステップを実行するため、Decisonステップにより判断を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/ca4f08bb-1a64-9671-3d1e-9eb2166f3a29.png)

If-Elseにより、以下のルールを設定しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/c54555f0-60d4-3af9-1080-100dfdd51824.png)

## 改善ポイント
### 1. Cloud Foundryのスペースへのユーザ登録
Cloud Foundryのスペースへのユーザ登録はAPIがないため自動化に含められませんでした。このためシステム管理者のステップで手動で行うこととしています。[btp-setup-automator](https://github.com/SAP-samples/btp-setup-automator)などを参考に、Cloud Foundry CLIをAPIで呼ぶことができないかを検討してみたいです。

### 2. ロールコレクションの選択
リクエストフォームでのロールコレクションの選択は、現状チェックボックスにより行うこととしています。しかし、これだと選択させたいロールコレクションが増えたときにマニュアルで追加が必要です。
フォームのドロップダウンで"From data set"を選択するとデータソースを外部から引っ張ってくることができそうなので、対象のサブアカウントのロールコレクションをAPIで取得して出すといったことができるか検討してみたいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/dae0ec5c-9d96-ccfa-6b66-640c90338576.png)