go-common/app/admin/ep/saga/service/config.go
2019-04-22 18:49:16 +08:00

519 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"encoding/json"
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"time"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
"go-common/library/ecode"
"go-common/library/log"
"github.com/BurntSushi/toml"
)
const (
_configFlag = "\r\n"
_sagaConfigFlag = "[[property.repos]]"
)
const (
_svenConfigAppName = "app_name"
_svenConfigEnv = "env"
_svenConfigZone = "zone"
_svenConfigTreeID = "tree_id"
_svenConfigToken = "token"
_svenConfigBuild = "build"
_svenConfigUser = "user"
_svenConfigData = "data"
_svenConfigNames = "names"
_svenConfigMark = "mark"
_svenConfigConfigIDs = "config_ids"
_svenConfigForce = "force"
_svenConfigIncrement = "increment"
)
const (
_formatStr = ` %s=%s`
_formatStrQuo = ` %s="%s"`
_formatValue = ` %s=%v`
_formatInt = ` %s=%d`
)
const (
_defaultBranch = "master"
_defaultLockTimeout = 600
)
const (
_repoURL = "URL"
_repoGroup = "Group"
_repoName = "Name"
_repoLanguage = "Language"
_repoLockTimeout = "LockTimeout"
_repoAuthBranches = "AuthBranches"
_repoTargetBranches = "TargetBranches"
)
var (
sagaConfigCnName = []string{
"仓库地址",
"仓库组名",
"仓库名称",
"仓库别名",
"开发语言",
"权限分支",
"目标分支",
"MR锁定超时时间(s)",
"最少review人数",
"是否关联pipeline",
"自动合并",
"权限限制",
"准入标签",
"超级权限用户",
}
sagaConfigMark = []string{
"仓库地址",
"仓库组名",
"仓库名称",
"仓库别名",
"仓库使用语言",
"saga的权限管控将以此分支配置的CONTRIBUTORS.md为准即使CONTRIBUTORS.md在其他分支上更改了也都会以此分支的鉴权信息为准。",
"配置的分支可以触发saga行为如配置 targetBranches为master、release分支则MR的目标分支为master或release时都能触发saga行为。支持通配",
"每个仓库在每个时间点只能允许一个MR在合并。MR合并时会取得一个锁并在其合并结束后将其释放如果获取的锁MR在lockTimeout时间内都未能结束则会认为超时并将锁自动释放这样其他MR才能有机会获取到独享锁及合并的机会。",
"最终合并前除了需要owner点赞外还需通过权限文件配置的Reviewer中minReviewer数量的人点赞后可合并。",
"配置后saga将会检查pipeline执行结果并会在最后merge前再次retry pipeline。不配置的话saga不会对pipeline执行结果进行判断。",
"此配置以 relatePipeline 为基础打开后saga在MR最终合并前将不再retry pipeline并且 +mr 时如果pipeline还在运行中待pipeline运行通过后MR将会自动合并。",
"打开后owner的权限将只限定在当前目录即如果子目录配置了owner等信息根目录的owner等将不能再管控子目录。",
"如果配置了标签saga只合入打了此label的MR。",
"如果有配置原来的鉴权文件CONTRIBUTORS.md将会失效需要合并的MR都必须通过super users的review。super users本身也拥有+mr直接合并的权利。",
}
)
// SagaUserList ...
func (s *Service) SagaUserList(c context.Context) (resp []string, err error) {
resp = conf.Conf.Property.Sven.SagaConfigsParam.UserList
return
}
// QueryAllConfigFile ...
func (s *Service) QueryAllConfigFile(c context.Context, sessionID string, isSaga bool) (resp *model.ConfigData, err error) {
var (
url = conf.Conf.Property.Sven.Configs + "?app_name=%s&tree_id=%s&env=%s&zone=%s&build_id=%s"
sagaConfig = conf.Conf.Property.Sven.SagaConfigsParam
runnerConfig = conf.Conf.Property.Sven.ConfigsParam
)
if isSaga {
url = fmt.Sprintf(url, sagaConfig.AppName, strconv.Itoa(sagaConfig.TreeID), sagaConfig.Env, sagaConfig.Zone, strconv.Itoa(sagaConfig.BuildId))
} else {
url = fmt.Sprintf(url, runnerConfig.AppName, strconv.Itoa(runnerConfig.TreeID), runnerConfig.Env, runnerConfig.Zone, strconv.Itoa(runnerConfig.BuildId))
}
return s.dao.QueryAllConfigFile(c, sessionID, url)
}
// QueryConfigFileContent ...
func (s *Service) QueryConfigFileContent(c context.Context, sessionID string) (content string, err error) {
var (
url = conf.Conf.Property.Sven.ConfigValue
fileName = conf.Conf.Property.Sven.SagaConfigsParam.FileName
configs *model.ConfigData
)
if configs, err = s.QueryAllConfigFile(c, sessionID, true); err != nil {
return
}
for _, confValue := range configs.BuildFiles {
if confValue.Name == fileName {
log.Info("QueryConfigFileContent get config name: %s", fileName)
id := strconv.Itoa(confValue.ID)
url = fmt.Sprintf(url+"?config_id=%s", id)
if content, err = s.dao.QueryConfigFileContent(c, sessionID, url); err != nil {
return
}
}
}
return
}
// QueryProjectSagaConfig ...
func (s *Service) QueryProjectSagaConfig(c context.Context, sessionID string, projectID int) (sagaConfig *model.RepoConfig, err error) {
var (
projectInfo *model.ProjectInfo
content string
)
if content, err = s.QueryConfigFileContent(c, sessionID); err != nil {
return
}
index := strings.Index(content, _sagaConfigFlag)
if index < 0 {
return
}
content = content[index:]
log.Info("QueryProjectSagaConfig content: %s", content)
Conf := &model.Config{}
if _, err = toml.Decode(content, &Conf); err != nil {
log.Error("QueryProjectSagaConfig Decode err(%+v)", err)
return
}
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
log.Error("QueryProjectSagaConfig ProjectInfoByID err(%+v)", err)
return
}
projectUrl := strings.Replace(projectInfo.Repo, "git-test", "git", 1)
for _, r := range Conf.Property.Repos {
if r.URL == projectUrl {
sagaConfig = r
return
}
}
return
}
// UpdateConfig ...
func (s *Service) UpdateConfig(c context.Context, sessionID, user, configFileName, configContent, mark string, isSaga bool) (resp *model.CommonResp, err error) {
var (
reqUrl = conf.Conf.Property.Sven.ConfigUpdate
params = url.Values{}
)
if isSaga {
params.Set(_svenConfigAppName, conf.Conf.Property.Sven.SagaConfigsParam.AppName)
params.Set(_svenConfigEnv, conf.Conf.Property.Sven.SagaConfigsParam.Env)
params.Set(_svenConfigZone, conf.Conf.Property.Sven.SagaConfigsParam.Zone)
params.Set(_svenConfigTreeID, strconv.Itoa(conf.Conf.Property.Sven.SagaConfigsParam.TreeID))
params.Set(_svenConfigToken, conf.Conf.Property.Sven.SagaConfigsParam.Token)
} else {
params.Set(_svenConfigAppName, conf.Conf.Property.Sven.ConfigsParam.AppName)
params.Set(_svenConfigEnv, conf.Conf.Property.Sven.ConfigsParam.Env)
params.Set(_svenConfigZone, conf.Conf.Property.Sven.ConfigsParam.Zone)
params.Set(_svenConfigTreeID, strconv.Itoa(conf.Conf.Property.Sven.ConfigsParam.TreeID))
params.Set(_svenConfigToken, conf.Conf.Property.Sven.ConfigsParam.Token)
}
data := `[{"name":"%s","comment":"%s","mark":"%s"}]`
data = fmt.Sprintf(data, configFileName, configContent, mark)
params.Set(_svenConfigData, data)
params.Set(_svenConfigUser, user)
log.Info("UpdateConfig params:%v", params)
if resp, err = s.dao.RequestConfig(c, sessionID, reqUrl, params); err != nil {
return
}
if resp.Code == ecode.OK.Code() && resp.Message == "0" {
log.Info("RequestConfig success")
resp = nil
}
return
}
// PublicConfig ...
func (s *Service) PublicConfig(c context.Context, sessionID, user, configFileName, mark string, isSaga bool) (resp *model.CommonResp, err error) {
var (
reqUrl = conf.Conf.Property.Sven.TagUpdate
params = url.Values{}
)
if isSaga {
params.Set(_svenConfigAppName, conf.Conf.Property.Sven.SagaConfigsParam.AppName)
params.Set(_svenConfigEnv, conf.Conf.Property.Sven.SagaConfigsParam.Env)
params.Set(_svenConfigZone, conf.Conf.Property.Sven.SagaConfigsParam.Zone)
params.Set(_svenConfigTreeID, strconv.Itoa(conf.Conf.Property.Sven.SagaConfigsParam.TreeID))
params.Set(_svenConfigToken, conf.Conf.Property.Sven.SagaConfigsParam.Token)
params.Set(_svenConfigBuild, conf.Conf.Property.Sven.SagaConfigsParam.Build)
} else {
params.Set(_svenConfigAppName, conf.Conf.Property.Sven.ConfigsParam.AppName)
params.Set(_svenConfigEnv, conf.Conf.Property.Sven.ConfigsParam.Env)
params.Set(_svenConfigZone, conf.Conf.Property.Sven.ConfigsParam.Zone)
params.Set(_svenConfigTreeID, strconv.Itoa(conf.Conf.Property.Sven.ConfigsParam.TreeID))
params.Set(_svenConfigToken, conf.Conf.Property.Sven.ConfigsParam.Token)
params.Set(_svenConfigBuild, conf.Conf.Property.Sven.ConfigsParam.Build)
}
params.Set(_svenConfigForce, strconv.Itoa(conf.Conf.Property.Sven.SagaConfigsParam.Force))
params.Set(_svenConfigIncrement, strconv.Itoa(conf.Conf.Property.Sven.SagaConfigsParam.Increment))
params.Set(_svenConfigMark, mark)
params.Set(_svenConfigUser, user)
params.Set(_svenConfigNames, configFileName)
params.Set(_svenConfigConfigIDs, "")
if resp, err = s.dao.RequestConfig(c, sessionID, reqUrl, params); err != nil {
return
}
if resp.Code == ecode.OK.Code() && resp.Message == "0" {
log.Info("RequestConfig success")
resp = nil
}
return
}
// ParseRequestConfig ...
func (s *Service) ParseRequestConfig(projectInfo *model.ProjectInfo, configs []model.ConfigSagaItem) (requestConfig *model.RepoConfig, requestConfigStr string, err error) {
var (
configStr string
content []byte
)
configStr = doBasicDefault(projectInfo)
for _, config := range configs {
rv := reflect.ValueOf(config.Value)
if !rv.IsValid() {
configStr, _ = doDefault(config.Name, configStr)
continue
}
if rv.Kind() == reflect.Slice {
log.Info("%s is slice", config.Name)
if rv.IsNil() {
configStr, _ = doDefault(config.Name, configStr)
continue
}
if content, err = json.Marshal(config.Value); err != nil {
log.Error("ParseRequestConfig err(%+v)", err)
return
}
configTr := fmt.Sprintf(_formatStr, config.Name, string(content))
configStr = configStr + configTr + _configFlag
} else {
configTr := fmt.Sprintf(_formatValue, config.Name, config.Value)
configStr = configStr + configTr + _configFlag
}
}
log.Info("ParseRequestConfig: %s", configStr)
requestConfigStr = configStr
requestConfig = &model.RepoConfig{}
if _, err = toml.Decode(configStr, &requestConfig); err != nil {
log.Error("ParseRequestConfig toml decode err(%+v)", err)
return
}
return
}
// doBasicDefault ...
func doBasicDefault(projectInfo *model.ProjectInfo) (configStr string) {
var configTr string
configStr = _configFlag
configTr = fmt.Sprintf(_formatStrQuo, _repoURL, projectInfo.Repo)
configStr = configStr + configTr + _configFlag
configTr = fmt.Sprintf(_formatStrQuo, _repoGroup, projectInfo.SpaceName)
configStr = configStr + configTr + _configFlag
configTr = fmt.Sprintf(_formatStrQuo, _repoName, projectInfo.Name)
configStr = configStr + configTr + _configFlag
return configStr
}
// doDefault ...
func doDefault(name, config string) (configStr string, err error) {
var (
content []byte
defaultBr = []string{_defaultBranch}
)
configStr = config
if strings.ToLower(name) == strings.ToLower(_repoLockTimeout) {
configTr := fmt.Sprintf(_formatInt, name, _defaultLockTimeout)
configStr = configStr + configTr + _configFlag
}
if strings.ToLower(name) == strings.ToLower(_repoAuthBranches) || strings.ToLower(name) == strings.ToLower(_repoTargetBranches) {
if content, err = json.Marshal(defaultBr); err != nil {
log.Error("Marshal err(%+v)", err)
return
}
configTr := fmt.Sprintf(_formatStr, name, string(content))
configStr = configStr + configTr + _configFlag
}
return
}
// ParseSvenConfig ...
func (s *Service) ParseSvenConfig(c context.Context, sessionID, projectUrl string) (fileContent, svenConfig string, err error) {
var (
content string
projectConfigs []string
)
if fileContent, err = s.QueryConfigFileContent(c, sessionID); err != nil {
return
}
log.Info("ParseSvenConfig fileContent : %s", fileContent)
index := strings.Index(fileContent, _sagaConfigFlag)
if index < 0 {
log.Warn("ParseSvenConfig not found any config flag: %s", projectUrl)
return
}
content = fileContent[index:]
projectConfigs = strings.Split(content, _sagaConfigFlag)
for i := 0; i < len(projectConfigs); i++ {
if strings.Contains(projectConfigs[i], projectUrl) {
svenConfig = projectConfigs[i]
return
}
}
return
}
// ReplaceConfig ...
func (s *Service) ReplaceConfig(c context.Context, username, sessionID string, projectInfo *model.ProjectInfo, req *model.ConfigList) (newConfig string, err error) {
var (
requestConfig *model.RepoConfig
requestConfigStr string
fileContent string
svenConfig string
)
if requestConfig, requestConfigStr, err = s.ParseRequestConfig(projectInfo, req.Configs); err != nil {
return
}
if fileContent, svenConfig, err = s.ParseSvenConfig(c, sessionID, projectInfo.Repo); err != nil {
return
}
if len(svenConfig) <= 0 {
return
}
index := strings.Index(svenConfig, "#")
if index > 0 {
annotate := svenConfig[index:]
requestConfigStr = requestConfigStr + _configFlag + " " + annotate
}
log.Info("ReplaceConfig requestConfig: %v", requestConfig)
log.Info("ReplaceConfig requestConfigStr: %s", requestConfigStr)
log.Info("ReplaceConfig svenConfig: %s", svenConfig)
newConfig = strings.Replace(fileContent, svenConfig, requestConfigStr, 1)
log.Info("ReplaceConfig newConfig: %s", newConfig)
return
}
// ReleaseSagaConfig ...
func (s *Service) ReleaseSagaConfig(c context.Context, username, sessionID string, req *model.ConfigList) (resp *model.CommonResp, err error) {
var (
configFileName = conf.Conf.Property.Sven.SagaConfigsParam.FileName
sagaConfig *model.RepoConfig
projectInfo *model.ProjectInfo
projectID = req.ProjectID
newConfigContent string
)
if sagaConfig, err = s.QueryProjectSagaConfig(c, sessionID, req.ProjectID); err != nil || sagaConfig == nil {
log.Error("ReleaseSagaConfig err(%+v)", err)
return
}
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
log.Error("ProjectInfoByID err(%+v)", err)
return
}
projectInfo.Repo = strings.Replace(projectInfo.Repo, "git-test", "git", 1)
log.Info("ReleaseSagaConfig query project: %s, sagaConfig: %v", projectInfo.Name, sagaConfig)
if sagaConfig.Name == projectInfo.Name {
if newConfigContent, err = s.ReplaceConfig(c, username, sessionID, projectInfo, req); err != nil {
return
}
year, month, day := time.Now().Date()
monthInt := int(month)
hour := time.Now().Hour()
updateMark := fmt.Sprintf("%s-%d-%d-%d-%d | from saga-admin", username, year, monthInt, day, hour)
newConfigContent = strconv.Quote(newConfigContent)[1 : len(strconv.Quote(newConfigContent))-1]
log.Info("ReleaseSagaConfig newConfig: %s", newConfigContent)
if _, err = s.UpdateConfig(c, sessionID, username, configFileName, newConfigContent, updateMark, true); err != nil {
log.Error("UpdateConfig err(%+v)", err)
return
}
if _, err = s.PublicConfig(c, sessionID, username, configFileName, updateMark, true); err != nil {
log.Error("PublicConfig err(%+v)", err)
return
}
}
return
}
// OptionSaga ...
func (s *Service) OptionSaga(c context.Context, projectID, sessionID string) (resp []*model.OptionSagaItem, err error) {
var sagaConfig *model.RepoConfig
projectIDInt, _ := strconv.Atoi(projectID)
if sagaConfig, err = s.QueryProjectSagaConfig(c, sessionID, projectIDInt); err != nil || sagaConfig == nil {
log.Error("QueryProjectSagaConfig err(%+v)", err)
return
}
t := reflect.TypeOf(sagaConfig)
if t.Kind() != reflect.Ptr {
log.Info("OptionSaga the object is not a Ptr, but it is : %v", t.Kind())
return
}
t = reflect.TypeOf(sagaConfig).Elem()
if t.Kind() != reflect.Struct {
log.Info("OptionSaga the object is not a struct, but it is : %v", t.Kind())
return
}
v := reflect.ValueOf(sagaConfig).Elem()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if f.Name == _repoURL || f.Name == _repoGroup || f.Name == _repoName || f.Name == _repoLanguage {
continue
}
val := v.Field(i).Interface()
log.Info("OptionSaga === %s: %v = %v", f.Name, f.Type, val)
sagaItem := &model.OptionSagaItem{}
sagaItem.Name = f.Name
sagaItem.Value = val
sagaItem.CNName = sagaConfigCnName[i]
sagaItem.Remark = sagaConfigMark[i]
configTr := fmt.Sprintf(`%v`, f.Type)
sagaItem.Type = configTr
resp = append(resp, sagaItem)
}
return
}