refactor(ci): extract inline scripts to ci/scripts/
- ci/scripts/read_meta.py legge version|build_from da config.yaml/json - ci/scripts/update_repo.py upserta repository.json - ci/scripts/lint_addon.sh hadolint + yaml/json + shellcheck - ci/scripts/git_push_repo.sh commit e push di repository.json Rimuove writeFile /tmp/ dal Jenkinsfile Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
142
ci/Jenkinsfile
vendored
142
ci/Jenkinsfile
vendored
@@ -210,30 +210,7 @@ pipeline {
|
|||||||
[(addon): {
|
[(addon): {
|
||||||
stage("lint ❯ ${addon}") {
|
stage("lint ❯ ${addon}") {
|
||||||
try {
|
try {
|
||||||
// ---- Dockerfile ----
|
sh "bash ci/scripts/lint_addon.sh ${addon}"
|
||||||
sh "docker run --rm -i hadolint/hadolint < ${addon}/Dockerfile"
|
|
||||||
|
|
||||||
// ---- Config (yaml o json) ----
|
|
||||||
def configYaml = "${addon}/config.yaml"
|
|
||||||
def configJson = "${addon}/config.json"
|
|
||||||
sh """
|
|
||||||
if [ -f ${configYaml} ]; then
|
|
||||||
python3 -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))" ${configYaml}
|
|
||||||
echo "[OK] ${addon}/config.yaml valido"
|
|
||||||
elif [ -f ${configJson} ]; then
|
|
||||||
python3 -c "import json,sys; json.load(open(sys.argv[1]))" ${configJson}
|
|
||||||
echo "[OK] ${addon}/config.json valido"
|
|
||||||
fi
|
|
||||||
"""
|
|
||||||
|
|
||||||
// ---- Shell scripts ----
|
|
||||||
sh """
|
|
||||||
find ${addon}/ -name '*.sh' | while read f; do
|
|
||||||
echo "[INFO] shellcheck: \$f"
|
|
||||||
docker run --rm -v \$PWD:/mnt koalaman/shellcheck:stable /mnt/\$f
|
|
||||||
done
|
|
||||||
"""
|
|
||||||
|
|
||||||
echo "[OK] ${addon}: tutti i check superati"
|
echo "[OK] ${addon}: tutti i check superati"
|
||||||
lintOk.add(addon)
|
lintOk.add(addon)
|
||||||
|
|
||||||
@@ -291,25 +268,7 @@ pipeline {
|
|||||||
[(addon): {
|
[(addon): {
|
||||||
stage("build ❯ ${addon}") {
|
stage("build ❯ ${addon}") {
|
||||||
try {
|
try {
|
||||||
// Legge versione e build_from dalla config (yaml o json)
|
def meta = sh(script: "python3 ci/scripts/read_meta.py ${addon}", returnStdout: true).trim()
|
||||||
def metaScript = """
|
|
||||||
import yaml, json, os, sys
|
|
||||||
|
|
||||||
addon = sys.argv[1]
|
|
||||||
for name in ('config.yaml', 'config.json'):
|
|
||||||
p = os.path.join(addon, name)
|
|
||||||
if os.path.exists(p):
|
|
||||||
with open(p) as f:
|
|
||||||
cfg = yaml.safe_load(f) if name.endswith('.yaml') else json.load(f)
|
|
||||||
version = str(cfg.get('version', 'latest'))
|
|
||||||
build_from = (cfg.get('build_from') or {}).get('amd64', '')
|
|
||||||
print(f'{version}|{build_from}')
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
print('latest|')
|
|
||||||
"""
|
|
||||||
writeFile file: '/tmp/read_meta.py', text: metaScript
|
|
||||||
def meta = sh(script: "python3 /tmp/read_meta.py ${addon}", returnStdout: true).trim()
|
|
||||||
def parts = meta.split('\\|')
|
def parts = meta.split('\\|')
|
||||||
def version = parts[0]
|
def version = parts[0]
|
||||||
def buildFrom = parts.size() > 1 ? parts[1] : ''
|
def buildFrom = parts.size() > 1 ? parts[1] : ''
|
||||||
@@ -381,108 +340,16 @@ print('latest|')
|
|||||||
|
|
||||||
echo "[INFO] Aggiorno repository.json per: ${successAddons}"
|
echo "[INFO] Aggiorno repository.json per: ${successAddons}"
|
||||||
|
|
||||||
def updateScript = '''\
|
sh "python3 ci/scripts/update_repo.py \"${params.GITEA_USER}\" ${successAddons.join(' ')}"
|
||||||
import json, os, sys
|
|
||||||
|
|
||||||
# Carica yaml solo se disponibile (config.yaml), altrimenti json
|
|
||||||
try:
|
|
||||||
import yaml
|
|
||||||
HAS_YAML = True
|
|
||||||
except ImportError:
|
|
||||||
HAS_YAML = False
|
|
||||||
|
|
||||||
REGISTRY = os.environ['REGISTRY']
|
|
||||||
GITEA_URL = os.environ['GITEA_BASE_URL']
|
|
||||||
GITEA_USER = sys.argv[1]
|
|
||||||
addons_arg = sys.argv[2:]
|
|
||||||
|
|
||||||
repo_file = 'repository.json'
|
|
||||||
skeleton = {
|
|
||||||
'name': f'HA Add-ons by {GITEA_USER}',
|
|
||||||
'url': f'{GITEA_URL}/{GITEA_USER}/HomeAssistantAddOns',
|
|
||||||
'maintainer': GITEA_USER,
|
|
||||||
'addons': []
|
|
||||||
}
|
|
||||||
repo = skeleton.copy()
|
|
||||||
if os.path.exists(repo_file):
|
|
||||||
with open(repo_file) as f:
|
|
||||||
repo = json.load(f)
|
|
||||||
repo.setdefault('addons', [])
|
|
||||||
|
|
||||||
def load_cfg(addon_dir):
|
|
||||||
for name, loader in [('config.yaml', 'yaml'), ('config.json', 'json')]:
|
|
||||||
p = os.path.join(addon_dir, name)
|
|
||||||
if not os.path.exists(p):
|
|
||||||
continue
|
|
||||||
with open(p) as f:
|
|
||||||
if loader == 'yaml' and HAS_YAML:
|
|
||||||
return yaml.safe_load(f)
|
|
||||||
return json.load(f)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
changed = False
|
|
||||||
for addon in addons_arg:
|
|
||||||
cfg = load_cfg(addon)
|
|
||||||
if not cfg:
|
|
||||||
print(f'[WARN] Nessuna config trovata per {addon}, skip', flush=True)
|
|
||||||
continue
|
|
||||||
|
|
||||||
slug = cfg.get('slug', addon)
|
|
||||||
version = str(cfg.get('version', 'latest'))
|
|
||||||
entry = {
|
|
||||||
'slug': slug,
|
|
||||||
'name': cfg.get('name', addon),
|
|
||||||
'description': cfg.get('description', ''),
|
|
||||||
'version': version,
|
|
||||||
'url': cfg.get('url', ''),
|
|
||||||
'arch': cfg.get('arch', ['amd64']),
|
|
||||||
'image': f'{REGISTRY}/hassio-addons/{slug}:{version}',
|
|
||||||
}
|
|
||||||
|
|
||||||
idx = next((i for i, a in enumerate(repo['addons']) if a.get('slug') == slug), None)
|
|
||||||
if idx is not None:
|
|
||||||
old_ver = repo['addons'][idx].get('version', '?')
|
|
||||||
repo['addons'][idx] = entry
|
|
||||||
print(f'[UPDATE] {slug}: {old_ver} → {version}', flush=True)
|
|
||||||
else:
|
|
||||||
repo['addons'].append(entry)
|
|
||||||
print(f'[ADD] {slug} v{version}', flush=True)
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if changed:
|
|
||||||
with open(repo_file, 'w') as f:
|
|
||||||
json.dump(repo, f, indent=2, ensure_ascii=False)
|
|
||||||
f.write('\\n')
|
|
||||||
print('[OK] repository.json aggiornato', flush=True)
|
|
||||||
else:
|
|
||||||
print('[INFO] Nessuna modifica a repository.json', flush=True)
|
|
||||||
'''
|
|
||||||
writeFile file: '/tmp/update_repo.py', text: updateScript
|
|
||||||
|
|
||||||
sh "python3 /tmp/update_repo.py \"${params.GITEA_USER}\" ${successAddons.join(' ')}"
|
|
||||||
|
|
||||||
sh 'git diff repository.json || true'
|
sh 'git diff repository.json || true'
|
||||||
|
|
||||||
// Commit e push solo se ci sono modifiche staged
|
|
||||||
withCredentials([usernamePassword(
|
withCredentials([usernamePassword(
|
||||||
credentialsId: 'GIT_TOKEN',
|
credentialsId: 'GIT_TOKEN',
|
||||||
usernameVariable: 'GITEA_PUSH_USR',
|
usernameVariable: 'GITEA_PUSH_USR',
|
||||||
passwordVariable: 'GITEA_PUSH_PSW'
|
passwordVariable: 'GITEA_PUSH_PSW'
|
||||||
)]) {
|
)]) {
|
||||||
sh """
|
sh "bash ci/scripts/git_push_repo.sh \"${params.GITEA_USER}\" \"${env.GITEA_BASE_URL}\""
|
||||||
git config user.email "jenkins@pipelines.mt-home.uk"
|
|
||||||
git config user.name "Jenkins CI"
|
|
||||||
git add repository.json
|
|
||||||
if git diff --staged --quiet; then
|
|
||||||
echo "[INFO] Nessuna modifica a repository.json da committare"
|
|
||||||
else
|
|
||||||
git commit -m "chore: update repository.json [skip ci]"
|
|
||||||
git push \\
|
|
||||||
"https://oauth2:\${GITEA_PUSH_PSW}@git.mt-home.uk/${params.GITEA_USER}/HomeAssistantAddOns.git" \\
|
|
||||||
HEAD:main
|
|
||||||
echo "[OK] repository.json pushato su main"
|
|
||||||
fi
|
|
||||||
"""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -571,7 +438,6 @@ else:
|
|||||||
script {
|
script {
|
||||||
try {
|
try {
|
||||||
sh 'docker logout ${REGISTRY} 2>/dev/null || true'
|
sh 'docker logout ${REGISTRY} 2>/dev/null || true'
|
||||||
sh 'rm -f /tmp/read_meta.py /tmp/update_repo.py'
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
echo "[WARN] cleanup: ${e.message}"
|
echo "[WARN] cleanup: ${e.message}"
|
||||||
}
|
}
|
||||||
|
|||||||
36
ci/scripts/git_push_repo.sh
Normal file
36
ci/scripts/git_push_repo.sh
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# git_push_repo.sh <gitea_user> <gitea_base_url>
|
||||||
|
#
|
||||||
|
# Committa repository.json (se modificato) e lo pusha su main.
|
||||||
|
# Il messaggio include [skip ci] per evitare loop webhook.
|
||||||
|
#
|
||||||
|
# Variabili d'ambiente richieste:
|
||||||
|
# GITEA_PUSH_PSW — password/token Gitea (iniettata da withCredentials)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
GITEA_USER=${1:?Uso: git_push_repo.sh <gitea_user> <gitea_base_url>}
|
||||||
|
GITEA_BASE_URL=${2:?Uso: git_push_repo.sh <gitea_user> <gitea_base_url>}
|
||||||
|
GITEA_PSW=${GITEA_PUSH_PSW:?GITEA_PUSH_PSW non impostata}
|
||||||
|
|
||||||
|
git config user.email "jenkins@pipelines.mt-home.uk"
|
||||||
|
git config user.name "Jenkins CI"
|
||||||
|
|
||||||
|
git add repository.json
|
||||||
|
|
||||||
|
if git diff --staged --quiet; then
|
||||||
|
echo "[INFO] Nessuna modifica a repository.json — niente da committare"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
git commit -m "chore: update repository.json [skip ci]"
|
||||||
|
|
||||||
|
# Rimuove schema (https://) per costruire l'URL con credenziali
|
||||||
|
GITEA_HOST=${GITEA_BASE_URL#https://}
|
||||||
|
GITEA_HOST=${GITEA_HOST#http://}
|
||||||
|
|
||||||
|
git push \
|
||||||
|
"https://oauth2:${GITEA_PSW}@${GITEA_HOST}/${GITEA_USER}/HomeAssistantAddons.git" \
|
||||||
|
HEAD:main
|
||||||
|
|
||||||
|
echo "[OK] repository.json pushato su main"
|
||||||
38
ci/scripts/lint_addon.sh
Normal file
38
ci/scripts/lint_addon.sh
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# lint_addon.sh <addon_dir>
|
||||||
|
#
|
||||||
|
# Esegue tre check su un addon HA:
|
||||||
|
# 1. hadolint — Dockerfile best practices
|
||||||
|
# 2. yaml/json — config.yaml o config.json valido
|
||||||
|
# 3. shellcheck — tutti gli *.sh nella directory
|
||||||
|
#
|
||||||
|
# Esce con codice 0 se tutti i check passano, 1 altrimenti.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ADDON=${1:?Uso: lint_addon.sh <addon_dir>}
|
||||||
|
|
||||||
|
echo "[INFO] === Lint: ${ADDON} ==="
|
||||||
|
|
||||||
|
# ── 1. hadolint ────────────────────────────────────────────────────────────────
|
||||||
|
echo "[INFO] hadolint: ${ADDON}/Dockerfile"
|
||||||
|
docker run --rm -i hadolint/hadolint < "${ADDON}/Dockerfile"
|
||||||
|
|
||||||
|
# ── 2. Config valida ───────────────────────────────────────────────────────────
|
||||||
|
if [ -f "${ADDON}/config.yaml" ]; then
|
||||||
|
python3 -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))" "${ADDON}/config.yaml"
|
||||||
|
echo "[OK] ${ADDON}/config.yaml valido"
|
||||||
|
elif [ -f "${ADDON}/config.json" ]; then
|
||||||
|
python3 -c "import json,sys; json.load(open(sys.argv[1]))" "${ADDON}/config.json"
|
||||||
|
echo "[OK] ${ADDON}/config.json valido"
|
||||||
|
else
|
||||||
|
echo "[WARN] Nessuna config trovata in ${ADDON}/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 3. shellcheck ──────────────────────────────────────────────────────────────
|
||||||
|
find "${ADDON}/" -name '*.sh' | while read -r f; do
|
||||||
|
echo "[INFO] shellcheck: ${f}"
|
||||||
|
docker run --rm -v "$PWD:/mnt" koalaman/shellcheck:stable "/mnt/${f}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "[OK] ${ADDON}: lint completato"
|
||||||
32
ci/scripts/read_meta.py
Normal file
32
ci/scripts/read_meta.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Legge version e build_from.amd64 dalla config di un addon HA.
|
||||||
|
|
||||||
|
Uso: python3 read_meta.py <addon_dir>
|
||||||
|
Output su stdout: version|build_from_amd64
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
HAS_YAML = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_YAML = False
|
||||||
|
|
||||||
|
addon = sys.argv[1]
|
||||||
|
|
||||||
|
for filename in ('config.yaml', 'config.json'):
|
||||||
|
path = os.path.join(addon, filename)
|
||||||
|
if not os.path.exists(path):
|
||||||
|
continue
|
||||||
|
with open(path) as f:
|
||||||
|
cfg = yaml.safe_load(f) if (filename.endswith('.yaml') and HAS_YAML) else json.load(f)
|
||||||
|
version = str(cfg.get('version', 'latest'))
|
||||||
|
build_from = (cfg.get('build_from') or {}).get('amd64', '')
|
||||||
|
print(f'{version}|{build_from}')
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Nessuna config trovata
|
||||||
|
print('latest|')
|
||||||
87
ci/scripts/update_repo.py
Normal file
87
ci/scripts/update_repo.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Aggiorna repository.json con i metadati degli addon buildati con successo.
|
||||||
|
Upserta ogni addon (match su slug). Il file viene creato se non esiste.
|
||||||
|
|
||||||
|
Uso: python3 update_repo.py <gitea_user> <addon1> [addon2 ...]
|
||||||
|
|
||||||
|
Variabili d'ambiente richieste:
|
||||||
|
REGISTRY es. registry.mt-home.uk
|
||||||
|
GITEA_BASE_URL es. https://git.mt-home.uk
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
HAS_YAML = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_YAML = False
|
||||||
|
|
||||||
|
REGISTRY = os.environ['REGISTRY']
|
||||||
|
GITEA_BASE_URL = os.environ['GITEA_BASE_URL']
|
||||||
|
GITEA_USER = sys.argv[1]
|
||||||
|
addons_arg = sys.argv[2:]
|
||||||
|
|
||||||
|
REPO_FILE = 'repository.json'
|
||||||
|
skeleton = {
|
||||||
|
'name': f'HA Add-ons by {GITEA_USER}',
|
||||||
|
'url': f'{GITEA_BASE_URL}/{GITEA_USER}/HomeAssistantAddons',
|
||||||
|
'maintainer': GITEA_USER,
|
||||||
|
'addons': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
repo = skeleton.copy()
|
||||||
|
if os.path.exists(REPO_FILE):
|
||||||
|
with open(REPO_FILE) as f:
|
||||||
|
repo = json.load(f)
|
||||||
|
repo.setdefault('addons', [])
|
||||||
|
|
||||||
|
|
||||||
|
def load_cfg(addon_dir):
|
||||||
|
for name, use_yaml in [('config.yaml', True), ('config.json', False)]:
|
||||||
|
p = os.path.join(addon_dir, name)
|
||||||
|
if not os.path.exists(p):
|
||||||
|
continue
|
||||||
|
with open(p) as f:
|
||||||
|
return yaml.safe_load(f) if (use_yaml and HAS_YAML) else json.load(f)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
for addon in addons_arg:
|
||||||
|
cfg = load_cfg(addon)
|
||||||
|
if not cfg:
|
||||||
|
print(f'[WARN] Nessuna config trovata per {addon}, skip', flush=True)
|
||||||
|
continue
|
||||||
|
|
||||||
|
slug = cfg.get('slug', addon)
|
||||||
|
version = str(cfg.get('version', 'latest'))
|
||||||
|
entry = {
|
||||||
|
'slug': slug,
|
||||||
|
'name': cfg.get('name', addon),
|
||||||
|
'description': cfg.get('description', ''),
|
||||||
|
'version': version,
|
||||||
|
'url': cfg.get('url', ''),
|
||||||
|
'arch': cfg.get('arch', ['amd64']),
|
||||||
|
'image': f'{REGISTRY}/hassio-addons/{slug}:{version}',
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = next((i for i, a in enumerate(repo['addons']) if a.get('slug') == slug), None)
|
||||||
|
if idx is not None:
|
||||||
|
old_ver = repo['addons'][idx].get('version', '?')
|
||||||
|
repo['addons'][idx] = entry
|
||||||
|
print(f'[UPDATE] {slug}: {old_ver} → {version}', flush=True)
|
||||||
|
else:
|
||||||
|
repo['addons'].append(entry)
|
||||||
|
print(f'[ADD] {slug} v{version}', flush=True)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
with open(REPO_FILE, 'w') as f:
|
||||||
|
json.dump(repo, f, indent=2, ensure_ascii=False)
|
||||||
|
f.write('\n')
|
||||||
|
print('[OK] repository.json aggiornato', flush=True)
|
||||||
|
else:
|
||||||
|
print('[INFO] Nessuna modifica a repository.json', flush=True)
|
||||||
Reference in New Issue
Block a user