feat(update): независимые обновления компонентов bj
- internal/release/components.go: список компонентов (ядро bj-server, криптосервис, bj-lk, bj-npr, bj-quikexport) с путём и службой, установленные версии в components.json, расчёт обновлений по версии каждого файла и sha256 (без отката, модули — только если уже установлены на сервере) - cmd/bj-component-install: root-установщик — сам перепроверяет подпись манифеста ключом /etc/bj/update.pub и sha256, копия прежнего файла, перезапуск только затронутых служб - updater: bj-server (без прав root) скачивает в update-staging и передаёт установку root-воркеру (действие component-install); установка в фоне - настройки: таблица компонентов с версиями, «Обновить» и «Обновить всё» - bj-release: версия файла из <файл>.version; bj-keymedia-worker: component-install Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
package release
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func writeComp(t *testing.T, root, name, body string) string {
|
||||
t.Helper()
|
||||
c, _ := ComponentByName(name)
|
||||
p := c.PathAt(root)
|
||||
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(p, []byte(body), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sha, _, err := HashFile(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return sha
|
||||
}
|
||||
|
||||
func state(states []ComponentState, name string) ComponentState {
|
||||
for _, s := range states {
|
||||
if s.Name == name {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ComponentState{}
|
||||
}
|
||||
|
||||
func TestPlanIndependentVersions(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
npr := writeComp(t, root, "bj-npr", "npr 1.0.0")
|
||||
writeComp(t, root, "bj-server", "core old")
|
||||
installed := map[string]Installed{
|
||||
"bj-npr": {Version: "1.0.0", SHA256: npr},
|
||||
"bj-server": {Version: "2.0.0", SHA256: "другой файл"},
|
||||
}
|
||||
m := &Manifest{Version: "2026.09.1", Artifacts: []Artifact{
|
||||
{Name: "bj-server", Version: "2.1.0", SHA256: "new-core"},
|
||||
{Name: "bj-npr", Version: "1.0.0", SHA256: npr},
|
||||
{Name: "bj-quikexport", Version: "1.2.0", SHA256: "qe"},
|
||||
}}
|
||||
st := Plan(root, m, installed, "2.0.0")
|
||||
|
||||
if s := state(st, "bj-npr"); !s.Present || s.Installed != "1.0.0" || !s.SameFile || s.Update {
|
||||
t.Fatalf("bj-npr актуален и не должен обновляться: %+v", s)
|
||||
}
|
||||
// Запись об ядре не совпала по sha — берётся версия работающего bj-server, релиз новее.
|
||||
if s := state(st, "bj-server"); s.Installed != "2.0.0" || s.Available != "2.1.0" || !s.Update {
|
||||
t.Fatalf("ядро должно обновиться до 2.1.0: %+v", s)
|
||||
}
|
||||
// Модуль не установлен на этом сервере — не ставим.
|
||||
if s := state(st, "bj-quikexport"); s.Present || s.Update {
|
||||
t.Fatalf("неустановленный модуль не должен предлагаться: %+v", s)
|
||||
}
|
||||
// Старая версия в релизе не предлагается (без отката).
|
||||
m.Artifacts[1] = Artifact{Name: "bj-npr", Version: "0.9.0", SHA256: "old"}
|
||||
if s := state(Plan(root, m, installed, "2.0.0"), "bj-npr"); s.Update {
|
||||
t.Fatalf("откат на 0.9.0 не должен предлагаться: %+v", s)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user