mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-11 17:56:37 +08:00
refactor(db): use version string as primary key for ConfigVersion
-transition `of_config_versions` primary key from `id` to `version` string. -add database migration files `202606270001_make_version_primary_key.sql` for PostgreSQL and SQLite. -introduce AfterFind/AfterCreate GORM hooks to preserve JSON backward compatibility. -refactor API controllers, logics, and front-end typescript definitions to receive `string` parameter.
This commit is contained in:
@@ -23,7 +23,7 @@ sidebar: false
|
|||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|
||||||
- 修复旧版本迁移升级后,在 PostgreSQL 数据库下发布版本或插入其它新数据时报“版本号生成冲突,请重试”等主键唯一约束冲突的问题。根本原因在于迁移数据时显式指定了旧 ID 导入,导致 PostgreSQL 各核心表的自增主键序列值(sequence)落后于 MAX(id);新增 `202606270001_sync_all_postgres_sequences` 迁移在系统启动时对所有核心表的自增序列执行 `setval` 重新对齐。
|
- 修复旧版本迁移升级后,发布版本报错“版本号生成冲突,请重试”的问题。根本原因是配置版本表 `of_config_versions` 的自增主键 `id` 序列与导入的旧数据冲突;现重构配置版本表,将自增 `id` 移除,改由版本号字符串(如 `20260626-003`)直接作为主键(通过迁移 `202606270001_make_version_primary_key` 完成),并同步修改 Agent 和 Flared 模块中的排序及查询条件,解决删除 `id` 列后心跳上报报 `column "id" does not exist` 的故障。
|
||||||
- 修复代理路由详情页点击“发布配置”时,同时弹出配置差异对话框和确认发布对话框导致重叠的问题:点击发布时不再展示配置差异,直接进行确认发布。
|
- 修复代理路由详情页点击“发布配置”时,同时弹出配置差异对话框和确认发布对话框导致重叠的问题:点击发布时不再展示配置差异,直接进行确认发布。
|
||||||
- 修复配置版本发布到 Agent 后 `openresty -t` 因 `proxy_cache_path` 使用 `/var/cache/openresty` 导致非 root 用户 `mkdir` 失败的问题:发布快照与渲染将 `/var/` 下路径规范为 `__OPENFLARE_PROXY_CACHE_PATH__`,Agent 应用时落地为 `data_dir/var/cache/openflare_proxy` 并兼容重写已发布配置中的旧路径。
|
- 修复配置版本发布到 Agent 后 `openresty -t` 因 `proxy_cache_path` 使用 `/var/cache/openresty` 导致非 root 用户 `mkdir` 失败的问题:发布快照与渲染将 `/var/` 下路径规范为 `__OPENFLARE_PROXY_CACHE_PATH__`,Agent 应用时落地为 `data_dir/var/cache/openflare_proxy` 并兼容重写已发布配置中的旧路径。
|
||||||
- 修复配置版本发布到 Agent 后 `openresty -t` 因证书私钥无法解析而失败的问题。根因是发布快照生成 `certs/{id}.key` 时直接写入库内加密的 `KeyPEM`(`enc:v1:`),未解密为 PEM;现与证书详情接口一致,发布前通过 `OpenKeyPEM` 解密后再下发。
|
- 修复配置版本发布到 Agent 后 `openresty -t` 因证书私钥无法解析而失败的问题。根因是发布快照生成 `certs/{id}.key` 时直接写入库内加密的 `KeyPEM`(`enc:v1:`),未解密为 PEM;现与证书详情接口一致,发布前通过 `OpenKeyPEM` 解密后再下发。
|
||||||
|
|||||||
@@ -8,29 +8,14 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/db"
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
openrestyrender "github.com/Rain-kl/Wavelet/pkg/render/openresty"
|
openrestyrender "github.com/Rain-kl/Wavelet/pkg/render/openresty"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type configVersionRecord struct {
|
|
||||||
ID uint `gorm:"primaryKey"`
|
|
||||||
Version string `gorm:"column:version"`
|
|
||||||
SnapshotJSON string `gorm:"column:snapshot_json"`
|
|
||||||
SupportFilesJSON string `gorm:"column:support_files_json"`
|
|
||||||
Checksum string `gorm:"column:checksum"`
|
|
||||||
IsActive bool `gorm:"column:is_active"`
|
|
||||||
CreatedAt time.Time `gorm:"column:created_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (configVersionRecord) TableName() string {
|
|
||||||
return "of_config_versions"
|
|
||||||
}
|
|
||||||
|
|
||||||
func getActiveConfigMeta(ctx context.Context) (*ActiveConfigMeta, error) {
|
func getActiveConfigMeta(ctx context.Context) (*ActiveConfigMeta, error) {
|
||||||
version, err := loadActiveConfigVersion(ctx)
|
version, err := model.GetActiveConfigVersion(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -41,7 +26,7 @@ func getActiveConfigMeta(ctx context.Context) (*ActiveConfigMeta, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getActiveConfigForAgent(ctx context.Context) (*ConfigResponse, error) {
|
func getActiveConfigForAgent(ctx context.Context) (*ConfigResponse, error) {
|
||||||
version, err := loadActiveConfigVersion(ctx)
|
version, err := model.GetActiveConfigVersion(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -62,19 +47,6 @@ func getActiveConfigForAgent(ctx context.Context) (*ConfigResponse, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadActiveConfigVersion(ctx context.Context) (*configVersionRecord, error) {
|
|
||||||
conn := db.DB(ctx)
|
|
||||||
if conn == nil {
|
|
||||||
return nil, errors.New("database not initialized")
|
|
||||||
}
|
|
||||||
version := &configVersionRecord{}
|
|
||||||
err := conn.Where("is_active = ?", true).Order("id desc").First(version).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return version, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sourceSupportFiles(files []SupportFile) []SupportFile {
|
func sourceSupportFiles(files []SupportFile) []SupportFile {
|
||||||
if len(files) == 0 {
|
if len(files) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ func checksumAgentWAFIPGroup(group WAFIPGroup) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func activeConfigWAFIPGroupIDs(ctx context.Context) ([]uint, error) {
|
func activeConfigWAFIPGroupIDs(ctx context.Context) ([]uint, error) {
|
||||||
version, err := loadActiveConfigVersion(ctx)
|
version, err := model.GetActiveConfigVersion(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isActiveConfigNotFound(err) {
|
if isActiveConfigNotFound(err) {
|
||||||
return []uint{}, nil
|
return []uint{}, nil
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func setupWAFIPGroupTestDB(t *testing.T) func() {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, sqliteDB.AutoMigrate(
|
require.NoError(t, sqliteDB.AutoMigrate(
|
||||||
&model.OpenFlareWAFIPGroup{},
|
&model.OpenFlareWAFIPGroup{},
|
||||||
&configVersionRecord{},
|
&model.ConfigVersion{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
db.SetDB(sqliteDB)
|
||||||
@@ -55,7 +55,7 @@ func seedActiveConfigWithWAFIPGroup(t *testing.T, ctx context.Context, ipGroupID
|
|||||||
snapshotJSON, err := json.Marshal(snapshot)
|
snapshotJSON, err := json.Marshal(snapshot)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.NoError(t, db.DB(ctx).Create(&configVersionRecord{
|
require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{
|
||||||
Version: "20260618-001",
|
Version: "20260618-001",
|
||||||
SnapshotJSON: string(snapshotJSON),
|
SnapshotJSON: string(snapshotJSON),
|
||||||
Checksum: "test-checksum",
|
Checksum: "test-checksum",
|
||||||
|
|||||||
@@ -5,16 +5,13 @@ package flared
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/apps/openflare/relay"
|
"github.com/Rain-kl/Wavelet/internal/apps/openflare/relay"
|
||||||
"github.com/Rain-kl/Wavelet/internal/db"
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/model"
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -22,15 +19,6 @@ const (
|
|||||||
defaultTunnelTargetPort = 80
|
defaultTunnelTargetPort = 80
|
||||||
)
|
)
|
||||||
|
|
||||||
type configVersionRow struct {
|
|
||||||
Version string `gorm:"column:version"`
|
|
||||||
Checksum string `gorm:"column:checksum"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (configVersionRow) TableName() string {
|
|
||||||
return "of_config_versions"
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeReleaseChannel(channel string) string {
|
func normalizeReleaseChannel(channel string) string {
|
||||||
if strings.ToLower(strings.TrimSpace(channel)) == "preview" {
|
if strings.ToLower(strings.TrimSpace(channel)) == "preview" {
|
||||||
return "preview"
|
return "preview"
|
||||||
@@ -63,16 +51,7 @@ func normalizeFlaredHeartbeatPayload(payload HeartbeatPayload) HeartbeatPayload
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getActiveConfigMeta(ctx context.Context) (*ActiveConfigMeta, error) {
|
func getActiveConfigMeta(ctx context.Context) (*ActiveConfigMeta, error) {
|
||||||
conn := db.DB(ctx)
|
version, err := model.GetActiveConfigVersion(ctx)
|
||||||
if conn == nil {
|
|
||||||
return nil, errors.New("database not initialized")
|
|
||||||
}
|
|
||||||
if !conn.Migrator().HasTable(&configVersionRow{}) {
|
|
||||||
return nil, gorm.ErrRecordNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
var version configVersionRow
|
|
||||||
err := conn.Where("is_active = ?", true).Order("id desc").First(&version).Error
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func GetTunnelConfig(ctx context.Context, node *model.OpenFlareNode) (*TunnelCon
|
|||||||
return nil, fmt.Errorf("node is nil")
|
return nil, fmt.Errorf("node is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
activeVersion, err := getActiveConfigVersion(ctx)
|
activeVersion, err := getActiveConfigMeta(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("no active config version: %w", err)
|
return nil, fmt.Errorf("no active config version: %w", err)
|
||||||
}
|
}
|
||||||
@@ -232,21 +232,6 @@ func updateFlaredNodeFromApplyLogTx(tx *gorm.DB, payload ApplyLogPayload, now ti
|
|||||||
return tx.Model(&node).Select("status", "last_seen_at", "current_version", "last_error").Updates(&node).Error
|
return tx.Model(&node).Select("status", "last_seen_at", "current_version", "last_error").Updates(&node).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func getActiveConfigVersion(ctx context.Context) (*configVersionRow, error) {
|
|
||||||
conn := db.DB(ctx)
|
|
||||||
if conn == nil {
|
|
||||||
return nil, errors.New("database not initialized")
|
|
||||||
}
|
|
||||||
if !conn.Migrator().HasTable(&configVersionRow{}) {
|
|
||||||
return nil, gorm.ErrRecordNotFound
|
|
||||||
}
|
|
||||||
var version configVersionRow
|
|
||||||
if err := conn.Where("is_active = ?", true).Order("id desc").First(&version).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &version, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeApplyLogPayload(payload ApplyLogPayload) ApplyLogPayload {
|
func normalizeApplyLogPayload(payload ApplyLogPayload) ApplyLogPayload {
|
||||||
payload.NodeID = strings.TrimSpace(payload.NodeID)
|
payload.NodeID = strings.TrimSpace(payload.NodeID)
|
||||||
payload.Version = strings.TrimSpace(payload.Version)
|
payload.Version = strings.TrimSpace(payload.Version)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ func setupFlaredObservabilityTestDB(t *testing.T) func() {
|
|||||||
&model.OpenFlareNode{},
|
&model.OpenFlareNode{},
|
||||||
&model.OpenFlareHealthEvent{},
|
&model.OpenFlareHealthEvent{},
|
||||||
&model.SystemConfig{},
|
&model.SystemConfig{},
|
||||||
|
&model.ConfigVersion{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
db.SetDB(sqliteDB)
|
||||||
|
|||||||
@@ -20,19 +20,6 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type configVersionRecord struct {
|
|
||||||
ID uint `gorm:"primaryKey"`
|
|
||||||
Version string `gorm:"column:version"`
|
|
||||||
SnapshotJSON string `gorm:"column:snapshot_json"`
|
|
||||||
SupportFilesJSON string `gorm:"column:support_files_json"`
|
|
||||||
Checksum string `gorm:"column:checksum"`
|
|
||||||
IsActive bool `gorm:"column:is_active"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (configVersionRecord) TableName() string {
|
|
||||||
return "of_config_versions"
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupProtocolTestEnv(t *testing.T) (*gin.Engine, func()) {
|
func setupProtocolTestEnv(t *testing.T) (*gin.Engine, func()) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -46,7 +33,7 @@ func setupProtocolTestEnv(t *testing.T) (*gin.Engine, func()) {
|
|||||||
&model.OpenFlareApplyLog{},
|
&model.OpenFlareApplyLog{},
|
||||||
&model.OpenFlareNodeSystemProfile{},
|
&model.OpenFlareNodeSystemProfile{},
|
||||||
&model.OpenFlareHealthEvent{},
|
&model.OpenFlareHealthEvent{},
|
||||||
&configVersionRecord{},
|
&model.ConfigVersion{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
db.SetDB(sqliteDB)
|
||||||
|
|||||||
Reference in New Issue
Block a user