GitHub Actions でブログの更新をツイートする
面白そうだったので先に作っちゃいました。
GitHub Actions 自体はすでに導入したので、あとは Tweet をできるようにするだけです。
ツイートメッセージを組み立てる
こういう感じにツイートしたい:
【ブログを更新しました】libnss-json 始めました https://t.co/M6g35h7vuj
— IGGG(群馬大学電子計算機研究会) (@IGGGorg) September 25, 2019
このためには
- 記事のタイトル
- 記事のリンク
が必要ですね。これらを git の差分などから構築するために THE シェル芸します。
ブログは Hexo で作っており、記事は source/_posts/hoge.md
に追加します。最終的なリンクは [base_url]/YYYY/MM/DD/hoge
となるので、リンクを得るには日付の情報とファイル名が必要です。
マークダウンには front matter でタイトルや日付が書いてあります:
--- |
これをよしなにパースします:
- 更新の有無:
git diff fdd0928^...fdd0928 --name-only --diff-filter=A -- source/_posts/*.md
- (1) のファイルパス
path/to/file
からタイトル取得:head path/to/file | grep '^title:' | sed 's/^title: *//g'
- (1) のファイルパスから日付を取得:
head path/to/file | grep '^date:' | sed -e 's/^date: *\([0-9]\{4\}\)-\([0-9]\{2\}\)-\([0-9]\{2\}\) .*$/\1\/\2\/\3/g'
- (1) のファイルパスから拡張子抜きのファイル名を取得:
echo path/to/file | sed -e 's/source\/_posts\/\(.*\)\.md/\1/'
- (3) の日付と (5) のファイル名から URL を取得:
echo "https://iggg,github.io/${date}/${file_name}"
これらをするシェルスクリプトがこちら:
BASE_URL="https://iggg.github.io" |
TARGET_BRANCH
だけ外から与えます。ループしているのは、(1) で複数投稿があった場合に最新のものだけを拾うためです。
ツイートする
最初は curl で API でも叩けばいいかなぁって思ってたけど Twitter API は意外とめんどい。そこでひらめく、せっかく GitHub Actions だし、アクションを使えばいいんだと(天才)。
ググってもなさそうだったので作りました:
Python の tweepy
を使っています。理由は (1) スクリプト系の言語で (2) 扱いが簡単(クライアントオブジェクト生成してメソッド叩くだけ)で (3) 今でもメンテナンスされているのがちょうどこれだったからです(Ruby の twitter
gem は2017から更新止まってた)。
使い方はこんな感じ:
uses: matsubara0507/actions/tweet@master |
ここで問題が1つ。どうやってさっき生成したツイートメッセージを with.message
に渡すか。
ここで、なんかしらのコマンドの実行を与えることはどうやらできないっぽい:
- name: Build tweet message |
この位置で変数を使うには steps.[step_id].outputs.hoge
を作る必要がある。
しかし、これはアクション側で事前に設定するもの(少なくとも現在は)で、独自で定義することはできない:
# こう言うのができれば良いのに |
だったら、なんかスクリプト実行してその出力を outputs に退避させるアクションを使えばいいんだと(天才)。
はい、ググってなさそうだったんで、ないなら作る精神:
これを利用するとこんな感じでツイートできました:
jobs: |
おまけ: アクションの作り方
作り方は2つあります。JavaScript (TypeScript) を使う方法と Docker を使う方法。
JavaScript | Docker | |
---|---|---|
仮想環境 | Linux, MacOS, Windows | Linux |
起動速度 | 速い | 遅い(pull or build) |
依存関係 | 前後に影響(たぶん) | アクションで独立 |
Docker の方が簡単ですが、JavaScript は次のステップにも影響を与えることができます。
ちなみに、outputs
アクションは JavaScript で、tweet
アクションは Docker で作りました。
両方とも、GitHub リポジトリにあげておけば直接利用できます。
JavaScript の場合
- ココを見て
- アクションのプリミティブなやつはだいたい actions/toolkit リポジトリにあります
- 使い方の例が TypeScript だったりするのが罠
Docker の場合
- ココを見て
- GitHub リポジトリの場合は
docker build
- レジストリにあげると
docker pull
おしまい
GitHub Actions は、いよいよ 11/13 に GA されるんで楽しみです!
iggg.org を移行する その2
これの続きです。
半年前…やりきりました。
実際に iggg.org はすでに新しくなっており、IGGG/new.iggg.org というリポジトリで動いてます。
前回からの残タスク
やったのはこれ
- NEWSの記事の細かい修正
- wiki の移行
- コミットから自動生成
- ドメインを iggg.org にする
1. NEWSの記事の細かい修正
鬼門その1。
いくつか古い記法が残っていました。
埋め込み系
まずは埋め込み系:
- スライド の埋め込み (SlideShare)
- Twitter と YouTube の埋め込み
ええ、この辺りは機械的にやりようがないので。。。
- 問題の箇所がどこか grep
- 対応する旧ページを見に行く
- Embed 記法を書き換える
愚直です(たいした数がないのでいいんですけど)。
画像
そして次は画像。
記法の変換は大体できていたのが、サイズがめちゃくちゃデカイので Hugo のショートコードを設定して直した:
{{ $src := .Get "src" }} |
これを layouts/shortcodes/img.html
に保存し、{< img s1c="/path/to/image" scale="0.2" title="タイトル" >}}
と書くことでサイズを指定したり、正しいパスに変換してくれたりしてくれる。
完璧だ。
alias
旧ページと新ページでページの URL が変わってしまう。
旧はページのタイトルや設定した URL になってるのだが、対して新は日付の URL。
さて、どうするか。
alias の設定自体は Hugo のフロントマターで指定できます:
--- |
ではどうやって元の URL と新しい URL を対応させるか。
根性です。
根性しました。
2. wiki の移行
鬼門その2。
静的サイトはキッツイので代替の要件から考えた。
- ページは公開されていい
- ユーザー登録は(多少)クローズド
- マークダウンか何かでインポートできる
以上を踏まえた結果 Scrapbox.io にしました:
で、以降手順はこんな感じ
- PukiWiki のデータ全部抜く
- PukiWiki から MD に変換
- MD 内の wiki へのリンクを Scrapbox に差し替え
- 画像も Scrapbox にいい感じに
- MD を Scrapbox にインポート
PukiWiki のデータ全部抜く
自分でセットアップしてないので、まずは PukiWiki のデータを探した:
$ ls /srv/http/wiki/wiki-data/wiki/ | head |
発見。ファイル名はどうやら16進数でエンコードされたURL(タイトル)らしい。
Ruby で適当にデコードしてみた:
$ pwd |
謎は解けたので後は固めて scp するだけ:
# これは SSH 先 |
PukiWiki から MD に変換
魔法の sed 芸した:
とはいえいくつか漏れがある:
-hoge
みたいな h1 要素があり、スペースが無い#contents
などもともとマジックワードのようなのがある- 画像の形式が変
[[xxx:yyy]]
形式のリンク
どうしようもないので手動で。。。
後、メタっぽいページはいらないので削除した(e.g. Help
)。
後、タイトルを Ruby 芸:
ls | grep '.txt' | xargs -IORIG bash -c 'ruby -e "puts [ARGV[0].delete(%!.txt!)].pack(%!H*!).gsub(?\s, ?_)" ORIG | xargs -INEW echo mv ORIG NEW.md' |
これでも漏れがあるので手動で直す。。。(空白とか)
MD 内の wiki へのリンクを Scrapbox に差し替え
参照:
タイトルと同じならこの記法に変換。
それ以外は普通のリンクに。
ほぼ手作業で。
画像も Scrapbox にいい感じに
まず画像を持ってくる:
$ cd /srv/http/wiki/wiki-data/attach |
やり方は *.txt
と同じ(割愛)。
それを同じようにデコード(これは名前を出してみてるだけだけど):
$ ls | xargs -I{} ruby -e 'puts ARGV[0].split(?_).map{|x| [x].pack("H*")}.flatten.join(?/)' {} |
画像は Scrapbox にインポートできないっぽいので、大した量じゃないし雑なリポジトリを作って雑にあげた:
あとは画像のリンクを直すだけ(半ば手作業で)。
MD を Scrapbox にインポート
参照:
MD から Scrapbox にインポートできる形式に変換するには scrapbox-converter
という CLI ツールを使う:
ガット変換して、試しにフォーマットして見て変な部分があれば 手作業で 直してインポート!
やったね!
new.iggg.org 側のリンクを修正
一括置換してみたが記法にいくつか種類があり、半ば手作業(完)
3. コミットから自動生成
せっかくなので GitHub Actions を使った。
その辺りは前回の記事に書いた:
4. ドメインを iggg.org にする
あとはドメインの設定を変えるだけ。
リポジトリの Settings で別のカスタムドメインを設定すると勝手に CNAME
をプッシュしてくれる。
おしまい
無事管理するものを減らせたぜ。
GitHub Actions を使ってみた
IGGG ソフトウェア基盤部のひげです。
GitHub Pages へのデプロイに GitHub Actions を使ってみたので、そのことについて記事を書きます。
ちなみに IGGG/new.iggg.org と IGGG/IGGG.github.io に GitHub Actions を使ってみました。
GitHub Actions
GitHub が用意した CI/CD。
.github/workflows
配下に YAML ファイルで設定を置くことができます。
まだベータ版な点に注意。
設定する
やりたいことは2つ:
- PR では静的サイトを生成できるか試す
- メインブランチ(
master
)なら静的サイトをデプロイする(GitHub Pages)
こんな感じにした:
/ |
うまく Condition を使って一つの YAML にまとめてもよかったんだけど、めんどくさくなったので分けた。
ファイル名から察せれる通り、verify.yml
が (1) を deploy.yml
が (2) のための設定ファイルだ。
verify.yml
verify.yml
は次の通り:
name: Verify PR |
(ちなみにこれは IGGG/new.iggg.org の方で、これは Hugo による静的サイト)
on: pull_request
と記述することで PR に対してのみ動作します。jobs
以下が実際の動作の内容で、各ステップでは状態を共有します。uses
で GitHub Actions で実行するアクション(リポジトリ)を指定できます(actions
で始まるものは公式です):
- actions/checkout - GitHub
- 対象のリポジトリのブランチへクローンしてチェックアウトする
frtch-depth: 1
とすることでシャロークローンしてくれますsubmodules: true
とすることで--recursive
オプション付きでクローンしてくれます(Hugo は利用するテーマを submodule として置くことが多い)
- peaceiris/actions-hugo - GitHub
- Hugo をセットアップする
hugo-version
でバージョンを指定できる
docker://xxx
という指定をすることで、Docker Hub などの Docker イメージのレジストリから直接指定することもできます。
で、結局このジョブは、単純に Hugo をビルドしてみてるだけですね。
deploy.yml
ここからが鬼門。
対象は GitHub Pages なので、デプロイするとはすなわち GitHub にプッシュすることですね。
その時に CI 側に権限を与える必要があるのですが、個人的にパーソナルトークンを使うのがいやで、可能ならリポジトリごとに設定できる SSH 鍵を使いたい。
そのように設定した deploy.yml
は次の通り:
name: Deploy GitHub Pages |
on.push.branches
でこのワークフローが動作するブランチを指定しています。on.push.paths-ignore: ["docs/**"]
とすることで、もし 差分が docs
配下にしかない場合は動作しない ようにしています。
この paths-ignore
と **
は最近追加された機能で、詳しくは後述します。
jobs
の前半は verify.yml
と同じです。
違うのは name: deploy
のステップだけ。
これは .github/scripts/deploy.bash
を実行しているだけですね。
中身を見てます:
#!/bin/bash |
DEPLOY_KEY
で指定する SSH 鍵はリポジトリごとに設定するものを指定しています(その方が権限管理が楽で個人的には好みです)。git diff --staged --quiet || git commit -m "..."
することで docs
配下に差分があった時にだけコミットを作ります。
もし差分がなく、コミットを作らなかった場合は git push
は変更がなかったとメッセージを吐いて終了します。docs
配下に差分があった時だけプッシュすることで on.push.paths-ignore: ["docs/**"]
と組み合わさって、GitHub Actions によるプッシュ(デプロイ)で GitHub Actions が再度動作することは無くなります(残念ながら skip ci
のような機能は現状無いので)。
さて、これでリポジトリごとの SSH 鍵でデプロイする設定ができました!
ちなみに、本当は Secret に秘密鍵を直接置くのは嫌なんですけど、、、まぁとりあえず妥協しました。
躓いたこと: on.push.paths
公式ドキュメントには当時、以下のようにすれば「docs
配下にのみ差分があったら動作しないようにできる」と書いてありました:
on: |
これではうまくいきません。
色々調べた結果、*
はディレクトリ階層を掘ってはくれないのです。
これを読む限り、これはどうやら Go のモジュールの仕様らしいですね。
もし *.md
の差分だけ動作して欲しい場合は:
on: |
みたいなアホな設定をする必要がありました。
「ベータだなぁ〜」って思ってた矢先、なんと神アップデートがありました:
**
でディレクトリ階層を吸収してくれるのです。
つまり、**/*.md
と書けば任意の深さのマークダウンの差分を検知してくれます。
また、paths-ignore
は !
を省くことができる機能ですね。
おしまい
GitHub Actions を初めて使ってみましたが、結構満足してます(paths
の修正のおかげで)。
あとはキャッシュぐらいかな。
それと、同じ GitHub 内だし GitHub Actions 用の SSH 鍵を設定する機能を公式が用意して欲しい。
libnss-json 始めました
こんにちは。IGGG 何もしない部のatponsです。みなさんはサークルのサーバのユーザ管理、どうしていますか?
われわれのサークルは、そこまで規模が大きくないため、サーバの数は少なく、一台のみVPSをレンタルしています。
なので、これまでは手作業でユーザの追加を行ったり、時にはLDAPを用いてユーザを管理していました。しかし、年々メンテナンスをしていくユーザが卒業していき、LDAPなどを全て停止していました。
正しく設定が変更されてLDAPとかが抜けていればいいのですが、実際pam.d
以下をちゃんとみてsssd
(サークルではSSSDを利用していました)を削除するのがつらく、あるあるなsudo遅い問題(解決しにいくため)などが多発していました。
libnss-jsonを知る
先日行われた技術書典7に参加し、東工大デジタル創作同好会traPのSysAd班が出している「traP SysAd TechBook」を買って色々と読んでいたところ、libnss-jsonというのがあると言うことを知りました。
libnssとは
Linux(*nixにもあるらしい)にはName Service Switch(NSS)と呼ばれる、/etc/passwd
などをファイルからどこから読むのかを管理する機構があります。これにLDAPなどを読みに行くようなものを書けば、getent
をした際にそこに読みに行きます、というワケです。これらは、NSSサービスとして書くことができます。
libnss-jsonを使う
これをJSONファイルで定義して、なおかつリモートから読み込んでくれるようなNSSサービスが、libnss-jsonです。導入方法などは上で挙げたtraPの本がとても参考になりました。
実際に導入する際には、導入用のAnsible Playbookを用意したり、Vagrantでしっかりと動作確認できるようにしました。既存のサーバで適用する前に、さまざまなケースを試し、問題ないことを確認した上でデプロイしました。
traPの本では、traPがフォークしたこのリポジトリを使って紹介されていました。
SSHも便利に使う
traPの本では、OpenSSHの設定でAuthorizedKeysCommand
を上手く使って、上のJSONで定義したユーザ名を使ってGitHubの公開鍵と組み合わせていました。われわれのサークルもこの方式を採用させていただきました。
おわりに
Name Service Switchのバックエンドをいろんなモノに差し替えるという発想は、最近だとSTNSが有名だと思います。ただ、大学のサークル、しかも小規模となると、あまりメンテナンス性とか(抜けることが少ない)、階層性についてこだわりたくないなと思っていました。実際、プロビジョニングツールを書いたりはしていたのですが…。
しかし、このlibnss-jsonでかなりLightweightに管理できて個人的にはとても満足しています。
このlibnss-jsonを自動で展開するAnsible Playbookも書いたので、これで将来サーバが増えても簡単に管理できると思います!
さいごに、有益な情報を書いてくださったtraPのみなさまには感謝しかないです!
ありがとうございました。
esaの利用をはじめました
本記事はIGGG アドベントカレンダー 2018 24日目の記事です。
群馬大学電子計算機研究会 IGGGでは,2018年の2月頃から情報共有の場としてesaを利用させていただいています.
esaとは
esaは「情報を育てる」という視点で作られた、自律的なチームのための情報共有サービス(esa公式ページより)です.詳しくは公式ページをみていただくとして,とりあえずMarkdownで書けて便利です.
なぜesaか
IGGGでは,以前からPukiWikiが情報共有の場として利用されてきました.これは,外部向けにも閲覧可能であることから,情報発信等には向いていましたが,内部で持っておきたい情報(引き継ぎ・会計等)についてはここには書けない状況になっていました.
先日公開された記事中でも,IGGGがGitHubのissueやWikiをベースとして運営の情報を管理しているということが挙がっていましたが,GitHubを普段利用しないメンバーにとってはなかなか見づらい・使いづらいということが頻繁に起こっていました.
サーバ管理等のコストなどもあり,より良いものに移行していきたい,引き継ぎがうまく出来るようにしたいというところで,esaの存在を知りました.
アカデミックプランの存在
esaにはアカデミックプランが存在しており(2018/12現在),条件を満たしていれば一定期間無償(再申請可能)で利用することが可能です.
そこで,申請を行い,現在無償で利用させて頂いています.
esaの使いどころ
個人的には,以下のように使い分けて行っています.
- esa
- 議事録
- 会計
- 周知事項など
- PukiWiki
- イベント/メンバ向け
- GitHub Wiki
- 引き継ぎ資料など(esaに移行したいかも?)
誰が見たか,記事へのコメント,WIP機能は今までの他のツールにはなく,とても使い勝手が良いです.
副産物として,群馬大学では学内でG Suiteが利用されており,群馬大学のアカウントでログインできるのも,使いやすくて便利です.
所感
メンバのみなさんには,もっとesaを使って色々書いて行ってみて欲しいです.部内Wikiですから,誰もちょっかい出さないと思うので(笑),あとバージョニングもあるので戻せますよ!
そろそろ引き継ぎを考える時期になりました.esaをはじめとしたツールを使いこなして,スムーズに引き継ぎが出来るようにがんばります!
最後に,申請を承認していただいたesaのみなさまにはこの場を借りて感謝申し上げます.
記事中の esa アイコンは クリエイティブ・コモンズ 表示 - 非営利 - 改変禁止 4.0 国際 ライセンスの下に提供されています。© esa LLC
iggg.org を移行する
本記事はIGGG アドベントカレンダー 2018 22日目の記事です。
本記事では前々からやろうとしていた iggg.org の移行作業について書こうと思います。
現状ほとんど出来上がっていて、あとは細かいところの確認とドメインの変更を残すだけです。
GitHub Pages としてホストしており、下記URLよりアクセスできます。
なぜ移行するか
現在(2018/12/22) iggg.org はさくらのマシン上で WordPress を使って動いています。
諸々運用・管理がめんどくさくなってきた(アクティブな部員も少なってきたし)ので、(更新しなければ)運用コストゼロの静的サイトにしてしまおうとなったのです。
移行作業
実はうちには IGGG/management という議論用のプライベートリポジトリがあります。
作業はだいたい、そこの Issue に書いてあります。
社会人になってから Issue に途中作業を雑に書き連ねていく癖がついた。
データを抽出
まずは WP にあるデータを抽出する必要があります:
- 記事のデータ: 可能なら Markdown として
- メディア系(主に画像)
記事のデータは最初 wordpress-to-jekyll-exporter
を使おうとしましたが、なんかうまくいかず断念。
そこで以下の記事を参考にして抽出しました:
結構古い記事ですが、ちゃんと動作しました。
記事自体は xml2md という自作ツールの紹介ですが、中盤に WordPress を XML としてエクスポートする方法が書かれています。
ただ、変な Markdown になっていたり、いらないページまで Markdown になっていたりするので、そこは手作業で間引きます。
さて、次にメディア系です。
メディア系の抽出にか以下の記事を参考にしました:
このプラグインを利用してローカルにダウンロードしました。
あとはこれらを適当に git リポジトリに入れてプッシュすれば抽出完了。
Hugo
静的サイトジェネレーターには Hugo を使いました。
このサイトは Hexo (JS 製)だし、他の IGGG のサイトは Jekyll (Ruby 製)なのですが、せっかくなので使ったことないものを選択してみました。
CSS 職人になって WordPress で利用してたテーマを再現するのは苦行なので、なんとなく構造が似て入ればいいかなぐらいの気持ちで作ります。
そのため、なんとなく構造を再現できそうなテーマをベースとして選びました:
Hugo 利用するテーマをサブモジュールとして設定するみたいです(多分)。
なので、テーマをカスタマイズするためにベースにしたいテーマをフォークしました:
基本的にうちのサイトのホームには:
- 上部にナビゲーター
- 画像スライダー
- 最近の記事の更新
- IGGGについての説明
- イベントボード
- Twitter タイムライン
があります。
ナビゲーターは dopetrope のものを利用しています。
ただし,config からナビゲーターの要素を次のように指定できるように変更しました:
[params.pages.home] |
順番をうまくコントロールすることができなかったので order
というパラメタを持たせています。
呼び出し側は次のようになります:
<nav id="nav"> |
sort
関数に配列とパラメタ名を渡すと、そのパラメタでソートしてイテレーターに渡してくれます。
Hugo のテンプレートには結構リッチな組み込み関数が多いので面白いですね.
結構詰まったのがスコープです。$baseUrl := .Site.BaseURL
のように range
の外で変数に定義しないと、range
の中で .Site.BaseURL
を呼び出しても想定通り取得できません(もしかしたら別の方法があるかも)。
画像のスライダーには balaramadurai/hugo-travelify-theme のモノを拝借しました。
ただ、次のように config からスライダーの画像を指定できるようにしています:
[params.slider] |
「最近の記事の更新」や「IGGGについての説明」は dopetrope のモノを使い、CSSで調整しています。
ちなみに、CSSを変更しても、うまく読み込まれず苦労しました(リロードしたり変更したり、結局正しいやり方は分からず)。
イベントボードは自作して、今まで同様に外から設定できるようにしています:
sections: |
Twitter のタイムラインはここで生成したモノをただ単純に埋め込んでいます。
GitHub Pages
さて、ここまでくれば後は Public にするだけだ(ここまでは Private リポジトリにしてた)。
Settings で Public に変更し、GitHub Pages の設定を docs
にしてオンする。
意気揚々と iggg.github.io/new.iggg,org
にアクセスすると。。。。
見れない!
あれ、なぜだ??
答えはこれ:
コミットしないと GitHub Pages の生成がされないらしい。
なので空コミットしたら無事表示された!
残タスク
- 古いページは多分崩れてるので直さないと
- DNS を iggg.org にする
- コミットから自動生成する仕組み
- プレビュー機能
おしまい
Hugo 結構使いやすい。
CircleCI の設定ファイルを 2.0 に更新
IGGG ソフトウェア基盤部のひげです。
1年ぶりの更新です。
本記事はIGGG アドベントカレンダー 2018 15日目の記事です。
まぁ今回はほとんど埋まっていませんが(笑)
本当は別の話を書こうと思っていたんだけど、記事を書くためにこのサイトを整備、というかほったらかしにいていた CircleCI 2.0 へのアップデートをしたら思いの外大変だったのでその過程を書きたいと思います。
まぁみんな既に 2.0 への更新は済んでそうですけどね。
ここの構成
このサイトは CircleCI を使って自動デプロイなどを導入している:
実はこれ、 CircleCI 1.0 のままだった。
もう2年半近く前だからしょうがないね。
元々の設定はこんな感じ:
machine: |
更新作業
ちなみに最終的にこんな感じ:
defaults: &defaults |
やっつけでやったからだいぶ余計な部分がありそう。
CircleCI 2.0
旧バージョンである CircleCI 1.0 は2018年8月31日に終了し、以降は 2.0 でしか CI を回せなくなった(このサイトは2017年3月以降回していない笑)。
変更の仕方はそこまで難しくない。
下記の公式ドキュメントにしたがって変更して行けば良い:
手順をざっくりと抜粋すると:
/circle.yml
を/.circleci/config.yml
に置換version: 2
を冒頭に追加deployment
はjobs
に変更:commands
はsteps
にするcommands
のリストの要素はrun:
にする
machine
以下の設定をdocker
にしてjobs
に書き加える- 適当に
workflows
を定義branch
の設定はこっちでする
あとは checkout
やキャッシュ回りの設定、job 間でワークスペースを共有するために persist_to_workspace
と attach_workspace
を追記した。
Hexo の更新
Node が古すぎて CircleCI の Docker イメージがなかった:
Build-agent version 0.1.1250-22bf9f5d (2018-12-12T11:32:15+0000) |
ので、随分古い Node と Hexo を使っていたので更新した。
- Node: 4.4.5 -> 11.4.0
- Hexo: 3.3.5 -> 3.8.0
ここは特に問題なく動作した(たぶん)。
Heroku と CircleCI
ここからがしんどい。。。
上の手順で適当に書き換えても動かなかった。
何がかというと、最終的なデプロイの部分だ。
まずは Staging である Heroku の部分。
CircleCI でデプロイしてみたら:
create mode 100644 public/tags/Twitter/index.html |
という状態で固まってしまった。
いろいろ調べてみたら、そもそも過去に設定したやり方はもう古いみたいだ(本当に?)。
なので 2.0 の資料を参考にし HEROKU_API_KEY
を使った方法にしようと思う。
Hexo の Heroku へのデプロイには hexo-deployer-heroku というライブラリを使っている。
このライブラリで HEROKU_API_KEY
環境変数を埋め込むのは難しそうなので、hexo-deployer-heroku のコードを読んで同様の手順を CI で直接実行するようにした:
mkdir .deploy |
しかし、次のようなエラーが出た:
... |
git push
のところで起きている。
buildpack に heroku/php
を指定しているが、この場合はワークスペースに index.php
などがないとダメらしい。
hexo-deployer-heroku ではこの assets の中身をコピーして heroku/php
に合わせていた。
同じようにしてもいいが、別に PHP ではないので静的サイト用の buildpack に変更するようにした:
heroku-buildpack-static の設定ファイルとして static.json
をワークスペースに置く必要がある。
なので、下記のコマンドを git init
と cp -r ../public .
の間で実行する:
echo "{ \"root\": \"public/\" }" > static.json |
これで無事 Heroku にデプロイできた!
GitHub と CircleCI
こっちも案の定ダメだった。
Heroku の時と同様に yes/no
と聞かれて止まってしまう。
GitHub へのデプロイには hexojs/hexo-deployer-git を使っている。
Heroku の時みたいに同様の動作を直接書き込んでもいいが、できれば API トークンを使いたくないので、別の方法を調べた。
良さそうな CircleCI の質問ページがあった:
次のようなのをデプロイする前に書けばいいらしい:
mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config |
エラーが変わった:
... |
これは設定している GitHub の SSH 鍵に書き込み権限が無いせいだ。
読み込みだけのやつはコンソールからボタン一つでできたが、書き込み権限付きの鍵は自分で作る必要がある。
以下の記事がわかりやすかった(似たような記事はたくさんあると思うけど):
これで無事本番にもデプロイできた!
残り作業
README とか Docker のところとかもほんとは整備しなきゃ。。。
おしまい
更新は計画的に。
Slack から特定のアカウントでツイートする Bot を作った
IGGG ソフトウェア基盤部のひげです。
Slack をインターフェースにして特定の Twitter アカウントでツイートする Slack Bot を GAS で作った話。
なんか Bot ばっかり作ってる気がする。
いきさつ
IGGG は部としての活動はあまりなく、個人での活動が多い(ちなみに、それを支援する部になればなぁと思ってます。)。
いっけん何もしてないように見えるが、各々で何かしてる場合があるので、それをもっと広報してみようという話が、この前の Meetup のときにあった。
そこで、個人の活動を PR するための Twitter アカウントを作って、活動、例えば、各々のWebサイトの更新や GitHub リポジトリの更新などをツイートしようとなった。
ただ、いちいちログインしてツイートするのはめんどくさい。
ということで、Slack の特定のチャンネルで発言すると、それを本文としてツイートできるようにすることにした。
作る
ステップバイステップに作ったので、せっかくだから、その過程を書いておく。
最終的な GAS コードはココにある。
いくつかのプロパティの他に、以下の外部ライブラリを使用した。
- SlackApp :
M3W5Ut3Q39AaIwLquryEPMwV62A3znfOO
- OAuth1 :
1CXDCY5sqT9ph64fFwSzVtXnbjpSfWdRymafDrtIZ7Z_hwysTY7IIhi7s
- Underscore :
M3i7wmUA_5n0NSEaa6NnNqOBao7QLBR4j
1. とりあえずそのままツイート
まずは何も考えずにそのままツイートしてくれる Slack Bot を作る。
GAS なので、Outgoing Webhook を使う。
Bot の名前を announcer
にするということにして(Customize Name をいじるわけではない、いじってもいいけど),@announcer
を Trigger Word(s) に設定する。
イメージはこんな感じ
と打つと、Twitterで「こんにちは、テスト !!」をつぶやいてくれる。
を参考にして作った。
1-1. Twitter API Token の取得
Twitter API を叩くために必要。
電話番号が登録されているアカウントでないとダメなので、自分のツイッターアカウントで発行した(ちなみに、こういう開発での用途か ROM 専でしか使ってない、ぼくは)。
発行の手順は簡単
- Twitter にログイン
- https://dev.twitter.com/docs にアクセスして上の方にある
My apps
をクリック Create New App
をクリックして必要事項を埋めるName
,Description
,Website
を埋める必要があるが、正直なんでもいい- アプリで重要なのは
Callback URL
だが、まだ埋めなくても平気
Keys and Access Tokens
というタブをクリックすると、そこに必要なトークンがある
1-2. Bot を書く!
サンプルコードを参考にして、ソースコードはこんな感じ。
ちなみに、プロパティは以下のようになっている
VERIFY_TOKEN
: Outgoing Webhook Slack App のToken
SLACK_API_TOKEN
: ココから発行できる Slack の API トークンICON_ID
: Google Drive にある Slack Bot のアイコンに使う画像の ID (別に無くてもいいし、URL を使うように書き換えたって良い)TWITTER_CONSUMER_KEY
: 1-1 で用意した Consumer Key (API Key)TWITTER_CONSUMER_SECRET
: 1-1 で用意した Consumer Secret (API Secret)
function doPost(e) { |
reset
, getService
, authCallback
関数はサンプルコードをそのまんま、 postTweet
関数はサンプルコードの run
関数を返り値があるように書き換えたモノだ。
次に、Twitter側に https://script.google.com/macros/d/{SCRIPT_ID}/usercallback
を Setting からの Callback URL に書き込む。
ここでの SCRIPT_ID
は ファイル
の プロジェクトのプロパティ
にある スクリプト ID
に書いてある文字列である(URLからも実はわかる)。
できたら、いちど postTweet
関数を GAS 側で実行すると、現在ログインしている Twitter アカウント でのアプリケーション連携の認証ページへ飛ばされるので許可すればよい。
そして最後に、GAS側の 公開
の ウェブアプリケーションとして導入
に書いてある URL を Outgoing Webhook の URL(s) にコピペすれば Slack 側とも繫がることができる。
1-3. ためしに実行
こんな感じ
2. 適当にフィルタリング
なんでもかんでもツイートされては困るので、http
ってキーワードとかでフィルターを掛けてみてはどうか、という話があったので、簡単にかけてみた。
function doPost(e) { |
GAS の JavaScript は古いため、文字列が任意の文字列を含むかどうかを indexOf
メソッドで調べるしかないらしい。
雑な実装ですね…
3. Tweet Request (TR) によるレビュー
どう考えてもガバガバフィルターだなぁと思ってるところに神からのお告げが来た。
Real Time Messaging API であれば何でも取得できるので実装できそうだったが、GAS では RTM は使えない…orz
だがしかし、Add Reaction をフックして投稿することはできないけど、
- PR を作るように Tweet Request を作成するメッセージを打つ
- TR に LGTM な Add Reaction をする
- PR をマージするみたいに TR を許可(ツイート)する用のメッセージを打つ
- 但し、Add Reaction が少ないとツイートできない
って感じに、リクエストの作成とツイートをポストするのを PR みたいに分ければできそうだ!
Add Reaction の取得自体は RTM じゃなくても、Slack の REST API の reactions.get
を使えばできる。
TR の管理にはスプレッドシートを使う(GitHub の Issue でもいい気はするけど)。
なので、スプレッドシート用に以下のプロパティを追加した。
SPREAD_SHEET_ID
: TRを管理するためのスプレッドシートのIDSHEET_NAME
: TRを管理するためのスプレッドシートのシート名
コード書き直す
ソースコードはこんな感じになった(無駄に長い気もする)
function doPost(e) { |
あんまりスプレッドシートのオブジェクトを伝搬させたくなくて、奇妙な返り値になっている。
まぁとりあえずはこれで良しとします…
CTO の助言のもと、TRの作成とTRのツイートのコマンドを $tweet?
と $tweet!
にした。
スプレッドシートのカラムは、ツイートしたい本文, チャンネルID, タイムスタンプ, ツイート済みかのフラグ
となっている。reactions.get
を使って特定のメッセージの Add Reaction を取得するには、メッセージを特定するために、チャンネルIDとタイムスタンプが必要だ(チャンネル名ではダメ)。
ちなみに、チャンネル名からチャンネルIDを調べるには、ココ を使うのが良いみたい。
また、タイムスタンプはメッセージの時刻を右クリックして取得できる URL、例えば https://iggg.slack.com/archives/C06FXCF4K/p1496313613432037
の 1496313613432037
を 1496313613.432037
のように前から10-11番目の数字の間に .
を入れるだけで良い。
まぁ実際は Slack から飛んできたメッセージの情報に載ってるので、わざわざ手作業で集める必要はないんだけど、テストしたいときとかに使った。
Slack API を便利に使う GAS ライブラリでは reactions.get
を実行でき無さそうだったので、UrlFetchApp.fetch
を以下のように直接使った。
var url = 'https://slack.com/api/reactions.get'; |
実行
いい感じ b
おしまい
みんなツイートしてくれるといいなぁ。
Slack と GAS と GitHub を使って部内の問題・情報管理を円滑にしたい話
IGGG ソフトウェア基盤部のひげです。
特定の GitHub リポジトリの任意の Issue を任意の Slack のチャンネルに通知を飛ばすための Bot (これは公式のインテグレーションではできないはず) を GAS で作った話です。
いきさつ
IGGG では予てより Slack というチャットサービスを利用して日々 雑談 ディスカッションをしております。
しかし、フリープランの Slack では ログが一万件しか残らない!
割と重要な事案が しょーもない雑談 活発な技術的な議論によって、気づいたら流れてしまう…。
皆を説得して有償プランにしようとか、ログの残るサービスに移ろうとか、何度かいろんな案で話し合ったのですが…いろいろな理由で難しい。
そこで、IGGG の 外圧担当 CTO こと 擬音 より GitHub の Issue で管理しよう との提案が、今年の4月中旬ぐらいにされた。
しっかりと問題提起・議論が残り、とてもいい感じ。
問題
ただ、いくつか問題があった。
- IGGG の GitHub 組織アカウントに所属しないと議論が見れない
- 誰しもが GitHub アカウントを持ってるわけでは…
もっておこうよ
- Slack の GitHub Integration で特定のチャンネル(#management)に飛ばす
- 飛ばしてるけど、決定事項(Issueの結論)だけ #general に飛ばしたい
- 全部 #general に飛ばしたらうるさい
- 誰しもが GitHub アカウントを持ってるわけでは…
- 特定のチャンネルで見たい特定の Issue がある
- インフラチャンネルで IPアドレスが欲しい という Issue が見たいとか
- いちいち #management に移るのめんどい
要するに 1) #general に Issue の Open と Close だけをコメント付きで通知したいのと、 2) 任意の Issue のコメントを任意のチャンネルに通知したい。
ということで、これらの問題を解決するために Slack の Bot を作った。
1. #general に Issue の Open と Close だけをコメント付きで通知する
GitHub インテグレーションでも、Issue の Open, Close だけを飛ばすという設定はある。
だがしかし、これでは
という感じの Issue に対し(てててすと
というコメントは Close 時に書いたコメント)
と感じに来る(そりゃそう)。
ここで、最後のコメントも通知してほしいのだ(Issueの結論を書いて)。
Manager 1号
ということで Bot を作った。
ソースコードはこちら。
以下の3つのGASライブラリを用いている。
- SlackApp :
M3W5Ut3Q39AaIwLquryEPMwV62A3znfOO
- GitHubAPI :
1F4yn329GjHKdcXu9nm0uBZHFo40NGRUF8dfZCTHM1KjXpOXYr2BzIIcJ
- Underscore :
M3i7wmUA_5n0NSEaa6NnNqOBao7QLBR4j
また、SlackBot の APIトークン と GitHub の APIトークン を利用している。
GitHub の Personal Token を利用しているので、念のため IGGG の GAS では無く、個人の GAS 上で作った。
実装
動作は Issue の Open と Close にフックして動作させ、POSTデータを解析し、適切なメッセージ作成して、Slack に投げている。
function doPost(e) { |
メッセージは Open, Close, ReOpen の場合に分けて作成している。
Close の場合だけ直近のコメントを GitHub API を使って取ってきている(getIssueResentCommentBody
)。
function makeMessage(action, issue, repo, prop) { |
attachments
を使って、頑張っていい感じのメッセージにしている。
これを模索するために、テストとして無駄にメッセージを投げてしまった(ごめんね)。
ちなみに、webhook せずにテストするときは
function test() { |
こんな感じの関数を作って実行する。
あとは、このスクリプトを、公開 -> ウェブアプリケーションとして導入 を押して webhook 用の URL を発行し、これを リポジトリの Webhook に設定するだけ。
実行
2. 任意の Issue のコメントを任意のチャンネルに通知する
GitHub インテグレーションは特定のリポジトリと特定のチェンネルをつなぐ。
よって、特定のリポジトリの特定の Issue と特定の特定のチャンネル繋ぐことはできない。
Manager 2号
どのチャンネルにどの Issue のを通知するかの設定も Slack からしたいよね。
そのため、設定する側と、コメントにフックして通知する側の2つに分けて書くことにする。
それらのソースコードは、これ(設定) と これ(通知)。
ライブラリは 1号のと同じ。
チャンネルと Issue の対応表は(めちゃ簡単な)スプレッドシートで残しておくことにした。
設定側の実装
Slack の Outgoing Webhook Integration にフックしてスプレッドシートに対応関係を書き込むことにする。
@manager <cmd>: <issue-num>
というフォーマットでメッセージ送られてくると想定している。
コマンド (<cmd>
)には、チャンネルと Issue の対応関係をセットする set-issue
と、対応関係をアンセットする unset-issue
がある。
また set-issue
では、指定された Issue の番号 (<issue-num>
) が本当に存在するかや、既にセット済みかを確認している(existRow
)。
function doPost(e) { |
指定された Issue の番号が本当に存在するかを確認するために、GitHubAPI をたたいて、リポジトリ内の Issue を全て取得し、愚直に線形探索している。
見つかれば、その Issue をそのまま返し、無い場合は "error"
という文字列を返している。
function getIssue(number, prop) { |
こいつも attachments
でいい感じのメッセージにしている。
function makeMessage(issue, repo, prop) { |
設定側の実行
通知側の実装
GitHub リポジトリに Issue のコメントだけに Webhook されるようにする。
そしたら POST データを解析し、スプレッドシートの中から2列目が等しい行の1列目だけ取ってきて(高階関数最高)、POST データからいい感じのメッセージ生成して、Slack Bot に通知している。
function doPost(e) { |
メッセージはコメントの作成・編集・削除ごとに異なる。
ただ、編集時には編集前のコメントしか手に入らないので、GitHub API をたたいて編集後のコメントを取りに行ってる(getIssueCommentBody)。
function makeMessage(action, comment, issue, repo, prop) { |
通知側の実行
おしまい
これで部内の問題・情報管理がさらに円滑になるはず!(願望)
IGGG の GitHub Pages の開発環境の Dockerイメージを作る
IGGG 名古屋支部のひげです。
久々に更新。
Docker がマイブームだったので、この iggg.github.io の開発環境も Docker 化しようという話です。
基本的に話は簡単。
Windows上でやるせいで悪戦苦闘したという感じです。
開発環境
ワタシのパソコンはこんな感じ
- Windows10 Home
- Docker ToolBox
- Docker version 17.03.1-ce
- VirtualBox 5.1.18
Windows10 Home なので Docker for Windows は使えず、仕方がないので VirtualBox を利用する Docker ToolBox を使ってる。
Dockerfile
いろいろ参考にしつつ、試した結果、これだけで良い。
FROM node:6 |
このサイトは Hexo を使っているので、Node.js が必要だ。
なので、ベースは公式の node
Docker イメージを使った。
これに,hexo-cli
をインストールした。-g
はグローバル環境にインストールするというオプション。
あとは、
$ cd C:\Users\hoge\git\iggg.github.io |
などして bash を実行する。-v
オプションで、現在のディレクトリを /app
にマウントしている。
そして、Dockerコンテナ内で
$ npm install --no-bin-links |
を実行する。
ワークディレクトリにマウントするため、ビルド時に npm install
をするわけにはいかない。
そのため、ビルド後のコンテナ内で npm install
をしている。
また、--no-bin-links
を指定しないと、Windows ではうまくいかない。
これで、VirtualBox で指定したIPアドレス(ToolBoxを使ってなければ localhost)の4000ポートにアクセスすれば見れるはずだ。
docker-compose
docker build
してからの操作が多いので docker-compose
にまとめてしまおう。
本来の使い方とは異なってるが、こういう用途でも十分使える。
blog: |
run.sh
は
#! /bin/sh |
Windowsだと、ここで docker-compose up
しても次のようなエラーが返ってくる。
ERROR: for browser Cannot create container for service browser: invalid bind mount spec "C:\\Users\\hoge\\git\\iggg.github.io:/app:rw": invalid volume specification: 'C:\Users\hoge\git\iggg.github.io:/app:rw' |
原因はパスの指定の仕方で、相対パスで .:/app
としてるとおかしくなる。
ググった結果
COMPOSE_CONVERT_WINDOWS_PATHS=1 |
と書いてある .env
ファイルをカレントディレクトリに置くことうまく動作した。
あと、よく怒られたのが、run.sh
の改行文字で、LF でないといけないのに、Windows では時折 CRLF に書き換わる(gitで落としてきたときとか)。
実行
docker-compose up
して特定のIPアドレスの4000ポートを見ればうまくいく。
おしまい
思いのほか時間かかった。
やっぱ Windows での開発はなかなかきついね。