zsh 프롬프트에 pulumi stack 출력하기

SDuck·2021년 5월 22일
0
post-thumbnail

Pulumi를 도입하며

최근 회사에서 Pulumi를 사용하게 됐다. Pulumi는 IaC(Infrastructure as Code) 툴인데, HCL이라는 고유 언어를 사용하는 Terraform과는 다르게 범용 프로그래밍 언어(TypeScript, Python, Go 등)를 사용한다. 때문에 IaC를 처음 접하는 나 같은 사람에게 러닝 커브가 상대적으로 완만하다는 장점이 있어 도입했다.

Pulumi는 리소스의 그룹을 스택(stack)이라는 개념으로 표현한다. Pulumi에서는 여러 리소스를 사용하는 목적과 환경에 따라 여러 스택으로 나누어 관리하는 방법을 권장하는데, 이런 방법을 마이크로 스택(Micrro-Stacks)이라고 한다. 마이크로 스택을 사용하면 관리해야 하는 스택이 많아지고, 스택을 변경할 일이 잦아진다. 실제 업무를 할 때에도 프로젝트 디렉터리와 스택을 햇갈려 엉뚱한 스택에 변경사항을 적용을 시도하는 일이 종종 발생했다.

이런 실수를 예방할 방법을 생각해보다 zsh 프롬프트에서 git 브랜치 같은 정보들이 표시되는 것이 생각났다. 현재 선택중인 스택을 프롬프트에 표시하면 명령어를 칠 때 스택을 눈으로 확인할 수 있기 때문에 실수를 줄일 수 있을 것 같았다.

zsh과 powerlevel10k

현재 사용하는 Mac에 개발 환경을 셋팅할 때 @subicura님의 본격 macOS에 개발 환경 구축하기를 보고 했던 기억이 났다. 지금 사용하는 zsh 테마가 powerlevel10k(p10k)라는 것을 확인하고, 관련 문서를 찾아보았다.

p10k GitHub의 README 문서를 보니 프롬프트를 구성하는 요소들을 세그먼트(segment)라고 부르는데, 위 그림에서 git 정보를 출력하는 것, 현재 시각을 출력하는 것 모두 세그먼트였다.

p10k 설정파일(~/.p10k.zsh)에 있는 예시를 참고해 Pulumi 스택을 출력하는 세그먼트를 만들어서 추가해보기로 했다.

pulumistack 세그먼트 만들기

v1. pulumi stack 결과 출력하기

현재 스택 이름을 확인하는 명령어 pulumi stack --show-name의 출력을 세그먼트로 추가하는 함수 prompt_pulumistack()를 p10k 설정파일에 추가했다.

function prompt_pulumistack() {
  local stack_name=$(pulumi stack --show-name)
  if [ ! -z "${stack_name}" ]; then
    p10k segment -i '⏣' -f 13 -t ${stack_name}
  fi
}

프롬프트 오른쪽에 표시되도록 POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS를 수정해 pulumistack 세그먼트를 추가했다.

typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(
  ...
  pulumistack
  ...
)

쉘을 재실행하니 아래와 같은 에러가 발생했다.

[WARNING]: Console output during zsh initialization detected.

When using Powerlevel10k with instant prompt, console output during zsh
initialization may indicate issues.

...

-- console output produced during zsh initialization follows --

error: no Pulumi.yaml project file found (searching upwards from /Users/sduck). If you have not created a project yet, use `pulumi new` to do so

zsh 초기화 중 콘솔 출력이 발생했다는 경고였다. 출력을 확인해보니, 현재 Pulumi 프로젝트 디렉터리가 아닐 때 발생하는 메시지였다. 쉘이 처음 실행되는 홈 디렉터리에서 세그먼트 함수가 실행되어 발생한 오류였다.

이 문제는 명령어를 실행할 때 stderr(2)/dev/null로 리디렉션해서 해결했다.

function prompt_pulumistack() {
  local stack_name=$(pulumi stack --show-name 2>/dev/null)
  if [ ! -z "${stack_name}" ]; then
    p10k segment -i '⏣' -f 13 -t ${stack_name}
  fi
}

이제 pulumi stack의 에러를 콘솔에 출력하지 않기 때문에 zsh 에러는 발생하지 않았다. 다만 프롬프트가 실행될 때 2~3초 정도 딜레이가 생긴게 느껴졌다. 세그먼트 함수는 Pulumi 프로젝트 디렉터리가 아닌 경우에도 항상 실행되기 때문에 발생하는 문제였다.

v2. Pulumi 프로젝트 디렉터리 구분하기

pulumistack 세그먼트는 Pulumi 프로젝트 디렉터리가 아닌 다른 디렉터리에서는 사용하지 않는 세그먼트다. 때문에 현재 작업 디렉터리가 Pulumi 프로젝트 디렉터리인 경우에만 pulumistack 세그먼트를 추가해주면 됐다.

Pulumi 프로젝트 디렉터리는 Pulumi.yml 파일을 기준으로 구분한다. 따라서 이 파일이 존재하지 않으면 return하는 로직을 추가했다.

function prompt_pulumistack() {
  # Check working directory is a pulumi project
  if [ ! -f "./Pulumi.yml" ]; then
    return
  fi

  # Print stack name
  local stack_name=$(pulumi stack --show-name 2>/dev/null)
  if [ ! -z "${stack_name}" ]; then
    p10k segment -i '⏣' -f 13 -t ${stack_name}
  fi
}

Pulumi 프로젝트 디렉터리가 아닌 경우에는 프롬프트가 기존 처럼 빠르게 실행됐지만, Pulumi 프로젝트 디렉터리에서는 여전히 느리게 실행됐다.

v3. workspace에서 현재 스택 이름 가져오기

문제는 pulumi stack 명령어가 생각보다 느리다는 것이다. 이 명령어는 Pulumi 백엔드와 통신하는 부분이 있는데, 리모트 백엔드를 사용하면 네트워크를 통해 통신하는 과정에서 어쩔 수 없는 지연이 생겼다. 2~3초 정도의 시간은 그리 길지 않아 보이지만, 프롬프트는 매우 자주 갱신되므로 이런 지연 시간은 치명적이었다.

지연 시간을 줄이거나 없애는 방법을 찾기 위해 pulumi stack 문서를 다시 보니, workspace라는 단어가 눈에 띄었다.

Each stack has a configuration and update history associated with it, stored in the workspace, ...

Pulumi 로컬 디렉터리(~/.pulumi)를 확인해보니 workspaces 디렉터리가 있었고, 그 안에는 아래와 같은 패턴의 파일들이 있었다.

project-3700978a2cfd95d15a2f7a75c9b4d08d09bb4d55-workspace.json

파일의 내용을 확인해보니 현재 스택 이름을 포함하고 있는 간단한 파일이었다.

{
  "stack": "sduck.project.dev"
}

이 파일을 파싱하면 pulumi stack 명령어를 실행하지 않아도 마지막으로 선택한 스택을 알 수 있었다. 문제는 이 파일의 이름 규칙이었는데, Pulumi GitHub 저장소에서 workspace로 검색해서 관련 규칙을 찾았다.

uniqueFileName := string(pw.name) + "-" + sha1HexString(pw.project) + "-" + WorkspaceFile

workspace 파일 이름 규칙은 다음과 같았다.

[프로젝트 이름]-[프로젝트 파일 절대경로의 SHA-1 해시]-workspace.json

이 규칙을 활용해 pulumi stack 명령어를 실행하는 대신, workspace.json 파일이 존재하는지 확인하고, 파일 내용을 파싱해 현재 스택 이름을 가져오도록 수정했다.

function prompt_pulumistack() {
  # Check working directory is a pulumi project
  if [ ! -f "./Pulumi.yml" ]; then
    return
  fi

  # Check pulumi workspace file exist
  local pjt_name=$(basename $PWD)
  local pjt_hash=$(echo -n $PWD/Pulumi.yml | sha1sum | head -c 40)
  local ws_path="$HOME/.pulumi/workspaces/${pjt_name}-${pjt_hash}-workspace.json"
  if [ ! -f ${ws_path} ]; then
    return
  fi

  # Print stack name
  local stack_name=$(cat ${ws_path} | jq ".stack" -r)
  if [ ! -z "${stack_name}" ]; then
    p10k segment -i '⏣' -f 13 -t ${stack_name}
  fi
}

v4. pulumi 명령어 사용할 때만 표시하기

Pulumi CLI를 사용하지 않을 때는 굳이 표시되어 프롬프트 공간을 차지할 이유가 없으므로, pulumi 명령어를 사용할 때만 표시되도록 스크립트를 추가했다.

typeset -g POWERLEVEL9K_PULUMISTACK_SHOW_ON_COMMAND='pulumi'

이제 pulumistack 세그먼트는 아래와 같이 pulumi 명령어를 사용할 때만 표시된다.

v5. Pulumi 프로젝트 파일 확장자 처리하기

일부 Pulumi 프로젝트 디렉터리에서 pulumistack 세그먼트가 표시되지 않아 확인해보니, Pulumi 프로젝트 파일의 확장자가 .yml이 아닌 .yaml 이었다. Pulumi 프로젝트 파일 문서에서는 프로젝트 파일 이름의 규칙을 다음과 같이 정의했다.

The project file must begin with a capitalized P, although either .yml or .yaml extension will work.

현재 코드에서는 Pulumi.yml 파일만 확인하고 있어 이 부분을 수정했다.

다음과 같이 ext에 확장자를 저장하고,

# Check working directory is a pulumi project
local ext
if [ -f "./Pulumi.yaml" ]; then
  ext="yaml"
elif [ -f "./Pulumi.yml" ]; then
  ext="yml"
else
  return
fi

pjt_hash에서 Pulumi 프로젝트 파일의 경로를 설정할 때 ext를 활용하도록 수정했다.

# Before
local pjt_hash=$(echo -n $PWD/Pulumi.yml | sha1sum | head -c 40)

# After
local pjt_hash=$(echo -n $PWD/Pulumi.${ext} | sha1sum | head -c 40)

소스코드

https://github.com/SDuck4/settings/blob/main/p10k/pulumistack.zsh

끝으로

본격적으로 쉘 스크립트를 써본 것은 처음이라 조금 어려웠지만, 여러 명령어를 조합해서 새로운 결과를 만드는 것이 재미있었다. 그리고 이제 pulumistack 세그먼트를 활용해 Pulumi를 사용할 때 실수를 줄일 수 있게 되어 안심이 된다.

항상 velog에 글 써야지 생각만 하다가, 처음으로 글을 써서 조금 떨린다. 앞으로 좋은 글 많이 읽고, 글 쓰는 연습도 해야겠다. 부족한 글이지만 p10k 세그먼트를 만들게 되는 누군가에게 도움이 되었으면 좋겠다.


참고

profile
a.k.a. 승덕 / 클라우드&인프라 병아리

0개의 댓글