第8回Web System Architecture研究会に参加して「システムの変化に追従可能でかつ理解し易いドキュメントシステムのモデル化」について発表した #wsa研

wsa.connpass.com

オンライン開催に参加してきました。

予稿

github.com

発表資料

システムの変化に追従可能でかつ理解し易いドキュメントシステム

発表内容はドキュメントシステム(ドキュメンテーションツール)についてです。

私は、システムを理解するためにかかる時間(いわゆる「オンボーディングまでのコスト」。私は「開発開始までのオーバーヘッド」と呼んでいます)をいかに継続的に削減できるかに興味をもっています。

それはなぜかというと「私がシステムの理解のセンスがないからそれをなんとか技術で解決したい」という個人的欲求に他ならないのですが、「まあオンボーディングのコストが小さくなればそれはエンジニア全員にも良いことだろうな」と勝手に思い込んでいろいろ作ったりしています。

実は今回の発表にいたるまでには過程があって、July Tech Festa 2021 winter ではDocumentation as Codeというキーワードで継続的なドキュメント運用の実現方法について主張しPHPerKaigi 2021ではその主張をさらに進めて発表しました

www.youtube.com

そして今回のWSA研では、集合を使って「継続的なドキュメント運用」「理解しやすいドキュメント」についてモデル化し、有効性を主張してみました。まだまだ前提などが甘々ですが、やはりWSA研、有用なフィードバックを多くいただきました。ありがとうございました。

実装としてのndiag

さて、上記の発表の主張に沿って実装しているのが ndiag です。

github.com

嘘です。

実際には逆で、いかに継続的にドキュメントを運用できるかウンウン考えて試行錯誤しながらndiagを開発し、その設計思想を整理したのが上記の発表です。

ndiagがどのようなドキュメンテーションツールなのか?については割愛します。公式チュートリアルや、実際に使ってみたエントリを書いてくださっている方もいらっしゃるので(本当にありがとうございます!!)そちらをご覧ください。

zenn.dev

「システムが頻繁に更新されるならシステム自体を観測してそこからドキュメントを作成すればいい」というアイデアは、真新しいものではありません(PHPerKaigi 2021の資料にその例が掲載されていて、わかりやすいと思います)。私自身も tbls という自身で作成したデータベースドキュメンテーションツールの開発で効果を実感しています。

github.com

ndiagで実現したいと思っていることは、まだまだ全ては実装できていません。それは、主に私の技術力や、開発に確保できる時間、ついつい発散してしまうモチベーションなどが原因です。

ただ、システムの継続的なドキュメンテーションを実現するのに有用そうな技術もどんどん生まれてきているので、もう1、2歩進めることができるんじゃないかなあと思っています。

とりあえず今の主張は以下です。

まとめ

今回もWSA研は楽しかったです。いつも受け入れていただいてありがたいです。

特に @YutaroHayakawaさん の発表と、懇親会でのいろいろなお話が「頑張らないとな」と思うことが多々あり「頑張らないとな」という気持ちです。

みなさんお疲れ様でした!!

任意のPull Requestの内容を複数のリポジトリに対して一括コピーするツールpr-bulletを作った

GitHub Actions便利ですよね。

ペパボではGitHub Enterprise Server(以下、GHES)が運用されており、GHESでもGitHub Actionsが利用できます。

uses: だけで利用できるリポジトリを横断で再利用可能なActionの存在はかなり生産性を上げていると思います。

そういった便利なワークフローを複数のリポジトリに対して適用していきたいことが時々あります。

一気に複数のリポジトリに同じワークフローを適用したいこともあれば、「あ、このリポジトリにはあのリポジトリのワークフローをいれたほうがいいな」となることもあります。

その時、それぞれのリポジトリに対して「突然のデフォルトブランチへのpush」はあまりにも乱暴なのでPull Requestを作成していくことになります。

ただ、適用したいレポジトリが2桁あったとき、Pull Requestを作成するのも心が折れそうになります。

そこで、任意のPull Requestの内容を他の複数のリポジトリに対して「コピーPull Request」として一括作成するツールpr-bulletを作ってみました。

github.com

pr-bullet

ちなみに、同様の、pr-bulletよりも高機能なツールとして NerdWalletOSS/shepherd があります。

それなのに、なぜわざわざ作ったかというと

  • 単機能で迷わない
  • 設定ファイルが必要ない
  • 全てオンメモリで完結

を実現したかったからです。

pr-bulletの使い方は簡単で、

  1. どこか適当なリポジトリでオリジナルとなるPull Requestを作成
  2. pr-bulletを実行して指定のリポジトリへPull Requestをコピー

で終わりです。

実行している様子は以下のような感じです。

$ pr-bullet https://github.com/k1LoW/my-bullets/pull/3 k1LoW/tbls k1LoW/ndiag
Original pull request:
  Title ... Add allow-auto-merge label action using ghdag
  URL   ... https://github.com/k1LoW/my-bullets/pull/3
  Files ... 34
Target repositories:
  k1LoW/tbls, k1LoW/ndiag
Do you want to create pull requests? (y/n) [y]: y

Copying k1LoW/my-bullets pull request #3 to k1LoW/tbls ... https://github.com/k1LoW/tbls/pull/999 as draft
Copying k1LoW/my-bullets pull request #3 to k1LoW/ndiag ... https://github.com/k1LoW/ndiag/pull/333 as draft
$

実際は

  • 追加もしくは修正したファイルのみサポート(削除やリネームは未対応)
  • ファイルのパーミッションは一律 100644 v0.3.0 で修正しました
  • オリジナルPull Requestのコミットは強制で1つにまとめられる
  • ラベルやAssigneesはコピーされない

といった感じで本当の意味での「コピー」ではないのですが、それでも毎回Pull Requestタイトルや文章をコピペしなくて良いだけだいぶ楽です。

とりあえず社内のGHESに k1low/my-bullets リポジトリを作成して、Pull Request発射基地としてみました。

1コマンドでPull Requestがコピーできてしまうので、ついつい濫用しそうですが気をつけていこうと思います。

`container:` でUbuntuのBionic以下のDockerイメージを指定してactions/checkout@v2を利用する場合は注意が必要

GitHub Actionsを使っているとき、あまり container: を指定することはないかもしれませんが、例えば以下のように ubuntu:bionic を指定して事前にGitをインストールした上で actions/checkout@v2 を実行したとき

name: CI

on:
  push:

jobs:
  build:
    runs-on: ubuntu-latest
    container: ubuntu:bionic
    steps:
      - run: |
          apt-get update -qqy
          apt-get install -qqy git
      - uses: actions/checkout@v2
      - run: git status

最後の git status が失敗します。

f:id:k1LoW:20210317230131p:plain

actions/checkout@v2 は Git 2.18 以上を要求している。しかし...

これの答えは actions/checkout@v2 のログに書かれています。

f:id:k1LoW:20210317230346p:plain

The repository will be downloaded using the GitHub REST API To create a local Git repository instead, add Git 2.18 or higher to the PATH

Git 2.18 以上がインストールされていないと git clone ではなくGitHub REST APIを使ってダウンロードしてくるのですね。

しかし、Ubuntu BionicにインストールされるGitのバージョンは

f:id:k1LoW:20210317230707p:plain

残念ながら1ポイント及ばす 2.17 なのです(2021年3月17日)

actions/checkout@v2 on Ubuntu Bionicでgit cloneしてもらうにはGitのバージョンをあげるしかない

ということで、私は以下のようにしています。

name: CI

on:
  push:

jobs:
  build:
    runs-on: ubuntu-latest
    container: ubuntu:bionic
    steps:
      - name: Update Git
        run: |
          apt-get update -qqy
          apt-get -qqy install software-properties-common
          add-apt-repository -y ppa:git-core/ppa
          apt-get update -qqy
          apt-get -qqy install git
      - uses: actions/checkout@v2
      - run: git status
  1. add-apt-repository を使いたいので software-properties-common をインストール
  2. “Ubuntu Git Maintainers” teamのPPAをリポジトリとして追加
  3. Gitをインストール

f:id:k1LoW:20210317232218p:plain

無事 git clone されました。

以上、メモでした。

GitHub Container RegistoryのDockerイメージをリポジトリに紐づける方法

GitHub Container Registoryは、GitHub Package Registryとは異なりPublicなDockerイメージのpullにGitHub認証の必要がないなど、利用者にとって便利です。

また、リポジトリとの紐付けが強制されていないので自由に様々なイメージを管理できます。

イメージはProfileページのPackagesタブにリストアップされます。

f:id:k1LoW:20210306193712p:plain

ただ、私の主な用途としてはリポジトリで公開しているライブラリのDockerイメージの提供なのでリポジトリと紐付けたい。

具体的にはリポジトリの Packages の欄に表示させたい。

f:id:k1LoW:20210306193956p:plain

どう紐づければいいのかわからなかったのですが、やっとわかりました。

DockerfileにLABELとして org.opencontainers.image.source を設定すればいいようです。

LABEL org.opencontainers.image.source=https://github.com/k1LoW/tbls

もしくはdocker build時にビルドフラグとして --label=org.opencontainers.image.source=https://github.com/k1LoW/tbls を渡せばいいみたいです。

私はGoReleaserを利用しているので以下のような設定を追加しています。

dockers:
  -
    goos: linux
    goarch: amd64
    image_templates:
      - 'ghcr.io/k1low/tbls:v{{ .Version }}'
      - 'ghcr.io/k1low/tbls:latest'
    dockerfile: docker/Dockerfile.ghcr.io
    build_flag_templates:
      - "--pull"
      - "--label=org.opencontainers.image.created={{.Date}}"
      - "--label=org.opencontainers.image.name={{.ProjectName}}"
      - "--label=org.opencontainers.image.revision={{.FullCommit}}"
      - "--label=org.opencontainers.image.version={{.Version}}"
      - "--label=org.opencontainers.image.source=https://github.com/k1LoW/tbls"

これで、無事リポジトリにDockerイメージを紐づけることができました。Dockerイメージの設定画面でも青の認証バッジがつきます。

f:id:k1LoW:20210306194539p:plain

そして、最後にDockerイメージをPublicにするのを忘れないでください。

どうやらGitHub Container RegistoryにpushしたイメージはデフォルトはPrivateなようです。

これを忘れると誰にも使ってもらえません。

というわけでメモでした。

GitHub Actionsを使用してバージョンを指定してパッチを当てたバイナリをビルドするというフローを自動化する

リポジトリはこちらです。

github.com

何をやっているかというと過去にも実施したWazuh agentにパッチを1行当ててビルドするだけです。

k1low.hatenablog.com

ただ、GitHub Actionsの利用方法としてはなかなか面白い使い方かなと思ったのでエントリにしました。

実現しているのは

欲しい「パッチ済みWazuh agentのバージョン」をタグとして git push すると、それをトリガーにGitHub Actionsのワークフローが起動して

  1. 指定のバージョンの wazuh/wazuh のソースをgit clone
  2. パッチを適用
  3. ディストリビューションごとにビルド
  4. ビルドしたバイナリをtarでまとめてReleasesにアップロード

を並列に実行するというものです。

以下YAML全文

name: build

on:
  push:
    tags:
      - v*

jobs:
  debug-build:
    name: Build agent with patch
    strategy:
      matrix:
        platform: [ubuntu-xenial, ubuntu-bionic, centos-6, centos-7]
    runs-on: ubuntu-latest
    steps:
      - name: Install packages
        run: |
          sudo apt-get -qq update
          sudo apt-get install -qq git tar
          curl -sL https://github.com/tcnksm/ghr/releases/download/v0.13.0/ghr_v0.13.0_linux_amd64.tar.gz -o ghr.tar.gz
          sudo sh -c "tar xf ghr.tar.gz -O ghr_v0.13.0_linux_amd64/ghr > /usr/local/bin/ghr"
          sudo chmod +x /usr/local/bin/ghr
      - name: Check out source code
        uses: actions/checkout@v2

      - name: Make directory
        run: |
          mkdir dist
          mkdir pkg
      - name: Build agent with patch
        run: |
          docker build . -f Dockerfile.${{ matrix.platform }} -t wazuh-debug
          docker run --rm -v $(pwd)/dist:/dist --env WAZUH_VERSION=${GITHUB_REF##*/} wazuh-debug
      - name: Release
        run: |
          tar cfz pkg/wazuh-debug-${GITHUB_REF##*/}-${{ matrix.platform }}.tar.gz dist/
          ghr -replace ${GITHUB_REF##*/} pkg/
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

指定のバージョンでビルドする

前提として

  • wazuh/wazuh がバージョンでタグを切っている

というのがあるのですが、それを利用して各DockerfileのCMDに設定している build.sh 内で

git clone https://github.com/wazuh/wazuh.git -b ${WAZUH_VERSION} --depth 1

を実行し、必要なソースコードをgit cloneしてきています。

${WAZUH_VERSION} はどう取得してきているかというと、環境変数 GITHUB_REF${GITHUB_REF##*/} で文字列置換して取得しています。

GITHUB_REFGitHub Actionsにおいてブランチやタグのrefなのですが、on.push.tags: でタグpushのみでジョブが実行されるようにしているので必然的に「タグ=Wazuh agentのバージョン」を取得できる仕組みになっています。

並列実行する

これは strategy.matrix: を利用しています。

よく strategy.matrix: は言語のバージョンや実行環境の種類などが例にあげられますが、別にそれらに限らず並列実行するのに使えるので便利です。

今回はディストリビューション名を与えて、それを使用してDockerfileのファイル名やリリースするアーカイブ名に使っています。

作成したバイナリは ghr でアップロードしています。

ディストリビューションごとにバイナリファイル名をうまく分けて、かつ -replace オプションを使うことで

  • 並列で
  • 何度でも同じタグのReleaseに

アップロードできるので便利です。


以上です。

今年はさらにGitHub Actionsを使い倒すことになりそうです。

2020年の振り返りと2021年の抱負

年が変わるタイミングは寝ていました。起きたら明けてました。

2020年の振り返り

内に籠もった年だったのかなと思います。これには様々な要因があるとは思いますが、あまりアクティブな活動はなかったかなと思います。

(子育てが自分のライフサイクルに深く組み込まれたのは前からですが)在宅勤務になったことと、外でのイベントごとが皆無になったことで発散する場が無くなってしまった感じです(「発散」というと何か鬱憤が溜まっているように聞こえるかもしれませんが、そうではなくて「うまくリズムが取れない」というか)。

自分は生活環境の変化に弱い自覚があるのですが、2020年1年かけてもまだ慣れていないのかも?という感覚です。

2020年の抱負は「視座を上げる」でした。

エンジニアとしては、完全に外的要因なのですが社内の評価制度や周辺の目標(?)設定スキームがさらに洗練されたことがキッカケで中長期的な視点で考える機会が増えました。本当にありがたい。

その結果自分に足りないモノがよく見えるようになってしまい凹むこともありますが、気づかないままでいるより断然マシです。気づきから改善に繋げていきたいです。

ヒトとしての視座は、、、、イマイチ上がらず。です。

OSS活動

いろいろ作りました。

2020年はtblsをより良くすることができた年だったかなと思います。

tbls自体の設計方針と目の前のtblsで解決したい課題にズレがある状態から、tblsの設計を曲げるのではなくtblsにプラグイン機能を追加することで課題解決をする機能を外出しすることにしたのですが、結果としてtblsの可能性を広げることにつながりました。

自分のOSS活動はやっぱり「課題があってこそ」なんだなと実感できました。

そういえば作るOSSも構想や設計に時間がかかるものや機能がポツポツ出てきた気がします(これ、単純にエンジニアとしての能力が衰えたから時間がかかるとかじゃなければいいんですけど。。。)。

2020年に作成したけれどもまだもう少し洗練させたいと思っているツールが1、2個あるので、何か良いアイデアが降ってくるのを待ちつつドッグフーディングをして、良いタイミングで紹介したいと思います。

発表

2020年はほとんど発表していません。ただ、発表しているものは(自分の中では)一貫していて、その点に関しては良かったかなと思っています。

2021年の抱負

今年は「インプットができるようにする」です。読んで字の如くです。

私は解決したい課題ドリブンで技術をインプットする(調査したり習得したりする)ことが多いです。逆に作りたいモノがない状態で何かを1から学ぶというのが苦手です。

このいわゆる「遅延勉強法」はモチベーションがないと何もできない私にはしっくりきているのですが、実は私の場合は大きな欠点があります。

課題を解決してしまうとその技術の習得サイクルが終わってしまい、結果として体系的な知識を獲得できないという点です。「雰囲気でやっている」というやつです。

最近この欠点がじわじわと自分を苦しめているような気がしてならないのです。やっと気づいた。

いきなり遅延勉強法をやめるというのは難しいので、まずはあえて今まで避けてきた「コンフォートゾーンから大きく外れている技術」が必要な課題を解決することを目指したいと思っています。解決までに時間がかかる分、そこまでに得られるインプットも大きいと思っています。

さっそく解決したい課題からかなり遠いチュートリアルからはじめていて新年早々イライラしているのですが、あせらずやっていこうと思います。

あ、あと分割キーボードを手に入れたい。MacBook Proとほぼ同じキー配置で。

今年もよろしくお願いいたします。

フォント周りで作成したGoパッケージの紹介

Goでフォントを扱うことって(おそらく、たぶん、きっと)ほとんどないと思うのですが、私はなぜか文字が入っているpngファイルの生成とかER図とかを出すようなOSSを開発していることから、フォント周りのGoパッケージを作る機会がありました。

どれもパッケージというには小さいコード片で紹介することもないだろうと思っていたのですが、せっかくなので紹介したいと思います*1

fontdir

github.com

フォントを指定するためには大抵フォントファイルのパスを指定しなければいけません。

fontdirは各環境でフォントファイルが格納されているディレクトリを取得できるだけのパッケージです。

これはパッケージを作ったと言うより https://github.com/flopp/go-findfont のフォントディレクトリ取得関数がprivateだったのでその関数だけポーティングしました。

ffff

github.com

何かのOSSとかでフォントを指定してもらうような設定を作った時、「自分でフォントファイルを指定する」ならまだしも「ユーザにフォント名を寸分違わず指定してもらう」って厳しいUXだろうなと思って作りました。

ffffはフォントをFuzzy FindしてくれるGoパッケージです。

今のところTrueTypeとOpenTypeに対応していて、検索するときに 検索結果のフォントがどのTypeかはわからないので両方のOptionを指定しておく というアプローチをとっています。

package main

import (
    "fmt"

    "github.com/beta/freetype/truetype"
    "github.com/k1LoW/ffff"
    "golang.org/x/image/font"
    "golang.org/x/image/font/opentype"
)

func main() {
    fontSize := 12.0
    dpi := 72.0
    to := &truetype.Options{
        Size:              fontSize,
        DPI:               dpi,
        Hinting:           font.HintingNone,
        GlyphCacheEntries: 0,
        SubPixelsX:        0,
        SubPixelsY:        0,
    }
    oo := &opentype.FaceOptions{
        Size:    fontSize,
        DPI:     dpi,
        Hinting: font.HintingNone,
    }

    face, err := ffff.FuzzyFindFace("Arial", to, oo)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%v", face)
}

mplus-fonts

github.com

golang.org/x/image/font/gofont のフォントの組み込みの仕組み面白いですよね。中身は単純にTTFファイルを []byte にして突っ込んでいるだけという。

面白いと思ったのでgofontの []byte 化の仕組みを使って、M+FONTSのTTFファイルをGoパッケージにしてみました。

本当は他のOSSで使おうと思って作ったのですが、他のアプローチでいろいろ解決したのでまだ使っていません。

ちなみに、デザイナーの人からしたら :thinking: な感じかもしれませんが「好きなフォントは何ですか?」と聞かれたら私は「Century GothicかM+FONTS」と答えるくらいにはM+FONTSの印象が強いです。


紹介は以上です。

他にもGoでSVGの扱うときの細かいTipsとかあったりするのですが、GoでSVGを扱うってほとんd(ry

*1:本エントリはGo 5 Advent Calendar 2020 20日目の代理投稿です