super-dev 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- super_dev/__init__.py +11 -0
- super_dev/analyzer/__init__.py +34 -0
- super_dev/analyzer/analyzer.py +440 -0
- super_dev/analyzer/detectors.py +511 -0
- super_dev/analyzer/models.py +285 -0
- super_dev/cli.py +3257 -0
- super_dev/config/__init__.py +11 -0
- super_dev/config/frontend.py +557 -0
- super_dev/config/manager.py +281 -0
- super_dev/creators/__init__.py +26 -0
- super_dev/creators/creator.py +134 -0
- super_dev/creators/document_generator.py +2473 -0
- super_dev/creators/frontend_builder.py +371 -0
- super_dev/creators/implementation_builder.py +789 -0
- super_dev/creators/prompt_generator.py +289 -0
- super_dev/creators/requirement_parser.py +354 -0
- super_dev/creators/spec_builder.py +195 -0
- super_dev/deployers/__init__.py +20 -0
- super_dev/deployers/cicd.py +1269 -0
- super_dev/deployers/delivery.py +229 -0
- super_dev/deployers/migration.py +1032 -0
- super_dev/design/__init__.py +74 -0
- super_dev/design/aesthetics.py +530 -0
- super_dev/design/charts.py +396 -0
- super_dev/design/codegen.py +379 -0
- super_dev/design/engine.py +528 -0
- super_dev/design/generator.py +395 -0
- super_dev/design/landing.py +422 -0
- super_dev/design/tech_stack.py +524 -0
- super_dev/design/tokens.py +269 -0
- super_dev/design/ux_guide.py +391 -0
- super_dev/exceptions.py +119 -0
- super_dev/experts/__init__.py +19 -0
- super_dev/experts/service.py +161 -0
- super_dev/integrations/__init__.py +7 -0
- super_dev/integrations/manager.py +264 -0
- super_dev/orchestrator/__init__.py +12 -0
- super_dev/orchestrator/engine.py +958 -0
- super_dev/orchestrator/experts.py +423 -0
- super_dev/orchestrator/knowledge.py +352 -0
- super_dev/orchestrator/quality.py +356 -0
- super_dev/reviewers/__init__.py +17 -0
- super_dev/reviewers/code_review.py +471 -0
- super_dev/reviewers/quality_gate.py +964 -0
- super_dev/reviewers/redteam.py +881 -0
- super_dev/skills/__init__.py +7 -0
- super_dev/skills/manager.py +307 -0
- super_dev/specs/__init__.py +44 -0
- super_dev/specs/generator.py +264 -0
- super_dev/specs/manager.py +428 -0
- super_dev/specs/models.py +348 -0
- super_dev/specs/validator.py +415 -0
- super_dev/utils/__init__.py +11 -0
- super_dev/utils/logger.py +133 -0
- super_dev/web/api.py +1402 -0
- super_dev-2.0.0.dist-info/METADATA +252 -0
- super_dev-2.0.0.dist-info/RECORD +61 -0
- super_dev-2.0.0.dist-info/WHEEL +5 -0
- super_dev-2.0.0.dist-info/entry_points.txt +2 -0
- super_dev-2.0.0.dist-info/licenses/LICENSE +21 -0
- super_dev-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CI/CD 生成器 - 自动生成 CI/CD 流水线配置
|
|
3
|
+
|
|
4
|
+
开发:Excellent(11964948@qq.com)
|
|
5
|
+
功能:生成 GitHub Actions / GitLab CI / Jenkins / Azure DevOps / Bitbucket 配置
|
|
6
|
+
作用:实现自动化构建、测试、部署
|
|
7
|
+
创建时间:2025-12-30
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Literal
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CICDGenerator:
|
|
16
|
+
"""CI/CD 配置生成器"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
project_dir: Path,
|
|
21
|
+
name: str,
|
|
22
|
+
tech_stack: dict,
|
|
23
|
+
platform: Literal["github", "gitlab", "jenkins", "azure", "bitbucket", "all"] = "github"
|
|
24
|
+
):
|
|
25
|
+
self.project_dir = Path(project_dir).resolve()
|
|
26
|
+
self.display_name = name
|
|
27
|
+
self.name = self._sanitize_resource_name(name)
|
|
28
|
+
self.tech_stack = tech_stack
|
|
29
|
+
self.platform = platform
|
|
30
|
+
self.frontend = tech_stack.get("frontend", "react")
|
|
31
|
+
self.backend = tech_stack.get("backend", "node")
|
|
32
|
+
|
|
33
|
+
def _sanitize_resource_name(self, name: str) -> str:
|
|
34
|
+
lowered = name.strip().lower()
|
|
35
|
+
sanitized = re.sub(r"[^a-z0-9-]+", "-", lowered)
|
|
36
|
+
sanitized = re.sub(r"-{2,}", "-", sanitized).strip("-")
|
|
37
|
+
if not sanitized:
|
|
38
|
+
return "super-dev-app"
|
|
39
|
+
return sanitized[:63]
|
|
40
|
+
|
|
41
|
+
def generate(self) -> dict[str, str]:
|
|
42
|
+
"""生成所有 CI/CD 配置文件"""
|
|
43
|
+
files = {}
|
|
44
|
+
|
|
45
|
+
if self.platform in {"github", "all"}:
|
|
46
|
+
files[".github/workflows/ci.yml"] = self._generate_github_ci()
|
|
47
|
+
files[".github/workflows/cd.yml"] = self._generate_github_cd()
|
|
48
|
+
if self.platform in {"gitlab", "all"}:
|
|
49
|
+
files[".gitlab-ci.yml"] = self._generate_gitlab_ci()
|
|
50
|
+
if self.platform in {"jenkins", "all"}:
|
|
51
|
+
files["Jenkinsfile"] = self._generate_jenkins()
|
|
52
|
+
if self.platform in {"azure", "all"}:
|
|
53
|
+
files[".azure-pipelines.yml"] = self._generate_azure()
|
|
54
|
+
if self.platform in {"bitbucket", "all"}:
|
|
55
|
+
files["bitbucket-pipelines.yml"] = self._generate_bitbucket()
|
|
56
|
+
|
|
57
|
+
# Docker 配置
|
|
58
|
+
files["Dockerfile"] = self._generate_dockerfile()
|
|
59
|
+
files["docker-compose.yml"] = self._generate_docker_compose()
|
|
60
|
+
files[".dockerignore"] = self._generate_dockerignore()
|
|
61
|
+
|
|
62
|
+
# Kubernetes 配置
|
|
63
|
+
files["k8s/deployment.yaml"] = self._generate_k8s_deployment()
|
|
64
|
+
files["k8s/service.yaml"] = self._generate_k8s_service()
|
|
65
|
+
files["k8s/ingress.yaml"] = self._generate_k8s_ingress()
|
|
66
|
+
files["k8s/configmap.yaml"] = self._generate_k8s_configmap()
|
|
67
|
+
files["k8s/secret.yaml"] = self._generate_k8s_secret()
|
|
68
|
+
|
|
69
|
+
return files
|
|
70
|
+
|
|
71
|
+
def _generate_github_ci(self) -> str:
|
|
72
|
+
"""生成 GitHub Actions CI 配置"""
|
|
73
|
+
return f"""name: CI
|
|
74
|
+
|
|
75
|
+
on:
|
|
76
|
+
push:
|
|
77
|
+
branches: [main, develop]
|
|
78
|
+
pull_request:
|
|
79
|
+
branches: [main, develop]
|
|
80
|
+
|
|
81
|
+
jobs:
|
|
82
|
+
quality:
|
|
83
|
+
name: Quality
|
|
84
|
+
runs-on: ubuntu-latest
|
|
85
|
+
steps:
|
|
86
|
+
- uses: actions/checkout@v3
|
|
87
|
+
|
|
88
|
+
- name: Setup Node.js
|
|
89
|
+
uses: actions/setup-node@v3
|
|
90
|
+
with:
|
|
91
|
+
node-version: '18'
|
|
92
|
+
|
|
93
|
+
- name: Setup Python
|
|
94
|
+
uses: actions/setup-python@v4
|
|
95
|
+
with:
|
|
96
|
+
python-version: '3.11'
|
|
97
|
+
|
|
98
|
+
- name: Install frontend dependencies
|
|
99
|
+
if: ${{{{ hashFiles('frontend/package.json') != '' }}}}
|
|
100
|
+
run: npm --prefix frontend ci
|
|
101
|
+
|
|
102
|
+
- name: Install backend node dependencies
|
|
103
|
+
if: ${{{{ hashFiles('backend/package.json') != '' }}}}
|
|
104
|
+
run: npm --prefix backend ci
|
|
105
|
+
|
|
106
|
+
- name: Install Python dependencies
|
|
107
|
+
run: |
|
|
108
|
+
if [ -f backend/requirements.txt ] || [ -f requirements.txt ] || [ -f pyproject.toml ] || [ -f backend/pyproject.toml ]; then
|
|
109
|
+
pip install -e ".[dev]"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
- name: Run linters
|
|
113
|
+
run: |
|
|
114
|
+
if [ -f frontend/package.json ]; then
|
|
115
|
+
npm --prefix frontend run lint --if-present
|
|
116
|
+
fi
|
|
117
|
+
if [ -f backend/package.json ]; then
|
|
118
|
+
npm --prefix backend run lint --if-present
|
|
119
|
+
fi
|
|
120
|
+
if [ -d super_dev ]; then
|
|
121
|
+
ruff check super_dev tests
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
- name: Run type checks
|
|
125
|
+
run: |
|
|
126
|
+
if [ -f frontend/package.json ]; then
|
|
127
|
+
npm --prefix frontend run type-check --if-present
|
|
128
|
+
fi
|
|
129
|
+
if [ -f backend/package.json ]; then
|
|
130
|
+
npm --prefix backend run type-check --if-present
|
|
131
|
+
fi
|
|
132
|
+
if [ -d super_dev ]; then
|
|
133
|
+
mypy .
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
- name: Run tests
|
|
137
|
+
run: |
|
|
138
|
+
if [ -f frontend/package.json ]; then
|
|
139
|
+
npm --prefix frontend run test --if-present
|
|
140
|
+
fi
|
|
141
|
+
if [ -f backend/package.json ]; then
|
|
142
|
+
npm --prefix backend run test --if-present
|
|
143
|
+
fi
|
|
144
|
+
if [ -d tests ]; then
|
|
145
|
+
pytest -q
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
- name: Upload coverage
|
|
149
|
+
if: ${{{{ hashFiles('coverage.xml', 'frontend/coverage/cobertura-coverage.xml', 'backend/coverage/cobertura-coverage.xml') != '' }}}}
|
|
150
|
+
uses: codecov/codecov-action@v3
|
|
151
|
+
with:
|
|
152
|
+
files: ./coverage.xml,./frontend/coverage/cobertura-coverage.xml,./backend/coverage/cobertura-coverage.xml
|
|
153
|
+
flags: unittests
|
|
154
|
+
name: codecov-umbrella
|
|
155
|
+
|
|
156
|
+
security:
|
|
157
|
+
name: Security Scan
|
|
158
|
+
runs-on: ubuntu-latest
|
|
159
|
+
steps:
|
|
160
|
+
- uses: actions/checkout@v3
|
|
161
|
+
|
|
162
|
+
- name: Run Trivy vulnerability scanner
|
|
163
|
+
uses: aquasecurity/trivy-action@master
|
|
164
|
+
with:
|
|
165
|
+
scan-type: 'fs'
|
|
166
|
+
scan-ref: '.'
|
|
167
|
+
format: 'sarif'
|
|
168
|
+
output: 'trivy-results.sarif'
|
|
169
|
+
|
|
170
|
+
- name: Upload Trivy results to GitHub Security tab
|
|
171
|
+
uses: github/codeql-action/upload-sarif@v2
|
|
172
|
+
with:
|
|
173
|
+
sarif_file: 'trivy-results.sarif'
|
|
174
|
+
|
|
175
|
+
- name: Run npm audit
|
|
176
|
+
run: |
|
|
177
|
+
if [ -f frontend/package.json ]; then
|
|
178
|
+
(cd frontend && npm audit --audit-level=moderate || true)
|
|
179
|
+
fi
|
|
180
|
+
if [ -f backend/package.json ]; then
|
|
181
|
+
(cd backend && npm audit --audit-level=moderate || true)
|
|
182
|
+
fi
|
|
183
|
+
continue-on-error: true
|
|
184
|
+
|
|
185
|
+
build:
|
|
186
|
+
name: Build Docker Image
|
|
187
|
+
runs-on: ubuntu-latest
|
|
188
|
+
needs: [quality, security]
|
|
189
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
190
|
+
steps:
|
|
191
|
+
- uses: actions/checkout@v3
|
|
192
|
+
|
|
193
|
+
- name: Set up Docker Buildx
|
|
194
|
+
uses: docker/setup-buildx-action@v2
|
|
195
|
+
|
|
196
|
+
- name: Login to Docker Hub
|
|
197
|
+
uses: docker/login-action@v2
|
|
198
|
+
with:
|
|
199
|
+
username: ${{{{ secrets.DOCKER_USERNAME }}}}
|
|
200
|
+
password: ${{{{ secrets.DOCKER_PASSWORD }}}}
|
|
201
|
+
|
|
202
|
+
- name: Build and push
|
|
203
|
+
uses: docker/build-push-action@v4
|
|
204
|
+
with:
|
|
205
|
+
context: .
|
|
206
|
+
push: true
|
|
207
|
+
tags: |
|
|
208
|
+
${{{{ secrets.DOCKER_USERNAME }}}}/{self.name}:latest
|
|
209
|
+
${{{{ secrets.DOCKER_USERNAME }}}}/{self.name}:${{{{ github.sha }}}}
|
|
210
|
+
cache-from: type=gha
|
|
211
|
+
cache-to: type=gha,mode=max
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
def _generate_github_cd(self) -> str:
|
|
215
|
+
"""生成 GitHub Actions CD 配置"""
|
|
216
|
+
return f"""name: CD
|
|
217
|
+
|
|
218
|
+
on:
|
|
219
|
+
push:
|
|
220
|
+
branches: [main, develop]
|
|
221
|
+
workflow_dispatch:
|
|
222
|
+
|
|
223
|
+
jobs:
|
|
224
|
+
# ========== 部署到开发环境 ==========
|
|
225
|
+
deploy-dev:
|
|
226
|
+
name: Deploy to Development
|
|
227
|
+
runs-on: ubuntu-latest
|
|
228
|
+
if: github.ref == 'refs/heads/develop'
|
|
229
|
+
environment:
|
|
230
|
+
name: development
|
|
231
|
+
url: https://dev.{self.name}.example.com
|
|
232
|
+
steps:
|
|
233
|
+
- uses: actions/checkout@v3
|
|
234
|
+
|
|
235
|
+
- name: Configure kubectl
|
|
236
|
+
uses: azure/k8s-set-context@v3
|
|
237
|
+
with:
|
|
238
|
+
method: kubeconfig
|
|
239
|
+
kubeconfig: ${{{{ secrets.KUBE_CONFIG_DEV }}}}
|
|
240
|
+
|
|
241
|
+
- name: Deploy to Kubernetes
|
|
242
|
+
run: |
|
|
243
|
+
kubectl apply -f k8s/ -n dev
|
|
244
|
+
kubectl rollout restart deployment/{self.name} -n dev
|
|
245
|
+
|
|
246
|
+
- name: Verify deployment
|
|
247
|
+
run: |
|
|
248
|
+
kubectl rollout status deployment/{self.name} -n dev
|
|
249
|
+
|
|
250
|
+
# ========== 部署到生产环境 ==========
|
|
251
|
+
deploy-prod:
|
|
252
|
+
name: Deploy to Production
|
|
253
|
+
runs-on: ubuntu-latest
|
|
254
|
+
if: github.ref == 'refs/heads/main'
|
|
255
|
+
environment:
|
|
256
|
+
name: production
|
|
257
|
+
url: https://{self.name}.example.com
|
|
258
|
+
steps:
|
|
259
|
+
- uses: actions/checkout@v3
|
|
260
|
+
|
|
261
|
+
- name: Configure kubectl
|
|
262
|
+
uses: azure/k8s-set-context@v3
|
|
263
|
+
with:
|
|
264
|
+
method: kubeconfig
|
|
265
|
+
kubeconfig: ${{{{ secrets.KUBE_CONFIG_PROD }}}}
|
|
266
|
+
|
|
267
|
+
- name: Create backup
|
|
268
|
+
run: |
|
|
269
|
+
kubectl get deployment {self.name} -n prod -o yaml > backup-${{{{ github.sha }}}}.yaml
|
|
270
|
+
|
|
271
|
+
- name: Deploy to Kubernetes
|
|
272
|
+
run: |
|
|
273
|
+
kubectl apply -f k8s/ -n prod
|
|
274
|
+
kubectl rollout restart deployment/{self.name} -n prod
|
|
275
|
+
|
|
276
|
+
- name: Verify deployment
|
|
277
|
+
run: |
|
|
278
|
+
kubectl rollout status deployment/{self.name} -n prod
|
|
279
|
+
|
|
280
|
+
- name: Health check
|
|
281
|
+
run: |
|
|
282
|
+
for i in {{1..30}}; do
|
|
283
|
+
if curl -f https://{self.name}.example.com/health; then
|
|
284
|
+
echo "Health check passed"
|
|
285
|
+
exit 0
|
|
286
|
+
fi
|
|
287
|
+
echo "Waiting for health check... ($i/30)"
|
|
288
|
+
sleep 10
|
|
289
|
+
done
|
|
290
|
+
echo "Health check failed"
|
|
291
|
+
exit 1
|
|
292
|
+
|
|
293
|
+
- name: Rollback on failure
|
|
294
|
+
if: failure()
|
|
295
|
+
run: |
|
|
296
|
+
kubectl apply -f backup-${{{{ github.sha }}}}.yaml
|
|
297
|
+
kubectl rollout undo deployment/{self.name} -n prod
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
def _generate_gitlab_ci(self) -> str:
|
|
301
|
+
"""生成 GitLab CI 配置"""
|
|
302
|
+
return f"""stages:
|
|
303
|
+
- quality
|
|
304
|
+
- test
|
|
305
|
+
- build
|
|
306
|
+
- deploy
|
|
307
|
+
|
|
308
|
+
variables:
|
|
309
|
+
DOCKER_IMAGE: ${{CI_REGISTRY_IMAGE}}/{self.name}
|
|
310
|
+
DOCKER_TLS_CERTDIR: "/certs"
|
|
311
|
+
|
|
312
|
+
# ========== 质量检查 ==========
|
|
313
|
+
quality:
|
|
314
|
+
stage: quality
|
|
315
|
+
image: python:3.11-alpine
|
|
316
|
+
before_script:
|
|
317
|
+
- apk add --no-cache nodejs npm git
|
|
318
|
+
script:
|
|
319
|
+
- |
|
|
320
|
+
if [ -f frontend/package.json ]; then
|
|
321
|
+
npm --prefix frontend ci
|
|
322
|
+
fi
|
|
323
|
+
if [ -f backend/package.json ]; then
|
|
324
|
+
npm --prefix backend ci
|
|
325
|
+
fi
|
|
326
|
+
if [ -f backend/requirements.txt ] || [ -f requirements.txt ] || [ -f pyproject.toml ]; then
|
|
327
|
+
pip install -e ".[dev]"
|
|
328
|
+
fi
|
|
329
|
+
- |
|
|
330
|
+
if [ -f frontend/package.json ]; then
|
|
331
|
+
npm --prefix frontend run lint --if-present
|
|
332
|
+
npm --prefix frontend run type-check --if-present
|
|
333
|
+
fi
|
|
334
|
+
if [ -f backend/package.json ]; then
|
|
335
|
+
npm --prefix backend run lint --if-present
|
|
336
|
+
fi
|
|
337
|
+
if [ -d super_dev ]; then
|
|
338
|
+
ruff check super_dev tests
|
|
339
|
+
mypy super_dev
|
|
340
|
+
fi
|
|
341
|
+
cache:
|
|
342
|
+
paths:
|
|
343
|
+
- frontend/node_modules/
|
|
344
|
+
- backend/node_modules/
|
|
345
|
+
only:
|
|
346
|
+
- merge_requests
|
|
347
|
+
- main
|
|
348
|
+
- develop
|
|
349
|
+
|
|
350
|
+
# ========== 单元测试 ==========
|
|
351
|
+
test:
|
|
352
|
+
stage: test
|
|
353
|
+
image: python:3.11-alpine
|
|
354
|
+
before_script:
|
|
355
|
+
- apk add --no-cache nodejs npm
|
|
356
|
+
services:
|
|
357
|
+
- postgres:15-alpine
|
|
358
|
+
- redis:7-alpine
|
|
359
|
+
variables:
|
|
360
|
+
POSTGRES_HOST: postgres
|
|
361
|
+
POSTGRES_PASSWORD: postgres
|
|
362
|
+
POSTGRES_DB: test_db
|
|
363
|
+
REDIS_HOST: redis
|
|
364
|
+
script:
|
|
365
|
+
- |
|
|
366
|
+
if [ -f frontend/package.json ]; then
|
|
367
|
+
npm --prefix frontend ci
|
|
368
|
+
npm --prefix frontend run test --if-present
|
|
369
|
+
fi
|
|
370
|
+
if [ -f backend/package.json ]; then
|
|
371
|
+
npm --prefix backend ci
|
|
372
|
+
npm --prefix backend run test --if-present
|
|
373
|
+
fi
|
|
374
|
+
if [ -d tests ]; then
|
|
375
|
+
pip install -e ".[dev]"
|
|
376
|
+
pytest -q
|
|
377
|
+
fi
|
|
378
|
+
cache:
|
|
379
|
+
paths:
|
|
380
|
+
- frontend/node_modules/
|
|
381
|
+
- backend/node_modules/
|
|
382
|
+
artifacts:
|
|
383
|
+
reports:
|
|
384
|
+
coverage_report:
|
|
385
|
+
coverage_format: cobertura
|
|
386
|
+
path: frontend/coverage/cobertura-coverage.xml
|
|
387
|
+
paths:
|
|
388
|
+
- coverage/
|
|
389
|
+
- frontend/coverage/
|
|
390
|
+
- backend/coverage/
|
|
391
|
+
expire_in: 1 week
|
|
392
|
+
only:
|
|
393
|
+
- merge_requests
|
|
394
|
+
- main
|
|
395
|
+
- develop
|
|
396
|
+
|
|
397
|
+
# ========== 安全扫描 ==========
|
|
398
|
+
security:
|
|
399
|
+
stage: test
|
|
400
|
+
image: aquasec/trivy:latest
|
|
401
|
+
script:
|
|
402
|
+
- trivy fs --format sarif --output trivy-results.sarif .
|
|
403
|
+
artifacts:
|
|
404
|
+
reports:
|
|
405
|
+
sast: trivy-results.sarif
|
|
406
|
+
expire_in: 1 week
|
|
407
|
+
only:
|
|
408
|
+
- merge_requests
|
|
409
|
+
- main
|
|
410
|
+
- develop
|
|
411
|
+
|
|
412
|
+
# ========== 构建镜像 ==========
|
|
413
|
+
build:
|
|
414
|
+
stage: build
|
|
415
|
+
image: docker:24-dind
|
|
416
|
+
services:
|
|
417
|
+
- docker:24-dind
|
|
418
|
+
before_script:
|
|
419
|
+
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
|
420
|
+
script:
|
|
421
|
+
- docker build -t $DOCKER_IMAGE:$CI_COMMIT_SHA .
|
|
422
|
+
- docker push $DOCKER_IMAGE:$CI_COMMIT_SHA
|
|
423
|
+
- docker tag $DOCKER_IMAGE:$CI_COMMIT_SHA $DOCKER_IMAGE:latest
|
|
424
|
+
- docker push $DOCKER_IMAGE:latest
|
|
425
|
+
only:
|
|
426
|
+
- main
|
|
427
|
+
- develop
|
|
428
|
+
|
|
429
|
+
# ========== 部署到开发环境 ==========
|
|
430
|
+
deploy:dev:
|
|
431
|
+
stage: deploy
|
|
432
|
+
image: bitnami/kubectl:latest
|
|
433
|
+
script:
|
|
434
|
+
- kubectl config use-context $KUBE_CONTEXT_DEV
|
|
435
|
+
- kubectl apply -f k8s/ -n dev
|
|
436
|
+
- kubectl set image deployment/{self.name} {self.name}=$DOCKER_IMAGE:$CI_COMMIT_SHA -n dev
|
|
437
|
+
- kubectl rollout status deployment/{self.name} -n dev
|
|
438
|
+
environment:
|
|
439
|
+
name: development
|
|
440
|
+
url: https://dev.{self.name}.example.com
|
|
441
|
+
only:
|
|
442
|
+
- develop
|
|
443
|
+
|
|
444
|
+
# ========== 部署到生产环境 ==========
|
|
445
|
+
deploy:prod:
|
|
446
|
+
stage: deploy
|
|
447
|
+
image: bitnami/kubectl:latest
|
|
448
|
+
script:
|
|
449
|
+
- kubectl config use-context $KUBE_CONTEXT_PROD
|
|
450
|
+
- kubectl apply -f k8s/ -n prod
|
|
451
|
+
- kubectl set image deployment/{self.name} {self.name}=$DOCKER_IMAGE:$CI_COMMIT_SHA -n prod
|
|
452
|
+
- kubectl rollout status deployment/{self.name} -n prod
|
|
453
|
+
environment:
|
|
454
|
+
name: production
|
|
455
|
+
url: https://{self.name}.example.com
|
|
456
|
+
when: manual
|
|
457
|
+
only:
|
|
458
|
+
- main
|
|
459
|
+
"""
|
|
460
|
+
|
|
461
|
+
def _generate_jenkins(self) -> str:
|
|
462
|
+
"""生成 Jenkinsfile"""
|
|
463
|
+
return f"""pipeline {{
|
|
464
|
+
agent any
|
|
465
|
+
|
|
466
|
+
environment {{
|
|
467
|
+
DOCKER_IMAGE = "your-registry/{self.name}"
|
|
468
|
+
DOCKER_TAG = "${{sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()}}"
|
|
469
|
+
KUBECONFIG_DEV = credentials('kubeconfig-dev')
|
|
470
|
+
KUBECONFIG_PROD = credentials('kubeconfig-prod')
|
|
471
|
+
}}
|
|
472
|
+
|
|
473
|
+
stages {{
|
|
474
|
+
stage('Checkout') {{
|
|
475
|
+
steps {{
|
|
476
|
+
checkout scm
|
|
477
|
+
}}
|
|
478
|
+
}}
|
|
479
|
+
|
|
480
|
+
stage('Install Dependencies') {{
|
|
481
|
+
parallel {{
|
|
482
|
+
stage('Frontend') {{
|
|
483
|
+
steps {{
|
|
484
|
+
script {{
|
|
485
|
+
if (fileExists('frontend/package.json')) {{
|
|
486
|
+
sh 'npm --prefix frontend ci'
|
|
487
|
+
}}
|
|
488
|
+
}}
|
|
489
|
+
}}
|
|
490
|
+
}}
|
|
491
|
+
stage('Backend') {{
|
|
492
|
+
steps {{
|
|
493
|
+
script {{
|
|
494
|
+
if (fileExists('backend/package.json')) {{
|
|
495
|
+
sh 'npm --prefix backend ci'
|
|
496
|
+
}}
|
|
497
|
+
if (fileExists('requirements.txt') || fileExists('backend/requirements.txt') || fileExists('pyproject.toml')) {{
|
|
498
|
+
sh 'pip install -e ".[dev]"'
|
|
499
|
+
}}
|
|
500
|
+
}}
|
|
501
|
+
}}
|
|
502
|
+
}}
|
|
503
|
+
}}
|
|
504
|
+
}}
|
|
505
|
+
|
|
506
|
+
stage('Lint') {{
|
|
507
|
+
steps {{
|
|
508
|
+
script {{
|
|
509
|
+
if (fileExists('frontend/package.json')) {{
|
|
510
|
+
sh 'npm --prefix frontend run lint --if-present'
|
|
511
|
+
}}
|
|
512
|
+
if (fileExists('backend/package.json')) {{
|
|
513
|
+
sh 'npm --prefix backend run lint --if-present'
|
|
514
|
+
}}
|
|
515
|
+
if (fileExists('super_dev')) {{
|
|
516
|
+
sh 'ruff check super_dev tests'
|
|
517
|
+
}}
|
|
518
|
+
}}
|
|
519
|
+
}}
|
|
520
|
+
}}
|
|
521
|
+
|
|
522
|
+
stage('Type Check') {{
|
|
523
|
+
steps {{
|
|
524
|
+
script {{
|
|
525
|
+
if (fileExists('frontend/package.json')) {{
|
|
526
|
+
sh 'npm --prefix frontend run type-check --if-present'
|
|
527
|
+
}}
|
|
528
|
+
if (fileExists('backend/package.json')) {{
|
|
529
|
+
sh 'npm --prefix backend run type-check --if-present'
|
|
530
|
+
}}
|
|
531
|
+
if (fileExists('super_dev')) {{
|
|
532
|
+
sh 'mypy super_dev'
|
|
533
|
+
}}
|
|
534
|
+
}}
|
|
535
|
+
}}
|
|
536
|
+
}}
|
|
537
|
+
|
|
538
|
+
stage('Test') {{
|
|
539
|
+
steps {{
|
|
540
|
+
script {{
|
|
541
|
+
if (fileExists('frontend/package.json')) {{
|
|
542
|
+
sh 'npm --prefix frontend run test --if-present'
|
|
543
|
+
}}
|
|
544
|
+
if (fileExists('backend/package.json')) {{
|
|
545
|
+
sh 'npm --prefix backend run test --if-present'
|
|
546
|
+
}}
|
|
547
|
+
if (fileExists('tests')) {{
|
|
548
|
+
sh 'pytest -q'
|
|
549
|
+
}}
|
|
550
|
+
}}
|
|
551
|
+
}}
|
|
552
|
+
post {{
|
|
553
|
+
always {{
|
|
554
|
+
echo 'Tests finished'
|
|
555
|
+
}}
|
|
556
|
+
}}
|
|
557
|
+
}}
|
|
558
|
+
|
|
559
|
+
stage('Security Scan') {{
|
|
560
|
+
steps {{
|
|
561
|
+
sh 'trivy fs --format json --output trivy-results.json .'
|
|
562
|
+
recordIssues toolexpression: [tool: 'trivy'])
|
|
563
|
+
}}
|
|
564
|
+
}}
|
|
565
|
+
|
|
566
|
+
stage('Build Docker Image') {{
|
|
567
|
+
when {{
|
|
568
|
+
anyOf {{
|
|
569
|
+
branch 'main'
|
|
570
|
+
branch 'develop'
|
|
571
|
+
}}
|
|
572
|
+
}}
|
|
573
|
+
steps {{
|
|
574
|
+
script {{
|
|
575
|
+
docker.build("${{DOCKER_IMAGE}}:${{DOCKER_TAG}}")
|
|
576
|
+
docker.withRegistry("https://your-registry", "docker-credentials") {{
|
|
577
|
+
docker.image("${{DOCKER_IMAGE}}:${{DOCKER_TAG}}").push()
|
|
578
|
+
docker.image("${{DOCKER_IMAGE}}:${{DOCKER_TAG}}").push("latest")
|
|
579
|
+
}}
|
|
580
|
+
}}
|
|
581
|
+
}}
|
|
582
|
+
}}
|
|
583
|
+
|
|
584
|
+
stage('Deploy to Dev') {{
|
|
585
|
+
when {{
|
|
586
|
+
branch 'develop'
|
|
587
|
+
}}
|
|
588
|
+
steps {{
|
|
589
|
+
sh '''
|
|
590
|
+
echo ${{KUBECONFIG_DEV}} > kubeconfig
|
|
591
|
+
kubectl --kubeconfig=kubeconfig apply -f k8s/ -n dev
|
|
592
|
+
kubectl --kubeconfig=kubeconfig set image deployment/{self.name} {self.name}=${{DOCKER_IMAGE}}:${{DOCKER_TAG}} -n dev
|
|
593
|
+
kubectl --kubeconfig=kubeconfig rollout status deployment/{self.name} -n dev
|
|
594
|
+
'''
|
|
595
|
+
}}
|
|
596
|
+
}}
|
|
597
|
+
|
|
598
|
+
stage('Deploy to Prod') {{
|
|
599
|
+
when {{
|
|
600
|
+
branch 'main'
|
|
601
|
+
}}
|
|
602
|
+
steps {{
|
|
603
|
+
input message: 'Deploy to production?', ok: 'Deploy'
|
|
604
|
+
sh '''
|
|
605
|
+
echo ${{KUBECONFIG_PROD}} > kubeconfig
|
|
606
|
+
kubectl --kubeconfig=kubeconfig apply -f k8s/ -n prod
|
|
607
|
+
kubectl --kubeconfig=kubeconfig set image deployment/{self.name} {self.name}=${{DOCKER_IMAGE}}:${{DOCKER_TAG}} -n prod
|
|
608
|
+
kubectl --kubeconfig=kubeconfig rollout status deployment/{self.name} -n prod
|
|
609
|
+
'''
|
|
610
|
+
}}
|
|
611
|
+
}}
|
|
612
|
+
}}
|
|
613
|
+
|
|
614
|
+
post {{
|
|
615
|
+
always {{
|
|
616
|
+
cleanWs()
|
|
617
|
+
}}
|
|
618
|
+
success {{
|
|
619
|
+
emailext(
|
|
620
|
+
subject: "Success: ${{env.JOB_NAME}} - ${{env.BUILD_NUMBER}}",
|
|
621
|
+
body: "Build succeeded!\\n\\n${{env.BUILD_URL}}",
|
|
622
|
+
to: "${{env.CHANGE_AUTHOR_EMAIL}}"
|
|
623
|
+
)
|
|
624
|
+
}}
|
|
625
|
+
failure {{
|
|
626
|
+
emailext(
|
|
627
|
+
subject: "Failed: ${{env.JOB_NAME}} - ${{env.BUILD_NUMBER}}",
|
|
628
|
+
body: "Build failed!\\n\\n${{env.BUILD_URL}}console",
|
|
629
|
+
to: "${{env.CHANGE_AUTHOR_EMAIL}}"
|
|
630
|
+
)
|
|
631
|
+
}}
|
|
632
|
+
}}
|
|
633
|
+
}}
|
|
634
|
+
"""
|
|
635
|
+
|
|
636
|
+
def _generate_dockerfile(self) -> str:
|
|
637
|
+
"""生成 Dockerfile"""
|
|
638
|
+
return f"""# Multi-stage build for {self.name}
|
|
639
|
+
|
|
640
|
+
# ========== Build stage ==========
|
|
641
|
+
FROM node:18-alpine AS builder
|
|
642
|
+
|
|
643
|
+
WORKDIR /app
|
|
644
|
+
|
|
645
|
+
# Install dependencies
|
|
646
|
+
COPY package*.json ./
|
|
647
|
+
RUN npm ci
|
|
648
|
+
|
|
649
|
+
# Copy source and build
|
|
650
|
+
COPY . .
|
|
651
|
+
RUN npm run build
|
|
652
|
+
|
|
653
|
+
# ========== Production stage ==========
|
|
654
|
+
FROM node:18-alpine AS production
|
|
655
|
+
|
|
656
|
+
WORKDIR /app
|
|
657
|
+
|
|
658
|
+
# Install dumb-init for proper signal handling
|
|
659
|
+
RUN apk add --no-cache dumb-init
|
|
660
|
+
|
|
661
|
+
# Create non-root user
|
|
662
|
+
RUN addgroup -g 1001 -S nodejs && \\
|
|
663
|
+
adduser -S nodejs -u 1001
|
|
664
|
+
|
|
665
|
+
# Copy package files and install production dependencies
|
|
666
|
+
COPY package*.json ./
|
|
667
|
+
RUN npm ci --only=production && npm cache clean --force
|
|
668
|
+
|
|
669
|
+
# Copy built assets from builder
|
|
670
|
+
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
|
|
671
|
+
COPY --from=builder --chown=nodejs:nodejs /app/public ./public
|
|
672
|
+
|
|
673
|
+
# Switch to non-root user
|
|
674
|
+
USER nodejs
|
|
675
|
+
|
|
676
|
+
EXPOSE 3000
|
|
677
|
+
|
|
678
|
+
# Use dumb-init to handle signals properly
|
|
679
|
+
ENTRYPOINT ["dumb-init", "--"]
|
|
680
|
+
|
|
681
|
+
# Start the application
|
|
682
|
+
CMD ["node", "dist/main.js"]
|
|
683
|
+
|
|
684
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \\
|
|
685
|
+
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
|
|
686
|
+
"""
|
|
687
|
+
|
|
688
|
+
def _generate_docker_compose(self) -> str:
|
|
689
|
+
"""生成 docker-compose.yml"""
|
|
690
|
+
return f"""version: '3.8'
|
|
691
|
+
|
|
692
|
+
services:
|
|
693
|
+
# ========== Frontend ==========
|
|
694
|
+
frontend:
|
|
695
|
+
build:
|
|
696
|
+
context: .
|
|
697
|
+
dockerfile: Dockerfile
|
|
698
|
+
target: production
|
|
699
|
+
ports:
|
|
700
|
+
- "3000:3000"
|
|
701
|
+
environment:
|
|
702
|
+
- NODE_ENV=production
|
|
703
|
+
- API_URL=http://api:8080
|
|
704
|
+
depends_on:
|
|
705
|
+
- api
|
|
706
|
+
networks:
|
|
707
|
+
- app-network
|
|
708
|
+
restart: unless-stopped
|
|
709
|
+
|
|
710
|
+
# ========== Backend API ==========
|
|
711
|
+
api:
|
|
712
|
+
build:
|
|
713
|
+
context: ./backend
|
|
714
|
+
dockerfile: Dockerfile
|
|
715
|
+
ports:
|
|
716
|
+
- "8080:8080"
|
|
717
|
+
environment:
|
|
718
|
+
- NODE_ENV=production
|
|
719
|
+
- DATABASE_URL=postgresql://postgres:password@postgres:5432/{self.name}
|
|
720
|
+
- REDIS_URL=redis://redis:6379
|
|
721
|
+
depends_on:
|
|
722
|
+
- postgres
|
|
723
|
+
- redis
|
|
724
|
+
networks:
|
|
725
|
+
- app-network
|
|
726
|
+
restart: unless-stopped
|
|
727
|
+
|
|
728
|
+
# ========== PostgreSQL ==========
|
|
729
|
+
postgres:
|
|
730
|
+
image: postgres:15-alpine
|
|
731
|
+
volumes:
|
|
732
|
+
- postgres-data:/var/lib/postgresql/data
|
|
733
|
+
environment:
|
|
734
|
+
- POSTGRES_DB={self.name}
|
|
735
|
+
- POSTGRES_USER=postgres
|
|
736
|
+
- POSTGRES_PASSWORD=password
|
|
737
|
+
networks:
|
|
738
|
+
- app-network
|
|
739
|
+
restart: unless-stopped
|
|
740
|
+
|
|
741
|
+
# ========== Redis ==========
|
|
742
|
+
redis:
|
|
743
|
+
image: redis:7-alpine
|
|
744
|
+
volumes:
|
|
745
|
+
- redis-data:/data
|
|
746
|
+
networks:
|
|
747
|
+
- app-network
|
|
748
|
+
restart: unless-stopped
|
|
749
|
+
|
|
750
|
+
# ========== Nginx ==========
|
|
751
|
+
nginx:
|
|
752
|
+
image: nginx:alpine
|
|
753
|
+
ports:
|
|
754
|
+
- "80:80"
|
|
755
|
+
- "443:443"
|
|
756
|
+
volumes:
|
|
757
|
+
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
|
758
|
+
- ./ssl:/etc/nginx/ssl:ro
|
|
759
|
+
depends_on:
|
|
760
|
+
- frontend
|
|
761
|
+
- api
|
|
762
|
+
networks:
|
|
763
|
+
- app-network
|
|
764
|
+
restart: unless-stopped
|
|
765
|
+
|
|
766
|
+
networks:
|
|
767
|
+
app-network:
|
|
768
|
+
driver: bridge
|
|
769
|
+
|
|
770
|
+
volumes:
|
|
771
|
+
postgres-data:
|
|
772
|
+
redis-data:
|
|
773
|
+
"""
|
|
774
|
+
|
|
775
|
+
def _generate_dockerignore(self) -> str:
|
|
776
|
+
"""生成 .dockerignore"""
|
|
777
|
+
return """# Dependencies
|
|
778
|
+
node_modules/
|
|
779
|
+
__pycache__/
|
|
780
|
+
*.pyc
|
|
781
|
+
*.pyo
|
|
782
|
+
*.pyd
|
|
783
|
+
.Python
|
|
784
|
+
|
|
785
|
+
# Testing
|
|
786
|
+
.coverage
|
|
787
|
+
.pytest_cache/
|
|
788
|
+
.tox/
|
|
789
|
+
.nox/
|
|
790
|
+
|
|
791
|
+
# Environments
|
|
792
|
+
.env
|
|
793
|
+
.env.local
|
|
794
|
+
.env.*.local
|
|
795
|
+
venv/
|
|
796
|
+
ENV/
|
|
797
|
+
env/
|
|
798
|
+
|
|
799
|
+
# IDE
|
|
800
|
+
.idea/
|
|
801
|
+
.vscode/
|
|
802
|
+
*.swp
|
|
803
|
+
*.swo
|
|
804
|
+
*~
|
|
805
|
+
.DS_Store
|
|
806
|
+
|
|
807
|
+
# Git
|
|
808
|
+
.git/
|
|
809
|
+
.gitignore
|
|
810
|
+
.gitattributes
|
|
811
|
+
|
|
812
|
+
# Documentation
|
|
813
|
+
*.md
|
|
814
|
+
docs/
|
|
815
|
+
|
|
816
|
+
# CI/CD
|
|
817
|
+
.github/
|
|
818
|
+
.gitlab-ci.yml
|
|
819
|
+
Jenkinsfile
|
|
820
|
+
|
|
821
|
+
# Tests
|
|
822
|
+
tests/
|
|
823
|
+
test_*
|
|
824
|
+
*_test.py
|
|
825
|
+
*.test.ts
|
|
826
|
+
*.spec.ts
|
|
827
|
+
|
|
828
|
+
# Logs
|
|
829
|
+
logs/
|
|
830
|
+
*.log
|
|
831
|
+
npm-debug.log*
|
|
832
|
+
|
|
833
|
+
# Misc
|
|
834
|
+
*.bak
|
|
835
|
+
*.tmp
|
|
836
|
+
.cache/
|
|
837
|
+
"""
|
|
838
|
+
|
|
839
|
+
def _generate_k8s_deployment(self) -> str:
|
|
840
|
+
"""生成 Kubernetes Deployment 配置"""
|
|
841
|
+
return f"""apiVersion: apps/v1
|
|
842
|
+
kind: Deployment
|
|
843
|
+
metadata:
|
|
844
|
+
name: {self.name}
|
|
845
|
+
labels:
|
|
846
|
+
app: {self.name}
|
|
847
|
+
version: v1
|
|
848
|
+
spec:
|
|
849
|
+
replicas: 3
|
|
850
|
+
strategy:
|
|
851
|
+
type: RollingUpdate
|
|
852
|
+
rollingUpdate:
|
|
853
|
+
maxSurge: 1
|
|
854
|
+
maxUnavailable: 0
|
|
855
|
+
selector:
|
|
856
|
+
matchLabels:
|
|
857
|
+
app: {self.name}
|
|
858
|
+
template:
|
|
859
|
+
metadata:
|
|
860
|
+
labels:
|
|
861
|
+
app: {self.name}
|
|
862
|
+
version: v1
|
|
863
|
+
spec:
|
|
864
|
+
containers:
|
|
865
|
+
- name: {self.name}
|
|
866
|
+
image: your-registry/{self.name}:latest
|
|
867
|
+
ports:
|
|
868
|
+
- containerPort: 3000
|
|
869
|
+
name: http
|
|
870
|
+
protocol: TCP
|
|
871
|
+
env:
|
|
872
|
+
- name: NODE_ENV
|
|
873
|
+
value: "production"
|
|
874
|
+
- name: DATABASE_URL
|
|
875
|
+
valueFrom:
|
|
876
|
+
secretKeyRef:
|
|
877
|
+
name: {self.name}-secret
|
|
878
|
+
key: database-url
|
|
879
|
+
- name: REDIS_URL
|
|
880
|
+
valueFrom:
|
|
881
|
+
configMapKeyRef:
|
|
882
|
+
name: {self.name}-config
|
|
883
|
+
key: redis-url
|
|
884
|
+
resources:
|
|
885
|
+
requests:
|
|
886
|
+
memory: "256Mi"
|
|
887
|
+
cpu: "250m"
|
|
888
|
+
limits:
|
|
889
|
+
memory: "512Mi"
|
|
890
|
+
cpu: "500m"
|
|
891
|
+
livenessProbe:
|
|
892
|
+
httpGet:
|
|
893
|
+
path: /health
|
|
894
|
+
port: http
|
|
895
|
+
initialDelaySeconds: 30
|
|
896
|
+
periodSeconds: 10
|
|
897
|
+
timeoutSeconds: 5
|
|
898
|
+
failureThreshold: 3
|
|
899
|
+
readinessProbe:
|
|
900
|
+
httpGet:
|
|
901
|
+
path: /ready
|
|
902
|
+
port: http
|
|
903
|
+
initialDelaySeconds: 5
|
|
904
|
+
periodSeconds: 5
|
|
905
|
+
timeoutSeconds: 3
|
|
906
|
+
failureThreshold: 3
|
|
907
|
+
securityContext:
|
|
908
|
+
runAsNonRoot: true
|
|
909
|
+
runAsUser: 1001
|
|
910
|
+
allowPrivilegeEscalation: false
|
|
911
|
+
readOnlyRootFilesystem: true
|
|
912
|
+
capabilities:
|
|
913
|
+
drop:
|
|
914
|
+
- ALL
|
|
915
|
+
"""
|
|
916
|
+
|
|
917
|
+
def _generate_k8s_service(self) -> str:
|
|
918
|
+
"""生成 Kubernetes Service 配置"""
|
|
919
|
+
return f"""apiVersion: v1
|
|
920
|
+
kind: Service
|
|
921
|
+
metadata:
|
|
922
|
+
name: {self.name}
|
|
923
|
+
labels:
|
|
924
|
+
app: {self.name}
|
|
925
|
+
spec:
|
|
926
|
+
type: ClusterIP
|
|
927
|
+
ports:
|
|
928
|
+
- port: 80
|
|
929
|
+
targetPort: http
|
|
930
|
+
protocol: TCP
|
|
931
|
+
name: http
|
|
932
|
+
selector:
|
|
933
|
+
app: {self.name}
|
|
934
|
+
"""
|
|
935
|
+
|
|
936
|
+
def _generate_k8s_ingress(self) -> str:
|
|
937
|
+
"""生成 Kubernetes Ingress 配置"""
|
|
938
|
+
return f"""apiVersion: networking.k8s.io/v1
|
|
939
|
+
kind: Ingress
|
|
940
|
+
metadata:
|
|
941
|
+
name: {self.name}
|
|
942
|
+
annotations:
|
|
943
|
+
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
|
944
|
+
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
|
945
|
+
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
|
|
946
|
+
spec:
|
|
947
|
+
ingressClassName: nginx
|
|
948
|
+
tls:
|
|
949
|
+
- hosts:
|
|
950
|
+
- {self.name}.example.com
|
|
951
|
+
secretName: {self.name}-tls
|
|
952
|
+
rules:
|
|
953
|
+
- host: {self.name}.example.com
|
|
954
|
+
http:
|
|
955
|
+
paths:
|
|
956
|
+
- path: /
|
|
957
|
+
pathType: Prefix
|
|
958
|
+
backend:
|
|
959
|
+
service:
|
|
960
|
+
name: {self.name}
|
|
961
|
+
port:
|
|
962
|
+
number: 80
|
|
963
|
+
"""
|
|
964
|
+
|
|
965
|
+
def _generate_k8s_configmap(self) -> str:
|
|
966
|
+
"""生成 Kubernetes ConfigMap 配置"""
|
|
967
|
+
return f"""apiVersion: v1
|
|
968
|
+
kind: ConfigMap
|
|
969
|
+
metadata:
|
|
970
|
+
name: {self.name}-config
|
|
971
|
+
data:
|
|
972
|
+
log-level: "info"
|
|
973
|
+
redis-url: "redis://redis:6379"
|
|
974
|
+
api-timeout: "30s"
|
|
975
|
+
max-upload-size: "10M"
|
|
976
|
+
"""
|
|
977
|
+
|
|
978
|
+
def _generate_k8s_secret(self) -> str:
|
|
979
|
+
"""生成 Kubernetes Secret 配置"""
|
|
980
|
+
return f"""apiVersion: v1
|
|
981
|
+
kind: Secret
|
|
982
|
+
metadata:
|
|
983
|
+
name: {self.name}-secret
|
|
984
|
+
type: Opaque
|
|
985
|
+
stringData:
|
|
986
|
+
database-url: "postgresql://user:password@postgres:5432/{self.name}"
|
|
987
|
+
jwt-secret: "your-jwt-secret-here"
|
|
988
|
+
api-key: "your-api-key-here"
|
|
989
|
+
"""
|
|
990
|
+
|
|
991
|
+
def _generate_azure(self) -> str:
|
|
992
|
+
"""生成 Azure DevOps Pipelines 配置"""
|
|
993
|
+
return f"""# Azure Pipelines CI/CD for {self.name}
|
|
994
|
+
trigger:
|
|
995
|
+
branches:
|
|
996
|
+
include:
|
|
997
|
+
- main
|
|
998
|
+
- develop
|
|
999
|
+
|
|
1000
|
+
pr:
|
|
1001
|
+
branches:
|
|
1002
|
+
include:
|
|
1003
|
+
- main
|
|
1004
|
+
- develop
|
|
1005
|
+
|
|
1006
|
+
pool:
|
|
1007
|
+
vmImage: 'ubuntu-latest'
|
|
1008
|
+
|
|
1009
|
+
variables:
|
|
1010
|
+
imageName: '{self.name}'
|
|
1011
|
+
dockerRegistry: 'your-registry.azurecr.io'
|
|
1012
|
+
NODE_VERSION: '18'
|
|
1013
|
+
|
|
1014
|
+
stages:
|
|
1015
|
+
# ========== Build and Test ==========
|
|
1016
|
+
- stage: Build
|
|
1017
|
+
displayName: 'Build and Test'
|
|
1018
|
+
jobs:
|
|
1019
|
+
- job: Build
|
|
1020
|
+
displayName: 'Build Job'
|
|
1021
|
+
steps:
|
|
1022
|
+
- checkout: self
|
|
1023
|
+
|
|
1024
|
+
- task: NodeTool@0
|
|
1025
|
+
inputs:
|
|
1026
|
+
versionSpec: '$(NODE_VERSION)'
|
|
1027
|
+
displayName: 'Install Node.js'
|
|
1028
|
+
|
|
1029
|
+
- script: |
|
|
1030
|
+
if [ -f frontend/package.json ]; then
|
|
1031
|
+
npm --prefix frontend ci
|
|
1032
|
+
fi
|
|
1033
|
+
if [ -f backend/package.json ]; then
|
|
1034
|
+
npm --prefix backend ci
|
|
1035
|
+
fi
|
|
1036
|
+
if [ -f requirements.txt ] || [ -f backend/requirements.txt ] || [ -f pyproject.toml ]; then
|
|
1037
|
+
python -m pip install -e ".[dev]"
|
|
1038
|
+
fi
|
|
1039
|
+
displayName: 'Install dependencies'
|
|
1040
|
+
|
|
1041
|
+
- script: |
|
|
1042
|
+
if [ -f frontend/package.json ]; then
|
|
1043
|
+
npm --prefix frontend run lint --if-present
|
|
1044
|
+
npm --prefix frontend run type-check --if-present
|
|
1045
|
+
fi
|
|
1046
|
+
if [ -f backend/package.json ]; then
|
|
1047
|
+
npm --prefix backend run lint --if-present
|
|
1048
|
+
fi
|
|
1049
|
+
if [ -d super_dev ]; then
|
|
1050
|
+
ruff check super_dev tests
|
|
1051
|
+
mypy super_dev
|
|
1052
|
+
fi
|
|
1053
|
+
displayName: 'Run quality checks'
|
|
1054
|
+
|
|
1055
|
+
- script: |
|
|
1056
|
+
if [ -f frontend/package.json ]; then
|
|
1057
|
+
npm --prefix frontend run test --if-present
|
|
1058
|
+
fi
|
|
1059
|
+
if [ -f backend/package.json ]; then
|
|
1060
|
+
npm --prefix backend run test --if-present
|
|
1061
|
+
fi
|
|
1062
|
+
if [ -d tests ]; then
|
|
1063
|
+
pytest -q
|
|
1064
|
+
fi
|
|
1065
|
+
displayName: 'Run tests'
|
|
1066
|
+
|
|
1067
|
+
- task: PublishCodeCoverageResults@1
|
|
1068
|
+
inputs:
|
|
1069
|
+
codeCoverageTool: 'Cobertura'
|
|
1070
|
+
summaryFileLocation: '$(System.DefaultWorkingDirectory)/coverage/cobertura-coverage.xml'
|
|
1071
|
+
displayName: 'Publish coverage'
|
|
1072
|
+
|
|
1073
|
+
- task: PublishTestResults@2
|
|
1074
|
+
inputs:
|
|
1075
|
+
testResultsFiles: '**/junit.xml'
|
|
1076
|
+
testRunTitle: 'Test Results'
|
|
1077
|
+
displayName: 'Publish test results'
|
|
1078
|
+
|
|
1079
|
+
- script: |
|
|
1080
|
+
docker build -t $(dockerRegistry)/$(imageName):$(Build.BuildId) .
|
|
1081
|
+
docker tag $(dockerRegistry)/$(imageName):$(Build.BuildId) $(dockerRegistry)/$(imageName):latest
|
|
1082
|
+
displayName: 'Build Docker image'
|
|
1083
|
+
|
|
1084
|
+
- task: Docker@2
|
|
1085
|
+
displayName: 'Push to ACR'
|
|
1086
|
+
inputs:
|
|
1087
|
+
command: push
|
|
1088
|
+
repository: $(imageName)
|
|
1089
|
+
dockerfile: Dockerfile
|
|
1090
|
+
containerRegistry: 'your-acr-service-connection'
|
|
1091
|
+
tags: |
|
|
1092
|
+
$(Build.BuildId)
|
|
1093
|
+
latest
|
|
1094
|
+
|
|
1095
|
+
# ========== Deploy to Dev ==========
|
|
1096
|
+
- stage: DeployDev
|
|
1097
|
+
displayName: 'Deploy to Dev'
|
|
1098
|
+
dependsOn: Build
|
|
1099
|
+
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
|
|
1100
|
+
jobs:
|
|
1101
|
+
- deployment: DeployDev
|
|
1102
|
+
environment: 'dev'
|
|
1103
|
+
strategy:
|
|
1104
|
+
runOnce:
|
|
1105
|
+
deploy:
|
|
1106
|
+
steps:
|
|
1107
|
+
- task: KubernetesManifest@0
|
|
1108
|
+
displayName: 'Deploy to Dev'
|
|
1109
|
+
inputs:
|
|
1110
|
+
action: 'deploy'
|
|
1111
|
+
kubernetesServiceConnection: 'dev-k8s-connection'
|
|
1112
|
+
manifests: |
|
|
1113
|
+
k8s/deployment.yaml
|
|
1114
|
+
k8s/service.yaml
|
|
1115
|
+
containers: |
|
|
1116
|
+
$(imageName):$(Build.BuildId)
|
|
1117
|
+
|
|
1118
|
+
# ========== Deploy to Prod ==========
|
|
1119
|
+
- stage: DeployProd
|
|
1120
|
+
displayName: 'Deploy to Production'
|
|
1121
|
+
dependsOn: Build
|
|
1122
|
+
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
|
|
1123
|
+
jobs:
|
|
1124
|
+
- deployment: DeployProd
|
|
1125
|
+
environment: 'production'
|
|
1126
|
+
strategy:
|
|
1127
|
+
runOnce:
|
|
1128
|
+
deploy:
|
|
1129
|
+
steps:
|
|
1130
|
+
- task: KubernetesManifest@0
|
|
1131
|
+
displayName: 'Deploy to Production'
|
|
1132
|
+
inputs:
|
|
1133
|
+
action: 'deploy'
|
|
1134
|
+
kubernetesServiceConnection: 'prod-k8s-connection'
|
|
1135
|
+
manifests: |
|
|
1136
|
+
k8s/deployment.yaml
|
|
1137
|
+
k8s/service.yaml
|
|
1138
|
+
k8s/ingress.yaml
|
|
1139
|
+
containers: |
|
|
1140
|
+
$(imageName):$(Build.BuildId)
|
|
1141
|
+
"""
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
def _generate_bitbucket(self) -> str:
|
|
1145
|
+
"""生成 Bitbucket Pipelines 配置"""
|
|
1146
|
+
return f"""# Bitbucket Pipelines CI/CD for {self.name}
|
|
1147
|
+
image: node:18
|
|
1148
|
+
|
|
1149
|
+
definitions:
|
|
1150
|
+
services:
|
|
1151
|
+
postgres:
|
|
1152
|
+
image: postgres:15
|
|
1153
|
+
variables:
|
|
1154
|
+
POSTGRES_DB: test_db
|
|
1155
|
+
POSTGRES_PASSWORD: postgres
|
|
1156
|
+
redis:
|
|
1157
|
+
image: redis:7
|
|
1158
|
+
|
|
1159
|
+
caches:
|
|
1160
|
+
node_frontend: frontend/node_modules
|
|
1161
|
+
node_backend: backend/node_modules
|
|
1162
|
+
|
|
1163
|
+
pipelines:
|
|
1164
|
+
branches:
|
|
1165
|
+
main:
|
|
1166
|
+
- step:
|
|
1167
|
+
name: 'Build and Test'
|
|
1168
|
+
caches:
|
|
1169
|
+
- node_frontend
|
|
1170
|
+
- node_backend
|
|
1171
|
+
services:
|
|
1172
|
+
- postgres
|
|
1173
|
+
- redis
|
|
1174
|
+
script:
|
|
1175
|
+
- if [ -f frontend/package.json ]; then npm --prefix frontend ci; fi
|
|
1176
|
+
- if [ -f backend/package.json ]; then npm --prefix backend ci; fi
|
|
1177
|
+
- if [ -f frontend/package.json ]; then npm --prefix frontend run lint --if-present; npm --prefix frontend run type-check --if-present; fi
|
|
1178
|
+
- if [ -f backend/package.json ]; then npm --prefix backend run lint --if-present; fi
|
|
1179
|
+
- if [ -f frontend/package.json ]; then npm --prefix frontend run test --if-present; fi
|
|
1180
|
+
- if [ -f backend/package.json ]; then npm --prefix backend run test --if-present; fi
|
|
1181
|
+
after-script:
|
|
1182
|
+
- pipe: atlassian/code-insights:0.5.0
|
|
1183
|
+
variables:
|
|
1184
|
+
REPORT_TYPE: 'coverage'
|
|
1185
|
+
FORMAT: 'cobertura'
|
|
1186
|
+
FILE: 'coverage/cobertura-coverage.xml'
|
|
1187
|
+
|
|
1188
|
+
- step:
|
|
1189
|
+
name: 'Build Docker Image'
|
|
1190
|
+
script:
|
|
1191
|
+
- docker build -t ${{REGISTRY_URL}}/{self.name}:${{BITBUCKET_BUILD_NUMBER}} .
|
|
1192
|
+
- docker push ${{REGISTRY_URL}}/{self.name}:${{BITBUCKET_BUILD_NUMBER}}
|
|
1193
|
+
services:
|
|
1194
|
+
- docker
|
|
1195
|
+
|
|
1196
|
+
- step:
|
|
1197
|
+
name: 'Deploy to Production'
|
|
1198
|
+
deployment: production
|
|
1199
|
+
script:
|
|
1200
|
+
- pipe: atlassian/kubectl-deploy:1.7.0
|
|
1201
|
+
variables:
|
|
1202
|
+
KUBE_CONFIG: ${{KUBE_CONFIG_PROD}}
|
|
1203
|
+
KUBECTL_VERSION: '1.28.0'
|
|
1204
|
+
RESOURCE_PATH: 'k8s/'
|
|
1205
|
+
SELECTOR: 'app={self.name}'
|
|
1206
|
+
CONTAINER: '{self.name}'
|
|
1207
|
+
IMAGE: ${{REGISTRY_URL}}/{self.name}:${{BITBUCKET_BUILD_NUMBER}}
|
|
1208
|
+
|
|
1209
|
+
develop:
|
|
1210
|
+
- step:
|
|
1211
|
+
name: 'Build and Test'
|
|
1212
|
+
caches:
|
|
1213
|
+
- node_frontend
|
|
1214
|
+
- node_backend
|
|
1215
|
+
services:
|
|
1216
|
+
- postgres
|
|
1217
|
+
- redis
|
|
1218
|
+
script:
|
|
1219
|
+
- if [ -f frontend/package.json ]; then npm --prefix frontend ci; fi
|
|
1220
|
+
- if [ -f backend/package.json ]; then npm --prefix backend ci; fi
|
|
1221
|
+
- if [ -f frontend/package.json ]; then npm --prefix frontend run lint --if-present; npm --prefix frontend run type-check --if-present; fi
|
|
1222
|
+
- if [ -f backend/package.json ]; then npm --prefix backend run lint --if-present; fi
|
|
1223
|
+
- if [ -f frontend/package.json ]; then npm --prefix frontend run test --if-present; fi
|
|
1224
|
+
- if [ -f backend/package.json ]; then npm --prefix backend run test --if-present; fi
|
|
1225
|
+
|
|
1226
|
+
- step:
|
|
1227
|
+
name: 'Build Docker Image'
|
|
1228
|
+
script:
|
|
1229
|
+
- docker build -t ${{REGISTRY_URL}}/{self.name}:${{BITBUCKET_BUILD_NUMBER}} .
|
|
1230
|
+
- docker push ${{REGISTRY_URL}}/{self.name}:${{BITBUCKET_BUILD_NUMBER}}
|
|
1231
|
+
services:
|
|
1232
|
+
- docker
|
|
1233
|
+
|
|
1234
|
+
- step:
|
|
1235
|
+
name: 'Deploy to Dev'
|
|
1236
|
+
deployment: development
|
|
1237
|
+
script:
|
|
1238
|
+
- pipe: atlassian/kubectl-deploy:1.7.0
|
|
1239
|
+
variables:
|
|
1240
|
+
KUBE_CONFIG: ${{KUBE_CONFIG_DEV}}
|
|
1241
|
+
KUBECTL_VERSION: '1.28.0'
|
|
1242
|
+
RESOURCE_PATH: 'k8s/'
|
|
1243
|
+
SELECTOR: 'app={self.name}'
|
|
1244
|
+
CONTAINER: '{self.name}'
|
|
1245
|
+
IMAGE: ${{REGISTRY_URL}}/{self.name}:${{BITBUCKET_BUILD_NUMBER}}
|
|
1246
|
+
|
|
1247
|
+
pull-requests:
|
|
1248
|
+
- step:
|
|
1249
|
+
name: 'PR Build and Test'
|
|
1250
|
+
caches:
|
|
1251
|
+
- node_frontend
|
|
1252
|
+
- node_backend
|
|
1253
|
+
services:
|
|
1254
|
+
- postgres
|
|
1255
|
+
- redis
|
|
1256
|
+
script:
|
|
1257
|
+
- if [ -f frontend/package.json ]; then npm --prefix frontend ci; fi
|
|
1258
|
+
- if [ -f backend/package.json ]; then npm --prefix backend ci; fi
|
|
1259
|
+
- if [ -f frontend/package.json ]; then npm --prefix frontend run lint --if-present; npm --prefix frontend run type-check --if-present; fi
|
|
1260
|
+
- if [ -f backend/package.json ]; then npm --prefix backend run lint --if-present; fi
|
|
1261
|
+
- if [ -f frontend/package.json ]; then npm --prefix frontend run test --if-present; fi
|
|
1262
|
+
- if [ -f backend/package.json ]; then npm --prefix backend run test --if-present; fi
|
|
1263
|
+
after-script:
|
|
1264
|
+
- pipe: atlassian/code-insights:0.5.0
|
|
1265
|
+
variables:
|
|
1266
|
+
REPORT_TYPE: 'coverage'
|
|
1267
|
+
FORMAT: 'cobertura'
|
|
1268
|
+
FILE: 'coverage/cobertura-coverage.xml'
|
|
1269
|
+
"""
|