A브랜치에 변경사항이 생기면 A브랜치를 Base로 하는 B브랜치에도 변경사항을 반영하고, A브랜치의 env를 수정하여 B브랜치에 맞는 env를 설정한 후 Vercel로 Deploy하는 방법이 없을까에 대한 해결방안입니다.
github action을 사용해서, main브랜치에 push 이벤트가 발생하면 아래 yml파일이 실행됩니다.
name: Sync Main To Main With Static IP
on:
push:
branches:
- main
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 0 // <= 필수, fetch-depth의 default 값은 1인데, 이는 1개의 commit history만 가져온다는 뜻.
// 그래서 auto merge를 할 수 없어 conflict 해결이 안된다.
// value에 0 을 주어야 모든 커밋 히스토리를 가져올 수 있고 auto mege를 문제없이 수행할 수 있음.
- name: Configure git
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com'
- name: Fetch all branches
run: git fetch origin
- name: Sync changes to target branch
id: sync
run: |
git checkout -b main-with-static-ip origin/main || git checkout main-with-static-ip
git pull origin main
git push origin main-with-static-ip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
이러면 main 브랜치에 push 이벤트가 발생했을 때, github action이 실행되며 main-with-static-ip 브랜치가 없으면 새로 만들고, 있다면 체크아웃 한 후 main에 있는 코드들을 pull 하고 push하게 되고, vercel을 통해 배포하게 됩니다 .
문제는, main에 있는 env 값들이 변경될 때(업데이트노트, 버전, 공지사항, 공사중 시간과 onoff, 사용자 가이드, api 주소… 등등…) 마다 일일이 env를 업데이트 해줘야하고, main-with-static-ip만의 env 가 있는데 이것도 따로 변경해줘야합니다.
이러다보면 분명 몇개의 키값들을 놓치는 불상사가 발생할 수 있습니다.
그래서 고안해낸 방법은 vercel cli 와 github action의 event중 하나인 deployment_status.state를 사용하는 방법입니다.
name: Sync Branches and Vercel Environments
on:
deployment_status:
jobs:
sync:
// deploy state가 in-progress 인 경우에만 아래 스텝들을 실행합니다.
// 상태리스트 : error, failure, inactive, in_progress, queued, pending, success
if: github.event.deployment_status.state == 'in-progress'
runs-on: ubuntu-latest
env: // 변수를 할당해줍니다.
SOURCE_BRANCH: test-deploy-1
TARGET_BRANCH: test-deploy-2
steps:
// 만약, SOURCE_BRANCH에서 발생한 이벤트가 아닐 경우 exit 합니다.
- name: Check if source branch was deployed
id: check_branch
run: |
if [[ "${{ github.ref }}" != "refs/heads/${{ env.SOURCE_BRANCH }}" ]]; then
echo "This deployment is not for the source branch. Exiting."
exit 0
fi
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Configure git
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com'
- name: Fetch all branches
run: git fetch origin
- name: Export Vercel project environment variables from ${{ env.SOURCE_BRANCH }}
// vercel env pull .env.local --environment=production 을 사용하면 production에 있는 모든 env를 env.local 파일에 저장하게 됩니다.
run: vercel env pull .env.local --environment=production --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
GIT_BRANCH: ${{ env.SOURCE_BRANCH }}
- name: Remove existing environment variables from target branch
run: |
// 현재 브랜치에 저장되어 있는 env 값들을 모두 지웁니다.
existing_vars=$(vercel env ls preview ${{ env.TARGET_BRANCH }} --token=${{ secrets.VERCEL_TOKEN }} | awk '/Encrypted/ {print $1}')
echo "Existing vars: $existing_vars"
for var in $existing_vars; do
echo "Removing $var from Vercel environment for branch ${{ env.TARGET_BRANCH }}"
vercel env rm $var preview ${{ env.TARGET_BRANCH }} --yes --token=${{ secrets.VERCEL_TOKEN }}
done
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
GIT_BRANCH: ${{ env.TARGET_BRANCH }}
- name: Apply environment variables to target branch (excluding VERCEL_ and comments)
run: |
// env.local에 있는 모든 값들을 현재 브랜치의 env로 등록합니다. 만약, value에 '/n'이나 빈 값이 들어 있다면 '' 으로 대체합니다.
// env의 value 중 https://files.~ 는 https://static-files. 로 , https://api. 는 https://static-api. 로 변경합니다.
while IFS='=' read -r name value; do
if [[ ! $name =~ ^# && ! $name =~ ^VERCEL_ ]]; then
# Remove surrounding quotes and newlines
sanitized_value=$(echo $value | sed -e 's/^"//' -e 's/"$//' -e 's/\\n//g')
# Replace specific patterns
sanitized_value=$(echo $sanitized_value | sed -e 's|https://files.|https://static-files.|g' -e 's|https://api.|https://static-api.|g')
echo "Adding $name with value $sanitized_value to Vercel environment for branch ${{ env.TARGET_BRANCH }}"
printf '%s' "$sanitized_value" | vercel env add $name preview ${{ env.TARGET_BRANCH }} --token=${{ secrets.VERCEL_TOKEN }}
fi
done < .env.local
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
GIT_BRANCH: ${{ env.TARGET_BRANCH }}
- name: Sync changes to target branch
// 체크아웃 후 source branch를 pull 하고, 강제 push로 마무리합니다.
id: sync
run: |
git checkout -b ${{ env.TARGET_BRANCH }} origin/${{ env.TARGET_BRANCH }} || git checkout ${{ env.TARGET_BRANCH }}
git pull origin ${{ env.SOURCE_BRANCH }}
git push origin ${{ env.TARGET_BRANCH }} -f
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
이대로하면 어떤 브랜치는 잘 되고 어떤 브랜치는 잘 안됩니다.. 이유는 push이벤트가 아닌 redeploy같은 경우 vercel에서 github action으로 event를 넘겨주어 실행하게 되는데 이 때 브랜치 정보가 어디에도 담겨있지 않기 때문입니다.
그래서 아래같이 vercel inspect 를 통해 현재 배포되고 있는 url에 대한 정보를 받아오고, Aliases에 env.REQUIRED_ALIAS 와 동일한 값이 있다면 해당 배포로 인식하고 다음 스텝을 이어가게 됩니다. (vercel 명령어중 inspect만 특이하게 2>&1 인 표준에러 출력을 사용하고 있습니다.. 그래서 다른 명령어 처럼 vercel 명령어 > result.txt 인 표준 출력을 사용할 수 없습니다. 참 희안하죠 이것때문에 삽질 엄청 했습니다.)
name: Sync code&env from main -> main-with-static-ip
on:
deployment_status:
jobs:
sync:
if: github.event.deployment_status.state == 'success' // in-progress는 없는 status 입니다. success를 사용해야합니다
runs-on: ubuntu-latest
env:
SOURCE_BRANCH: main
TARGET_BRANCH: main-with-static-ip
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REQUIRED_ALIAS: https://memopatch.care
steps:
- name: Check deployment info and alias
run: |
DEPLOYMENT_INFO=$(vercel inspect ${{ github.event.deployment_status.target_url }} --scope=huinno --token=${{ secrets.VERCEL_TOKEN }} 2>&1)
echo "Deployment Info:"
echo "$DEPLOYMENT_INFO"
// vercel inspect를 통해 현재 배포하고 있는 브랜치 정보를 확인합니다. (main 브랜치에서 발생한 deploy 인지 확인)
// 만약 main 브랜치에서 발생한 액션이 아니라면 exit 1로 action을 종료합니다.
if echo "$DEPLOYMENT_INFO" | grep -q "${{ env.REQUIRED_ALIAS }}"; then
echo "Required alias ${{ env.REQUIRED_ALIAS }} found. Proceeding with sync."
else
echo "Required alias ${{ env.REQUIRED_ALIAS }} not found. Exiting."
exit 1
fi
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Configure git
run: |
git config --global user.name 'Huinno-ParkJinHyun' // vercel에 유료사용자로 등록되어 있는 계정으로 커밋을 생성해야
git config --global user.email 'jinhyunpark@huinno.com' // vercel deploy 가 정상적으로 작동합니다.
- name: Fetch all branches
run: git fetch origin
- name: Create or update target branch
run: |
// 만약 env를 등록하려는 branch 가 remote에 없다면 ' Remove existing environment variables from target branch' action 진행 시 에러가 발생합니다.
// 아래 명령어로 remote에 해당 브랜치가 있는지 확인하고, 없다면 새로 만들어서 푸시해줍니다.
if ! git ls-remote --exit-code --heads origin ${TARGET_BRANCH}; then
git checkout -b ${{ env.TARGET_BRANCH }}
git push -u origin ${{ env.TARGET_BRANCH }}
fi
- name: Export Vercel project environment variables from ${{ env.SOURCE_BRANCH }}
// production version의 env들을 .env.local 파일에 담습니다.
run: vercel env pull .env.local --environment=production --token=${{ env.VERCEL_TOKEN }}
- name: Remove existing environment variables from target branch
run: |
// TARGET_BRANCH에 등록되어 있는 모든 env를 제거합니다.
existing_vars=$(vercel env ls preview ${{ env.TARGET_BRANCH }} --token=${{ env.VERCEL_TOKEN }} | awk '/Encrypted/ {print $1}')
echo "Existing vars: $existing_vars"
for var in $existing_vars; do
echo "Removing $var from Vercel environment for branch ${{ env.TARGET_BRANCH }}"
vercel env rm $var preview ${{ env.TARGET_BRANCH }} --yes --token=${{ env.VERCEL_TOKEN }}
done
- name: Apply environment variables to target branch (excluding VERCEL_ and comments)
run: |
// .env.local 파일을 순회하며 if문을 충족하는 조건들에 한해 TARGET_BRANCH에 env를 등록합니다.
// sanitize 가 필요한 값들도 여기서 처리해줍니다.
while IFS='=' read -r name value; do
if [[ ! $name =~ ^# && ! $name =~ ^VERCEL_ ]]; then
# Remove surrounding quotes and newlines
sanitized_value=$(echo $value | sed -e 's/^"//' -e 's/"$//' -e 's/\\n//g')
# Replace specific patterns
sanitized_value=$(echo $sanitized_value | sed -e 's|https://files.|https://static-files.|g' -e 's|https://api.|https://static-api.|g')
# Check if the variable is REACT_APP_CUSTOM_ENV and set its value to "prod"
if [[ $name == "REACT_APP_CUSTOM_ENV" ]]; then
sanitized_value=$(echo 'prod' | sed -e 's/^"//' -e 's/"$//' -e 's/\\n//g')
fi
echo "Adding $name with value $sanitized_value to Vercel environment for branch ${{ env.TARGET_BRANCH }}"
printf '%s' "$sanitized_value" | vercel env add $name preview ${{ env.TARGET_BRANCH }} --token=${{ env.VERCEL_TOKEN }}
fi
done < .env.local
- name: Sync changes to target branch
id: sync
run: |
// merge conflict를 방지하기 위해 체크아웃 후 main 버전으로 코드를 완전히 리셋 한 후,
// deploy 용 빈 커밋을 생성해서 푸시합니다.
// vercel deploy 권한이 있는 계정으로 커밋햇기 때문에 deploy action이 발생합니다.
git checkout --orphan ${{ env.TARGET_BRANCH }}
git reset --hard origin/${{ env.SOURCE_BRANCH }}
git commit --allow-empty -am "Sync code&env from ${{ env.SOURCE_BRANCH }} -> ${{ env.TARGET_BRANCH }}"
git push origin ${{ env.TARGET_BRANCH }} -f
이로써 A브랜치를 추종하는 B브랜치에 대한 관리를 따로 해주지 않아도 되어 휴먼에러도 사라지고, 반복되는 작업에 의한 생산성 저하도 사라졌습니다.