mirror of
https://github.com/frappe/frappe_docker.git
synced 2026-06-22 07:45:09 +00:00
Add GitHub Actions workflows (pending workflow scope token)
check-app-updates.yml: weekly cron, checks GitHub releases, opens PR on updates build-image.yml: builds frappe-custom to GHCR on apps.json changes README.md: instructions for activating with workflow-scoped PAT To push workflows: generate PAT with repo+workflow scopes and re-push Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
717d19f966
commit
52337216f0
3 changed files with 264 additions and 0 deletions
40
.github/workflows/README.md
vendored
Normal file
40
.github/workflows/README.md
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# GitHub Actions Workflows
|
||||||
|
|
||||||
|
## Активация workflows
|
||||||
|
|
||||||
|
Для публикации workflow-файлов нужен Personal Access Token (PAT) со scope `workflow`.
|
||||||
|
|
||||||
|
### Создание токена:
|
||||||
|
1. GitHub → **Settings** → **Developer settings** → **Personal access tokens** → **Tokens (classic)**
|
||||||
|
2. **Generate new token (classic)**
|
||||||
|
3. Выбрать scopes: ✅ `repo` + ✅ `workflow`
|
||||||
|
4. Скопировать токен
|
||||||
|
|
||||||
|
### Обновление remote URL:
|
||||||
|
```bash
|
||||||
|
cd /home/mkr/frappe-project
|
||||||
|
git remote set-url origin https://<NEW_TOKEN>@github.com/abounoone/frappe_docker.git
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Описание workflows
|
||||||
|
|
||||||
|
### `check-app-updates.yml`
|
||||||
|
- **Когда:** каждый понедельник в 06:00 UTC + ручной запуск
|
||||||
|
- **Что делает:** проверяет новые теги на GitHub для каждого приложения из `apps.json`
|
||||||
|
- **Результат:** создаёт PR с обновлёнными версиями
|
||||||
|
|
||||||
|
### `build-image.yml`
|
||||||
|
- **Когда:** push в `main` с изменениями `apps.json` или `Containerfile`
|
||||||
|
- **Что делает:** собирает `frappe-custom:v16` и пушит в GHCR
|
||||||
|
- **Образ:** `ghcr.io/abounoone/frappe-custom:v16`
|
||||||
|
- **Теги:** `v16`, `v16-YYYYMMDD`, `latest`
|
||||||
|
|
||||||
|
### Использование образа из GHCR в .env:
|
||||||
|
```env
|
||||||
|
CUSTOM_IMAGE=ghcr.io/abounoone/frappe-custom
|
||||||
|
CUSTOM_TAG=v16
|
||||||
|
PULL_POLICY=always
|
||||||
|
```
|
||||||
92
.github/workflows/build-image.yml
vendored
Normal file
92
.github/workflows/build-image.yml
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
name: Build frappe-custom image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- 'apps.json'
|
||||||
|
- 'images/layered/Containerfile'
|
||||||
|
- 'images/custom/Containerfile'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Docker image tag'
|
||||||
|
required: false
|
||||||
|
default: 'v16'
|
||||||
|
frappe_branch:
|
||||||
|
description: 'Frappe branch'
|
||||||
|
required: false
|
||||||
|
default: 'version-16'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set build variables
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
TAG="${{ github.event.inputs.tag || 'v16' }}"
|
||||||
|
FRAPPE_BRANCH="${{ github.event.inputs.frappe_branch || 'version-16' }}"
|
||||||
|
APPS_JSON_B64=$(base64 -w 0 apps.json)
|
||||||
|
DATE=$(date +%Y%m%d)
|
||||||
|
|
||||||
|
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||||
|
echo "frappe_branch=${FRAPPE_BRANCH}" >> $GITHUB_OUTPUT
|
||||||
|
echo "apps_json_b64=${APPS_JSON_B64}" >> $GITHUB_OUTPUT
|
||||||
|
echo "date=${DATE}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
echo "=== Приложения для сборки ==="
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
apps = json.load(open('apps.json'))
|
||||||
|
for a in apps:
|
||||||
|
name = a['url'].split('/')[-1]
|
||||||
|
print(f' - {name} @ {a[\"branch\"]}')
|
||||||
|
"
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: images/layered/Containerfile
|
||||||
|
push: true
|
||||||
|
build-args: |
|
||||||
|
APPS_JSON_BASE64=${{ steps.vars.outputs.apps_json_b64 }}
|
||||||
|
FRAPPE_BRANCH=${{ steps.vars.outputs.frappe_branch }}
|
||||||
|
tags: |
|
||||||
|
ghcr.io/${{ github.repository_owner }}/frappe-custom:${{ steps.vars.outputs.tag }}
|
||||||
|
ghcr.io/${{ github.repository_owner }}/frappe-custom:${{ steps.vars.outputs.tag }}-${{ steps.vars.outputs.date }}
|
||||||
|
ghcr.io/${{ github.repository_owner }}/frappe-custom:latest
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: |
|
||||||
|
echo "## ✅ Образ собран" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| | |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|---|---|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| **Image** | \`ghcr.io/${{ github.repository_owner }}/frappe-custom:${{ steps.vars.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| **Frappe branch** | \`${{ steps.vars.outputs.frappe_branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| **Date tag** | \`${{ steps.vars.outputs.tag }}-${{ steps.vars.outputs.date }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Приложения" >> $GITHUB_STEP_SUMMARY
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
apps = json.load(open('apps.json'))
|
||||||
|
for a in apps:
|
||||||
|
name = a['url'].split('/')[-1]
|
||||||
|
print(f'- \`{name}\` @ \`{a[\"branch\"]}\`')
|
||||||
|
" >> $GITHUB_STEP_SUMMARY
|
||||||
132
.github/workflows/check-app-updates.yml
vendored
Normal file
132
.github/workflows/check-app-updates.yml
vendored
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
name: Check App Updates
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 6 * * 1' # каждый понедельник в 06:00 UTC
|
||||||
|
workflow_dispatch: # ручной запуск
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-updates:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Check for new app versions
|
||||||
|
id: check
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
python3 - <<'PYEOF'
|
||||||
|
import json, urllib.request, os, sys
|
||||||
|
|
||||||
|
def gh(path):
|
||||||
|
url = f"https://api.github.com/{path}"
|
||||||
|
req = urllib.request.Request(url, headers={
|
||||||
|
"Authorization": f"Bearer {os.environ['GH_TOKEN']}",
|
||||||
|
"User-Agent": "frappe-update-checker",
|
||||||
|
"Accept": "application/vnd.github+json"
|
||||||
|
})
|
||||||
|
try:
|
||||||
|
return json.loads(urllib.request.urlopen(req, timeout=10).read())
|
||||||
|
except Exception as e:
|
||||||
|
print(f" API error for {path}: {e}", file=sys.stderr)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def latest_tag(owner, repo):
|
||||||
|
"""Возвращает последний стабильный тег (не pre-release)."""
|
||||||
|
releases = gh(f"repos/{owner}/{repo}/releases?per_page=10")
|
||||||
|
for r in (releases if isinstance(releases, list) else []):
|
||||||
|
if not r.get("prerelease") and not r.get("draft"):
|
||||||
|
return r["tag_name"]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def branch_sha(owner, repo, branch):
|
||||||
|
"""SHA последнего коммита ветки."""
|
||||||
|
data = gh(f"repos/{owner}/{repo}/commits/{branch}")
|
||||||
|
return data.get("sha", "")[:8] if data else ""
|
||||||
|
|
||||||
|
apps = json.load(open("apps.json"))
|
||||||
|
updates = []
|
||||||
|
report_lines = []
|
||||||
|
|
||||||
|
for app in apps:
|
||||||
|
url = app["url"].rstrip("/")
|
||||||
|
branch = app["branch"]
|
||||||
|
name = url.split("/")[-1]
|
||||||
|
owner = url.split("/")[-2]
|
||||||
|
|
||||||
|
tag = latest_tag(owner, name)
|
||||||
|
sha = branch_sha(owner, name, branch)
|
||||||
|
current = app.get("tag") or app.get("branch")
|
||||||
|
|
||||||
|
status = ""
|
||||||
|
if tag and tag != app.get("tag"):
|
||||||
|
status = f"NEW TAG {tag}"
|
||||||
|
if "tag" not in app or app.get("tag") != tag:
|
||||||
|
app["_new_tag"] = tag
|
||||||
|
else:
|
||||||
|
status = f"up-to-date (tag={tag or 'none'}, sha={sha})"
|
||||||
|
|
||||||
|
line = f" {name:20s} branch={branch:12s} {status}"
|
||||||
|
print(line)
|
||||||
|
report_lines.append(line)
|
||||||
|
|
||||||
|
# Записываем отчёт для следующих шагов
|
||||||
|
report = "\n".join(report_lines)
|
||||||
|
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
|
||||||
|
f.write(f"report<<EOF\n{report}\nEOF\n")
|
||||||
|
|
||||||
|
# Проверяем есть ли новые теги
|
||||||
|
has_updates = any("_new_tag" in a for a in apps)
|
||||||
|
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
|
||||||
|
f.write(f"has_updates={'true' if has_updates else 'false'}\n")
|
||||||
|
|
||||||
|
# Обновляем apps.json если есть новые теги
|
||||||
|
if has_updates:
|
||||||
|
for app in apps:
|
||||||
|
if "_new_tag" in app:
|
||||||
|
app["branch"] = app.pop("_new_tag")
|
||||||
|
app.pop("_new_tag", None)
|
||||||
|
with open("apps.json", "w") as f:
|
||||||
|
json.dump(apps, f, indent=2)
|
||||||
|
print("\n✓ apps.json обновлён")
|
||||||
|
else:
|
||||||
|
print("\n✓ Все приложения актуальны")
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
- name: Show update report
|
||||||
|
run: |
|
||||||
|
echo "=== Статус приложений ==="
|
||||||
|
echo "${{ steps.check.outputs.report }}"
|
||||||
|
|
||||||
|
- name: Create Pull Request with updates
|
||||||
|
if: steps.check.outputs.has_updates == 'true'
|
||||||
|
uses: peter-evans/create-pull-request@v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
commit-message: "chore: update app versions in apps.json"
|
||||||
|
branch: auto/update-apps
|
||||||
|
delete-branch: true
|
||||||
|
title: "🔄 Обновление версий приложений Frappe"
|
||||||
|
body: |
|
||||||
|
Автоматическое обновление версий приложений.
|
||||||
|
|
||||||
|
**Изменения в apps.json:**
|
||||||
|
|
||||||
|
```
|
||||||
|
${{ steps.check.outputs.report }}
|
||||||
|
```
|
||||||
|
|
||||||
|
После merge необходимо:
|
||||||
|
1. `make build` — пересобрать образ
|
||||||
|
2. `make update` — задеплоить с миграциями
|
||||||
|
|
||||||
|
> Создано автоматически workflow `check-app-updates`
|
||||||
|
labels: |
|
||||||
|
dependencies
|
||||||
|
automated
|
||||||
Loading…
Reference in a new issue