IBM Cloud Functionsを使ってLINE Notifyを送ってみる #LINEDC


追加: 書いてから気づいたんですけど非推奨になってました。

https://cloud.ibm.com/docs/openwhisk?topic=openwhisk-dep-overview

IBM Cloud FunctionsがCloud Foundryで使ってたときのユースケースっぽい挙動してくれるかもと思い、すこし触ってみました。

https://cloud.ibm.com/docs/cloud-foundry-public?topic=cloud-foundry-public-deprecation#dep_details

## 大きな流れ

Functionsを使おうとすると、認証で`IBM Cloud IAM トークン`が必要と言われるのですが、トークン取得にCLIツールを使う必要があるっぽい（？）ので、CLIの設定 -> トークン取得 -> Functionsの利用とう流れで進めます。

調べるとIBM Cloud CLIは必要そうだったのでこの手順が良さそうな雰囲気です。

## 1. IBM Cloud CLI周りのセットアップ

### IBM Cloud CLIのインストール

https://cloud.ibm.com/docs/cli?topic=cli-install-ibmcloud-cli

GitHub CodeSpacesの環境にインストールします。

```bash
$ curl -fsSL https://clis.cloud.ibm.com/install/linux | sh
```

ちゃんとインストールされた模様です。

```bash
$ ibmcloud
NAME:
  ibmcloud - A command line tool to interact with IBM Cloud
  Find more information at: https://ibm.biz/cli-docs

USAGE:

・・・略
```

### IBM Cloud CLIでログイン

マイページのユーザーアイコンから`CLIとAPIにログイン`を選択します。

> ![スクリーンショット 2023-11-07 18.01.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/600cb346-21f0-51b7-e22a-c569cb6d9349.png "スクリーンショット 2023-11-07 18.01.57.png")

ワンタイムパスコードでのログイン方法が表示されるので、コピペ実行します。

> ![](https://i.gyazo.com/9f05ab398ef7f89671584569bafaafe4.png)

```bash
$ ibmcloud login -a https://cloud.ibm.com -u passcode -p <ワンタイムパスコード>
```

## 2. トークンの取得
次にトークン取得ですが以下のコマンドで取得できました。

```bash
$ ibmcloud iam oauth-tokens
```

以前触った時に事前にアカウントで何か設定をしてしまってる可能性もありますが、、一旦これでOKです。

```bash
IAM token:  Bearer xxxxxxxxxxxxxxxxxxxxxxxxxx
```

といった形で取得できます。これを後で利用します。

参考
https://qiita.com/testnin2/items/3a4ca8a1a2b79b713c48

## 3. IBM Cloud Functionsの利用

https://cloud.ibm.com/functions/ にアクセスします。

> ![スクリーンショット 2023-11-07 18.05.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/1b86ea1b-2213-eac8-6333-8c49e892cec6.png "スクリーンショット 2023-11-07 18.05.49.png")

### 名前空間の作成

ネームスペースの作成をします。

> ![スクリーンショット 2023-11-07 18.07.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/063a98a8-9ffc-107d-44aa-c8245b308c2a.png "スクリーンショット 2023-11-07 18.07.23.png")
> Notion2Discordっていう名前で何かやろうとした跡がある...

識別できれば良いと思うので適当につけます。

ロケーションは東京で、リソースグループもDefaultのままにします。

> ![スクリーンショット 2023-11-07 18.08.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/90d0b960-f755-aaf4-624a-8b3730e1ca5d.png "スクリーンショット 2023-11-07 18.08.49.png")

### Functionsの作成

"作成の開始"から進みます。

アクションの作成をしてみます。

> ![スクリーンショット 2023-11-07 18.09.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/563a2686-a099-7976-6771-4f8ee1f58722.png "スクリーンショット 2023-11-07 18.09.57.png")![スクリーンショット 2023-11-07 18.10.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/e7c2577a-6364-b686-13aa-75aa1da80dc4.png "スクリーンショット 2023-11-07 18.10.45.png")

アクションごとにNode.jsなどのランタイムを選べるみたいですね。

### LINEに通知するコードを元に処理を書く

こちらの記事の内容をもとにLINE Notifyを使ってLINE通知をしてみます。

https://qiita.com/n0bisuke/items/3416356eda8a715f46ae

コードのところに以下をコピペしていきます。`const axios = require('axios');`などは特にnpm installなどをする操作はしてないので自動で裏側でインストールしてくれてる模様です。

> ![スクリーンショット 2023-11-07 18.40.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/08ab1fad-bb22-7bdb-db4e-7cd30af5ce4e.png "スクリーンショット 2023-11-07 18.40.46.png")

```js
/**
  *
  * main() このアクションを呼び出すときに実行されます
  *
  * @param Cloud Functions アクションは 1 つのパラメーターを受け入れます。このパラメーターは JSON オブジェクトでなければなりません。
  *
  * @return このアクションの出力。この出力は、JSON オブジェクトでなければなりません。
  *
  */
 
const axios = require('axios');
const qs = require('querystring');

const LINE_NOTIFY_API_URL = 'https://notify-api.line.me/api/notify';

const LINE_NOTIFY_TOKEN = 'LINE Notifyのトークン';

let config = {
    url: LINE_NOTIFY_API_URL,
    method: 'post',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': 'Bearer ' + LINE_NOTIFY_TOKEN
    },
    data: qs.stringify({
      imageFullsize: `https://images.dog.ceo/breeds/airedale/n02096051_3443.jpg`,
      imageThumbnail: `https://images.dog.ceo/breeds/airedale/n02096051_3443.jpg`,
      message: 'ProtoOut Studioからの通知だよー！ https://images.dog.ceo/breeds/airedale/n02096051_3443.jpg',
    })
}

async function getRequest() {

  ////// LINE Notify に送る ////////////////////////

  try {
    const responseLINENotify = await axios.request(config);
    console.log(responseLINENotify.data);
  } catch (error) {
    console.error(error);
  }

}

function main(params) {
    getRequest();    // getRequest を呼び出してデータを読み込む
	return { message: 'Hello World' };
}
```

**`LINE Notifyのトークン`の箇所は書き換えましょう。**

main関数の中が起動に呼ばれてそうですね。

### 起動してみる

保存をして、起動ボタンを押すとLINEに通知がきます。

> ![スクリーンショット 2023-11-07 18.42.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/0262bc5f-9bf7-fe13-0a23-47980152ad68.png "スクリーンショット 2023-11-07 18.42.24.png")

無事に通知がきました。

> ![スクリーンショット 2023-11-07 18.42.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/74559609-6b48-3d2f-92af-9c5ac95adf3f.png "スクリーンショット 2023-11-07 18.42.11.png")

### エンドポイントをCURLで叩いて呼び出し

エンドポイントの項目から

```bash
curl --request POST --url 'https://xxxx.functions.cloud.ibm.com/api/v1/namespaces/xxxxxxxxxxxxxxxxxxxxxx/actions/アクション名?blocking=true' --header 'accept: application/json' --header 'authorization: Bearer <IAM token>'
```

のようなCURLで実行するコマンドサンプルを使って試してみます。

> ![](https://i.gyazo.com/9c7744be7cc4d2c434126f1b0313e33c.png)

ここでIAM tokenという記述があるので、先ほど入手したIAMトークン（ibmcloud iam oauth-tokensで取得したトークン）を使ってエンドポイントにリクエストをしてみましょう。

```bash
curl --request POST --url 'https://xxxx.functions.cloud.ibm.com/api/v1/namespaces/xxxxxxxxxxxxxxxxxxxxxx/actions/アクション名?blocking=true' --header 'accept: application/json' --header 'authorization: Bearer XXXXXXXXXXXXXXXXXXX'
```

のような感じですが、トークンは結構長いです。

実行するとこんな感じのレスポンスがあり、LINE Notifyで通知もされます。

ちなみに、以下JSONはレスポンスそのままではなく見やすいように整形ツールで整形してあります。

```json
{
	"activationId": "xxxxxxxxxxxx",
	"annotations": [
		{
			"key": "path",
			"value": "xxxxxxxxxxxxxxxxxxx/アクション名"
		},
		{
			"key": "waitTime",
			"value": 199
		},
		{
			"key": "transId",
			"value": "xxxxxxxxxxxxxxxxxxxxx"
		},
		{
			"key": "kind",
			"value": "nodejs:20"
		},
		{
			"key": "timeout",
			"value": false
		},
		{
			"key": "limits",
			"value": {
				"concurrency": 1,
				"logs": 10,
				"memory": 256,
				"timeout": 60000
			}
		},
		{
			"key": "initTime",
			"value": 83
		}
	],
	"duration": 185,
	"end": 1699350113386,
	"logs": [],
	"name": "reply",
	"namespace": "xxxxxxxxxxxxxxxxxxxx",
	"publish": false,
	"response": {
		"result": {
			"message": "Hello World"
		},
		"size": 25,
		"status": "success",
		"success": true
	},
	"start": 1699350113201,
	"subject": "nobisuke@hogehoge.com",
	"version": "0.0.1"
}
```

コードのmain関数内の`{message: 'Hello World'}`はこちらのレスポンスの中に含まれてました。

## 所感など

トリガー設定がなくアクションだけでもとりあえず試すことができました。

デバッグが辛いかもしれないという雰囲気を感じつつとりあえず動いたから一旦ここまででまとめておきます。

トリガーのところでトリガーを設定して任意のアクションと紐づける みたいな感じのことをやりたい気もしますがそれはまた機会があれば。