"일꾼이 일을 잘하려면 먼저 도구를 갈고 닦아야 한다." - 공자, 『논어』.
첫 장 > 프로그램 작성 > 기술 심층 분석: Go 및 Cobra를 사용하여 Pizza CLI를 구축한 방법

기술 심층 분석: Go 및 Cobra를 사용하여 Pizza CLI를 구축한 방법

2024-11-01에 게시됨
검색:736

Technical Deep Dive: How We Built the Pizza CLI Using Go and Cobra

지난주 OpenSauced 엔지니어링 팀은 CODEOWNER 파일을 생성하고 OpenSauced 플랫폼과 통합하기 위한 강력하고 구성 가능한 명령줄 도구인 Pizza CLI를 출시했습니다. 강력한 명령줄 도구를 구축하는 것은 간단해 보일 수 있지만 신중한 계획과 사려 깊은 패러다임이 없으면 CLI는 유지 관리가 어렵고 버그로 가득 찬 엉킨 코드가 될 수 있습니다. 이 블로그 게시물에서는 Go를 사용하여 이 CLI를 구축한 방법, Cobra를 사용하여 명령을 구성하는 방법, 린 엔지니어링 팀이 강력한 기능을 구축하기 위해 빠르게 반복하는 방법에 대해 자세히 살펴보겠습니다.

Go와 Cobra 사용

Pizza CLI는 여러 표준 라이브러리를 활용하는 Go 명령줄 도구입니다. Go의 단순성, 속도 및 시스템 프로그래밍 초점은 CLI 구축에 이상적인 선택입니다. 핵심적으로 Pizza-CLI는 Go의 CLI 부트스트래핑 라이브러리인 spf13/cobra를 사용하여 전체 명령 트리를 구성하고 관리합니다.

Cobra는 명령줄 인터페이스 자체를 작동시키고, 모든 플래그가 일관되게 작동하도록 하며, 도움말 메시지와 자동화된 문서를 통해 사용자와의 통신을 처리하는 스캐폴딩으로 생각할 수 있습니다.

코드베이스 구조화

Cobra 기반 Go CLI를 구축할 때 가장 먼저(그리고 가장 큰) 과제 중 하나는 모든 코드와 파일을 구조화하는 방법입니다. 대중적인 믿음과는 달리, Go에는 이 작업을 수행하는 규정된 방법이 없습니다. go build 명령이나 gofmt 유틸리티는 패키지 이름 지정이나 디렉터리 구성 방법에 대해 불평하지 않습니다. 이것이 Go의 가장 좋은 부분 중 하나입니다. Go의 단순성과 강력함 덕분에 귀하와 귀하의 엔지니어링 팀에 적합한 구조를 쉽게 정의할 수 있습니다!

궁극적으로 내 생각에는 Cobra 기반 Go 코드베이스를 명령 트리로 생각하고 구성하는 것이 가장 좋습니다.

├── Root command
│   ├── Child command
│   ├── Child command
│   │   └── Grandchild command

트리의 기본에는 루트 명령이 있습니다. 이는 전체 CLI 애플리케이션의 앵커이며 CLI 이름을 가져옵니다. 하위 명령으로 연결하면 전체 CLI 흐름이 작동하는 방식의 구조를 알려주는 분기 논리 트리가 제공됩니다.

CLI를 구축할 때 놓치기 쉬운 것 중 하나는 사용자 경험입니다. 나는 일반적으로 사람들이 명령과 하위 명령 구조를 작성할 때 "루트 동사 명사" 패러다임을 따르는 것을 권장합니다. 왜냐하면 그것이 논리적으로 흐르고 뛰어난 사용자 경험으로 이어지기 때문입니다.

예를 들어 Kubectl에서는 "kubectl get pods", "kubectl apply …" 또는 "kubectl label pods …"와 같은 패러다임을 어디서나 볼 수 있습니다. 이는 사용자가 명령줄과 상호 작용하는 방식에 대한 감각적인 흐름을 보장합니다. 응용 프로그램을 사용하면 다른 사람과 명령에 대해 이야기할 때 많은 도움이 됩니다.

결국 이 구조와 제안은 파일과 디렉터리를 구성하는 방법을 알려줄 수 있지만 CLI를 구성하고 최종 사용자에게 흐름을 제시하는 방법을 결정하는 것은 궁극적으로 사용자에게 달려 있습니다.

Pizza CLI에는 하위 명령(및 해당 하위 명령의 후속 손자)이 존재하는 잘 정의된 구조가 있습니다. 자체 패키지의 cmd 디렉터리 아래에서 각 명령은 자체적으로 구현됩니다. 루트 명령 스캐폴딩은 pkg/utils 디렉토리에 존재합니다. 왜냐하면 루트 명령을 유지 관리가 많이 필요한 명령이 아니라 main.go에서 사용하는 최상위 유틸리티로 생각하는 것이 유용하기 때문입니다. 일반적으로 루트 명령 Go 구현에는 많이 건드리지 않는 상용구 설정 항목이 많이 있으므로 해당 항목을 방해하지 않는 것이 좋습니다.

다음은 디렉토리 구조를 단순화한 보기입니다.

├── main.go
├── pkg/
│   ├── utils/
│   │   └── root.go
├── cmd/
│   ├── Child command dir
│   ├── Child command dir
│   │   └── Grandchild command dir

이 구조를 사용하면 문제를 명확하게 분리할 수 있으며 CLI가 성장하고 더 많은 명령을 추가함에 따라 CLI를 더 쉽게 유지 관리하고 확장할 수 있습니다.

go-git 사용하기

Pizza-CLI에서 사용하는 주요 라이브러리 중 하나는 확장성이 뛰어난 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 원격 측정 통합

저희 엔지니어링 및 제품 팀은 최종 사용자에게 가능한 최고의 명령줄 환경을 제공하기 위해 많은 투자를 하고 있습니다. 이는 실제 사용 및 오류에 대해 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를 사용하여 피자 CLI를 구축하는 것은 흥미진진한 여정이었으며 이를 여러분과 공유하게 되어 기쁩니다. Go의 성능 및 단순성과 Cobra의 강력한 명령 구조의 결합을 통해 우리는 강력하고 강력할 뿐만 아니라 사용자 친화적이고 유지 관리가 가능한 도구를 만들 수 있었습니다.

Pizza CLI GitHub 저장소를 탐색하고 도구를 사용해 본 후 의견을 알려주시기 바랍니다. 우리는 전 세계 개발팀의 코드 소유권 관리를 더 쉽게 만들기 위해 노력하고 있으므로 귀하의 피드백과 기여는 매우 중요합니다!

릴리스 선언문 이 글은 https://dev.to/opensauced/technical-deep-dive-how-we-build-the-pizza-cli-using-go-and-cobra-oad?1 에서 재현됩니다. 침해가 있는 경우 , [email protected]로 문의해주세요.
최신 튜토리얼 더>

부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.

Copyright© 2022 湘ICP备2022001581号-3