足元サーバがないのでlambdaでConfluenceに日報の投稿をさせてみる。

みなさまこんにちは。 
[AWS Lambda Advent Calendar 2017](https://qiita.com/advent-calendar/2017/aws-lambda) の 12/25分での投稿となります。最後の日に一枠空いていたので、Lambda自体初めてですが、投稿ドリブンで枠をとって実際に手を動かしてみました。

## はじめに

### やりたいこと / きっかけ

現在の職場は基本的にサービス用のインフラはクラウド（主にAWS) を使っており、オフィス内もネットワーク機器以外にはサーバと呼べるものがNASくらいしかありません。
(職場のみなさんも、それで特に困っている様子は無いみたいなのですが...)

ただ、定時ダッシュするわたしには、ちょっとでも時間が惜しい。
何かしら日常業務をcronなどで自動でやらせて負担を軽減したいところですが、足元サーバが無いので、その辺をどうしたものかと悩んでおりました。

さて、そんな折。
チームの毎週の振り返りをConfluenceのブログ投稿を使ってまとめているのですが、割とマクロを駆使してフォーマットを決めているページなので、毎回新規でページを準備するのが大変になって来ました。
（20171225現時点で、Confluenceのブログ投稿には、テンプレートの機能が無いのです！）

ついつい凝ったマクロ（TipとかTOCとかパネルとか）を利用しているため、わたしがお休みの場合に代わりの方が投稿を起こすのが大変...というのが何回か発生。
『さすがにそろそろ自動化出来ない？』と言うメンバーの声が出てきたので、なんとかしようというタスクがやってきました。

### まずはAPIを叩いて投稿テスト！

[Confluenceについては](https://qiita.com/akiko-pusu/items/86c4bcebdf2cf37d247d)過去いくつか記事を書いていますが、REST APIがあるので、そこを叩けば投稿はできそうです。

あとは、何かしらでスクリプトを組んだら、定期実行できるものがあればいい。
でも、AWSにあるサーバはデプロイや開発用で、個人のスクリプトを動かすわけにはいきません...。

### 足元サーバの代わりにLambda？

どうしたものかと思っていた時、DBのバックアップやインスタンスの上げ下げを自動化したいねーと言う話が出てきました。こちらも問題は、どこでスケジュール実行するかです。
そこで、「Lambda使ってできるんじゃないの？」と言うコメントをいただきました。それがつい最近です。

そして幸いに、この[AWS Lambda Advent Calendar 2017](https://qiita.com/advent-calendar/2017/aws-lambda)で、たくさんのナレッジが紹介されているのを発見。
AWSのリソースだけでなく、Slackをはじめ外部のAPIを叩ける、pythonのようなスクリプト言語でも処理を書けると言うのがわかりましたので、「じゃあConfluenceも叩いてみればいいかも！」と思い立ちました。

個人でAWS Lambda使うにはお金も敷居も高すぎる。でも、今回は会社のリソースで、それなりの名分もあり、しかもConfluenceもクラウド版を使っているので好都合です。

### Pythonのスクリプトを調整

confluenceBlogPostSample という関数を定義していきます。
この辺りはあまり考えずに設定できました。

![sample-screenshot.png](https://qiita-image-store.s3.amazonaws.com/0/27976/f90a2c58-8789-2dfb-676c-54676e925625.png)


実際の処理のスクリプトの作成は以下の通りです。
ConfluenceのREST API用のPythonのパッケージは、現在リポジトリがGitHub上に移動しています。

- https://github.com/pycontribs/confluence

こちらを使ってもいいのですが、Lambdaでスクリプトで関数を定義する場合は「お作法」があるため、このパッケージはオーバースペックです。
単純にPOSTができればいいので、requestsを使ったリックソフト様の[REST APIでConfleunceのページを生成](https://www.ricksoft.jp/blog/archives/2984/) という記事を参考にさせていただきました。

まずは面倒だった自分の日報での投稿を試しに、以下の様なスクリプトを組みました。
その際の前提は以下の通りです。

- ブログ投稿は個人スペースで試す
- 毎日同じタイトルでは投稿できないので、連続投稿でテストしても大丈夫な様に、タイトルを動的に変更 (YYYYmmddHHMMをそえる）
- Confluenceマクロを多用しているので、まずはTOC(見出し）マクロがちゃんと埋め込めるか確認

また、Lambdaとして利用するためには、以下の様なお作法があります。

- 単体のスクリプトなら画面に貼り付け出来る
- APIにアクセスするためのrequestsはLambdaのデフォルトでは使えない
- Lambda内で動的にpip installができないので、必要なモジュールがある場合は、ソースコードでzipにまとめてアップロードすること
- 実行のメインになる関数は、lambda_function.py というファイル名で、lambda_handler（）という関数

この辺り、今回のアドベントカレンダーの記事が本当に参考になりました！ みなさまありがとうございます！

#### lambda_function.py の中身

この様になっています。
実際は、requestsのソースコードが必要なので、zipファイルにまとめてアップロードしています。

```python
# coding: utf-8
import os, json, textwrap
from datetime import datetime
import requests

# ページの作成
# http://CONFLUENCEのサーバーURL/rest/api/content にアクセスして
# ページを作成します
# (HTTP メソッドは post )
def create_page():
  BASE_URL="CONFLUENCEのサーバーURL"
  AUTH=("akiko.xxxx@example.com", "パスワード")
  HEADERS = {"content-type":"application/json"}
  json_data = create_json_data()
  response = requests.post(
        BASE_URL + "/rest/api/content",
        auth=AUTH,
        data = json.dumps(json_data),
        headers=HEADERS)
  response.raise_for_status()
  return response
     
# json データの生成
def create_json_data():
  now = datetime.now()
  date_str = now.strftime("%Y%m%d%H:%M:%S")
 
  # テストなので個人スペース
  space_key="~akiko.xxxx"
  page_title='{} テストの投稿'.format(date_str)

  # Lambdaの画面で環境変数を設定できるので、そのテスト（Lambdaからのポストかどうかのチェック用）
  posted_by = os.environ["POSTED_BY"]

　　　　# 投稿内容はHTML形式で記載可能
  # Confluenceのマクロも、<ac:structured-macro/>で指定します
  # この例では、Tipマクロの中にTOCマクロをセットしています
  #
  content = textwrap.dedent('''
<ac:structured-macro ac:name="tip">
  <ac:rich-text-body>

  <ac:structured-macro ac:name="toc">
    <ac:parameter ac:name="printable">true</ac:parameter>
    <ac:parameter ac:name="style">square</ac:parameter>
    <ac:parameter ac:name="maxLevel">2</ac:parameter>
    <ac:parameter ac:name="indent">5px</ac:parameter>
    <ac:parameter ac:name="minLevel">2</ac:parameter>
    <ac:parameter ac:name="class">bigpink</ac:parameter>
    <ac:parameter ac:name="exclude">[1//2]</ac:parameter>
    <ac:parameter ac:name="type">list</ac:parameter>
    <ac:parameter ac:name="outline">true</ac:parameter>
    <ac:parameter ac:name="include">.*</ac:parameter>
  </ac:structured-macro>     

  </ac:rich-text-body>
</ac:structured-macro>
<h2>テストの投稿になります: 見出し１つめ</h2>
<h2>テストの投稿になります: 見出し２つめ</h2>
<h2>テストの投稿になります: 見出し３つめ</h2>
<br/>
<p>generated at: {date_str} {posted_by}</p>

''').format(date_str=date_str, posted_by=posted_by).strip()   
   
  payload = {
              "type":"blogpost",
              "title":page_title,
              "space":{"key":space_key},
              "body":{
                "storage":{
                  "value" : content,
                  "representation":"storage"
                }
              }
            }
  return payload
 
# main function
def lambda_handler(event, context):
  result = create_page()

　　　　# APIの結果を出力
  print(result.json())

```

## とりあえずやってみた！

定期実行はCloudWatchイベントとして設定できる様ですが、まずは手動実行を試します。
画面右上の「テスト」をクリックすると実行されます。

ただし、案の定最初は勝手がよくわからなかったので、何度か失敗....。
CloudWatch側にも、「ログストリーム」として記録されるんですね^^;
(後でチームの方にびっくりされない様にお話しせねば）

![script-error.png](https://qiita-image-store.s3.amazonaws.com/0/27976/06d60334-a1fc-8778-4257-16dd48e1df93.png)

Pythonのスクリプトの構文エラーやモジュールのエラーなどで何回かアップし直して、やっと成功しました。

### 成功しました！

何度目かのエラーのあと、晴れて成功！！
POST後のレスポンスが画面に出力されています。

![after-test.png](https://qiita-image-store.s3.amazonaws.com/0/27976/8251157e-62d5-2ae5-1d29-88abf233804b.png)

CloudWatch側のログにも、実行の前後とPOST後のレスポンスが出力されています。

![cloudwatch-log.png](https://qiita-image-store.s3.amazonaws.com/0/27976/df6307d2-347f-2e48-7905-40ddda93c581.png)


### Confluence側はどうなった？

さて、問題の投稿はどうなったかというと...。
だいぶ荒削りではありますが、　ac:structured-macro　のタグがうまく埋め込まれて、想定通りにブログ投稿が描画されていました！

![posted-via-lambda.png](https://qiita-image-store.s3.amazonaws.com/0/27976/fe2bf1dd-a81b-116f-9c15-142340702787.png)

同じ日に数回投稿しても、名前がかぶらないのでなんとか別々の投稿として扱われています。
これでやりたいことのめどが立ちそうです！

## これから先のこと

さて、なんとかLambdaから投稿ができましたが、まだ文章は荒削りなので、もっと見栄えを調整していかないといけません。
それから、Lambda側の定期実行の処理や、POST完了後にSlackに通知すると行った処理も必要になります。

テストに使っているアカウントとパスワードもソースに書いてしまっています。
Lambda側の環境変数に設定するかKMSを使って暗号化する、投稿用アカウントも権限を最小限に絞ったAPIアクセス専用のアカウント（&アクセスキー）に変更するといった処理が必要になります。
この辺り、調整できたら追記したいと思います。

また、内容的にはLambdaよりもConfluence寄りになっていますので、Confluenceを利用されている方の参考になりましたら幸いです。