mirror of
https://github.com/frappe/frappe_docker.git
synced 2026-06-17 13:55:08 +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