AWS SAMでBacklogとNotionの連携
概要
会社でBacklogを使用しているため、タスク管理として自身が活用しているNotionと連携してみました。
実装方針の調査
課題
- NotionはBacklogとの統合に対応していない
- 二つのアプリを連携させる記事が全くない
解決策
実装方針は、BacklogのWebhookとNotion APIを用いて連携させる方針としました。
また、APIは、AWS SAM(Serverless Application Model)を用いて構築します。
使用技術の参考場所
構成図
今回の構成図は、写真の通りです。
1.SAMの環境構築
AWS SAMはAWS上でサーバーレスアプリケーションを迅速に構築するためのツールです。
今回は、API GatewayとLambdaの構築に使用します。
-
SAM CLIのインストール
こちらのインストール手順に従ってください -
SAMの初期構築
sam init
% sam init
You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
Choose an AWS Quick Start application template
1 - Hello World Example
2 - Data processing
3 - Hello World Example with Powertools for AWS Lambda
4 - Multi-step workflow
5 - Scheduled task
6 - Standalone function
7 - Serverless API
8 - Infrastructure event management
9 - Lambda Response Streaming
10 - Serverless Connector Hello World Example
11 - Multi-step workflow with Connectors
12 - GraphQLApi Hello World Example
13 - Full Stack
14 - Lambda EFS example
15 - Hello World Example With Powertools for AWS Lambda
16 - DynamoDB Example
17 - Machine Learning
Template: 1
Use the most popular runtime and package type? (Python and zip) [y/N]: y
Would you like to enable X-Ray tracing on the function(s) in your application? [y/N]:
Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]:
Would you like to set Structured Logging in JSON format on your Lambda functions? [y/N]:
Project name [sam-app]:
-----------------------
Generating application:
-----------------------
Name: sam-app
Runtime: python3.9
Architectures: x86_64
Dependency Manager: pip
Application Template: hello-world
Output Directory: .
Configuration file: sam-app/samconfig.toml
Next steps can be found in the README file at sam-app/README.md
Commands you can use next
=========================
[*] Create pipeline: cd sam-app && sam pipeline init --bootstrap
[*] Validate SAM template: cd sam-app && sam validate
[*] Test Function in the Cloud: cd sam-app && sam sync --stack-name {stack-name} --watch
- 初期構築のデプロイ
sam deploy
% cd {プロジェクト名}
% sam deploy
Managed S3 bucket: 生成バケット名
A different default S3 bucket can be set in samconfig.toml
Or by specifying --s3-bucket explicitly.
Uploading to c6ce8fa8b5a97dd022ecd006536eb5a4 847 / 847 (100.00%)
Deploying with following values
===============================
Stack name : sam-app
Region : ap-northeast-1
Confirm changeset : True
Disable rollback : False
Deployment s3 bucket : 生成バケット名
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Signing Profiles : {}
Initiating deployment
=====================
Uploading to cdb3e4a740923d8a859b465974347896.template 1191 / 1191 (100.00%)
Waiting for changeset to be created..
CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType Replacement
-----------------------------------------------------------------------------------------------------------------------------------------------------
+ Add HelloWorldFunctionHelloWorldPermiss AWS::Lambda::Permission N/A
ionProd
+ Add HelloWorldFunctionRole AWS::IAM::Role N/A
+ Add HelloWorldFunction AWS::Lambda::Function N/A
+ Add ServerlessRestApiDeployment47fc2d5f AWS::ApiGateway::Deployment N/A
9d
+ Add ServerlessRestApiProdStage AWS::ApiGateway::Stage N/A
+ Add ServerlessRestApi AWS::ApiGateway::RestApi N/A
-----------------------------------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:008176221809:changeSet/samcli-deploy1712275627/c40f9997-9a0e-4991-87dd-ddfe5080cbd1
Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y
2024-04-05 09:07:17 - Waiting for stack create/update to complete
CloudFormation events from stack operations (refresh every 5.0 seconds)
-----------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus ResourceType LogicalResourceId ResourceStatusReason
-----------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS AWS::CloudFormation::Stack sam-app User Initiated
CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole -
CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole Resource creation Initiated
CREATE_COMPLETE AWS::IAM::Role HelloWorldFunctionRole -
CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction -
CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction Resource creation Initiated
CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction Eventual consistency check
initiated
CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi -
CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi Resource creation Initiated
CREATE_COMPLETE AWS::ApiGateway::RestApi ServerlessRestApi -
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5f -
9d
CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionHelloWorldPermiss -
ionProd
CREATE_COMPLETE AWS::Lambda::Function HelloWorldFunction -
CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionHelloWorldPermiss Resource creation Initiated
ionProd
CREATE_COMPLETE AWS::Lambda::Permission HelloWorldFunctionHelloWorldPermiss -
ionProd
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5f Resource creation Initiated
9d
CREATE_COMPLETE AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5f -
9d
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage -
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage Resource creation Initiated
CREATE_COMPLETE AWS::ApiGateway::Stage ServerlessRestApiProdStage -
CREATE_COMPLETE AWS::CloudFormation::Stack sam-app -
-----------------------------------------------------------------------------------------------------------------------------------------------------
CloudFormation outputs from deployed stack
--------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
--------------------------------------------------------------------------------------------------------------------------------------------------------
Key HelloWorldFunctionIamRole
Description Implicit IAM Role created for Hello World function
Value arn:aws:iam::008176221809:role/sam-app-HelloWorldFunctionRole-L1JzW0JuQFHm
Key HelloWorldApi
Description API Gateway endpoint URL for Prod stage for Hello World function
Value apiGatewayのPath名
Key HelloWorldFunction
Description Hello World Lambda Function ARN
Value lambda名
--------------------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - sam-app in ap-northeast-1
4.今回触るファイルについて
現状のファイル構成
% tree
.
├── README.md
├── __init__.py
├── events
│ └── event.json
├── hello_world
│ ├── __init__.py
│ ├── app.py
│ └── requirements.txt
├── samconfig.toml
├── template.yaml
└── tests
├── __init__.py
├── integration
│ ├── __init__.py
│ └── test_api_gateway.py
├── requirements.txt
└── unit
├── __init__.py
└── test_handler.py
6 directories, 14 files
下記の3点を使用
- hello_worldディレクトリ
- APIのロジックが記載されているファイル
- samconfig.toml
- デプロイメントに関する設定が入っている。今回は、環境変数のセットに使用
- template.yaml
- サーバーレスアプリケーションの構成やリソースを定義するためのテンプレートファイル。今回は、API構成の設定に使用。
- APIのエンドポイントの確認
sam list endpoints --output json
% sam list endpoints --output json
[
{
"LogicalResourceId": "HelloWorldFunction",
"PhysicalResourceId": "xxxxx",
"CloudEndpoint": "-",
"Methods": "-"
},
{
"LogicalResourceId": "ServerlessRestApi",
"PhysicalResourceId": "xxxx",
"CloudEndpoint": [
"https://XXXX/Prod",
"https://XXXX/Stage"
],
"Methods": [
"/hello['get']"
]
}
]
今回使用するのは、https://XXXX/Prod/{API_PATH}
になります。
下記のように「hello world」が取得できれば、SAMの環境構築は完了となります。
$ curl https://XXXX/Prod/hello
{"message": "hello world"}
2.Notionの環境構築
ここでは、Notion APIを使用する準備と、Backlogのデータが保管されるデータベースをNotionに作成します。
Notion APIを使用する為の準備
- こちらから新しいインテグレーションを作成してください。
- 画像のシークレットトークンを表示コピーして、一旦メモしておいてください。
Notionのデータベース設定
- Notionで下記のデータベースを作成してください。
プロパティの種類 | カラムの名前 |
---|---|
タイトル | タイトル |
ステータス | 状態 未着手: 未対応 進行中: 処理中 完了: 完了/処理済み |
セレクト | タグ |
セレクト | マイルストーン |
セレクト | 優先順位 |
テキスト | 担当者 |
テキスト | 期限 |
テキスト | 作成者 |
作成日時 | 作成日時 |
日付 | 日付 |
- テーブルIDを取得します。テーブルIDは一旦メモしておいてください。
画像の「リンクをコピー」からURLを取得します。
おそらく下記のフォーマットのURLが取得できます。その中の「abcde」部分がテーブルIDとなります。
https://www.notion.so/abcde?v=a&pvs=1
- 作成したテーブルにAPIからの参照を許可します。
データベースを作成したページの設定から「接続先 → 作成したシークレットトークンの名前」を選択してください。
3.Backlogの環境構築
BacklogのWebhook機能を用いて、APIにリクエストを送る設定を行います。
-
webhookの設定ページへ遷移
プロジェクト設定 → インテグレーション → Webhookの「設定」を選択してください
-
webhookの設定
webhookの追加 → 画像のように情報を入力してください。「WebHook URL」は先ほど取得したAPIのパスになります。- API PathはAWS SAMが作成したデフォルトのhelloを使用します。
- API PathはAWS SAMが作成したデフォルトのhelloを使用します。
-
下に実行テストがありますので、プルダウンから「課題の追加」を選択し、テストを実行してください。「実行完了」とメッセージがでれば設定は成功ですので、「Webhookを追加する」を押下してください。
4.課題追加APIの作成
それぞれの環境構築が完了した為、次は課題追加用のAPIを作っていきます。
AWS SAMを用いて、課題追加用のAPIを作成していきます。
変更するのは、下記3つのファイルになります。
.
├── hello_world
│ ├── app.py
├── samconfig.toml
├── template.yaml
- 環境変数の設定
APIからNotionに書き込むためには、途中でメモをしたNotionのテーブルIDとシークレットトークンが必要になります。この二つのIDは公開するとセキュリティ上のリスクがある為、APIの環境変数として設定を行います。
samconfig.tomlに下記を追記してください。
※git管理されている方はファイルを.gitignoreの対象にすることをお勧めします。
[default]
[default.global.parameters]
~
parameter_overrides = "apiKey='Notionのシークレットキー' tableId='テーブルID'"
template.yamlのGlobal以降を下記に上書き
template.yaml Global以降
Globals:
Function:
Timeout: 3
MemorySize: 128
Parameters:
apiKey:
Type: String
tableId:
Type: String
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.9
Architectures:
- x86_64
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: post
Environment:
Variables:
API_KEY: !Ref apiKey
TABLE_ID: !Ref tableId
- APIの作成とデプロイ
app.pyに下記のapiをコピペ
app.py
import json
import requests
import os
import time
def lambda_handler(event, context):
API_KEY = os.getenv('API_KEY')
TABLE_ID = os.getenv('TABLE_ID')
# event["body"] をJSONオブジェクトにデシリアライズ
print(API_KEY, TABLE_ID, event["body"])
body = json.loads(event["body"]) if event["body"] else {}
# Notion APIへのリクエストデータ
data = {
"parent": {"type": "database_id", "database_id": TABLE_ID},
"properties": {
"タグ": {
"select": {
"name": body["content"]["issueType"].get("name", ""),
}
},
"状態": {
"type": "status",
"status": {
"name": body["content"]["status"].get("name", ""),
}
},
"タイトル": {
"title": [{"text": {"content": body["project"].get("projectKey", "") + "-" + str(body["content"].get("key_id", ""))+ "\n" + body["content"].get("summary", "")}}]
},
"担当者": {
"rich_text": [] if body["content"]["assignee"] is None else [{
"type": "text",
"text": {
"content": body["content"]["assignee"].get("name", "")
}
}]
},
"優先順位": {
"select": {
"name": body["content"]["priority"].get("name", "")
}
},
"期限": {
"rich_text": [{
"type": "text",
"text": {
"content": "" if body["content"]["dueDate"] is None else body["createdUser"].get("name", "")
}
}],
},
"作成者": {
"rich_text": [{
"type": "text",
"text": {
"content": "" if body.get("createdUser") is None else body["createdUser"].get("name", "")
}
}],
},
},
"children": [
{
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [{
"type": "text",
"text": {
"content": body.get("content", {}).get("description", "")
}
}
]}}
]
}
# "マイルストーン"プロパティの追加(空のリストでない場合のみ)
if body["content"].get("milestone") and body["content"]["milestone"]:
data["properties"]["マイルストーン"] = {
"select": {
"name": body["content"]["milestone"][0].get("name", "")
}
}
# Notion APIへのリクエストヘッダー
headers = {
'Authorization': 'Bearer ' + API_KEY,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
}
# Notion APIにデータをPOST
response = requests.post('https://api.notion.com/v1/pages', headers=headers, json=data)
print(response.json())
return {
"statusCode": 200,
"body": json.dumps({
"message": "Successfully created a new page in Notion",
}),
}
- sam build
- 変更内容の読み込みを行います。
- sam deploy
- 変更内容の反映をします
5.動作確認
-
Backlogで課題を追加します。
-
Notionのテーブルに追加されていれば成功です。
もし追加されなかった場合
手段1 ログからエラー調査
SAMで実施した場合、HelloWorldFunction
Lmabdaが作成されています。
Lambdaのコンソール画面に入り、写真の「CloudWatchログを表示」ボタンを押下すると、Lambdaのログを見ることができます。そこからエラー原因を調査してみてください。
手段2 Notionのテーブルを見直す
APIが想定している通りにNotionのテーブルを生成する必要があります。その為、「Notionのデータベース設定」のセクションに記載している通り下記2点を再度確認してください。
- 「プロパティの種類」と「カラムの名前」が正しいか再確認
- 状態のカラムについては、下記のプロパティ設定にする
まとめ
今回はNotionのAPIとBacklogのWebhookを用いて「課題追加の反映」のみを行いました。
この方法だとBacklogのアクション毎にAPIを追加する必要があります。その為、Backlogの全てのアクションを対応するとかなり手間になるのが難点です。
一方でカスタム制はかなり高いので、Backlogの内容をカスタマイズして自身のnoteにまとめるには適していると感じました。
今後BacklogとNotionの統合機能がリリースされれば良いと思いますが、それまでは一旦このやり方に落ち着きそうです。
Discussion