Rumah > Artikel > pembangunan bahagian belakang > Teknikal Deep Dive: Cara Kami Membina Pizza CLI Menggunakan Go dan Cobra
Minggu lepas, pasukan kejuruteraan OpenSauced mengeluarkan Pizza CLI, alat baris perintah yang berkuasa dan boleh digubah untuk menjana fail CODEOWNER dan menyepadukan dengan platform OpenSauced. Membina alatan baris perintah yang mantap mungkin kelihatan mudah, tetapi tanpa perancangan yang teliti dan paradigma yang bernas, CLI boleh menjadi kusut kod dengan cepat yang sukar untuk dikekalkan dan penuh dengan pepijat. Dalam catatan blog ini, kami akan mendalami cara kami membina CLI ini menggunakan Go, cara kami mengatur arahan kami menggunakan Cobra dan cara pasukan kejuruteraan kurus kami bergerak pantas untuk membina fungsi yang berkuasa.
Pizza CLI ialah alat baris arahan Go yang memanfaatkan beberapa perpustakaan standard. Kesederhanaan, kelajuan dan fokus pengaturcaraan sistem Go menjadikannya pilihan yang ideal untuk membina CLI. Pada terasnya, Pizza-CLI menggunakan spf13/cobra, pustaka bootstrapping CLI dalam Go, untuk mengatur dan mengurus keseluruhan pokok perintah.
Anda boleh menganggap Cobra sebagai perancah yang menjadikan antara muka baris arahan itu sendiri berfungsi, membolehkan semua bendera berfungsi secara konsisten dan mengendalikan komunikasi kepada pengguna melalui mesej bantuan dan dokumentasi automatik.
Salah satu cabaran pertama (dan terbesar) apabila membina Go CLI berasaskan Cobra ialah cara menstruktur semua kod dan fail anda. Bertentangan dengan kepercayaan popular, terdapat tiada cara yang ditetapkan untuk melakukan perkara ini dalam Go. Baik arahan go build mahupun utiliti gofmt tidak akan mengadu tentang cara anda menamakan pakej anda atau mengatur direktori anda. Ini adalah salah satu bahagian terbaik Go: kesederhanaan dan kuasanya memudahkan untuk menentukan struktur yang sesuai untuk anda dan pasukan kejuruteraan anda!
Akhirnya, pada pendapat saya, adalah lebih baik untuk memikirkan dan menyusun pangkalan kod Go berasaskan Cobra sebagai pepohon arahan:
├── Root command │ ├── Child command │ ├── Child command │ │ └── Grandchild command
Di pangkal pokok ialah arahan akar: ini adalah sauh untuk keseluruhan aplikasi CLI anda dan akan mendapat nama CLI anda. Dilampirkan sebagai perintah anak, anda akan mempunyai pepohon logik bercabang yang memaklumkan struktur cara keseluruhan aliran CLI anda berfungsi.
Salah satu perkara yang sangat mudah terlepas apabila membina CLI ialah pengalaman pengguna. Saya biasanya mengesyorkan orang mengikuti paradigma "kata nama akar" apabila membina perintah dan struktur perintah kanak-kanak kerana ia mengalir secara logik dan membawa kepada pengalaman pengguna yang sangat baik.
Sebagai contoh, dalam Kubectl, anda akan melihat paradigma ini di mana-mana sahaja: “kubectl get pods”, “kubectl apply …“, atau “kubectl label pods …” Ini memastikan aliran masuk akal tentang cara pengguna akan berinteraksi dengan baris arahan anda aplikasi dan banyak membantu apabila bercakap tentang arahan dengan orang lain.
Akhirnya, struktur dan cadangan ini boleh memaklumkan cara anda menyusun fail dan direktori anda, tetapi sekali lagi, terpulang kepada anda untuk menentukan cara anda menstruktur CLI anda dan membentangkan aliran kepada pengguna akhir.
Dalam Pizza CLI, kami mempunyai struktur yang jelas di mana perintah kanak-kanak (dan cucu-cucu seterusnya perintah kanak-kanak itu) hidup. Di bawah direktori cmd dalam pakej mereka sendiri, setiap arahan mendapat pelaksanaannya sendiri. Perancah arahan root wujud dalam direktori pkg/utils kerana ia berguna untuk memikirkan arahan root sebagai utiliti peringkat atas yang digunakan oleh main.go, dan bukannya arahan yang mungkin memerlukan banyak penyelenggaraan. Lazimnya, dalam arahan root anda, pelaksanaan Go, anda akan mempunyai banyak perkara yang tidak akan anda sentuh, jadi senang untuk menyingkirkan perkara itu.
Berikut ialah paparan ringkas struktur direktori kami:
├── main.go ├── pkg/ │ ├── utils/ │ │ └── root.go ├── cmd/ │ ├── Child command dir │ ├── Child command dir │ │ └── Grandchild command dir
Struktur ini membolehkan pemisahan kebimbangan yang jelas dan menjadikannya lebih mudah untuk mengekalkan dan memanjangkan CLI apabila ia berkembang dan semasa kami menambah lebih banyak arahan.
Salah satu perpustakaan utama yang kami gunakan dalam Pizza-CLI ialah perpustakaan go-git, pelaksanaan git tulen dalam Go yang sangat boleh dikembangkan. Semasa penjanaan CODEOWNERS, pustaka ini membolehkan kami mengulangi log ref git, melihat perbezaan kod dan menentukan pengarang git yang dikaitkan dengan atribusi yang dikonfigurasikan yang ditakrifkan oleh pengguna.
Mengulang log git ref repo git tempatan sebenarnya agak mudah:
// 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") }
Jika anda membina aplikasi berasaskan Git, saya pasti mengesyorkan menggunakan go-git: ia pantas, berintegrasi dengan baik dalam ekosistem Go dan boleh digunakan untuk melakukan pelbagai perkara!
Our engineering and product team is deeply invested in bringing the best possible command line experience to our end users: this means we’ve taken steps to integrate anonymized telemetry that can report to Posthog on usage and errors out in the wild. This has allowed us to fix the most important bugs first, iterate quickly on popular feature requests, and understand how our users are using the CLI.
Posthog has a first party library in Go that supports this exact functionality. First, we define a Posthog client:
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 }
Then, after initializing a new client, we can use it through the various struct methods we’ve defined. For example, when logging into the OpenSauced platform, we capture specific information on a successful login:
// 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 }
During command execution, the various “capture” functions get called to capture error paths, happy paths, etc.
For the anonymized IDs, we use Google’s excellent UUID Go library:
newUUID := uuid.New().String()
These UUIDs get stored locally on end users machines as JSON under their home directory: ~/.pizza-cli/telemtry.json. This gives the end user complete authority and autonomy to delete this telemetry data if they want (or disable telemetry altogether through configuration options!) to ensure they’re staying anonymous when using the CLI.
Our lean engineering team follows an iterative development process, focusing on delivering small, testable features rapidly. Typically, we do this through GitHub issues, pull requests, milestones, and projects. We use Go's built-in testing framework extensively, writing unit tests for individual functions and integration tests for entire commands.
Unfortunately, Go’s standard testing library doesn’t have great assertion functionality out of the box. It’s easy enough to use “==” or other operands, but most of the time, when going back and reading through tests, it’s nice to be able to eyeball what’s going on with assertions like “assert.Equal” or “assert.Nil”.
We’ve integrated the excellent testify library with its “assert” functionality to allow for smoother test implementation:
config, _, err := LoadConfig(nonExistentPath) require.Error(t, err) assert.Nil(t, config)
We heavily use Just at OpenSauced, a command runner utility, much like GNU’s “make”, for easily executing small scripts. This has enabled us to quickly onramp new team members or community members to our Go ecosystem since building and testing is as simple as “just build” or “just test”!
For example, to create a simple build utility in Just, within a justfile, we can have:
build: go build main.go -o build/pizza
Which will build a Go binary into the build/ directory. Now, building locally is as simple as executing a “just” command.
But we’ve been able to integrate more functionality into using Just and have made it a cornerstone of how our entire build, test, and development framework is executed. For example, to build a binary for the local architecture with injected build time variables (like the sha the binary was built against, the version, the date time, etc.), we can use the local environment and run extra steps in the script before executing the “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
We’ve even extended this to enable cross architecture and OS build: Go uses the GOARCH and GOOS env vars to know which CPU architecture and operating system to build against. To build other variants, we can create specific Just commands for that:
# 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}
Building the Pizza CLI using Go and Cobra has been an exciting journey and we’re thrilled to share it with you. The combination of Go's performance and simplicity with Cobra's powerful command structuring has allowed us to create a tool that's not only robust and powerful, but also user-friendly and maintainable.
We invite you to explore the Pizza CLI GitHub repository, try out the tool, and let us know your thoughts. Your feedback and contributions are invaluable as we work to make code ownership management easier for development teams everywhere!
Atas ialah kandungan terperinci Teknikal Deep Dive: Cara Kami Membina Pizza CLI Menggunakan Go dan Cobra. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!