Golang — Написание CLI-приложения на Golang с помощью Cobra

В Golang есть большое количество библиотек, которые собираются и поддерживаются сообществом. Одной из них является Cobra, которая довольно хороша для написания любых приложений. Сегодня мы попробуем посмотреть, как мы можем использовать эту библиотеку и какую функциональность она предоставляет.

Цель — понять, как написать cli приложение на Golang, используя Cobra. Итак, приложение будет выводить только строки для выполнения подкоманд*.

Гипотетические требования

Допустим, мы пытаемся создать простое приложение go cli. В нем будет 2 подкоманды check-url и check-status. Обе команды будут принимать в качестве аргумента url/api.

Структура проекта Go

Мы назовем наше приложение url-monitor, и вот как будет выглядеть вся структура папок

url-monitor
├── commands
│   ├── check-status.go
│   ├── check-url.go
│   ├── helpers.go
│   └── root.go
├── go.mod
└── main.go
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте напишем код

main.go ➡ Мы начнем с main.go, который будет находиться в пакете под названием main, и он также должен содержать функцию main{} для запуска приложения, а также import дополнительных пакетов и вложенных папок. Что-то вроде этого

package main

import "url-monitor/commands"

func main() {
   commands.Execute()
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Примечание: Не беспокойтесь о commands.Execute(), которую мы еще не определили. По сути, это вызов функции Execute() из package commands.

root.go ➡ Давайте проверим root.go, который будет содержать функцию init(), которая будет отвечать за запуск некоторых вещей, которые нам нужно выполнить в первую очередь. Подробнее о функции go init() читайте в моем предыдущем блоге.

Вот как выглядит мой root.go

package commands

import (
   "fmt"
   "github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
   Use:   "url-monitor",
   Short: "url-monitor",
   Long: `url-monitor`,
}


/*
The init function is responsible to run things
which we will require before anything else
say
  - Fetch API Keys
  - Set Logging level
  - Setup any environment variable required for the app
 */

func init() {
   rootCmd.AddCommand(checkUrlCmd, checkStatusCmd)
}

func Execute() {
   if err := rootCmd.Execute(); err != nil {
      log.Fatal(err)
   }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Позвольте мне объяснить пару вещей

Теперь у меня есть переменная rootCmd, которая имеет тип &cobra.Command{}. Тип &cobra.Command{} в основном имеет такие поля, как Use, Short и Long, которые должны быть заполнены. Это не что иное, как имя приложения cli и краткое и длинное описание приложения, которое будет отображаться при запуске приложения. Подробнее о типе команды.

var rootCmd = &cobra.Command{
   Use:   "url-monitor",
   Short: "url-monitor is used monitor api urls",
   Long: `url-monitor is used monitor api urls and some more detail stuff along with it`,
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Следующая важная вещь — функция init(), которая вызывает другой метод кобры AddCommand(). Здесь я добавил обе подкоманды, необходимые для моего приложения checkUrlsCmd и checkStatusCmd. Это просто переменные, поэтому не имеет значения, как вы их назовете. Чтобы узнать больше о функции go init(), посмотрите мой предыдущий блог.

func init() {
   rootCmd.AddCommand(checkUrlsCmd, checkStatusCmd)
}
Вход в полноэкранный режим Выход из полноэкранного режима

Наконец, у меня есть Execute(), которая, если вы помните, была вызвана в main.go, выполняя метод command.Execute() внутри него, и я проверяю наличие error в ответ.

func Execute() {
   if err := rootCmd.Execute(); err != nil {
      log.Fatal(err)
   }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь мне нужно поработать над фактическими подкомандами checkUrlCmd и checkStatusCmd, которые будут размещены в папке commands.

check-url.go ➡ Эта команда будет отвечать за проверку заданного urls/api конечной точки и возврат успешного результата при получении статуса 200. Она будет принимать urls/api в качестве аргумента для подкоманд

package commands

import (
   log "github.com/sirupsen/logrus"
   "github.com/spf13/cobra"
)

var checkUrlCmd = &cobra.Command{
   Use:   "check-url",
   Short: "check-url",
   Long:  `check-url`,
   Run: func(cmd *cobra.Command, args []string) {
      if len(args) > 1{
         log.Fatal("subcommand check-url only take one argument as url")
      }
      if _, err := url.ParseRequestURI(args[0]); err != nil {
         log.Fatalf("wrong url type [%s]", err)
      }
      err := checkUrl(args)
      if err != nil {
         log.Fatal(err)
      }
   },
}

func checkUrl(args []string) error {
   fmt.Printf("HI!! From check-url sub-command with %s as argument", args[0])
   return nil
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Здесь у меня есть переменная checkUrlCmd типа &cobra.Command{}, использующая такие поля как Use, Short и Long. Как вы видите, у нас есть еще одно поле под названием Run, которое принимает анонимную функцию в качестве значения и запускает другую функцию checkUrl(). Функция checkUrl() отвечает за выполнение всех проверок и возвращает ошибку, если таковая имеется. Запущенные функции выполняются в следующем порядке. Подробнее здесь

PersistentPreRun()
PreRun()
Run()
PostRun()
PersistentPostRun()
Вход в полноэкранный режим Выйти из полноэкранного режима

Все функции получают одинаковые args, аргументы после строки

check-status.go ➡ Эта команда будет отвечать за печать статуса полезной нагрузки url/apis на основе url/apis, предоставленного в качестве аргумента.

package commands

import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var checkStatusCmd = &cobra.Command{
   Use:   "check-status",
   Short: "check-status",
   Long:  `check-status`,
   Run: func(cmd *cobra.Command, args []string) {
      if len(args) > 1{
         log.Fatal("subcommand check-url only take one argument as url")
      }
      if _, err := url.ParseRequestURI(args[0]); err != nil {
         log.Fatalf("wrong url type [%s]", err)
      }
      err := checkStatus(args)
      if err != nil {
         log.Fatal(err)
      }
   },
}

func checkStatus(args []string) error {
   fmt.Printf("HI!! From check-status sub-command with %s as argument", args[0])
   return nil
}
Вход в полноэкранный режим Выход из полноэкранного режима
Теперь давайте соберем все зависимые пакеты, выполнив go mod init, а затем go mod tidy.
>> go mod init url-monitor
go: creating new go.mod: module url-monitor
go: to add module requirements and sums: go mod tidy

>> go mod tidy
go: finding module for package github.com/spf13/cobra
go: finding module for package github.com/sirupsen/logrus
go: found github.com/sirupsen/logrus in github.com/sirupsen/logrus v1.8.1
go: found github.com/spf13/cobra in github.com/spf13/cobra v1.4.0
go: downloading github.com/inconshreveable/mousetrap v1.0.0
go: downloading github.com/stretchr/testify v1.2.2
go: downloading github.com/pmezard/go-difflib v1.0.0
Вход в полноэкранный режим Выйти из полноэкранного режима

Круто!!! Мы готовы к сборке приложения, запустив go build . с папкой url-monitor. Это должно сгенерировать бинарный файл с именем url-monitor.

url-monitor
├── commands
│   ├── check-status.go
│   ├── check-urls.go
│   ├── helpers.go
│   └── root.go
├── go.mod
├── go.sum
├── main.go
└── url-monitor                                   // the binary
Войдите в полноэкранный режим Выйти из полноэкранного режима

Попробуем запустить бинарный файл url-monitor и посмотрим, что произойдет

>> ./url-monitor
url-monitor

Usage:
  url-monitor [command]

Available Commands:
  check-status check-status
  check-urls   check-urls
  completion   Generate the autocompletion script for the specified shell
  help         Help about any command

Flags:
  -h, --help   help for url-monitor

Use "url-monitor [command] --help" for more information about a command.
Вход в полноэкранный режим Выход из полноэкранного режима
Запустим первую подкоманду check-url
>> ./url-monitor check-urls https://example.com/apis/v1/get-status

HI!! From check-urls sub-command with https://example.com/apis/v1/get-status as argument
Войти в полноэкранный режим Выход из полноэкранного режима
Запустите вторую подкоманду check-status.
>> ./url-monitor check-status https://example.com/apis/v1/get-status

HI!! From check-status sub-command with https://example.com/apis/v1/get-status as argument
Вход в полноэкранный режим Выйти из полноэкранного режима
Попробуем использовать неверный URL
>> ./url-monitor check-status example.com/apis/v1/get-status

FATL 2022-03-25 12:43:07 wrong url type [parse "example.com/apis/v1/get-status": invalid URI for request]
Войти в полноэкранный режим Выйти из полноэкранного режима

Вот несколько проектов, где Cobra была использована Github CLI, Docker (дистрибутив), Etcd, GoReleaser, Helm, Kubernetes и т.д. Весь список можно найти здесь

Существуют и альтернативы Cobra, например, mitchellh/cli, go-flags, urfave/cli и т.д.

Надеюсь, это дает представление о пакете Cobra и о том, как его использовать.

Счастливого кодинга!!!

Оцените статью
devanswers.ru
Добавить комментарий