寝ても覚めてもこんぴうた

プログラム書いたり、ネットワーク設計したり、サーバ構築したり、車いじったり、ゲームしたり。そんなひとにわたしはなりたい。 投げ銭は kyash_id : chidakiyo マデ

GCPのIAMのプリンシパルって?

GCPのIAMのドキュメントにたびたび存在する プリンシパル
雰囲気では理解していたのだけど、実際にGCPのドキュメントでは同定義されているのか。

ドキュメントを見ると...

以下のように記述されています。

このアクセス管理モデルは、次の 3 つの主要な部分から構成されています。

  • プリンシパルプリンシパルは、リソースへのアクセスが許可されている Google アカウント(エンドユーザー)、サービス アカウント(アプリケーションまたはコンピューティング ワークロード)、Google グループ、Google Workspace アカウントまたは Cloud Identity ドメインです。各プリンシパルには、一意の識別子(通常はメールアドレス)があります。

  • ロール。ロールは権限のコレクションです。権限によって、リソースに対して許可されているオペレーションが決まります。プリンシパルにロールを付与すると、そのロールに含まれるすべての権限が付与されます。

  • ポリシー。許可ポリシーは、1 つ以上のプリンシパルを個々のロールにバインドするロール バインディングの集合です。リソースに対してどのようなアクセス権(ロール)を誰(プリンシパル)に許可するのかを定義する場合、許可ポリシーを作成して、そのポリシーをリソースに接続します。

つまり、

であり、
通常はメールアドレス である。

ということのようです。
そういうことだろうなと感覚的に把握していましたが、スッキリ把握できました。

ではでは。

GoのWebサービスのエラーハンドリングについてGoのblogに書かれていた内容を日本語でまとめてみた

GoでWebサービスを実装する際、Errorのログをどこで出すのかーという話、 少々古い記事になるけどGoのBlogに書かれていたのでサラッと日本語にしてみた。

GAEの実装を例にされているのと、2011年頃の記事なので、いろいろモダンなGo技術(?)を使うといい感じにできる部分もあるけど一旦そのまま。

内容としては https://go.dev/blog/error-handling-and-go#simplifying-repetitive-error-handling に書かれているものです。

何度もやるエラーハンドリングを簡素化する

Goはエラーハンドリングが重要。言語のデザインや習慣として発生した場所でエラーを明示的にチェックすることが推奨される。
(スローして場合によってキャッチする他の言語規則とは異なる)

場合によっては冗長になるが、度重なるエラー処理を最小限に抑える手法がある。
データストアからレコードを取得するAppEngineのコードで考えてみましょう。

func init() {
    http.HandleFunc("/view", viewRecord)
}

func viewRecord(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
    record := new(Record)
    if err := datastore.Get(c, key, record); err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    if err := viewTemplate.Execute(w, record); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

この関数は datastore.Get/viewTemplate.Executeから返されるエラーを処理する。
いずれもHTTP 500のメッセージが表示される。

これらは扱いやすいコード量に見えるが、更にHTTPハンドラを追加するとエラーハンドリングコードがたくさん作られる。

繰り返しを減らすため、エラーの戻り値を含む appHandler 型を定義する

type appHandler func(http.ResponseWriter, *http.Request) error

そして、viewRecordがerrorを返すように変更する

func viewRecord(w http.ResponseWriter, r *http.Request) error {
    c := appengine.NewContext(r)
    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
    record := new(Record)
    if err := datastore.Get(c, key, record); err != nil {
        return err
    }
    return viewTemplate.Execute(w, record)
}

これはオリジナルよりシンプルですが、httpパッケージはerrorを返す関数を利用できずないため、
http.HandlerインターフェースのServeHTTPをappHandlerに実装します

func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if err := fn(w, r); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

ServeHTTPメソッドはappHandler関数を呼び出し、返されたエラーをユーザに表示します。

appHandlerはhttp.Handlerなので、viewRecordをhttpパッケージに登録する際にはhttp.Handle関数を利用します。

func init() {
    http.Handle("/view", appHandler(viewRecord))
}

このようなベーシックなエラーハンドリング基盤を配置することでユーザフレンドリーにすることができる。
エラーを表示するだけではなく、適切なHTTPステータスを含むシンプルなエラーメッセージを表示し、
デバッグのために完全なログを出力することもできる。

それを行うためには、エラーとその他のフィールドを含むappErrorを作成する

type appError struct {
    Error   error
    Message string
    Code    int
}

そして、 *appError を返すようにappHandler型を変更する

type appHandler func(http.ResponseWriter, *http.Request) *appError

( こちら に書かれているように、errorではなくerrorのコンクリートタイプを返すことは通常は間違いですが、ServeHTTPは値を見てその内容を使用する唯一の場所になるためここで行うことは正しいといえます)

appHandlerのServeHTTPメソッドがappErrorのメッセージをとエラーコードをログにも落とせるようにします。

func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if e := fn(w, r); e != nil { // eは *appError、os.Errorではない。
        c := appengine.NewContext(r)
        c.Errorf("%v", e.Error)
        http.Error(w, e.Message, e.Code)
    }
}

最後に、viewRecordの関数シグネチャに変更し、エラーが発生した際には多くの情報を返すようにします。

func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
    c := appengine.NewContext(r)
    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
    record := new(Record)
    if err := datastore.Get(c, key, record); err != nil {
        return &appError{err, "Record not found", 404}
    }
    if err := viewTemplate.Execute(w, record); err != nil {
        return &appError{err, "Can't display record", 500}
    }
    return nil
}

この状態のviewRecordはオリジナルと同じようなながさになりますが、より使いやすいユーザエクスペリエンスを提供している。

  • エラーハンドラにわかりやすいHTMLテンプレートを与える
  • ユーザが管理者であればスタックトレースをレスポンスに含めるなどが可能(デバッグが容易になる)
  • デバッグを容易にするスタックトレースを持つappErrorコンストラクタを記述する
  • appHandler内のパニックからリカバーし、クリティカルのログを出力するし、重大なエラーが発生した旨をユーザに伝える。プログラミングエラーによって引き起こされる得体のしれないメッセージからユーザを守る

goでoapi-codegenを使ってopenapi 3.0してみた(chi-server)

OpenAPI何もわからんですが、
Go で OpenAPI 3.0 に対応したいと思ったのですが、swaggerはどうやら OpenAPI 2.0 とやららしいので、
oapi-codegen を利用してみました。

インストール

goがインストールされれている環境で

go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@latest

を実行すると oapi-codegen がインストールされます。

利用方法

今回は chi-server を利用しようと思います。
標準では echo サーバを生成したような記憶があります(ど忘れ)

また、READMEに書いてあるサンプルの静止コマンドでは、goのファイルが1つになってしまいますので、
運用も考え、server, types, spec の3ファイルに分かれるようにコマンドを実行します。

コマンドの引数がREADMEなどを読む感じでうまく適用できなかったのですが試行錯誤(やコードを読んで)以下で実行しています

oapi-codegen -package hoge -old-config-style -generate "chi-server" api.yaml > api.gen.go

oapi-codegen -package hoge -old-config-style -generate "types" api.yaml > types.gen.go

oapi-codegen -package hoge -old-config-style -generate "spec" api.yaml > spec.gen.go

このように実行することで、httpサーバとしての api.gen.go , リクエスト/レスポンスパラメータの型としての types.gen.go , oapiのspecファイルとしての spec.gen.go の3ファイルがそれぞれ生成されます。

httpサーバとしての組み込み

サーバとしての組み込みは難しいことはなく、 api.gen.go ファイルの中に Handler を生成する関数が複数用意されているので、
必要な項目て利用できるものを使いhttpサーバを起動しましょう。(詳細略:手抜き)

困ったこと

chi 以外の実装を試していないので、ほかがどうかはわかっていないのですが、chi 関しては、
swaggerを利用したバリデーションなどが標準的に生成されたコードの中で組み込まれず、自分でmiddlewareなどを実装する必要がありました。
これはもう少しよしなにやってくれるような期待をしていたのでびっくりしたのと、
security BearerAuth あたりも割と自分で実装する必要がありました。

最後に

openapi3.0 を使って実装するのが初だったので最初のYAMLの作成に結構手間取りましたが、
実際に開発の流れになると、yamlで変更したものがgoの実装に反映されたり、
webビューからリクエストを投げて動作確認をしたりということが気軽に行えるのでとても良かった。

YAMLを書く → 生成してみる → 生成された構造体/コードを見る(gitのdiff)

というのを繰り返すことで、YAMLで表現したことがどういう意味を持っているのかということが理解しやすい、
しかも生成が速いということで非常に良い感じでした。

ではでは。

参考(ツールの評価のためにも色々参考にしました)

github.com

future-architect.github.io

future-architect.github.io

future-architect.github.io

future-architect.github.io

github.com

zenn.dev

github.com

rinoguchi.net

github.com

times.hrbrain.co.jp

github.com

blog.ebiiim.com

github.com

ツール

editor.swagger.io

Alfread の代わりに Raycast を使い始めたメモ

身の回りで使い始めた人が増えてきたので、インストールしてみたメモです。

細かい部分は追記するかもしれませんししないかもしれません。笑

インストール

こちら からダウンロードし、ポチポチーっとインストールする。

設定した項目

Alfread に設定していた起動のショートカットを Raycast に向ける

単にキー設定を変更しただけ。(Alfreadの設定をずらして、Raycastの設定をAlfreadに設定していたものにする。一応ショートカットがぶつかってると重複しているよ、と教えてくれるようだ)

おいおいやっておきたいこと

参考記事に乗っている拡張機能を適当に必要な物を追加しておきたい。

デフォルトで使うといらない候補が結構出てくる感があるので、必要じゃないものは切っておきたい(めんどくさがりなので切ってない)

そのうち設定変更などしたら追記します。(Alfreadも課金していたのに何一つ便利設定していなかった人)

ではでは。

参考記事

motemen.hatenablog.com

zenn.dev

IntelliJでmermaid記法を有効化する

割と簡単な手順でIntelliJ上でmermaid記法を利用できるようになるのでメモ

IntelliJの設定

Preferences(⌘ + ,) -> Language & Frameworks -> Markdown -> Merkdown Extensions の Mermaid を install してチェックを有効化する

有効化したらIDEを再起動しましょう

利用方法

普通にMarkdownを書く(README.mdなど)際に、

`` `mermaid
erDiagram
    CUSTOMER ||--o{ ORDER : places
    ORDER ||--|{ LINE-ITEM : contains
    CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
`` `

※スペースが入っているのはわざとです

上記のように記述すると以下のようなプレビューが表示されます

便利

ではでは。

CloudRun 上でgoで書かれたサービスを動かす場合の基本的なmainの書き方

ドキュメントとしてはここ

cloud.google.com

GitHubはここにある

github.com

実際のコード

import (
        "context"
        "fmt"
        "log"
        "net/http"
        "os"
        "os/signal"
        "syscall"
        "time"
)

// Create channel to listen for signals.
var signalChan chan (os.Signal) = make(chan os.Signal, 1)

func main() {
        // Determine port for HTTP service.
        port := os.Getenv("PORT")
        if port == "" {
                port = "8080"
                log.Printf("defaulting to port %s", port)
        }

        srv := &http.Server{
                Addr:    ":" + port,
                Handler: http.HandlerFunc(handler),
        }

        // SIGINT handles Ctrl+C locally.
        // SIGTERM handles Cloud Run termination signal.
        signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)

        // Start HTTP server.
        go func() {
                log.Printf("listening on port %s", port)
                if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
                        log.Fatal(err)
                }
        }()

        // Receive output from signalChan.
        sig := <-signalChan
        log.Printf("%s signal caught", sig)

        // Timeout if waiting for connections to return idle.
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()

        // Add extra handling here to clean up resources, such as flushing logs and
        // closing any database or Redis connections.

        // Gracefully shutdown the server by waiting on existing requests (except websockets).
        if err := srv.Shutdown(ctx); err != nil {
                log.Printf("server shutdown failed: %+v", err)
        }
        log.Print("server exited")
}

*ドキュメントから転記

こんな感じ。