先週、OpenSauced エンジニアリング チームは、CODEOWNER ファイルを生成し、OpenSauced プラットフォームと統合するための強力で構成可能なコマンドライン ツールである Pizza CLI をリリースしました。堅牢なコマンドライン ツールの構築は簡単そうに見えますが、慎重な計画と思慮深いパラダイムがなければ、CLI はすぐに保守が困難でバグだらけの複雑なコードになってしまう可能性があります。このブログ投稿では、Go を使用してこの CLI を構築した方法、Cobra を使用してコマンドを編成した方法、そして無駄のないエンジニアリング チームが強力な機能を構築するためにどのように迅速に反復したかについて詳しく説明します。
Pizza CLI は、いくつかの標準ライブラリを利用する Go コマンドライン ツールです。 Go はシンプルさ、スピード、システム プログラミングに重点を置いているため、CLI を構築するのに理想的な選択肢です。 Pizza-CLI の中核では、Go の CLI ブートストラップ ライブラリである spf13/cobra を使用して、コマンド ツリー全体を編成および管理します。
Cobra は、コマンドライン インターフェイス自体を機能させ、すべてのフラグが一貫して機能できるようにし、ヘルプ メッセージや自動ドキュメントを介してユーザーとのコミュニケーションを処理する足場であると考えることができます。
Cobra ベースの Go CLI を構築する際の最初の (そして最大の) 課題の 1 つは、すべてのコードとファイルをどのように構成するかです。一般的な考えに反して、Go にはこれを行うための の規定された方法はありません。 go build コマンドも gofmt ユーティリティも、パッケージの名前の付け方やディレクトリの編成方法については問題を出しません。これは Go の最も優れた部分の 1 つです。そのシンプルさと強力さにより、あなたとあなたのエンジニアリング チームに適した構造を簡単に定義できます。
結局のところ、私の意見では、Cobra ベースの Go コードベースをコマンドのツリーとして考えて構造化するのが最善です:
├── Root command │ ├── Child command │ ├── Child command │ │ └── Grandchild command
ツリーのベースにはルート コマンドがあります。これは CLI アプリケーション全体のアンカーであり、CLI の名前を取得します。子コマンドとしてアタッチすると、CLI フロー全体がどのように機能するかの構造を伝える分岐ロジックのツリーが作成されます。
CLI を構築するときに非常に見落としがちなことの 1 つは、ユーザー エクスペリエンスです。コマンドや子コマンド構造を構築するときは、論理的に流れ、優れたユーザー エクスペリエンスにつながるため、私は通常、「ルート動詞名詞」パラダイムに従うことをお勧めします。
たとえば、Kubectl では、「kubectl get pods」、「kubectl apply …」、または「kubectl label pods …」というパラダイムが随所で見られます。これにより、ユーザーがコマンド ラインを操作する方法についての合理的なフローが保証されます。このアプリケーションは、他の人とコマンドについて話すときに非常に役立ちます。
最終的に、この構造と提案は、ファイルとディレクトリをどのように整理するかを示すことができますが、繰り返しになりますが、CLI をどのように構造化し、エンドユーザーにフローを提示するかを決定するのは最終的にはあなた次第です。
Pizza CLI には、子コマンド (およびそれらの子コマンドの後続の孫) が存在する、明確に定義された構造があります。独自のパッケージの cmd ディレクトリの下で、各コマンドは独自の実装を取得します。 root コマンドのスキャフォールディングは pkg/utils ディレクトリに存在します。これは、root コマンドを、多くのメンテナンスが必要になる可能性のあるコマンドではなく、main.go によって使用されるトップレベルのユーティリティとして考えると便利だからです。通常、root コマンドの Go 実装では、あまり触れない定型的な設定がたくさんあるため、それらを邪魔にならないようにしておくと便利です。
これはディレクトリ構造の簡略化された図です:
├── main.go ├── pkg/ │ ├── utils/ │ │ └── root.go ├── cmd/ │ ├── Child command dir │ ├── Child command dir │ │ └── Grandchild command dir
この構造により、懸念事項を明確に分離できるため、CLI の成長やコマンドの追加に応じて、CLI の保守と拡張が容易になります。
Pizza-CLI で使用する主要なライブラリの 1 つは go-git ライブラリです。これは、拡張性の高い Go の純粋な git 実装です。 CODEOWNERS の生成中に、このライブラリを使用すると、git ref ログを繰り返し、コードの差分を確認し、ユーザーが定義した設定済みの属性にどの git 作成者が関連付けられているかを判断できます。
ローカル git リポジトリの git ref ログを反復処理することは、実際には非常に簡単です:
// 1. Open the local git repository repo, err := git.PlainOpen("/path/to/your/repo") if err != nil { panic("could not open git repository") } // 2. Get the HEAD reference for the local git repo head, err := repo.Head() if err != nil { panic("could not get repo head") } // 3. Create a git ref log iterator based on some options commitIter, err := repo.Log(&git.LogOptions{ From: head.Hash(), }) if err != nil { panic("could not get repo log iterator") } defer commitIter.Close() // 4. Iterate through the commit history err = commitIter.ForEach(func(commit *object.Commit) error { // process each commit as the iterator iterates them return nil }) if err != nil { panic("could not process commit iterator") }
Git ベースのアプリケーションを構築している場合は、go-git を使用することを強くお勧めします。高速で、Go エコシステム内にうまく統合され、あらゆる種類の操作に使用できます。
当社のエンジニアリングおよび製品チームは、可能な限り最高のコマンド ライン エクスペリエンスをエンド ユーザーに提供することに深く投資しています。これは、実際の使用状況やエラーを Posthog に報告できる匿名化されたテレメトリを統合するための措置を講じたことを意味します。これにより、最も重要なバグを最初に修正し、人気のある機能リクエストを迅速に繰り返し、ユーザーが CLI をどのように使用しているかを理解できるようになりました。
Posthog には、まさにこの機能をサポートする Go のファーストパーティ ライブラリがあります。まず、Posthog クライアントを定義します:
import "github.com/posthog/posthog-go" // PosthogCliClient is a wrapper around the posthog-go client and is used as a // API entrypoint for sending OpenSauced telemetry data for CLI commands type PosthogCliClient struct { // client is the Posthog Go client client posthog.Client // activated denotes if the user has enabled or disabled telemetry activated bool // uniqueID is the user's unique, anonymous identifier uniqueID string }
その後、新しいクライアントを初期化した後、定義したさまざまな構造体メソッドを通じてそれを使用できるようになります。たとえば、OpenSauced プラットフォームにログインすると、成功したログインに関する特定の情報がキャプチャされます:
// CaptureLogin gathers telemetry on users who log into OpenSauced via the CLI func (p *PosthogCliClient) CaptureLogin(username string) error { if p.activated { return p.client.Enqueue(posthog.Capture{ DistinctId: username, Event: "pizza_cli_user_logged_in", }) } return nil }
コマンドの実行中に、エラー パスやハッピー パスなどをキャプチャするためにさまざまな「キャプチャ」関数が呼び出されます。
匿名化された ID には、Google の優れた UUID Go ライブラリを使用します。
newUUID := uuid.New().String()
これらの UUID は、エンド ユーザーのマシンのホーム ディレクトリ ~/.pizza-cli/telemtry.json に JSON としてローカルに保存されます。これにより、エンド ユーザーには、必要に応じてこのテレメトリ データを削除する完全な権限と自主性が与えられ (または、構成オプションでテレメトリを完全に無効にできます!)、CLI 使用時に匿名性を確保できます。
当社の無駄のないエンジニアリング チームは、反復的な開発プロセスに従い、小さくてテスト可能な機能を迅速に提供することに重点を置いています。通常、これは GitHub の問題、プル リクエスト、マイルストーン、プロジェクトを通じて行われます。私たちは Go の組み込みテスト フレームワークを広範囲に使用し、個々の関数の単体テストとコマンド全体の統合テストを作成します。
残念ながら、Go の標準テスト ライブラリには、そのままでは優れたアサーション機能がありません。 「==」や他のオペランドを使用するのは簡単ですが、ほとんどの場合、戻ってテストを読むときに、「assert.Equal」や「assert.Nil」などのアサーションで何が起こっているかを目視できると便利です。 ”.
優れた testify ライブラリとその「assert」機能を統合して、よりスムーズなテストの実装を可能にしました:
config, _, err := LoadConfig(nonExistentPath) require.Error(t, err) assert.Nil(t, config)
私たちは、小さなスクリプトを簡単に実行するために、GNU の「make」によく似たコマンド ランナー ユーティリティである Just at OpenSauced を頻繁に使用しています。これにより、ビルドとテストが「ビルドするだけ」または「テストするだけ」と同じくらい簡単なので、新しいチーム メンバーやコミュニティ メンバーを Go エコシステムに迅速に導入できるようになりました。
たとえば、Just で単純なビルド ユーティリティを作成するには、justfile 内に次のようにします:
build: go build main.go -o build/pizza
これにより、Go バイナリが build/ ディレクトリにビルドされます。現在、ローカルでのビルドは「ただ」コマンドを実行するだけで簡単になります。
しかし、私たちは Just の使用により多くの機能を統合することができ、これをビルド、テスト、開発フレームワーク全体の実行方法の基礎にしました。たとえば、挿入されたビルド時間変数 (バイナリがビルドされた SHA、バージョン、日時など) を使用してローカル アーキテクチャのバイナリをビルドするには、ローカル環境を使用してスクリプトで追加のステップを実行できます。 「go build」を実行する前に:
build: #!/usr/bin/env sh echo "Building for local arch" export VERSION="${RELEASE_TAG_VERSION:-dev}" export DATETIME=$(date -u "%Y-%m-%d-%H:%M:%S") export SHA=$(git rev-parse HEAD) go build \ -ldflags="-s -w \ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}' \ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=${SHA}' \ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}' \ -X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \ -o build/pizza
これを拡張して、クロス アーキテクチャと OS ビルドを可能にしました。Go は、GOARCH 環境変数と GOOS 環境変数を使用して、どの CPU アーキテクチャとオペレーティング システムに対してビルドするかを認識します。他のバリアントを構築するには、そのための特定の Just コマンドを作成できます:
# Builds for Darwin linux (i.e., MacOS) on arm64 architecture (i.e. Apple silicon) build-darwin-arm64: #!/usr/bin/env sh echo "Building darwin arm64" export VERSION="${RELEASE_TAG_VERSION:-dev}" export DATETIME=$(date -u "%Y-%m-%d-%H:%M:%S") export SHA=$(git rev-parse HEAD) export CGO_ENABLED=0 export GOOS="darwin" export GOARCH="arm64" go build \ -ldflags="-s -w \ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}' \ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=${SHA}' \ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}' \ -X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \ -o build/pizza-${GOOS}-${GOARCH}
Go と Cobra を使用して Pizza CLI を構築するのはエキサイティングな旅であり、それを皆さんと共有できることを嬉しく思います。 Go のパフォーマンスとシンプルさを Cobra の強力なコマンド構造と組み合わせることで、堅牢で強力なだけでなく、ユーザーフレンドリーで保守しやすいツールを作成することができました。
Pizza CLI GitHub リポジトリを探索し、ツールを試して、ご意見をお聞かせください。どこの開発チームにとってもコードの所有権管理が容易になるように取り組んでいるので、あなたのフィードバックと貢献は非常に貴重です!
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3