typescript-eslint: monorepoでVS Codeからtsconfig.jsonのパスを正しく認識させる

typescript-eslintは型情報が必要なルールのためにプロジェクトの場所をparserOptions.projectで指定できる。

monorepo構成の場合かつ相対パスで指定している時、 yarn workspace front eslint ... のように実行する場合は期待通りtsconfig.jsonを見つけてくれるのだけれど、VS Codeのeslint extensionが実行する時にはtsconfig.jsonを見つけられない。

これは既知のissueでこの記事を書いている現在でもopenのまま:
Make `tsconfigRootDir` relative to the `.eslintrc` file · Issue #251 · typescript-eslint/typescript-eslint · GitHub

workaroundはあって tsconfigRootDir という相対パスを解決する基準となるパスを指定できるので、これに __dirname を指定するとVS Codeからも正しく見つけられる。

    project: "./tsconfig.json",

    // tsconfigRootDir: __dirnameを指定しないとVS Codeでmonorepoのtsconfig.jsonを正しく解決できないので.eslintrc.jsにしている
    // refs. https://github.com/typescript-eslint/typescript-eslint/issues/251
    tsconfigRootDir: __dirname,

__dirname が必要なので .eslintrc.js に書く必要がある。package.jsonや.eslintrc.jsonではだめ。

制限されたIAMロールをAssumeRoleして安全に削除操作を実行する

三行

  • 怖い操作をする時は許可するAPIコールを制限したIAMロールをAssumeRoleすれば安心
  • IAMユーザーにアクセス許可を委任するロールは同じAWSアカウントを指定することもできる

怖い操作をする時は許可するAPIコールを制限したIAMロールをAssumeRoleすれば安心

大量のリソースを削除するような操作はコンソールでぽちぽちやるとミスりそうで怖いのでスクリプトを書いて適宜レビューしやすいようにしたい。
けれども、普段使うIAMユーザーの権限だと広すぎると間違ったリソースを削除するなどの事故が怖い。普段からやや広めの権限で生活しているといきなり狭めるのも難しい。

今回はversioningが有効なS3バケットを削除したいが、削除するためにはすべてのオブジェクトのすべてのバージョンが削除されてバケットが完全に空じゃないといけない、という問題に遭遇した。

そこで実行したいAPIコールとリソースを限定したポリシーをアタッチしたIAMロールを作る。そしてaws-cliを実行する際にrole_arnを指定してAssumeRoleすることで、リスクを抑えられる。
refs. AWS CLI での IAM ロールの使用 - AWS Command Line Interface

IAMユーザーにアクセス許可を委任するロールは同じAWSアカウントを指定することもできる

IAMロールは使用する主体に応じて作り方・コンソールの画面が違う。

ドキュメントには以下の3つが挙げられている:

  • IAM ユーザーにアクセス許可を委任するロールの作成
  • AWS サービスにアクセス許可を委任するロールの作成
  • サードパーティーの ID プロバイダー (フェデレーション) 用のロールの作成
IAM ロールの作成 - AWS Identity and Access Management

今回はこのうち「IAM ユーザーにアクセス許可を委任するロールの作成」を選ぶ。

IAM ユーザーにアクセス許可を委任するロールの作成 - AWS Identity and Access Managementを読むとあたかもAWSアカウントをまたいだ操作を許可するためだけに使うようにも読めるがそんなことはなくて、IAMロールを発行するAWSアカウントと使用するAWSアカウントが同一であっても構わない。

似た事例: [https://dev.classmethod.jp/cloud/aws/assume-role-deploy-iam-user-and-role/:title=[AssumeRole] アクセスキーが流出しても被害が最小限になるIAMユーザでCloudFormationにデプロイする方法 | DevelopersIO]

#builderscon tokyo 2019で「自動作曲入門」について話した

自分のトーク

speakerdeck.com

デモはこちらです: musik

社内勉強会でやったトーク (『Scalaで自動作曲の練習』を社内勉強会で話した - Sexually Knowing) をベースにしています。 自分は資料を使い回して登壇することに強い抵抗を感じるので心苦しくあったのですが、社内勉強会から飛び出して広いところでぜひ話したいという思いがある一方で、ここから進んだ話をスクラッチから立てるのは今は厳しいという評価もあったので、折衷案としてトークの内容はほぼそのままに、きちんと動くデモを作りなんなら登壇するその場でメロディをつけるくらいのライブ感を出すくらいはやれたら自分も納得できそうだということでproposalを出し、トークする場をいただきました。

当日はWeb Audioを使ったデモはうまくいったのですが、MIDIキーボードを挿してメロディをつけるところはうまくいかなかったのでそれだけ心残りです。 Oscillator nodeのゲイン調整をしておらず音が大きすぎたため、メロディが鳴っているはずだけどほとんど聞こえないということが起きていました。ちゃんとGain nodeを繋げたらよさそう。

正直、目新しくも高度でもない話なので失望されないかドキドキしていましたが「おもしろかった」「やってみようかと思った」といった感想をいただけて登壇冥利につきます。

トークを聞いた方・このブログを読んだ方はぜひaereal/musikをforkしておもしろコンポーザを作ってください!!!

聞いたトーク

当日朝までデモを作るなどした結果、時間も体力も尽きてあまり聞けなかったのが悔やまれます。

Open SKT: メルペイ開発の裏側 - builderscon tokyo 2019

メルペイのオンボーディング内容をベースに公開できる内容にしたということで、まずオンボーディングプロセスが充実していることに驚きました。

個人的には分散トランザクションを伴う決済システムを開発しているので、わかる〜〜〜と思いながら、やっぱりある程度泥臭くなるよね、と安心しました。

コンパイラをつくってみよう - builderscon tokyo 2019

DQNEOさんによるコンパイラを1から作るライブコーディング。

Goのscannerなども使わず、コアの部分はほぼスクラッチから書いていくかんじで他の言語でも実践できそうなつくりなのが真似しやすくて親切設計でした。

会場から「そこtypoしています」とかやりとりがあったり、ライブ感があってめちゃくちゃ楽しかった。

Ruby (off|with) the Rails - builderscon tokyo 2019

僕はこのトークを「Railsに乗る = 使わされるだけではなく、ツールとして使う対象にする」「守破離」という趣旨だと理解しました。

普段、Railsを使わない立場 (業務で書いた経験はある) なので「大変そうだなー ActiveRecordが向いていないところもけっこうあるよなー」とわりと対岸の火事気味に聞いていましたが、一方、ライブラリ・フレームワークを使う上で一般的な話だとも思います。

つまりライブラリの事情などを抜きにして、責任の分離など抽象度の高い設計の作業を行い、それを実装へ落とし込む際に設計で果たしたい分離や凝集を壊しそうなライブラリの機能 *1 はこれとこれがありますね、というような思考を辿っていくもので、Railsユーザー以外でも得るものが多い内容でした。

Building, and Upkeeping Super Kamiokande - builderscon tokyo 2019

事前から楽しみだったし実際にめちゃくちゃおもしろかったトーク

実はこのトークを聞くまでスーパーカミオカンデが何なのかよくわかっていなかったので、そういった点でもdiscover something newが果たされました。

基礎物理学の世界の遠大な観測対象であっても、個々は素朴な技術 (センサー) と素朴な役割のソフトウェアシステムでできあがるというのがおもしろい。ただそのスケールがめちゃくちゃに大きいというのが興奮します。

11月に一般公開があるみたいなので申し込みます。いやーよかった。

北千住

事前の公式ブログで北千住駅の案内で脅かされていたり、足立区はなかなかラディカル *2 な土地だよと聞いて、トラブルに巻き込まれずに済むのか内心ヒヤヒヤしていましたが、無事どころかずいぶん楽しいできごとが多くて好きになりました。かなりホーム感がありました。

駅の近くにクラフトビールのお店がいろいろあったり、雑なところからそこそこのところまで飲食に困らなかったり、なにより荒川が近いのが良い。

川で飲んだりすることはけっこう奇行じみていると思われがちだけど、実質立食パーティみたいなもので、席に囚われず歩きまわっていろんな人と話せるというとても合理的なかたちでかなり好き。 懇親会が終わったあと20人くらいで荒川に移動したけど、飛び込みで20人も入れるお店はなかなか無いと思うし、見つかっても近くの席以外の人と話すのはなかなか難しいと思う。 出入り自由で、飛び入り参加したければ任意でお酒や食べ物を持ち寄るだけでよく実質プリペイドなのも気軽。

他人との距離、たしかにという感じ。居酒屋だと近すぎて疲れるけど、川だと自由に歩いて離れられるから気が楽、とかある。あとは、クラブとか行くとうるさいから距離近くなるとかある気がする。 かくれた次元 - hitode909の日記

鴨川でビール飲んでた.15人くらい来てくれた.

コンビニでクリスマスケーキ買って川で立ってケーキ持ってたら雪降ってきて最高の誕生日みたいな感じだった.前回は寒すぎてすぐ店に入ってしまったけど今回は暗くなるまで川にいられて自由に歩きまわって会話できてよかったと思う. ■ - hitode909の日記

荒川はとても広く地元の石狩川を彷彿とさせるスケールで、懐しい顔ぶれと話しているシチュエーションも手伝ってやけに楽しくちょっと懐かしい感じがした。

*1:いわゆるハマりポイントと呼ばれがちなもの

*2:婉曲しています

Google App Engine Standard EnvironmentにScalaで書いたWebアプリケーションをデプロイ

してみた: GitHub - aereal/gae-scala

GAE SE (Google App Engine Standard Environment) のいわゆる2nd generationと呼ばれるgVisorで構築された世代でJava 8/11が使える。

GAE SE (Google App Engine Standard Environment) ではgVisor上で実行されるランタイムとして新たにJava 8/11が選べる。

※当初Java 8を含めて2nd gen.としていたが公式ドキュメントによるとJava 8はgVisorで仮想化されているが、世代としてはJava 11のみが2nd gen.だったので訂正します。

Second generation runtimes are: Python 3.7, Java 11, Node 8, Node 10 PHP 7.2, PHP 7.3, Ruby 2.5, Go 1.11, and Go 1.12.

The App Engine Standard Environment  |  App Engine Documentation  |  Google Cloud

かつ、Serveletに対応しているJVM言語なら基本なんでも動くっぽく、公式のサンプルではKotolinの例もあった
https://github.com/GoogleCloudPlatform/getting-started-java/tree/master/appengine-standard-java8

古いJava 7ランタイムとの詳しい違いは以下:

The App Engine Java 8 runtime, which is based on OpenJDK 8, supports all of the existing features available in the current Java 7 runtime, which is based on OpenJDK 7, but with the following upgrades and enhancements:

  • Doesn't impose a security manager as the Java 7 runtime does, which means your code won't be restricted by Java permissions issues.
  • Supports all the standard public Java libraries.
  • Uses Jetty 9
  • Supports the Java Servlet 3.1 and Java Servlet 2.5 specifications.
  • Supports all Google Cloud-based APIs accessible from the Google Cloud Client Library for Java.
Java 8 Runtime Environment  |  App Engine standard environment for Java 8  |  Google Cloud

で、Scalaも動きそうだったので試した。

やったことは:

  • Scalaなのでmvnではなくsbtを使いたいのでsbt-appengineを入れる
  • GAEと関係ないけどScala 2.13にしたかったのでScalatra 2.7 RC1にした
    • ドキュメントに書いてある依存ライブラリのバージョンだと古くて2.13に対応していなかったりするので適宜上げたりした
  • Homebrewで入れたApp Engine SDK for Javaのどこにパスを遠したらいいかわからなかったけど `libexec` が正解だった

元気に動いています: https://gae-scala-247510.appspot.com

Serveletに対応していないとだめなのでPlayは動かなさそう。
こういう時はScalaでもWSGI/Rack的なやつがはやく策定・普及するといいですね〜〜って思う。

#builderscon 2019で「自動作曲入門」というトークをします

8月29日の前夜祭から始まるbuilderscon tokyo 2019で「自動作曲入門」というトークをします。
以前、このブログで紹介したScalaで実装してみたものを発展させたものをベースにしながら「そもそも作曲という行為をソフトウェアエンジニアの視点で再解釈するとどうなるのか」といった話から始める予定なので「むずかしそう」と思っている方でも楽しめる・むしろそんな方にこそ楽しんでほしいトークになると思います!

あと、おそらく音がなります。

僕のトークは8月31日の11:30から1204 セミナー会議室です。

builderscon.io

そんなbuilderscon tokyo 2019のチケットは7月22日までの販売だそうなのでまだの方はいますぐカモンジョイナス

builderscon-tokyo-2019.eventbrite.com

SVGから複数のfaviconを出力したり開発環境用に色を変えたりする

  • faviconやらapple-touch-iconやらいろいろ必要なアイコンが多い問題
  • 開発環境と本番でfaviconを区別して事故を防ぎたい問題

……などの話題がアイコン界隈にはあります。

SVGを使ってどちらも解決してみよう! のコーナーです。

必要なアイコンを生成する

icon-genというnpmパッケージを使うとSVGからicoやらpngが生成できます。便利。

こういうかんじ:

      const results = await icongen(variant.src, destPath, {
        favicon: {
          sizes: [180, 192],
        },
      })

開発環境ごとにfaviconを変える

↑でSVGからico/pngを生成するグッズを手に入れたので、ソースのSVGを環境ごとに変えればよさそう。

こういうかんじ:

const generateVariantSource = (variant) => {
  const { fillColor, src: dest } = variant;
  if (fillColor === undefined) {
    throw new Error('fillColor is empty');
  }
  const buf = Buffer.from(readFileSync(variants.live.src)); // assumed buffer
  const content = buf.toString().replace(/fill="#000000"/, `fill="${fillColor}"`);
  writeFileSync(dest, content);
};

replace(/fill="#000000"/, `fill="${fillColor}"`) は色を変えるハイテクなコードです。

BuildKitによるレイヤキャッシュのtargetは変数 (ENV, ARG) を展開してくれない

feature request: allow variables in the `RUN --mount=type=bind` values · Issue #815 · moby/buildkit

RUN --mount=type=cache,target=${APP_DIR}/pkg/cache go get -v と書いても `${APP_DIR}` という名前のディレクトリが作られるだけです! びっくり!

✘╹◡╹✘ < docker run --rm api:builder ls
${APP_DIR}
Makefile
api
go.mod
go.sum

HTTP関連のRFCで現れる `N#token` はカンマ区切りのリストを表す

1#header-token みたいなのは「少なくとも1個以上のheader-tokenがカンマ区切りのリストとして現れる」と読める。

出典:

A #rule extension to the ABNF rules of [RFC5234] is used to improve
readability in the definitions of some header field values.

A construct "#" is defined, similar to "*", for defining
comma-delimited lists of elements. The full form is "#element"
indicating at least and at most elements, each separated by a
single comma (",") and optional whitespace (OWS).

RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing

aws-xray-sdk-nodeを使う時はexperimentalを入れよう、じゃないと最近のnpmパッケージとの組み合わせだと動かないぞ

タイトルがすべてです。

AWS X-RayをNodeアプリケーションに組み込むaws-xray-sdk-nodeというパッケージがありますが、これを使う時はlatest (何もバージョン指定しないとこれ) ではなくexperimentalを入れると良いです。
yarn add -D aws-xray-sdk@experimental こういうかんじ。

なぜかというとlatestだと使っているライブラリの問題で親segmentを見つけられず、Webアプリケーションのコントローラ内で発行されたHTTPリクエストがsubsegmentとして回収されないのでアプリケーショントレースとしてほとんど意味をなさないためです。

あらゆるケースで親segmentを見つけられないわけではなくautomatic modeでかつasync/awaitを使っている場合に限られます。
が、最近のアプリケーションおよびnpmパッケージはasync/awaitを使っていることが多いですし、明示的にsegmentを渡さずとも勝手にSDKが切ってくれるautomatic modeを無効にすることは現実的ではないので、多くのユースケースで問題になります。

特にREADMEに書かれていませんがissueでさらっと言及されています。

問題はautomatic modeで使っているcontinuation-local-storageというパッケージがasync/awaitに対応していないことです。experimentalではこれにパッチを当てたバージョンを使っているのでsubsegmentが回収される、ということのよう。
continuation-local-storageはいわゆるスレッドローカル変数にあたるものをNodeに提供するものですが、async/await関数のセマンティクスに対応できていないようです。

aws-xray-sdk-goを使う時はhttp.Client.Getとか使ってはダメで必ずcontext.Contextを渡さないといけない

aws-xray-sdk-goというAWS X-Rayでトレースを記録する便利グッズがある。

これはoutgoing HTTP requestも記録できるのだけれども、ある時を機会にトレースの記録に失敗するようになって試行錯誤したけど今日、IQ200になってすべてを理解した。
結論はタイトルの通りで、以下は詳細。

具体的にはこういうエラー・警告が出ていた:

[00] 2019-05-16T15:50:34+09:00 [Error] Suppressing AWS X-Ray context missing panic: failed to begin subsegment named 'example.com': segment cannot be found.
[00] 2019-05-16T15:50:35+09:00 [Warn] failed to record HTTP transaction: segment cannot be found.

つまり親のsegmentが取れていないということ。

HTTP APIなので xray.Handler を使ってsegmentを作っているはずだけど……。
コードはREADMEにある以下のようなかんじ:

func main() {
  http.Handle("/", xray.Handler(xray.NewFixedSegmentNamer("myApp"), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello!"))
  })))
  http.ListenAndServe(":8000", nil)
}

aws-xray-sdk-goはcontextを使ってrecorderというオブジェクトを持ち回しおり、このrecorderというオブジェクトがsegmentを保持している。

トレースできなかったコードはこういうかんじ:

	resp, err := c.httpClient.Get(endpointURL.String())

このメソッドにcontextは渡しているけれど、HTTPリクエストを送る時に渡していないのが原因
どうしたらいいかというとhttp.Requestオブジェクトを作ってWithContext()を呼ぶとうまくいった。

	req, err := http.NewRequest(http.MethodGet, endpointURL.String(), nil)
	if err != nil {
		return nil, fmt.Errorf("[BUG] failed to build request: %s", err)
	}
	resp, err := c.httpClient.Do(req.WithContext(ctx))

いや〜〜〜〜〜〜〜大変。

気付くのに時間がかかったポイントとしては、以前のリビジョンではうまくいっていたのでHTTPリクエストを送るコードより設定やライブラリのバージョンに関心が寄っていたこと、他にトレースを発行するコードがなかったので全体に問題があるのかHTTPリクエスト送信に問題があるのか切り分けがむずかしかったこと、があげられそう。
手でsubsegmentを作ってうまくいくか見れば、トレースが取れてxray.Clientの使い方がおかしいと気付けたかもしれない。

ちなみになぜ以前はうまくいっていたかというと、その当時呼んでいたメソッドはWithContextを呼んでおり、最新のアプリではhttp.Client.Getを呼ぶ実装になっていたため……。

#gcp Datastoreを使っているプロジェクトでCloud Firestoreを使うには

Firebaseのはなし。

で、ドキュメントを見たり上記ブログを見るとこういった状況になったGCPプロジェクトでは詰みのように見えるが、結論からいうとあとからなんとかなる

GCPのコンソールからDatastoreのダッシュボードへ移動するとFirestoreへ「アップグレードしますか?」というボタンがあるのでこれを押せば移行できるのでプロジェクトを作り直さなくとも良い。
ただし、データが空の時に限るようなので、既にデータがあったらやはり作り直さないといけないと思う。

dockerの.envファイルむずかしい

docker-compose/dockerで使える.envファイルを、docker-compose/docker以外からも使えるようにしたい。 ちょっとしたスクリプトの実行時に source .env すれば環境変数が設定されるような体験がほしい。

が、実際には一工夫いる。

dockerの .env ファイルは K=V という形式を厳密に守らないといけない。 ので export EK=EV みたいに書くとinvalidとみなされてdocker-compose/docker実行時に正しく設定されない。

一方、.envファイルとしてvalidなフォーマットだと export がないので source しても実行時のプロセスで環境変数は設定されない。

ので、けっきょくこうした:

|sh| eval "$(cat .env | ruby -anlpe '$ = %|export | + $')" ||<

@aereal/go-dsn: TypeScriptでgo-sql-driverのDSNを組み立てるNPMパッケージを作った

go-sql-driverのDSN (Data Source Name) をオブジェクトから生成するライブラリを書きました。

yarn add @aereal/go-dsn

github.com

使い方をsynopsisから引用します:

import { formatDSN } from "@aereal/go-dsn"

formatDSN({
  dbName: "test-db",
  passwd: "mypasswd",
  user: "root",
})
// => "root:mypasswd@/test-db"

便利。

AWS RDSへ接続するようなGoで書いたアプリをAWS CDKでECSにデプロイする際に使うと便利です。

import { Ec2TaskDefinition } from "@aws-cdk/aws-ecs";
import { DatabaseCluster } from "@aws-cdk/aws-rds";

const taskDef = new Ec2TaskDefinition(this, "TaskDefinition", {});

const dbCluster = DatabaseCluster.import(
  this,
  "DatabaseCluster",
  databaseClusterProps
);

const dsn = formatDSN({
  addr: dbCluster.clusterEndpoint.socketAddress,
  charset: "utf8mb4",
  collation: "utf8mb4_bin",
  dbName: "app",
  user: "root",
});

const appContainer = taskDef.addContainer("app", {
  environment: {
    DSN: dsn,
  },
  // ...
);

たいへん便利! どうぞご利用ください。

cdk-mackerel-container-agent: ECSのServiceにmackerel-container-agentを5行で追加

www.npmjs.com

GitHubリポジトリはこちら: GitHub - aereal/cdk-mackerel-container-agent: experimental: AWS-CDK library for mackerel-container-agent

mackerel-container-agentを5行くらいで追加

先日、mackerel-container-agentがベータリリースされましたね。めでたい。

mackerel.io

早速AWS ECSで使ってみたのですが、やや設定が煩雑な印象もあります (ベータなのでフィードバックしたら改善・検討してもらえるかもしれない)。 参考: コンテナを監視する - Mackerel ヘルプ

特に MACKEREL_CONTAINER_PLATFORM は使う側からすると自明な選択なので自動化したい! 構成管理の中にロジックを含められて、かつパッケージとして再配布できるとなるとAWS CDKだよね、ということで作った次第です。 使い方はsynopsisに書いた通りで:

import { addMackerelContainerAgent } from "@aereal/cdk-mackerel-container-agent"
import { Ec2TaskDefinition } from "@aws-cdk/aws-ecs"
import { Stack } from "@aws-cdk/cdk"

const stack = new Stack()
const taskDefinition = new Ec2TaskDefinition(stack, "TaskDefinition", {})

addMackerelContainerAgent({
  apiKey: 'keep-my-secret',
  taskDefinition,
})

……と、こういうかんじです。 TaskDefinitioncompatibilitynetworkMode を見てよしなに MACKEREL_CONTAINER_PLATFORM も設定します。便利。

注意点としてmackerel-container-agentはベータ版、AWS CDKはdeveloper previewということでこのライブラリもいきなり破壊的変更が入る可能性があります。 現時点でmackerel-container-agentやAWS CDKを利用している方はリスクを承知の上でのことと思いますが、念のため。

ちなみにAWS CDKとは The AWS Cloud Development Kit (AWS CDK) is an open-source software development framework to define cloud infrastructure in code and provision it through AWS CloudFormation. というもので、CloudFormationの高水準かつプログラマブルなインターフェースを提供するライブラリといった趣です。 JavaやTypeScriptで提供されているためIDEの恩恵を受けやすいことだけではなく、IAMやSecurity Groupなどセキュリティに関係する変更が含まれる場合は別途diffを出力し、ユーザが明示的に同意する入力をしないとデプロイしないといった、CloudFormationにはまだない改善点も含んだ便利なツールキットでもあります。

現在developer previewですが、GAに向けて開発が続いているので広く利用できる日も近いのではないでしょうか。

どうぞご利用ください!

どうぞご利用ください!