feat: config center (#2997)
* chore: config * chore: config * chore: config * chore: config * chore: config * feat: config * fix: config * fix: config * feat: config * feat: config * feat: config * feat: config * feat: config * feat: config * feat: config * feat: config * feat: config * feat: config * feat: config * feat: config * feat: config * feat: config * feat: config * feat: config * feat: config * feat: config * fix: config * fix: config * fix: config * fix: config * fix: config * fix: config * fix: config * fix: config * fix: config * fix: config * fix: config * fix: config * fix: config * fix: config * fix: config * fix: config * fix: config * fix: configpull/3009/head
parent
66abd9e1b9
commit
1110af98ef
@ -0,0 +1,250 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/apistruct"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/discovery/etcd"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/version"
|
||||||
|
"github.com/openimsdk/tools/apiresp"
|
||||||
|
"github.com/openimsdk/tools/errs"
|
||||||
|
"github.com/openimsdk/tools/log"
|
||||||
|
"github.com/openimsdk/tools/utils/runtimeenv"
|
||||||
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigManager struct {
|
||||||
|
imAdminUserID []string
|
||||||
|
config *config.AllConfig
|
||||||
|
client *clientv3.Client
|
||||||
|
configPath string
|
||||||
|
runtimeEnv string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfigManager(IMAdminUserID []string, cfg *config.AllConfig, client *clientv3.Client, configPath string, runtimeEnv string) *ConfigManager {
|
||||||
|
return &ConfigManager{
|
||||||
|
imAdminUserID: IMAdminUserID,
|
||||||
|
config: cfg,
|
||||||
|
client: client,
|
||||||
|
configPath: configPath,
|
||||||
|
runtimeEnv: runtimeEnv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *ConfigManager) CheckAdmin(c *gin.Context) {
|
||||||
|
if err := authverify.CheckAdmin(c, cm.imAdminUserID); err != nil {
|
||||||
|
apiresp.GinError(c, err)
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *ConfigManager) GetConfig(c *gin.Context) {
|
||||||
|
var req apistruct.GetConfigReq
|
||||||
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conf := cm.config.Name2Config(req.ConfigName)
|
||||||
|
if conf == nil {
|
||||||
|
apiresp.GinError(c, errs.ErrArgs.WithDetail("config name not found").Wrap())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(conf)
|
||||||
|
if err != nil {
|
||||||
|
apiresp.GinError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiresp.GinSuccess(c, string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *ConfigManager) GetConfigList(c *gin.Context) {
|
||||||
|
var resp apistruct.GetConfigListResp
|
||||||
|
resp.ConfigNames = cm.config.GetConfigNames()
|
||||||
|
resp.Environment = runtimeenv.PrintRuntimeEnvironment()
|
||||||
|
resp.Version = version.Version
|
||||||
|
|
||||||
|
apiresp.GinSuccess(c, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *ConfigManager) SetConfig(c *gin.Context) {
|
||||||
|
if cm.config.Discovery.Enable != config.ETCD {
|
||||||
|
apiresp.GinError(c, errs.New("only etcd support set config").Wrap())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req apistruct.SetConfigReq
|
||||||
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
switch req.ConfigName {
|
||||||
|
case cm.config.Discovery.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Discovery](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Kafka.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Kafka](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.LocalCache.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.LocalCache](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Log.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Log](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Minio.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Minio](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Mongo.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Mongo](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Notification.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Notification](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.API.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.API](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.CronTask.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.CronTask](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.MsgGateway.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.MsgGateway](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.MsgTransfer.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.MsgTransfer](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Push.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Push](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Auth.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Auth](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Conversation.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Conversation](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Friend.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Friend](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Group.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Group](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Msg.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Msg](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Third.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Third](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.User.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.User](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Redis.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Redis](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Share.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Share](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
case cm.config.Webhooks.GetConfigFileName():
|
||||||
|
err = compareAndSave[config.Webhooks](c, cm.config.Name2Config(req.ConfigName), &req, cm.client)
|
||||||
|
default:
|
||||||
|
apiresp.GinError(c, errs.ErrArgs.Wrap())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiresp.GinSuccess(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareAndSave[T any](c *gin.Context, old any, req *apistruct.SetConfigReq, client *clientv3.Client) error {
|
||||||
|
conf := new(T)
|
||||||
|
err := json.Unmarshal([]byte(req.Data), &conf)
|
||||||
|
if err != nil {
|
||||||
|
return errs.ErrArgs.WithDetail(err.Error()).Wrap()
|
||||||
|
}
|
||||||
|
eq := reflect.DeepEqual(old, conf)
|
||||||
|
if eq {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(conf)
|
||||||
|
if err != nil {
|
||||||
|
return errs.ErrArgs.WithDetail(err.Error()).Wrap()
|
||||||
|
}
|
||||||
|
_, err = client.Put(c, etcd.BuildKey(req.ConfigName), string(data))
|
||||||
|
if err != nil {
|
||||||
|
return errs.WrapMsg(err, "save to etcd failed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *ConfigManager) ResetConfig(c *gin.Context) {
|
||||||
|
go cm.resetConfig(c)
|
||||||
|
apiresp.GinSuccess(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *ConfigManager) resetConfig(c *gin.Context) {
|
||||||
|
txn := cm.client.Txn(c)
|
||||||
|
type initConf struct {
|
||||||
|
old any
|
||||||
|
new any
|
||||||
|
isChanged bool
|
||||||
|
}
|
||||||
|
configMap := map[string]*initConf{
|
||||||
|
cm.config.Discovery.GetConfigFileName(): {old: &cm.config.Discovery, new: new(config.Discovery)},
|
||||||
|
cm.config.Kafka.GetConfigFileName(): {old: &cm.config.Kafka, new: new(config.Kafka)},
|
||||||
|
cm.config.LocalCache.GetConfigFileName(): {old: &cm.config.LocalCache, new: new(config.LocalCache)},
|
||||||
|
cm.config.Log.GetConfigFileName(): {old: &cm.config.Log, new: new(config.Log)},
|
||||||
|
cm.config.Minio.GetConfigFileName(): {old: &cm.config.Minio, new: new(config.Minio)},
|
||||||
|
cm.config.Mongo.GetConfigFileName(): {old: &cm.config.Mongo, new: new(config.Mongo)},
|
||||||
|
cm.config.Notification.GetConfigFileName(): {old: &cm.config.Notification, new: new(config.Notification)},
|
||||||
|
cm.config.API.GetConfigFileName(): {old: &cm.config.API, new: new(config.API)},
|
||||||
|
cm.config.CronTask.GetConfigFileName(): {old: &cm.config.CronTask, new: new(config.CronTask)},
|
||||||
|
cm.config.MsgGateway.GetConfigFileName(): {old: &cm.config.MsgGateway, new: new(config.MsgGateway)},
|
||||||
|
cm.config.MsgTransfer.GetConfigFileName(): {old: &cm.config.MsgTransfer, new: new(config.MsgTransfer)},
|
||||||
|
cm.config.Push.GetConfigFileName(): {old: &cm.config.Push, new: new(config.Push)},
|
||||||
|
cm.config.Auth.GetConfigFileName(): {old: &cm.config.Auth, new: new(config.Auth)},
|
||||||
|
cm.config.Conversation.GetConfigFileName(): {old: &cm.config.Conversation, new: new(config.Conversation)},
|
||||||
|
cm.config.Friend.GetConfigFileName(): {old: &cm.config.Friend, new: new(config.Friend)},
|
||||||
|
cm.config.Group.GetConfigFileName(): {old: &cm.config.Group, new: new(config.Group)},
|
||||||
|
cm.config.Msg.GetConfigFileName(): {old: &cm.config.Msg, new: new(config.Msg)},
|
||||||
|
cm.config.Third.GetConfigFileName(): {old: &cm.config.Third, new: new(config.Third)},
|
||||||
|
cm.config.User.GetConfigFileName(): {old: &cm.config.User, new: new(config.User)},
|
||||||
|
cm.config.Redis.GetConfigFileName(): {old: &cm.config.Redis, new: new(config.Redis)},
|
||||||
|
cm.config.Share.GetConfigFileName(): {old: &cm.config.Share, new: new(config.Share)},
|
||||||
|
cm.config.Webhooks.GetConfigFileName(): {old: &cm.config.Webhooks, new: new(config.Webhooks)},
|
||||||
|
}
|
||||||
|
|
||||||
|
changedKeys := make([]string, 0, len(configMap))
|
||||||
|
for k, v := range configMap {
|
||||||
|
err := config.Load(
|
||||||
|
cm.configPath,
|
||||||
|
k,
|
||||||
|
config.EnvPrefixMap[k],
|
||||||
|
cm.runtimeEnv,
|
||||||
|
v.new,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(c, "load config failed", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v.isChanged = reflect.DeepEqual(v.old, v.new)
|
||||||
|
if !v.isChanged {
|
||||||
|
changedKeys = append(changedKeys, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ops := make([]clientv3.Op, 0)
|
||||||
|
for _, k := range changedKeys {
|
||||||
|
data, err := json.Marshal(configMap[k].new)
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(c, "marshal config failed", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ops = append(ops, clientv3.OpPut(etcd.BuildKey(k), string(data)))
|
||||||
|
}
|
||||||
|
if len(ops) > 0 {
|
||||||
|
txn.Then(ops...)
|
||||||
|
_, err := txn.Commit()
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(c, "commit etcd txn failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *ConfigManager) Restart(c *gin.Context) {
|
||||||
|
go cm.restart(c)
|
||||||
|
apiresp.GinSuccess(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *ConfigManager) restart(c *gin.Context) {
|
||||||
|
time.Sleep(time.Millisecond * 200) // wait for Restart http call return
|
||||||
|
t := time.Now().Unix()
|
||||||
|
_, err := cm.client.Put(c, etcd.BuildKey(etcd.RestartKey), strconv.Itoa(int(t)))
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(c, "restart etcd put key failed", err)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package apistruct
|
||||||
|
|
||||||
|
type GetConfigReq struct {
|
||||||
|
ConfigName string `json:"configName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetConfigListResp struct {
|
||||||
|
Environment string `json:"environment"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
ConfigNames []string `json:"configNames"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SetConfigReq struct {
|
||||||
|
ConfigName string `json:"configName"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
}
|
@ -1,96 +0,0 @@
|
|||||||
// Copyright © 2023 OpenIM. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
FileName string
|
|
||||||
NotificationFileName string
|
|
||||||
ShareFileName string
|
|
||||||
WebhooksConfigFileName string
|
|
||||||
LocalCacheConfigFileName string
|
|
||||||
KafkaConfigFileName string
|
|
||||||
RedisConfigFileName string
|
|
||||||
MongodbConfigFileName string
|
|
||||||
MinioConfigFileName string
|
|
||||||
LogConfigFileName string
|
|
||||||
OpenIMAPICfgFileName string
|
|
||||||
OpenIMCronTaskCfgFileName string
|
|
||||||
OpenIMMsgGatewayCfgFileName string
|
|
||||||
OpenIMMsgTransferCfgFileName string
|
|
||||||
OpenIMPushCfgFileName string
|
|
||||||
OpenIMRPCAuthCfgFileName string
|
|
||||||
OpenIMRPCConversationCfgFileName string
|
|
||||||
OpenIMRPCFriendCfgFileName string
|
|
||||||
OpenIMRPCGroupCfgFileName string
|
|
||||||
OpenIMRPCMsgCfgFileName string
|
|
||||||
OpenIMRPCThirdCfgFileName string
|
|
||||||
OpenIMRPCUserCfgFileName string
|
|
||||||
DiscoveryConfigFilename string
|
|
||||||
)
|
|
||||||
|
|
||||||
var ConfigEnvPrefixMap map[string]string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
FileName = "config.yaml"
|
|
||||||
NotificationFileName = "notification.yml"
|
|
||||||
ShareFileName = "share.yml"
|
|
||||||
WebhooksConfigFileName = "webhooks.yml"
|
|
||||||
LocalCacheConfigFileName = "local-cache.yml"
|
|
||||||
KafkaConfigFileName = "kafka.yml"
|
|
||||||
RedisConfigFileName = "redis.yml"
|
|
||||||
MongodbConfigFileName = "mongodb.yml"
|
|
||||||
MinioConfigFileName = "minio.yml"
|
|
||||||
LogConfigFileName = "log.yml"
|
|
||||||
OpenIMAPICfgFileName = "openim-api.yml"
|
|
||||||
OpenIMCronTaskCfgFileName = "openim-crontask.yml"
|
|
||||||
OpenIMMsgGatewayCfgFileName = "openim-msggateway.yml"
|
|
||||||
OpenIMMsgTransferCfgFileName = "openim-msgtransfer.yml"
|
|
||||||
OpenIMPushCfgFileName = "openim-push.yml"
|
|
||||||
OpenIMRPCAuthCfgFileName = "openim-rpc-auth.yml"
|
|
||||||
OpenIMRPCConversationCfgFileName = "openim-rpc-conversation.yml"
|
|
||||||
OpenIMRPCFriendCfgFileName = "openim-rpc-friend.yml"
|
|
||||||
OpenIMRPCGroupCfgFileName = "openim-rpc-group.yml"
|
|
||||||
OpenIMRPCMsgCfgFileName = "openim-rpc-msg.yml"
|
|
||||||
OpenIMRPCThirdCfgFileName = "openim-rpc-third.yml"
|
|
||||||
OpenIMRPCUserCfgFileName = "openim-rpc-user.yml"
|
|
||||||
DiscoveryConfigFilename = "discovery.yml"
|
|
||||||
|
|
||||||
ConfigEnvPrefixMap = make(map[string]string)
|
|
||||||
fileNames := []string{
|
|
||||||
FileName, NotificationFileName, ShareFileName, WebhooksConfigFileName,
|
|
||||||
KafkaConfigFileName, RedisConfigFileName,
|
|
||||||
MongodbConfigFileName, MinioConfigFileName, LogConfigFileName,
|
|
||||||
OpenIMAPICfgFileName, OpenIMCronTaskCfgFileName, OpenIMMsgGatewayCfgFileName,
|
|
||||||
OpenIMMsgTransferCfgFileName, OpenIMPushCfgFileName, OpenIMRPCAuthCfgFileName,
|
|
||||||
OpenIMRPCConversationCfgFileName, OpenIMRPCFriendCfgFileName, OpenIMRPCGroupCfgFileName,
|
|
||||||
OpenIMRPCMsgCfgFileName, OpenIMRPCThirdCfgFileName, OpenIMRPCUserCfgFileName, DiscoveryConfigFilename,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fileName := range fileNames {
|
|
||||||
envKey := strings.TrimSuffix(strings.TrimSuffix(fileName, ".yml"), ".yaml")
|
|
||||||
envKey = "IMENV_" + envKey
|
|
||||||
envKey = strings.ToUpper(strings.ReplaceAll(envKey, "-", "_"))
|
|
||||||
ConfigEnvPrefixMap[fileName] = envKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
FlagConf = "config_folder_path"
|
|
||||||
FlagTransferIndex = "index"
|
|
||||||
)
|
|
@ -0,0 +1,30 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
var EnvPrefixMap map[string]string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
EnvPrefixMap = make(map[string]string)
|
||||||
|
fileNames := []string{
|
||||||
|
FileName, NotificationFileName, ShareFileName, WebhooksConfigFileName,
|
||||||
|
KafkaConfigFileName, RedisConfigFileName,
|
||||||
|
MongodbConfigFileName, MinioConfigFileName, LogConfigFileName,
|
||||||
|
OpenIMAPICfgFileName, OpenIMCronTaskCfgFileName, OpenIMMsgGatewayCfgFileName,
|
||||||
|
OpenIMMsgTransferCfgFileName, OpenIMPushCfgFileName, OpenIMRPCAuthCfgFileName,
|
||||||
|
OpenIMRPCConversationCfgFileName, OpenIMRPCFriendCfgFileName, OpenIMRPCGroupCfgFileName,
|
||||||
|
OpenIMRPCMsgCfgFileName, OpenIMRPCThirdCfgFileName, OpenIMRPCUserCfgFileName, DiscoveryConfigFilename,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fileName := range fileNames {
|
||||||
|
envKey := strings.TrimSuffix(strings.TrimSuffix(fileName, ".yml"), ".yaml")
|
||||||
|
envKey = "IMENV_" + envKey
|
||||||
|
envKey = strings.ToUpper(strings.ReplaceAll(envKey, "-", "_"))
|
||||||
|
EnvPrefixMap[fileName] = envKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
FlagConf = "config_folder_path"
|
||||||
|
FlagTransferIndex = "index"
|
||||||
|
)
|
@ -0,0 +1,111 @@
|
|||||||
|
package etcd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/openimsdk/tools/errs"
|
||||||
|
"github.com/openimsdk/tools/log"
|
||||||
|
"github.com/openimsdk/tools/utils/datautil"
|
||||||
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConfigKeyPrefix = "/open-im/config/"
|
||||||
|
RestartKey = "restart"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ShutDowns []func() error
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterShutDown(shutDown ...func() error) {
|
||||||
|
ShutDowns = append(ShutDowns, shutDown...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigManager struct {
|
||||||
|
client *clientv3.Client
|
||||||
|
watchConfigNames []string
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildKey(s string) string {
|
||||||
|
return ConfigKeyPrefix + s
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfigManager(client *clientv3.Client, configNames []string) *ConfigManager {
|
||||||
|
return &ConfigManager{
|
||||||
|
client: client,
|
||||||
|
watchConfigNames: datautil.Batch(func(s string) string { return BuildKey(s) }, append(configNames, RestartKey))}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConfigManager) Watch(ctx context.Context) {
|
||||||
|
chans := make([]clientv3.WatchChan, 0, len(c.watchConfigNames))
|
||||||
|
for _, name := range c.watchConfigNames {
|
||||||
|
chans = append(chans, c.client.Watch(ctx, name, clientv3.WithPrefix()))
|
||||||
|
}
|
||||||
|
|
||||||
|
doWatch := func(watchChan clientv3.WatchChan) {
|
||||||
|
for watchResp := range watchChan {
|
||||||
|
if watchResp.Err() != nil {
|
||||||
|
log.ZError(ctx, "watch err", errs.Wrap(watchResp.Err()))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, event := range watchResp.Events {
|
||||||
|
if event.IsModify() {
|
||||||
|
if datautil.Contain(string(event.Kv.Key), c.watchConfigNames...) {
|
||||||
|
c.lock.Lock()
|
||||||
|
err := restartServer(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(ctx, "restart server err", err)
|
||||||
|
}
|
||||||
|
c.lock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ch := range chans {
|
||||||
|
go doWatch(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func restartServer(ctx context.Context) error {
|
||||||
|
exePath, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return errs.New("get executable path fail").Wrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
args := os.Args
|
||||||
|
env := os.Environ()
|
||||||
|
|
||||||
|
cmd := exec.Command(exePath, args[1:]...)
|
||||||
|
cmd.Env = env
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||||
|
}
|
||||||
|
log.ZInfo(ctx, "shutdown server")
|
||||||
|
for _, f := range ShutDowns {
|
||||||
|
if err = f(); err != nil {
|
||||||
|
log.ZError(ctx, "shutdown fail", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.ZInfo(ctx, "restart server")
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return errs.New("restart server fail").Wrap()
|
||||||
|
}
|
||||||
|
log.ZInfo(ctx, "cmd start over")
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in new issue