La semana pasada, el equipo de ingeniería de OpenSauced lanzó Pizza CLI, una herramienta de línea de comandos poderosa y componible para generar archivos CODEOWNER e integrarlos con la plataforma OpenSauced. Crear herramientas sólidas de línea de comandos puede parecer sencillo, pero sin una planificación cuidadosa y paradigmas bien pensados, las CLI pueden convertirse rápidamente en una maraña de código difícil de mantener y plagada de errores. En esta publicación de blog, profundizaremos en cómo creamos esta CLI usando Go, cómo organizamos nuestros comandos usando Cobra y cómo nuestro equipo de ingeniería eficiente itera rápidamente para crear una funcionalidad poderosa.
Pizza CLI es una herramienta de línea de comandos de Go que aprovecha varias bibliotecas estándar. La simplicidad, la velocidad y el enfoque en la programación de sistemas de Go lo convierten en una opción ideal para crear CLI. En esencia, Pizza-CLI utiliza spf13/cobra, una biblioteca de arranque CLI en Go, para organizar y administrar todo el árbol de comandos.
Puedes pensar en Cobra como el andamio que hace que una interfaz de línea de comandos funcione, permite que todos los indicadores funcionen de manera consistente y maneja la comunicación con los usuarios a través de mensajes de ayuda y documentación automatizada.
Uno de los primeros (y mayores) desafíos al crear una Go CLI basada en Cobra es cómo estructurar todo el código y los archivos. Contrariamente a la creencia popular, no existe ninguna forma prescrita de hacer esto en Go. Ni el comando go build ni la utilidad gofmt se quejarán de cómo nombras tus paquetes u organizas tus directorios. Esta es una de las mejores partes de Go: su simplicidad y potencia facilitan la definición de estructuras que funcionen para usted y su equipo de ingeniería.
En última instancia, en mi opinión, es mejor pensar y estructurar un código base de Go basado en Cobra como un árbol de comandos:
├── Root command │ ├── Child command │ ├── Child command │ │ └── Grandchild command
En la base del árbol está el comando raíz: este es el ancla para toda su aplicación CLI y obtendrá el nombre de su CLI. Adjunto como comandos secundarios, tendrá un árbol de lógica de ramificación que informa la estructura de cómo funciona todo el flujo CLI.
Una de las cosas que es increíblemente fácil pasar por alto al crear CLI es la experiencia del usuario. Normalmente recomiendo a las personas que sigan un paradigma de "sustantivo verbo raíz" al crear comandos y estructuras de comandos secundarios, ya que fluye de manera lógica y conduce a excelentes experiencias de usuario.
Por ejemplo, en Kubectl, verá este paradigma en todas partes: “kubectl get pods”, “kubectl apply…” o “kubectl label pods…”. Esto garantiza un flujo sensato sobre cómo los usuarios interactuarán con su línea de comando. aplicación y ayuda mucho cuando se habla de comandos con otras personas.
Al final, esta estructura y sugerencia pueden informarle cómo organizar sus archivos y directorios, pero nuevamente, en última instancia, depende de usted determinar cómo estructura su CLI y presentar el flujo a los usuarios finales.
En Pizza CLI, tenemos una estructura bien definida donde viven los comandos secundarios (y los nietos posteriores de esos comandos secundarios). En el directorio cmd de sus propios paquetes, cada comando tiene su propia implementación. La estructura del comando raíz existe en un directorio pkg/utils ya que es útil pensar en el comando raíz como una utilidad de nivel superior utilizada por main.go, en lugar de un comando que podría necesitar mucho mantenimiento. Por lo general, en la implementación de Go del comando raíz, tendrás muchas configuraciones estándar que no tocarás mucho, por lo que es bueno eliminarlas.
Aquí hay una vista simplificada de nuestra estructura de directorios:
├── main.go ├── pkg/ │ ├── utils/ │ │ └── root.go ├── cmd/ │ ├── Child command dir │ ├── Child command dir │ │ └── Grandchild command dir
Esta estructura permite una separación clara de las preocupaciones y facilita el mantenimiento y la ampliación de la CLI a medida que crece y agregamos más comandos.
Una de las principales bibliotecas que utilizamos en Pizza-CLI es la biblioteca go-git, una implementación pura de git en Go que es altamente extensible. Durante la generación de CODEOWNERS, esta biblioteca nos permite iterar el registro de referencia de git, observar las diferencias de código y determinar qué autores de git están asociados con las atribuciones configuradas definidas por un usuario.
Iterar el registro de referencia de git de un repositorio de git local es en realidad bastante simple:
// 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") }
Si estás creando una aplicación basada en Git, definitivamente recomiendo usar go-git: ¡es rápido, se integra bien con el ecosistema Go y se puede usar para hacer todo tipo de cosas!
Nuestro equipo de ingeniería y producto está profundamente comprometido en brindar la mejor experiencia de línea de comando posible a nuestros usuarios finales: esto significa que hemos tomado medidas para integrar telemetría anónima que puede informar a Posthog sobre el uso y los errores en la naturaleza. Esto nos ha permitido corregir primero los errores más importantes, iterar rápidamente en solicitudes de funciones populares y comprender cómo nuestros usuarios utilizan la CLI.
Posthog tiene una biblioteca propia en Go que admite exactamente esta funcionalidad. Primero, definimos un cliente 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 }
Luego, después de inicializar un nuevo cliente, podemos usarlo a través de los diversos métodos de estructura que hemos definido. Por ejemplo, al iniciar sesión en la plataforma OpenSauced, capturamos información específica sobre un inicio de sesión exitoso:
// 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 }
Durante la ejecución del comando, se llama a las diversas funciones de “captura” para capturar rutas de error, rutas felices, etc.
Para las identificaciones anónimas, utilizamos la excelente biblioteca UUID Go de Google:
newUUID := uuid.New().String()
Estos UUID se almacenan localmente en las máquinas de los usuarios finales como JSON en su directorio de inicio: ~/.pizza-cli/telemtry.json. Esto le otorga al usuario final total autoridad y autonomía para eliminar estos datos de telemetría si lo desea (¡o deshabilitar la telemetría por completo a través de las opciones de configuración!) para garantizar que permanezca en el anonimato cuando use la CLI.
Nuestro equipo de ingeniería eficiente sigue un proceso de desarrollo iterativo, centrándose en ofrecer funciones pequeñas y comprobables rápidamente. Normalmente, hacemos esto a través de problemas, solicitudes de extracción, hitos y proyectos de GitHub. Utilizamos ampliamente el marco de pruebas integrado de Go, escribiendo pruebas unitarias para funciones individuales y pruebas de integración para comandos completos.
Desafortunadamente, la biblioteca de pruebas estándar de Go no tiene una excelente funcionalidad de aserción lista para usar. Es bastante fácil usar “==” u otros operandos, pero la mayoría de las veces, al retroceder y leer las pruebas, es bueno poder observar lo que sucede con afirmaciones como “assert.Equal” o “assert.Nil ”.
Hemos integrado la excelente biblioteca testify con su funcionalidad de "afirmación" para permitir una implementación de pruebas más fluida:
config, _, err := LoadConfig(nonExistentPath) require.Error(t, err) assert.Nil(t, config)
Utilizamos mucho Just en OpenSauced, una utilidad de ejecución de comandos, muy parecida a “make” de GNU, para ejecutar fácilmente pequeños scripts. Esto nos ha permitido incorporar rápidamente nuevos miembros del equipo o miembros de la comunidad a nuestro ecosistema Go, ya que construir y probar es tan simple como "simplemente construir" o "simplemente probar".
Por ejemplo, para crear una utilidad de compilación simple en Just, dentro de un archivo just, podemos tener:
build: go build main.go -o build/pizza
Lo que creará un binario de Go en el directorio build/. Ahora, construir localmente es tan simple como ejecutar un comando "simple".
Pero hemos podido integrar más funcionalidades en el uso de Just y lo hemos convertido en la piedra angular de cómo se ejecuta todo nuestro marco de construcción, prueba y desarrollo. Por ejemplo, para crear un binario para la arquitectura local con variables de tiempo de compilación inyectadas (como el sha con el que se creó el binario, la versión, la fecha y hora, etc.), podemos usar el entorno local y ejecutar pasos adicionales en el script. antes de ejecutar el “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
Incluso hemos ampliado esto para permitir la arquitectura cruzada y la compilación del sistema operativo: Go usa las variables de entorno GOARCH y GOOS para saber con qué arquitectura de CPU y sistema operativo construir. Para construir otras variantes, podemos crear comandos Just específicos para eso:
# 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}
Crear Pizza CLI con Go y Cobra ha sido un viaje emocionante y estamos encantados de compartirlo con usted. La combinación del rendimiento y la simplicidad de Go con la poderosa estructura de comandos de Cobra nos ha permitido crear una herramienta que no solo es robusta y poderosa, sino también fácil de usar y fácil de mantener.
Lo invitamos a explorar el repositorio GitHub de Pizza CLI, probar la herramienta y contarnos su opinión. ¡Sus comentarios y contribuciones son invaluables mientras trabajamos para facilitar la administración de la propiedad del código para los equipos de desarrollo en todas partes!
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3