# GitLab CI/CD

Skill cha: [tdshift-paas](https://app.tdshift.info/skill)

Nguyên tắc: **GitLab CI/CD build và push image; PaaS chỉ pull và chạy.** PaaS không build trên server production. Hướng dẫn đầy đủ (Dockerfile, CI, tự điền cấu hình và deploy qua API): [build-image-rieng](https://app.tdshift.info/skill/build-image-rieng.md).

## Build và push image bằng GitLab CI

Ví dụ `.gitlab-ci.yml` trong repo ứng dụng (dùng biến có sẵn của GitLab):

```yaml
build:
  image: docker:27
  services: [docker:27-dind]
  script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
    - docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
    - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
```

Sau đó:
1. Trong Compose của project, đặt `image: registry.gitlab.com/<group>/<repo>:<tag>` đúng tag đã push.
2. Image private: tạo Deploy Token trên GitLab có quyền `read_registry`, nhập vào **Private Container Registry** ([deploy-compose](https://app.tdshift.info/skill/deploy-compose.md)).
3. Deploy lại bằng **Deploy Compose Stack ↗**, hoặc qua Agent API: `PUT .../image` với tag mới rồi `POST .../deploy`.

## Deployment API (tự động deploy từ CI)

Một lệnh gọi sau khi CI push image: đổi image (tuỳ chọn) và deploy lại Compose đã lưu của project.

- **Endpoint:** `POST https://app.tdshift.info/api/v1/projects/<uuid>/deploy` (hiển thị ở khu **Deployment API & Token** trên trang project Compose; `/build` là đường dẫn cũ, cùng hành vi).
- **Token:** **Personal API Token** của **chủ project**, tạo ở trang **Tài khoản** (`https://app.tdshift.info/account`, xem [tai-khoan](https://app.tdshift.info/skill/tai-khoan.md)). Header `Authorization: Bearer <token>`. Không còn nút tạo token riêng cho từng project; token riêng tạo trước đây vẫn dùng được cho tới khi bấm **Vô hiệu hóa token riêng cũ**.
- **Body JSON (tuỳ chọn):** `{"image":"registry.gitlab.com/<group>/<repo>:<tag>"}` đổi image của web service; thêm `"service":"<tên>"` để đổi service khác. Không gửi body thì deploy lại đúng Compose đã lưu.
- **Kết quả:** 202 `{"status":"accepted","project_id":"<uuid>",...}` rồi deploy chạy nền; theo dõi bằng `GET https://app.tdshift.info/api/v1/agent/projects/<uuid>` tới khi `status` khác `updating` (cùng token).
- **Lỗi:** 401 sai/thiếu token hoặc token của tài khoản khác; 409 đang deploy, không phải project Compose, hoặc chưa lưu Compose lần nào (`Save Compose with PUT /compose first`); 400/415 body sai; 422 service không có trong Compose hoặc image không hợp lệ (không deploy).

Job GitLab CI (thêm `deploy` vào `stages`; biến `PAAS_URL`, `PAAS_PROJECT_ID` = UUID project, `PAAS_API_TOKEN` = Personal API Token dạng **masked + protected**):

```yaml
deploy-paas:
  stage: deploy
  image: curlimages/curl:8.10.1
  needs: [build]
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script:
    - 'curl -fsS -X POST "$PAAS_URL/api/v1/projects/$PAAS_PROJECT_ID/deploy" -H "Authorization: Bearer $PAAS_API_TOKEN" -H "Content-Type: application/json" --data "{\"image\":\"$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA\"}"'
```

Personal API Token có quyền trên **mọi project của tài khoản**: chỉ thiết lập khi người dùng đồng ý; nên tạo một token riêng đặt tên theo repo để thu hồi độc lập. Không đưa token vào repo, log hay chat. Không gửi lặp request khi nhận 409.

## Nguồn Git và webhook (chỉ project WordPress/Odoo cũ)

Project loại WordPress/Odoo tạo trước khi có luồng chọn loại có khu **Cập nhật mã nguồn từ GitLab**:
- **Repo GitLab** (SSH `git@gitlab.com:group/repo.git` cho repo private, hoặc `https://gitlab.com/...` cho repo public), **Nhánh**, **Thư mục nguồn trong repo** → **Lưu cấu hình Git**.
- Thêm **Public Deploy Key của project** vào GitLab → Settings → Repository → Deploy keys (read-only).
- **Pull và triển khai mã nguồn**: lấy commit mới nhất và mount theme/plugin (WordPress) hoặc add-on (Odoo).
- Sau một lần triển khai thành công: **Bật cập nhật tự động** (kiểm tra nhánh định kỳ) và/hoặc **Bật webhook** (GitLab → Settings → Webhooks, Push events, URL và secret hiển thị trên trang).

Project mới chọn Odoo/WordPress là project Compose và không có khu này.
