Route53 でドメインを取得する

新しいドメインを取る必要があってどのサイトで取ろうかなーと悩んだ末、AWS で取ることにした。

AWS 自体はがっつり触ってきたが、ドメイン取得はやったことがなかったので公式ドキュメントを参考にしながら進めた。

手順

Route53 のコンソールにて、[登録済みドメイン]->[ドメインを登録]に遷移する。

検索バーに欲しいドメインを入力して、使用状況を確認する。

利用可能であれば、[チェックアウト]ボタンから遷移して、あとは画面に従って連絡先などを入力していく。

入力が完了すれば、そのうち AWS から「Verify your email address for ドメイン名」といったタイトルのメールが届く。

本文内に認証用URLが記載してあるので、アクセスして認証を完了させる。

登録状況は、[リクエスト]のページで確認できる。

認証を完了して暫くすると、「Registration of ドメイン名 succeeded」といったタイトルのメールが届き、[登録済みドメイン]のページにドメインが表示される。

SlackのBlock Kitでリッチなメッセージを構築しよう

Slack の Block Kit という機能の存在を知った。

この機能を活用することで、リッチで読みやすく理解しやすい Slack メッセージを構築することができるそうだ。

やってみる

Formatting text for app surfaces | Slack を参考に block を組み合わせてメッセージを構築してみる。

block は JSON で表現することができるので、プログラムから送信する際のリクエストボディに乗せる形となる。

下記がシンプルな block の例だ。

{
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "*request timeout*"
            }
        }
    ]
}

Block Kit Builderを使うと可視化して確認することができる。

全ての block はその種類を指定するための type フィールドを持ち、その他にも block ごとに固有のフィールドを持つ。

この辺りは都度都度ドキュメントを参照するのが良いだろう。

block は複数組み合わせて使うことができる。

様々な種類の block を組み合わせて好き勝手カスタマイズできるので、気がついたら思ったより時間が経っていたなんてこともありそう。

{
    "blocks": [
        {
            "type": "header",
            "text": {
                "type": "plain_text",
                "text": "Error"
            }
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "*request timeout*"
            }
        }
    ]
}

メッセージ送信時には block はオブジェクトの最上位フィールドに指定して送信する。

block を使っている場合は、同階層の text フィールドの値はフォールバック値となる。

{
    "channel": "xxxxxx",
    "text": "Fallback",
    "blocks": [
        {
            "type": "header",
            "text": {
                "type": "plain_text",
                "text": "Error"
            }
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "*request timeout*"
            }
        }
    ]
}

感想

Block Kit Builder がかなり優秀で、これはメッセージ作りが捗ると感じた。

ただ、Slack 通知処理を SDK を使って実装するとなると、JSON のままではそのままコピペで使えないのでその部分が少し面倒だなと気になった。

Go ジェネリクスの基礎を学ぶ

Go 1.18 でジェネリクスが導入されてからこれまで雰囲気でジェネリクスを使用してきました。

ここらでちゃんと仕様を体に覚え込ませたいと思い立ち、公式の文書を読みつつ手を動かした記録を記事として残すことにしました。

少しずつ理解を深めていく経緯を記録していく記事となるため、誤った情報を発信してしまう形になることもあるかもしれません。もしも内容に間違いがあれば、記事のコメントXのTweetなどで教えていただけると幸いです。

本記事では下記ページをベースに学んだことを記していきます。

Tutorial: Getting started with generics - The Go Programming Language

ジェネリクスの何が嬉しいか

ジェネリクスを使用することで、呼び出し元から提供される任意の型で動作するような関数や型を定義することができます。

ジェネリクスにより汎用的な関数、型を定義することで似たような処理の重複を排除された保守性の高いコードに繋がりそうです。

ジェネリクスを使わない実装

まずはジェネリクスを使わない実装を行います。

後に同じ結果を得られる処理をジェネリクスを用いて実装し両者を比較することで、ジェネリクスの利点を感じることができますね。

package main

import "fmt"

func avgInts(x []int64) int64 {
    var res int64
    for _, v := range x {
        res += v
    }
    return res / int64(len(x))
}

func avgFloats(x []float64) float64 {
    var res float64
    for _, v := range x {
        res += v
    }
    return res / float64(len(x))
}

func main() {
    fmt.Println(avgInts([]int64{1, 2, 3}))
    fmt.Println(avgFloats([]float64{1.2, 2.2, 3.2}))
}
2
2.2

avgIntsとavgFloatsは引数、戻り値の型が違うだけで計算ロジックは同じです。

ジェネリクスを使った実装

ジェネリクスを用いることで、前項で定義した二つの関数を一つにまとめることが可能です。

ここでは int64 または float64 を受け取られる関数を定義します。

複数の型を受け取られるように、型パラメータを宣言した関数を定義します。

型パラメータには型制約があり、型制約により呼び出し元が型引数として渡せる値の型は制限されます。

型引数の型が制約を違反している場合、コンパイルは失敗します。

型パラメーターは、関数が型パラメータに対して行う全ての操作をサポートする必要があり、制約に数値型が含まれている型パラメーターに対して文字列操作を行なった場合、コンパイルは失敗します。

具体的な例を挙げてみます。

下記のコードは一つの型パラメーターT、型パラメーターをT使用するT型の引数vを宣言しています。戻り値の型はT型となっています。

型パラメーターTは、制約として int64 と float64 のいずれかの型であることを指定します。どちらの型も型引数として許可されます。

func avg[T int64 | float64](x []T) T {
    var res T
    for _, v := range x {
        res += v
    }
    return res / T(len(x))
}

呼び出し元では関数引数に加えて型引数を渡して関数を呼び出します。

型引数として呼び出す関数の型パラメーターを置き換える型を明示的に指定し、コンパイラーにどの型を使用するかを伝えます。

コードを実行するために、コンパイラは型パラメーターを呼び出し先に指定された具象型に置き換えます。

fmt.Println(avg[int64]([]int64{1, 2, 3}))
fmt.Println(avg[float64]([]float64{1.2, 2.2, 3.2}))

使用する型をコンパイラが推測できる場合、型引数は省略可能です。

常に可能なわけではなく、例えば型パラメーターが宣言されているが関数引数のない関数を呼び出す場合には、型引数の指定は必須となります。

fmt.Println(avg([]int64{1, 2, 3}))
fmt.Println(avg([]float64{1.2, 2.2, 3.2}))

型制約はインターフェースとして宣言することが可能です。これにより型制約の再利用性が高まります。

インターフェースとして宣言すると、型制約はインタフェースを実装している任意の型を許可します。例えば A() というメソッドを持つ型制約インターフェースを宣言し型パラメーターとして使用した場合、型引数は A() というメソッドを持つ型である必要があります。

型制約インターフェースは特定の型を参照することもできます。

下記のような型制約インタフェースを宣言することで、型パラメーターの制約を int64 | float64 と記述する代わりに num を使用できます。

type num interface {
    int64 | float64
}

func avg[T num](x []T) T {
    var res T
    for _, v := range x {
        res += v
    }
    return res / T(len(x))
}

ここまでの学んだことを踏まえて実装した完成系は下記です。

package main

import "fmt"

type num interface {
    int64 | float64
}

func avg[T num](x []T) T {
    var res T
    for _, v := range x {
        res += v
    }
    return res / T(len(x))
}

func main() {
    fmt.Println(avg([]int64{1, 2, 3}))
    fmt.Println(avg([]float64{1.2, 2.2, 3.2}))
}

go.dev

最後に

Go のジェネリクスの基礎の基礎を改めて学びました。

正直今回の内容は理解して使っていたつもりですが、人にわかりやすく説明できるかと言われると自信を持ってYESとは言えないくらいの理解度だったので、記事にすることで理解を言語化できて良かったです。

Amazon Aurora カスタムエンドポイント覚書

Amazon Aurora カスタムエンドポイントの仕様がかなり複雑であり、都度都度ドキュメントを参照するのが辛いので要点と気になる点を備忘録として記す。

概要

カスタムエンドポイントは、任意の複数のDBインスタンスに接続するエンドポイントである。

用途に応じてインスタンスを使い分けたい場合に有効である。

カスタムエンドポイントへの接続は、メンバーとして定義されているインスタンスのいずれかへルーティングされる。

メンバーのインスタンスのいずれかが使用不可になった場合は、同じエンドポイントの他のインスタンスにルーティングされる。

エンドポイント名はリージョン内で一意になるように命名しなければいけない。

タイプ

カスタムエンドポイントにはREADER WRITER ANYの3種類のタイプのいずれかを指定する。

マネジメントコンソールから選択できるのは ANY のみである。

READERにメンバーとして追加できるのはリーダーインスタンスのみである。

ANYにはリーダーインスタンス、ライターインスタンスのいずれも追加可能である。

メンバー

メンバーのリスト定義は静的リスト、除外リストで指定できる。

静的リストを使用した場合、リストに登録したインスタンスのみがメンバーとなる。将来的にクラスターに追加されるインスタンスが自動的にメンバーに追加されることはない。

除外リストを使用した場合、リストに登録したインスタンス以外がメンバーとなる。将来的にクラスターに追加されるインスタンスも、ロールがエンドポイントのタイプと一致すれば自動的にメンバーに追加される。

使用不可になったレプリカはカスタムエンドポイントに残り続ける。

ただし、再度使用可能になるまでは接続はできない。

インスタンスをメンバーから削除、追加しても既存のインスタンスエンドポイントは引き続き使用することができる。

参考

docs.aws.amazon.com

GitHub で自分が Contribute した PullRequest を探す

今年はいくつかのOSSにコントリビュートしたので、マージされた PullRequest を一覧で見たいなと思い検索方法を調査しました。

PRを一覧で見る

https://github.com/pulls へアクセスすることで自分が作成したPRを一覧で取得できます。

OSSへのPRにのみ GitHub アカウントを利用しているのであれば、上記で十分です。

ですが、私は個人のリポジトリや勤務先のリポジトリにも同一アカウントでPRを作成しているため、OSSへのPRが埋もれてしまっています。

検索バーを活用する

検索バーを活用することで、フィルタリングを適用した結果を取得できます。

今回は、下記のようなフィルタリングを適用しました。

is:pr author:ss49919201 -org:{勤務先の組織名} -user:ss49919201 is:closed

{勤務先の組織名}には勤務先の GitHub 組織名を入力

-org:{勤務先の組織名} -user:ss49919201 のように - を付けることで、-に続く修飾子に一致する結果を除外できます。

参考

docs.github.com

Go Conference mini 2023 Winter IN KYOTO レポート

2023-12-02 に開催された「Go Conference mini 2023 Winter IN KYOTO」にオンライン参加しました。

今回は時間的に余裕があったので、ほとんど全てのセッションを聴くことができました。

本記事は、セッションの中でも特に興味深かったものについて簡単にまとめ記事となります。

Deep dive into log/slog package

Kanata Miyahana さん

Go1.21でリリースされた構造化ログのための標準ライブラリのlog/slogについてのセッションでした。

開発の背景として、ログを統一的に扱うことが目的というのがあるそうです。

メモリアロケーションを少なくするために、AttrのValueはanyではなくValue構造体型を定義している点、zapの使われ方を調査して初期化するキャパシティを決定した点などが非常に興味深かったです。

github.com

なぜそのような実装になっているのか?という点まで踏み込んでコードリーディングしていくことが重要ですね。

日時処理の新スタンダード: Synchro によるタイムゾーン安全、楽々開発

Kei Kamikawa さん

登壇者のKei Kamikawa さんの開発されているsynchroについてのセッションでした。

github.com

日付とテキストの変換が難しい、加算減算の使い方に気をつけないと危ない等の Go で日時周りの実装をする際の懸念点をカバーしてくれるライブラリのようです。

ISO8601完全サポートするためにかなりご苦労されたとのこと....!

実は Twitter で見かけてかなり気になっていたライブラリなので、作者の開発話を聞けたのはかなり良かったです。

Go1.22で導入予定だったzeroというbuilt-inについての紹介

Takuma Shibuya さん

Go1.22で導入予定だった識別子zeroについてのセッションでした。

一度AcceptedになってからDeclineに戻ったとのことですが、これは相当珍しいことみたいです。

個人的にはreturn time.Time{} ではなく、return zeroのように書けるのは可読性の高い書き振りだと感じたので、今後導入されたら良いなと思っています。

CUE+Goで安全かつ簡単に設定ファイルを自動生成してみた

Naoki Kuroda さん

設定記述言語であるCUE(キュー)についてのセッションでした。

値と型を同等に使える点、バリデーションしてYAML/JSONに変換できる点などが気になりました。

Goと相性も良いとのこと。

Playgroundがあるのでちょっと試してみたのですが、バリデーションや型定義の書き振りがかなり好みです。

cuelang.org

context.WithoutCancelについて語る

Momo Ito さん

Go1.21で追加されたcontext.WithoutCancelについてのセッションでした。

ロールバック中にキャンセルが起こると一貫性を失うので伝播させずにロールバックを続けたいなどのケースで、親がキャンセルされても子がキャンセルされないようにするのに使える機能のようです。

Webサービスではクライアント側から通信を切断されてしまったけど処理を中途半端に終了してしまうとまずいケースなどもあるかと思うので、覚えておきたい機能です。

The Future of encoding/json

Shunta Komatsu さん

encoding/jsonのV2パッケージについてのセッションでした。

encoding/jsonは5番目に多くimportされているらしくパッケージでファーストコミットは2008年という歴史のある人気パッケージですが、問題点がぼちぼちあるとのことです。

提案されているV2パッケージはRFCへの準拠、セキュリティ向上が主目標として進んでいるそうです。

構造体タグで様々な表現ができるようになるのが楽しみです。

まとめ

最近はあまりGoを書いていないのですが、かなり楽しめました。(またGoを書き続ける日々に戻りたい)

低レイヤー寄りの話や、機能の深堀りが多く非常にタメになるセッションが多かったです。

また、今回はオンライン参加でしたがオフラインで会場の雰囲気を味わってみたいなーと思いました。(次回の東京でのカンファレンスは出張しようかな...。)

terraform-aws-providerへコントリビュートした

本記事はZennに投稿した記事の複製です。


先日、terraform-aws-providerにコントリビュートし、大規模?OSSへのコントリビュート経験をGETしました。 マージされた改修は、v5.26.0にて無事リリースされました。 https://github.com/hashicorp/terraform-provider-aws/pull/33061 https://github.com/hashicorp/terraform-provider-aws/releases/tag/v5.26.0

かなり嬉しかった出来事であり、これは是非記録に残しておきたいと感じましたので、久しぶりにZennに投稿しようと筆を取りました。 本記事では、私が terraform-aws-provider へコントリビュート達成するまでの経緯を簡単にまとめます。

issue 作成

今回は、自分が terraform-aws-provider を使用している中で発見したバグを issue として報告しました。

https://github.com/hashicorp/terraform-provider-aws/issues/32981

報告した内容としてはaws_quicksight_data_sourceの apply 時の意図しない挙動に関するものとなります。 既にデプロイ済みのaws_quicksight_data_sourceの argument に対する変更を加えたコードを apply した際に、Error: updating QuickSight Data Source (xxxxxxxxx/example-id): InvalidParameterValueException: DataSourceParameters field is incorrectly setが発生していました。 エラー文言から内部で Data Source のAPIを叩いている辺りに何らかの問題あることが分かり、該当箇所の実装を軽く確認し Terraform の書き方に誤りがあるわけではなく実装のバグであると判断し issue として報告しました。

改修作業

まずは環境構築から行いました。 terraform-aws-provider は開発者向けのドキュメントが非常に充実しており、ドキュメントの通りに作業することで特別詰まることなく環境構築を進めることができました。

https://hashicorp.github.io/terraform-provider-aws/

コードの改修についてですが、PRのファイル差分はこちらです。 元々の実装はUpdateDataSource呼び出し時に、Terraform の記述に差分が生じている Attibution の値のみ入力構造体のフィールドとして送信するように実装されていました。 これにより値の入力が必須であるフィールドと対応する Attibution に差分が生じていない場合に、無効な入力であると判定され AWS API からエラーが返ってきていたようです。 差分が生じているかどうかに関わらず、Terraform で定義されている Attribution は入力構造体のフィールドとして送信するように改修し、想定通りの動作を得ることができました。

コードの改修に加えて、テストコードの追加、CHANGE_LOGの記載を行いました。 こちらについてもドキュメントを参考にしつつ、直近マージされたPRのコミットなども確認しながら作業を進めました。

PR作成

PRの作成についても特にコントリビュータ側が難しいことを考える必要はなく、PRテンプレートとドキュメントに従っていったという感じです。 PRマージの条件としてAWS のリソース操作を伴う受け入れテストがPASSしていることが求められるのですが、このテストの実行をメンテナ側(HashiCorp側?)に任せることもできるということに驚きました。 AWS 操作は料金が発生してしまう可能性もあるので、経済的理由がコントリビュートの妨げになることを防ぐというのがこの方針の意図のようです。

https://hashicorp.github.io/terraform-provider-aws/raising-a-pull-request/

無事マージへ

ドキドキしながらPRを作成し待つこと約3ヶ月、InboxにPRマージクローズの通知が来た時はかなりテンションが上がりました。 約4000のissue、約400のPRがOpenしている状態のリポジトリであることを考えると、3ヶ月は妥当な期間なのかなと思っています。(メンテナの皆さん本当にお疲れ様です)

最後に

今回の経験の中での私の1番の学びはなんといっても、ドキュメントの手厚さを感じることができたことです。 各ページ、項目のスコープの切り分けが非常に明確であること、網羅性も高いこと、などなどお手本にしたい点が盛り沢山でありました。 これからもOSS活動を続けて、自分の血肉になるような経験をプライベートでも積み続けていきたいですね。

この記事が、terraform-aws-provider、その他のOSSへのコントリビュートに興味のある方の参考になれば幸いです。