Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
224 lines (186 loc) · 9.66 KB
/
Copy pathMakefile
File metadata and controls
224 lines (186 loc) · 9.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# uv run 을 감싸는 진입점. 명령마다 uv run 을 치지 않도록 한다.
#
# 변수는 실행할 때 덮어쓸 수 있다.
# make server PORT=4300
# make deploy WORK_POOL=other-pool
UV ?= uv
HOST ?= 127.0.0.1
PORT ?= 4200
API_URL ?= http://$(HOST):$(PORT)/api
WORK_POOL ?= neki-pool
IMAGE ?= ghcr.io/team-neki/team-neki-workflow:local
# uv run 은 .env 를 자동으로 읽지 않는다. 파일이 있을 때만 지정한다.
# 없는 파일을 가리키면 uv 가 실패하므로 wildcard 로 존재를 확인한다.
ifneq ($(wildcard .env),)
export UV_ENV_FILE := .env
endif
# UI를 쓰려면 API 주소를 지정해야 한다. 지정하지 않으면 프로세스 안에
# 임시 서버가 떴다 사라져 UI로 접근할 수 없다.
PREFECT_ENV = PREFECT_API_URL=$(API_URL)
.DEFAULT_GOAL := help
.PHONY: help setup check spec-check hello lifefourcuts photoism dontlxxkup photosignature \
photogray planbstudio picdot monomansion harufilm photolabplus broomstudio \
collect enrich stores-sync stores-sync-dry-run test-stores-sync-db search-index legal-dong subway-station localstack localstack-down s3-init s3-ls \
serve server deploy build image clean
help: ## 명령 목록을 출력한다
@echo "사용법: make <명령>"
@echo
@grep -E '^[a-z][a-z-]*:.*## ' $(MAKEFILE_LIST) \
| awk -F':.*## ' '{printf " %-16s %s\n", $$1, $$2}'
@echo
@echo "변수: HOST=$(HOST) PORT=$(PORT) WORK_POOL=$(WORK_POOL) IMAGE=$(IMAGE)"
@echo "UI: http://$(HOST):$(PORT)/deployments"
setup: ## 의존성을 uv.lock 기준으로 설치한다
$(UV) sync
check: spec-check ## 임포트와 deployment 수집, spec anchor, 단위 테스트를 확인한다
@$(UV) run python -c "\
from deployments import collect; \
found = list(collect()); \
print('deployment', len(found), '건'); \
[print(' ', d.flow_name + '/' + d.name) for d in found]"
@$(UV) run pytest -q tests
# docs/spec 이 정본이다. anchor 가 어긋나면 문서나 코드 중 하나가 낡은 것이다.
spec-check: ## docs/spec 의 구현 anchor 가 코드 줄과 맞는지 확인한다
@$(UV) run python spec_check.py
hello: ## hello 워크플로를 실행한다
$(UV) run python -c "from flows.hello import hello; hello()"
lifefourcuts: ## 인생네컷 지점을 수집한다 (S3, DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.lifefourcuts_stores import lifefourcuts_stores; \
stores = lifefourcuts_stores(); \
print('수집', len(stores), '건')"
photoism: ## 포토이즘 지점을 수집한다 (S3, DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.photoism_stores import photoism_stores; \
stores = photoism_stores(); \
print('수집', len(stores), '건')"
dontlxxkup: ## 돈룩업 지점을 수집한다 (S3, DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.dontlxxkup_stores import dontlxxkup_stores; \
stores = dontlxxkup_stores(); \
print('수집', len(stores), '건')"
photosignature: ## 포토시그니처 지점을 수집한다 (S3, DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.photosignature_stores import photosignature_stores; \
stores = photosignature_stores(); \
print('수집', len(stores), '건')"
photogray: ## 포토그레이 지점을 수집한다 (KAKAO_API_KEY, S3, DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.photogray_stores import photogray_stores; \
stores = photogray_stores(); \
print('수집', len(stores), '건')"
planbstudio: ## 플랜비스튜디오 지점을 수집한다 (S3, DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.planbstudio_stores import planbstudio_stores; \
stores = planbstudio_stores(); \
print('수집', len(stores), '건')"
picdot: ## 픽닷 지점을 수집한다 (KAKAO_API_KEY, S3, DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.picdot_stores import picdot_stores; \
stores = picdot_stores(); \
print('수집', len(stores), '건')"
monomansion: ## 모노맨션 지점을 수집한다 (KAKAO_API_KEY, S3, DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.monomansion_stores import monomansion_stores; \
stores = monomansion_stores(); \
print('수집', len(stores), '건')"
harufilm: ## 하루필름 지점을 수집한다 (KAKAO_API_KEY, S3, DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.harufilm_stores import harufilm_stores; \
stores = harufilm_stores(); \
print('수집', len(stores), '건')"
photolabplus: ## 포토랩플러스 지점을 수집한다 (KAKAO_API_KEY, S3, DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.photolabplus_stores import photolabplus_stores; \
stores = photolabplus_stores(); \
print('수집', len(stores), '건')"
broomstudio: ## 비룸스튜디오 지점을 수집한다 (KAKAO_API_KEY, S3, DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.broomstudio_stores import broomstudio_stores; \
stores = broomstudio_stores(); \
print('수집', len(stores), '건')"
collect: ## 전체 브랜드를 병렬로 수집한다 (KAKAO_API_KEY, S3, DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.stores_collect import stores_collect; \
results = stores_collect(); \
print('성공', sum(1 for r in results.values() if r['status'] == 'ok'), '건'); \
print('합계', sum(r.get('count', 0) for r in results.values()), '건')"
enrich: ## 최신 collect 적재물에 법정동 코드를 붙여 Postgres 에만 적재한다 (KAKAO_API_KEY, S3 읽기, DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.stores_enrich import stores_enrich; \
result = stores_enrich(); \
print('보강', result['count'], '건', result['status'])"
stores-sync: ## 보강 지점을 안정적인 locationId로 동기화한다 (서버 V34, DATABASE_URL 필요)
@$(UV) run python -c "from flows.stores_sync import stores_sync; print(stores_sync())"
stores-sync-dry-run: ## 지점 동기화 예상 건수를 확인한다 (DB 쓰기 없음)
@$(UV) run python -c "from flows.stores_sync import stores_sync; print(stores_sync(persist=False))"
test-stores-sync-db: ## 전용 PostGIS DB에서 지점 동기화와 enrich 적재 통합 테스트를 실행한다
@test -n "$$STORES_SYNC_TEST_DATABASE_URL" || (echo 'STORES_SYNC_TEST_DATABASE_URL이 필요합니다'; exit 1)
@$(UV) run pytest -q tests/test_stores_sync_db.py tests/test_stores_enrich_db.py
search-index: ## 서버 검색 색인 잡을 k8s Job 으로 띄운다 (NEKI_BATCH_IMAGE 없으면 경고 후 끝남)
@$(UV) run python -c "\
from flows.search_index import search_index; \
result = search_index(); \
print('색인 실행' if result['launched'] else '색인 건너뜀', result['cycle'])"
subway-station: ## 지하철 역 정보를 수집해 Postgres 에 적재한다 (DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.subway_station import subway_station; \
stations = subway_station(); \
print('수집', len(stations), '건')"
legal-dong: ## 법정동 코드를 수집해 Postgres 에 적재한다 (DATABASE_URL 필요)
@$(UV) run python -c "\
from flows.legal_dong import legal_dong; \
dongs = legal_dong(); \
print('수집', len(dongs), '건')"
# docker compose 는 .env 를 알아서 읽으므로 LOCALSTACK_PORT 가 그대로 먹는다.
localstack: ## 로컬 S3(LocalStack)를 띄운다
docker compose up -d --wait
@$(MAKE) --no-print-directory s3-init
localstack-down: ## 로컬 S3를 내린다 (버킷 내용도 사라진다)
docker compose down
s3-init: ## 로컬 버킷을 만든다 (이미 있으면 넘어간다)
@$(UV) run python -c "\
import os, boto3; \
s3 = boto3.Session().client('s3'); \
bucket = os.environ['S3_BUCKET']; \
names = {b['Name'] for b in s3.list_buckets()['Buckets']}; \
print('버킷', bucket, '이미 있음') if bucket in names else ( \
s3.create_bucket(Bucket=bucket, CreateBucketConfiguration={ \
'LocationConstraint': s3.meta.region_name}), \
print('버킷', bucket, '생성'))"
s3-ls: ## 적재된 키를 나열한다
@$(UV) run python -c "\
import os, boto3; \
s3 = boto3.Session().client('s3'); \
bucket = os.environ['S3_BUCKET']; \
pages = s3.get_paginator('list_objects_v2').paginate(Bucket=bucket); \
keys = sorted(o['Key'] for p in pages for o in p.get('Contents', [])); \
print('s3://' + bucket, len(keys), '개'); \
[print(' ', k) for k in keys]"
serve: ## 로컬 개발용으로 deployment를 서빙한다 (server가 먼저 떠 있어야 함)
$(PREFECT_ENV) $(UV) run python serve.py
server: ## Prefect 서버를 띄운다 (UI 주소는 아래 변수 참고)
$(UV) run prefect server start --host $(HOST) --port $(PORT)
deploy: ## work pool에 스케줄을 등록한다 (pause 상태 보존)
$(PREFECT_ENV) PREFECT_WORK_POOL=$(WORK_POOL) $(UV) run python deploy.py
# 운영과 같은 조건(uid 1001, 읽기 전용 루트)으로 돌려 이미지 안 파일이 읽히는지 본다.
# prefect 버전이 베이스와 어긋나면(uv.lock 이 베이스의 prefect 를 덮어씀) 여기서 걸린다.
image: ## 컨테이너 이미지를 빌드하고 안에서 deployment 수집을 확인한다
docker build --build-arg IMAGE_REF=$(IMAGE) -t $(IMAGE) .
docker run --rm --user 1001 --read-only -e HOME=/tmp --tmpfs /tmp $(IMAGE) python -c "\
import os, prefect, prefect_kubernetes; \
from deployments import collect; \
assert prefect.__version__ == '3.8.5', prefect.__version__; \
print('prefect', prefect.__version__); \
print('deployment', len(list(collect())), '건'); \
print('WORKFLOW_IMAGE', os.environ['WORKFLOW_IMAGE'])"
build: ## wheel을 빌드하고 포함된 패키지를 확인한다
$(UV) build --wheel --out-dir dist
@$(UV) run python -c "\
import glob, zipfile; \
w = sorted(glob.glob('dist/*.whl'))[-1]; \
names = zipfile.ZipFile(w).namelist(); \
print('wheel 최상위:', sorted({n.split('/')[0] for n in names if '/' in n}))"
clean: ## 빌드 산출물과 캐시를 지운다
rm -rf dist build *.egg-info
find . -type d -name __pycache__ -not -path './.venv/*' -exec rm -rf {} +