terraformのRedash providerを書いた

業務で使っているRedashのアラートをGitHubで管理したくなったので、terraformのproviderを書いた。

github.com

registry.terraform.io

Redash providerはすでにいくつかあるが、テストがなかったり活発にメンテナンスされている様子がなかったりしたので、自分で書くことにした。というかまずクライアントライブラリから書いた。

Usage

terraform {
  required_providers {
    redash = {
      source = "winebarrel/redash"
    }
  }
}

provider "redash" {
  url     = "http://localhost:5001"
  api_key = "..."
}

resource "redash_data_source" "postgres" {
  name = "postgres"
  type = "pg"
  options = jsonencode({
    dbname = "postgres"
    host   = "postgres"
    port   = 5432
    user   = "postgres"
  })
}

resource "redash_query" "select_one" {
  data_source_id = resource.redash_data_source.postgres.id
  name           = "select one"
  query          = "select 1"
}

misc.

  • メールでのユーザーのアクティベーション挟むので、相性が悪そうなredash_userリソースは作らなかった。SSOを設定した方がよいと思う(追加自体はそんなに手間はかからないはず)
    • と思ったけど一応追加した cf.
  • データソースのoptionsがJSONになっているのは苦肉の策で、terraformだと任意の型を持つブロックが定義できないためこうなった。Redash側でいい感じに型を判断してほしいが、たとえばpgタイプのportに文字列を投げるとInternal Server Errorになる。terraform側で頑張って型を正しく定義しようとすると、すべてのデータソースのオプション属性をもつブロックを定義することになり、メンテナンス性を落としそうなのでそれはやめた
    • ignore_changesでpasswordを無視するという手段が使えなくなるが、そこは変数やSecrets Managerなどを使ってください、という感じ

要望等があればIssueまで。PRは随時歓迎

メモ: ECRのdockerイメージのタグを更新するGitHub Actions

  docker-tag:
    name: Tag docker images
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: aws-actions/configure-aws-credentials@v2
        with:
          #...
      - name: Tag branch to docker images
        run: |
          MANIFEST=$(aws ecr batch-get-image --repository-name my-repo --image-ids imageTag=${{ github.sha }} --output json | jq --raw-output --join-output '.images[0].imageManifest')
          aws ecr put-image --repository-name my-repo --image-tag latest --image-manifest "$MANIFEST"

Slack用のChatGPTボットを作った

Slackで会話できるChatGPTボットを作った。 Getting Startedの通りにすれば10分ぐらいでインストールできると思う。

github.com

すでにSlackの中の人がChatGPT-in-Slackとかchatgpt-on-denoというのを作っていてそっちの方がはるかに高機能だけど、社内のSlackボットを細々とメンテしているので、その延長で作ってみた。

仕組み

スレッドの発言をconversations.repliesでとってきて、ボットへのメンションをuser、ボットの発言をassistantとしてChat Completion APIに投げている。

すぐにトークンの上限に達するかと思ったけれど、そこそこきちんと動いているように見える。

system message

DEFAULT_SYSTEM_MESSAGEという環境変数でsystem messageを送るようにしてボットの振る舞いを変えることができる。たとえば

DEFAULT_SYSTEM_MESSAGE="あなたはメイド喫茶のメイドです。語尾には「にゃん」をつけてください"

と設定すると、こんな感じの会話になる。

また、systemをメッセージの頭につけることで、ボットの振る舞いを動的に変えられるようにした。

Chromeの拡張機能を作った

GitHub Actions issue linkというChrome拡張機能を作った。

chrome.google.com

github.com

pushイベントで実行されたGitHub ActionsにはマージされたPRへのリンクはないが、タイトルには「#1234」とPR(Issue)番号があることが多いので、それをPR(Issue)へのリンクに変換する拡張機能。develop→masterへのPRがマージされたタイミングでGitHub Actionsが走った場合などに、ActionsのページからトリガとなったPRへ遷移できる。

手始め

拡張機能の作成にあたって、まずは公式のチュートリアルドキュメントとサンプルを参考にした。

developer.chrome.com

github.com

manifestがV2からV3への移行中なのでサンプルの一部がアーカイブされていたりしたが、JavaScriptで書く分には十分に網羅されていると思う。

最初、content_scriptsでコンテンツを置換していたが、SPAだと置換されないので onUpdated をフックして置換するようにした。

TypeScript

その後、TypeScriptで実装したくなったので調査。ググるとWebpackを使った解説が多かったがそこまで必要性を感じず、vite@latestを使った r7kamuraさんのやり方 を参考にした。@crxjs/vite-plugin は 現在の vite@latest に対応していないので @crxjs/vite-plugin@beta をインストールする必要があるが、それ以外はほぼほぼそのまま。

Chrome Web Storeでの公開

Chrome Web Storeでの公開方法もオフィシャルのドキュメントに概ね従った。

developer.chrome.com

ただ、プライバシーポリシーや住所が必須と書かれているわりに必須でなかったり(素直に住所を登録すると拡張機能のページでインターネット公開されてしまった)、欧州経済領域(EEA)のトレーダー申告が必要だったり、gemやnpmのライブラリを公開するのとは趣が違うことに戸惑った。

結局、

  • プライバシーポリシーのURLはなし
  • 住所は未登録(登録したが削除した)
  • 欧州経済領域(EEA)のトレーダーではない」

として、審査を通した。

審査期間は2〜3日ほど。「host_permissionsを必要としているので時間がかかるかも」とのメッセージが出ていたが意外に時間はかからなかった。

その他

  • 公開にあたってアイコンが必須だったので icooon-mono.com のアイコンを使わせてもらった
    • iconfinderで購入したアイコンは「ロゴへの使用禁止」という項目があり、Chrome Web Storeに表示されるアイコンがロゴにあたるのかよくわからくて利用を避けた
  • プライバシーポリシーが必須だった場合に備えてポリシーを作ってくれるサービスを探した
    • 結局使わなかったが、そのうち使う機会があるかもしれない

termly.io

www.freeprivacypolicy.com

バベル案内での「STLの作者がOOPは嫌いだと言ったという話」

www.aoky.net

昔読んだSteve Yeggeの「バベル案内」のC++ の話に「STL作者が OOP は嫌いだと言っていてショックを受けた」ということが書いてあって、ふーんと思っていたが、最近Alexander Stepanovのインタビュー記事を読んでいてそれっぽい話があった。

www.stlport.org

Yes. STL is not object oriented. I think that object orientedness is almost as much of a hoax as Artificial Intelligence.

はい。STLオブジェクト指向ではありません。オブジェクト指向は、人工知能と同じくらいデマだと思います。

この人の別のインタビュー記事も面白い。

stepanovpapers.com

Using animals as an example (OO people love animals)

動物を例にとると (OO 人は動物が大好きです)


Let's consider now why C is a great language. It is commonly believed that C is a hack which was successful because Unix was written in it. I disagree. Over a long period of time computer architectures evolved, not because of some clever people figuring how to evolve architectures---as a matter of fact, clever people were pushing tagged architectures during that period of time---but because of the demands of different programmers to solve real problems. Computers that were able to deal just with numbers evolved into computers with byte-addressable memory, flat address spaces, and pointers. This was a natural evolution reflecting the growing set of problems that people were solving. C, reflecting the genius of Dennis Ritchie, provided a minimal model of the computer that had evolved over 30 years. C was not a quick hack. As computers evolved to handle all kinds of problems, C, being the minimal model of such a computer, became a very powerful language to solve all kinds of problems in different domains very effectively.

なぜ C が優れた言語なのかを考えてみましょう。C は Unix が C で書かれているために成功したハックであると一般に信じられています。同意しません。長い時間をかけてコンピュータ アーキテクチャが進化したのは、一部の賢い人々がアーキテクチャを進化させる方法を考え出したからではありません。実際の問題を解決するためのさまざまなプログラマ。数値だけを処理できるコンピューターは、バイト アドレス指定可能なメモリ、フラットなアドレス空間、およびポインターを備えたコンピューターに進化しました。これは、人々が解決しようとしていた一連の問題の増大を反映した自然な進化でした。C は、デニス リッチーの天才を反映して、30 年以上にわたって進化してきたコンピューターの最小モデルを提供しました。Cは簡単なハックではありませんでした。コンピューターがあらゆる種類の問題を処理するように進化するにつれて、そのようなコンピューターの最小モデルである C は、さまざまなドメインのあらゆる種類の問題を非常に効果的に解決するための非常に強力な言語になりました 。

Fargate: nginxコンテナをreadonlyRootFilesystemで動かす

監査上の理由からFargateがreadonlyRootFilesystemで動いてほしかったので、nginxコンテナを使った検証のメモ。

基本方針

  • nginxイメージそのままでは動かないのでカスタムイメージを作る
  • /var をタスクストレージにマウントする
  • /tmp/var/tmp へのsymlink
    • 二つ以上のタスクストレージをマウントできなかったため(二つ目以降のmountPointは無視された)

Dockerfile

FROM --platform=linux/amd64 nginx

VOLUME /var
# `VOLUME` を指定しておかないと
# 元々のイメージにあるファイルが
# マウントしたボリュームにコピーされなかった

RUN rmdir /tmp && \
    ln -s /var/tmp /tmp && \
    rm /var/run /var/lock && \
    mv /run /var/run && \
    ln -s /var/run/lock /var/lock

# このイメージでは `/var/run` `/var/lock` は
# ぞれぞれ `/var/run` 配下へのsymlinkになっているので
# 構成を変更する

ECSタスク定義 (ecspresso)

{
  containerDefinitions: [
    {
      cpu: 0,
      essential: true,
      image: '123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-nginx:latest',
      name: 'my-nginx-ro',
      portMappings: [
        {
          containerPort: 80,
          hostPort: 80,
          protocol: 'tcp',
        },
      ],
      readonlyRootFilesystem: true,
      mountPoints: [
        { sourceVolume: 'var', containerPath: '/var' },
      ],
    },
  ],
  cpu: '256',
  memory: '512',
  taskRoleArn: 'arn:aws:iam::123456789012:role/MyTaskRole',
  executionRoleArn: 'arn:aws:iam::123456789012:role/MyExecRole',
  family: 'my-nginx-ro',
  networkMode: 'awsvpc',
  requiresCompatibilities: [
    'FARGATE',
  ],
  volumes: [
    { name: 'var' },
  ],
  // 必須ではない
  // ephemeralStorage: {
  //   sizeInGiB: 50,
  // },
}

動作確認

root@ip-10-0-3-229:/# touch x
touch: cannot touch 'x': Read-only file system
root@ip-10-0-3-229:/# touch /tmp/x
root@ip-10-0-3-229:/# touch /var/tmp/x
root@ip-10-0-3-229:/#

その他

  • 何もマウントしないと /etc/hots がタスクストレージにマウントされているように見えたが、なぜ…
  • /varがread onlyになるとECS execできなくなるということがわかった
  • タスクストレージで複数のmountPointを使う方法はわからなかった
    • タスクストレージ以外でマウントするとなるとEFSかsidecarのボリュームか
    • DockerボリュームはFargateでは使えないっぽい*1

参考

2023/05/28 追記

ephemeralStorageをマウントしなくても書き込み可にはなった あとVOLUMEを複数書けば symlinkをごにょごにょしなくても、書き込み可のディレクトリ増やせた