-
Notifications
You must be signed in to change notification settings - Fork 0
264 lines (218 loc) · 8.67 KB
/
ci-cd.yml
File metadata and controls
264 lines (218 loc) · 8.67 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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
name: CI/CD
on:
push:
branches: [main]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
# Static analysis jobs run in parallel
lint:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
cache: 'pip'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install ruff black mypy
- name: Run linting with ruff
run: ruff check .
- name: Run code formatting check with black
run: black --check .
- name: Run type checking with mypy
run: mypy .
# Unit tests (when available)
unit-tests:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
cache: 'pip'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run unit tests
run: |
# TODO: Add unit tests
echo "Unit tests placeholder - add pytest tests later"
# Build Docker image
build-image:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
username: nikolajer
password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: nikolajer/life-as-code-app:${{ github.event.pull_request.head.sha || github.sha }},nikolajer/life-as-code-app:latest
deploy:
needs: [lint, unit-tests, build-image]
runs-on: ubuntu-latest
environment: production # Only production - no staging
permissions:
contents: read
env:
NAMESPACE: life-as-code-production
ENVIRONMENT: production
steps:
- uses: actions/checkout@v4
- name: Setup kubectl and Helm
run: |
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
- name: Configure kubeconfig
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > $HOME/.kube/config
chmod 600 $HOME/.kube/config
- name: Deploy Life-as-Code with Helm
id: helm-deploy
continue-on-error: true
run: |
# Create app namespace with proper labels if it doesn't exist
if ! kubectl get namespace $NAMESPACE 2>/dev/null; then
echo "Creating $NAMESPACE namespace..."
kubectl create namespace $NAMESPACE
kubectl label namespace $NAMESPACE app.kubernetes.io/managed-by=Helm
kubectl annotate namespace $NAMESPACE meta.helm.sh/release-name=life-as-code
kubectl annotate namespace $NAMESPACE meta.helm.sh/release-namespace=$NAMESPACE
fi
IMAGE_TAG="${{ github.event.pull_request.head.sha || github.sha }}"
echo "Deploying with image tag: $IMAGE_TAG"
# Use upgrade --install to preserve PVCs and handle both new installs and upgrades
helm upgrade --install life-as-code ./helm/life-as-code-app \
--namespace $NAMESPACE \
--set namespace=$NAMESPACE \
--set app.image.repository=nikolajer/life-as-code-app \
--set app.image.tag=$IMAGE_TAG \
--set ingress.host=life-as-code.nikolay-eremeev.com \
--set secrets.postgresDb="life_as_code" \
--set secrets.postgresUser="life_as_code_user" \
--set secrets.postgresPassword="${{ secrets.POSTGRES_LIFE_AS_CODE_PASSWORD }}" \
--set secrets.secretKey="${{ secrets.LIFE_AS_CODE_SECRET_KEY }}" \
--set secrets.fernetKey="${{ secrets.LIFE_AS_CODE_FERNET_KEY }}" \
--wait --timeout 5m
- name: Check Migration Logs
if: always()
run: |
echo "Checking for migration job..."
# Give it a moment for the job to be created
sleep 5
# Check if migration job exists
if kubectl get job life-as-code-migrations -n $NAMESPACE 2>/dev/null; then
echo "Migration job found. Monitoring for completion or failure..."
# Monitor job status in a loop to catch failure immediately
for i in $(seq 1 60); do
sleep 5
# Check job status
JOB_STATUS=$(kubectl get job life-as-code-migrations -n $NAMESPACE -o jsonpath='{.status}' 2>/dev/null || echo '{}')
COMPLETED=$(echo "$JOB_STATUS" | jq -r '.succeeded // 0')
FAILED=$(echo "$JOB_STATUS" | jq -r '.failed // 0')
echo "Attempt $i/60: Completed: $COMPLETED, Failed: $FAILED"
# If completed successfully
if [ "$COMPLETED" -gt "0" ]; then
echo "Migration job completed successfully!"
break
fi
# If failed, immediately capture logs before cleanup
if [ "$FAILED" -gt "0" ]; then
echo "Migration job failed! Capturing logs immediately..."
echo ""
echo "=== Migration Job Status ==="
kubectl describe job/life-as-code-migrations -n $NAMESPACE || true
echo ""
echo "=== Migration Pod Logs ==="
kubectl logs job/life-as-code-migrations -n $NAMESPACE --all-containers=true || true
# Also try to get logs from pods directly
echo ""
echo "=== Migration Pod Status ==="
kubectl get pods -l job-name=life-as-code-migrations -n $NAMESPACE || true
echo ""
echo "=== Direct Pod Logs ==="
kubectl logs -l job-name=life-as-code-migrations -n $NAMESPACE --all-containers=true || true
echo "Migration job failed!"
exit 1
fi
done
# If we exit the loop without completion, something's wrong
echo "Migration job monitoring timed out after 5 minutes"
echo ""
echo "=== Final Migration Job Status ==="
kubectl describe job/life-as-code-migrations -n $NAMESPACE || true
echo ""
echo "=== Final Migration Logs ==="
kubectl logs job/life-as-code-migrations -n $NAMESPACE --all-containers=true || true
else
echo "Migration job not found. Helm deployment may have failed."
fi
- name: Check Kubernetes Status
if: steps.helm-deploy.outcome == 'success'
run: |
echo "Checking Kubernetes namespaces and deployments..."
echo "=== Available Namespaces ==="
kubectl get namespaces
echo ""
echo "=== Life-as-Code Deployment Status ==="
kubectl get all -n $NAMESPACE
echo ""
echo "To create admin user manually, run:"
echo "kubectl exec -it deployment/life-as-code -n $NAMESPACE -- python manage_users.py"
- name: Check Deployment Status
if: steps.helm-deploy.outcome == 'failure'
run: |
echo "Helm deployment failed. Checking status..."
kubectl get all -n $NAMESPACE
exit 1
test-deployed:
needs: deploy
runs-on: ubuntu-latest
permissions:
contents: read
env:
APP_URL: https://life-as-code.nikolay-eremeev.com
steps:
- uses: actions/checkout@v4
- name: Test deployed application
run: |
echo "Testing deployed Life-as-Code application..."
# Wait for deployment to be fully ready
sleep 30
# Test basic connectivity
echo "Testing basic connectivity..."
curl -f -s -o /dev/null $APP_URL || {
echo "Failed to connect to $APP_URL"
exit 1
}
echo "✅ Application is responding"
# Test login page
echo "Testing login page..."
curl -s $APP_URL/login | grep -q "Life-as-Code" || {
echo "Login page doesn't contain expected content"
exit 1
}
echo "✅ Login page is working"
echo "🎉 Life-as-Code deployment test completed successfully!"