[TeamCity] Slack์์ ๋น๋ ๋ช ๋ น์ด๋ก TeamCity ๋น๋ํ๊ธฐ - (2)
์ด์ ๋จ๊ณ
1) TeamCity API ์ค์
๐ TeamCity์์ API ํ ํฐ ์ค์
1. TeamCity ์นํ์ด์ง -> Profile -> Access Tokens ์ด๋
2. Generate New Token ํด๋ฆญ -> teamcity_token ์ ์ฅ

๐ TeamCity์์ API ํ ์คํธ
API๊ฐ ์ ์ ์๋ํ๋์ง ํ์ธํ๊ธฐ ์ํด ์๋์ ๋ช ๋ น์ด ์คํํ๊ธฐ
curl -X GET "http://{TeamCity_IP}:{Port}/app/rest/buildTypes" \
-H "Accept: application/json" \
-H "Authorization: Bearer {Your_Token}"
์ ์์ ์ธ JSON ์๋ต์ด ์ค๋ฉด TeamCity API ๊ฐ ์ ์๋ํ๋ ๊ฒ์ ๋๋ค. โฏแตโฉแตโฏเฒฃ
2) Python Slack Bot ์ฝ๋ ์์ฑ
๐ slack_bot.py (slack์์ ๋น๋! ์ ๋ ฅ ์ TeamCity ์คํ)
import requests
import time
import threading
import xml.etree.ElementTree as ET
from flask import Flask, request, jsonify
app = Flask(__name__)
# ๐ฅ ํ๊ฒฝ ๋ณ์ ์ค์ (์ฌ๋ & TeamCity)
SLACK_BOT_TOKEN = "xoxb-..." # Slack Bot Token
TEAMCITY_URL = "http://192.168.50.46:80" # TeamCity ์๋ฒ ์ฃผ์
TEAMCITY_TOKEN = "eyJ0eXAiOiAiVENWMiJ9..." # TeamCity API ํ ํฐ
TEAMCITY_BUILD_ID = "Unity_Project_Build" # ์คํํ TeamCity ๋น๋ ID
processed_events = set() # ์ต๊ทผ ์ฒ๋ฆฌํ ์ด๋ฒคํธ ์ ์ฅ
def trigger_teamcity_build(branch="main"):
"""TeamCity ๋น๋ ์คํ ํ ๋น๋ ID ๋ฐํ"""
url = f"{TEAMCITY_URL}/app/rest/buildQueue"
headers = {
"Authorization": f"Bearer {TEAMCITY_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/xml" # XML ์๋ต์ ๋ฐ๋๋ก ์ค์
}
data = {
"buildType": {"id": TEAMCITY_BUILD_ID},
"properties": {"property": [{"name": "branch", "value": branch}]}
}
response = requests.post(url, headers=headers, json=data)
print(f"๐ก TeamCity ๋น๋ ์คํ ์์ฒญ ์ํ ์ฝ๋: {response.status_code}")
print(f"๐ก TeamCity ์๋ต ๋ด์ฉ:\n{response.text}") # XML ์๋ต์ ์ถ๋ ฅํ์ฌ ํ์ธ
if response.status_code == 200:
try:
root = ET.fromstring(response.text) # XML ํ์ฑ
build_id = root.get("id") # ๋น๋ ID ์ถ์ถ
print(f"๐ TeamCity ๋น๋ ์คํ ์๋ฃ! ๋น๋ ID: {build_id}")
return build_id
except ET.ParseError as e:
print(f"โ XML ํ์ฑ ์คํจ! ์ค๋ฅ: {e}")
return None
else:
print(f"โ TeamCity ๋น๋ ์คํ ์คํจ! ์ํ ์ฝ๋: {response.status_code}")
return None
def wait_for_build_completion(build_id, channel):
"""TeamCity ๋น๋ ์๋ฃ ์ฌ๋ถ ํ์ธ ํ Slack์ ์๋ฆผ ์ ์ก"""
url = f"{TEAMCITY_URL}/app/rest/builds/id:{build_id}"
headers = {
"Authorization": f"Bearer {TEAMCITY_TOKEN}",
"Accept": "application/xml" # XML ์๋ต์ ๋ฐ๋๋ก ์ค์
}
print(f"โณ ๋น๋ ์๋ฃ ๋๊ธฐ ์ค... (๋น๋ ID: {build_id})")
while True:
response = requests.get(url, headers=headers)
print(f"๐ก TeamCity ๋น๋ ์ํ ํ์ธ ์์ฒญ: {response.status_code}")
if response.status_code == 200:
try:
root = ET.fromstring(response.text) # XML ํ์ฑ
state = root.get("state") # ๋น๋ ์งํ ์ํ
status = root.get("status") # ๋น๋ ์ฑ๊ณต/์คํจ ์ํ
print(f"๐ TeamCity ๋น๋ ์ํ: state={state}, status={status}")
if state == "finished":
result_text = "โ
๋น๋ ์๋ฃ!" if status == "SUCCESS" else "โ ๋น๋ ์คํจ!"
send_slack_message(channel, result_text) # Slack ๋ฉ์์ง ์ ์ก
print(f"๐ {result_text} (๋น๋ ID: {build_id})")
break
except ET.ParseError as e:
print(f"โ XML ํ์ฑ ์คํจ! ์ค๋ฅ: {e}")
else:
print(f"โ TeamCity ๋น๋ ์ํ ํ์ธ ์คํจ! ์ํ ์ฝ๋: {response.status_code}, ์๋ต ๋ด์ฉ:\n{response.text}")
time.sleep(10) # 10์ด๋ง๋ค ํ์ธ
def send_slack_message(channel, text):
"""Slack ์ฑ๋์ ๋ฉ์์ง ์ ์ก"""
url = "https://slack.com/api/chat.postMessage"
headers = {
"Authorization": f"Bearer {SLACK_BOT_TOKEN}",
"Content-Type": "application/json"
}
data = {
"channel": channel,
"text": text
}
response = requests.post(url, headers=headers, json=data)
print(f"๐ค Slack ๋ฉ์์ง ์ ์ก ์ํ ์ฝ๋: {response.status_code}")
print(f"๐ค Slack API ์๋ต ๋ด์ฉ: {response.text}") # **Slack ์๋ต ๋ฐ์ดํฐ ํ์ธ**
if response.status_code != 200:
print(f"โ Slack ๋ฉ์์ง ์ ์ก ์คํจ! ์ํ ์ฝ๋: {response.status_code}")
@app.route("/slack", methods=["POST"])
def slack_events():
"""Slack์์ ์ด๋ฒคํธ๋ฅผ ์์ """
headers = dict(request.headers)
if "X-Slack-Retry-Num" in headers:
print(f"โ ๏ธ Slack Retry ์์ฒญ ๊ฐ์ง (ํ์: {headers['X-Slack-Retry-Num']}) - ์ค๋ณต ์คํ ๋ฐฉ์ง")
return jsonify({"status": "ok"}), 200
raw_data = request.get_data(as_text=True)
print(f"\n๐ฉ Raw Slack Request:\n{raw_data}\n")
try:
data = request.json
print(f"\n๐ฉ Parsed JSON:\n{data}\n")
except Exception as e:
print(f"\nโ JSON ๋ณํ ์คํจ: {e}\n")
return jsonify({"error": "Invalid JSON"}), 400
if "challenge" in data:
return jsonify({"challenge": data["challenge"]})
if "event" in data:
event = data["event"]
event_id = event.get("client_msg_id") or event.get("event_id")
if event_id in processed_events:
print(f"โ ๏ธ ์ด๋ฏธ ์ฒ๋ฆฌ๋ ์ด๋ฒคํธ (ID: {event_id}) - ์ค๋ณต ์คํ ๋ฐฉ์ง")
return jsonify({"status": "ok"}), 200
processed_events.add(event_id)
if "text" in event and "!๋น๋" in event["text"]:
print("\n๐ `!๋น๋` ๋ช
๋ น ๊ฐ์ง - TeamCity ๋น๋ ์คํ\n")
channel = event["channel"]
send_slack_message(channel, "๋น๋๋ฅผ ์์ํฉ๋๋ค. ๐")
# โ
๋น๋ ์คํ ํ ID ๋ฐํ
build_id = trigger_teamcity_build()
if build_id:
print(f"๐ ๋น๋ ์๋ฃ ๋๊ธฐ ์์ (๋น๋ ID: {build_id})")
build_thread = threading.Thread(
target=wait_for_build_completion,
args=(build_id, channel),
daemon=True # ํ๋ก๊ทธ๋จ ์ข
๋ฃ ์ ์ค๋ ๋๋ ์๋ ์ข
๋ฃ๋๋๋ก ์ค์
)
build_thread.start()
return jsonify({"status": "ok"}), 200
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
์ฌ๊ธฐ์ ๋ด teamcity ์๋ฒ์ ์ฃผ์(TEAMCITY_URL)๋ TeamCity๊ฐ ์คํ์ค์ธ ์ปดํจํฐ์ IP ์ฃผ์ + ํฌํธ ๋ฒํธ์ด๋ค.
Mac ๊ธฐ์ค ํ์ธํ๋ ๋ฐฉ๋ฒ์ ํฐ๋ฏธ๋์ ๋ค์์ ์ ๋ ฅํ์ ๋
ifconfig | grep "inet "
inet 127.0.0.1 netmask 0xff000000
inet 192.168.0.20 netmask 0xffffff00 broadcast 192.168.0.255
์ด ์ค 192.168.x.x ๋๋ 10.x.x.x ํ์์ IP ์ฃผ์๊ฐ TeamCity ์๋ฒ ์ฃผ์์ด๋ค.
์ด ์์์์๋ 192.168.0.20์ด TeamCity ์๋ฒ ์ฃผ์๊ฐ ๋๋ ๊ฒ! (TEAMCITY_URL = "http://192.168.0.20:8111")
3) ์คํ ๋ฐฉ๋ฒ
๐ python ํจํค์ง ์ค์น
pip install flask requests
๐ ์ฌ๋ API ์๋ฒ ์คํ
python slack_bot.py
๐ ngrok ์ ์ด์ฉํด ์ธ๋ถ์์ ์ ์ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๊ธฐ
1. ngrok ์ ์ค์นํด์ค๋ค.
brew install ngrok
2. ngrok ํํ์ด์ง์์ ๊ณ์ ์์ฑ: https://ngrok.com/
ngrok | API Gateway, Kubernetes Networking + Secure Tunnels
ngrok simplifies app delivery by unifying API gateway, Kubernetes Ingress, global load balancing, DDoS protection and more with secure tunnels.
ngrok.com
3. ๋ก๊ทธ์ธ ํ Auth Token์ ํ์ธ
4. ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด ์คํ (๋ฐ๊ธ๋ ํ ํฐ ์ฌ์ฉ)
ngrok config add-authtoken YOUR_AUTH_TOKEN
5. ์ค์ ์ด ์๋ฃ๋์๋์ง ํ์ธ
cat ~/.ngrok2/ngrok.yml
6. ngrok ์คํ ๋ฐ ํ ์คํธ
ngrok http 5000
์์ฑ๋ URL(https://1234.ngrok.io/slack)์ Slack API์ Event Subscriptions์ ๋ฑ๋ก
4) TeamCity ์ค์
๐ Unity ํ๋ก์ ํธ ๋น๋ ์ค์
1. TeamCity์์ +New Bulid Configuration ํด๋ฆญ
2. Git ์ ์ฅ์ ์ฐ๊ฒฐ
* version Control Settings -> GitHub / GitLab ์ ์ฅ์ ์ฐ๊ฒฐ
* Branch : develop ๋๋ main
3. Build Steps์์ Unity ๋น๋ ์ถ๊ฐ

* Runner Type : Command Line

์ต์ | ์ค๋ช |
-batchmode | Unity๋ฅผ UI ์์ด ์คํ |
-nographics | ๊ทธ๋ํฝ ํ๊ฒฝ ์์ด ์คํ |
-quit | ์คํ ํ ์๋ ์ข ๋ฃ |
-projectPath | Unity ํ๋ก์ ํธ ํด๋ ๊ฒฝ๋ก |
-executeMethod | C# ๋น๋ ๋ฉ์๋ ํธ์ถ |
-logFile | ๋น๋ ๋ก๊ทธ ์ ์ฅ ์์น |
5) Slack API ์ธํ ๋ฐ ์คํ
๐ Slack API ์ธํ
1. ngrok http 5000์ ์คํ

2. ํด๋น ์ฃผ์๋ฅผ Slack API ๊ด๋ฆฌ ํ์ด์ง์ Interactivity & Shortcuts ์ Request URL์ ๋ฑ๋กํด์ค๋ค.

์ฃผ์์ฌํญ. ๋งจ ๋ค์ /slack ๋ถ์ฌ์ค์ผ ํจ!
3. Event Subscriptions์ Request URL์๋ ๋์ผํ๊ฒ URL ์ ๋ ฅ
4. Event Subscriptions์ Subscribe to bot events์ Add Bot User Event ์ถ๊ฐ

๐ ์คํํด๋ณด๊ธฐ
1. python ํ์ผ ์คํํ๊ธฐ
python slack_bot.py
2. ๋ด์ ์ด๋ํ ์ฌ๋์ ์ฑ๋์์ !๋น๋๋ฅผ ์ ๋ ฅํ ํ ํ์ธํด์ฃผ๊ธฐ

'๐โ๏ธ(ใปโใป)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[TeamCity] Slack์์ ๋น๋ ๋ช ๋ น์ด๋ก TeamCity ๋น๋ํ๊ธฐ - (1) (0) | 2025.03.06 |
---|---|
[Jira] ๋ณด๋ ์์ฑ ์ต์ 3๊ฐ์ง ๋น๊ต (0) | 2025.02.17 |
[Jira] JQL ๊ฒ์ ์ ํ์ ์์ ์ด ์๋์ฌ ๋ (6) | 2025.02.05 |
[Jira] ๋ฏธ๊ฒฐ ์ด์ ํ๊บผ๋ฒ์ ํธ์งํ๊ธฐ (5) | 2025.02.05 |