config.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. package service
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "net/url"
  7. "reflect"
  8. "strconv"
  9. "strings"
  10. "time"
  11. "go-common/app/admin/ep/saga/conf"
  12. "go-common/app/admin/ep/saga/model"
  13. "go-common/library/ecode"
  14. "go-common/library/log"
  15. "github.com/BurntSushi/toml"
  16. )
  17. const (
  18. _configFlag = "\r\n"
  19. _sagaConfigFlag = "[[property.repos]]"
  20. )
  21. const (
  22. _svenConfigAppName = "app_name"
  23. _svenConfigEnv = "env"
  24. _svenConfigZone = "zone"
  25. _svenConfigTreeID = "tree_id"
  26. _svenConfigToken = "token"
  27. _svenConfigBuild = "build"
  28. _svenConfigUser = "user"
  29. _svenConfigData = "data"
  30. _svenConfigNames = "names"
  31. _svenConfigMark = "mark"
  32. _svenConfigConfigIDs = "config_ids"
  33. _svenConfigForce = "force"
  34. _svenConfigIncrement = "increment"
  35. )
  36. const (
  37. _formatStr = ` %s=%s`
  38. _formatStrQuo = ` %s="%s"`
  39. _formatValue = ` %s=%v`
  40. _formatInt = ` %s=%d`
  41. )
  42. const (
  43. _defaultBranch = "master"
  44. _defaultLockTimeout = 600
  45. )
  46. const (
  47. _repoURL = "URL"
  48. _repoGroup = "Group"
  49. _repoName = "Name"
  50. _repoLanguage = "Language"
  51. _repoLockTimeout = "LockTimeout"
  52. _repoAuthBranches = "AuthBranches"
  53. _repoTargetBranches = "TargetBranches"
  54. )
  55. var (
  56. sagaConfigCnName = []string{
  57. "仓库地址",
  58. "仓库组名",
  59. "仓库名称",
  60. "仓库别名",
  61. "开发语言",
  62. "权限分支",
  63. "目标分支",
  64. "MR锁定超时时间(s)",
  65. "最少review人数",
  66. "是否关联pipeline",
  67. "自动合并",
  68. "权限限制",
  69. "准入标签",
  70. "超级权限用户",
  71. }
  72. sagaConfigMark = []string{
  73. "仓库地址",
  74. "仓库组名",
  75. "仓库名称",
  76. "仓库别名",
  77. "仓库使用语言",
  78. "saga的权限管控将以此分支配置的CONTRIBUTORS.md为准,即使CONTRIBUTORS.md在其他分支上更改了,也都会以此分支的鉴权信息为准。",
  79. "配置的分支可以触发saga行为,如配置 targetBranches为master、release分支,则MR的目标分支为master或release时,都能触发saga行为。支持通配",
  80. "每个仓库在每个时间点只能允许一个MR在合并。MR合并时会取得一个锁并在其合并结束后将其释放;如果获取的锁MR在lockTimeout时间内都未能结束,则会认为超时并将锁自动释放,这样其他MR才能有机会获取到独享锁及合并的机会。",
  81. "最终合并前除了需要owner点赞外,还需通过权限文件配置的Reviewer中minReviewer数量的人点赞后可合并。",
  82. "配置后saga将会检查pipeline执行结果,并会在最后merge前再次retry pipeline。不配置的话saga不会对pipeline执行结果进行判断。",
  83. "此配置以 relatePipeline 为基础,打开后saga在MR最终合并前将不再retry pipeline,并且 +mr 时如果pipeline还在运行中,待pipeline运行通过后MR将会自动合并。",
  84. "打开后owner的权限将只限定在当前目录,即如果子目录配置了owner等信息,根目录的owner等将不能再管控子目录。",
  85. "如果配置了标签,saga只合入打了此label的MR。",
  86. "如果有配置,原来的鉴权文件CONTRIBUTORS.md将会失效,需要合并的MR都必须通过super users的review。super users本身也拥有+mr直接合并的权利。",
  87. }
  88. )
  89. // SagaUserList ...
  90. func (s *Service) SagaUserList(c context.Context) (resp []string, err error) {
  91. resp = conf.Conf.Property.Sven.SagaConfigsParam.UserList
  92. return
  93. }
  94. // QueryAllConfigFile ...
  95. func (s *Service) QueryAllConfigFile(c context.Context, sessionID string, isSaga bool) (resp *model.ConfigData, err error) {
  96. var (
  97. url = conf.Conf.Property.Sven.Configs + "?app_name=%s&tree_id=%s&env=%s&zone=%s&build_id=%s"
  98. sagaConfig = conf.Conf.Property.Sven.SagaConfigsParam
  99. runnerConfig = conf.Conf.Property.Sven.ConfigsParam
  100. )
  101. if isSaga {
  102. url = fmt.Sprintf(url, sagaConfig.AppName, strconv.Itoa(sagaConfig.TreeID), sagaConfig.Env, sagaConfig.Zone, strconv.Itoa(sagaConfig.BuildId))
  103. } else {
  104. url = fmt.Sprintf(url, runnerConfig.AppName, strconv.Itoa(runnerConfig.TreeID), runnerConfig.Env, runnerConfig.Zone, strconv.Itoa(runnerConfig.BuildId))
  105. }
  106. return s.dao.QueryAllConfigFile(c, sessionID, url)
  107. }
  108. // QueryConfigFileContent ...
  109. func (s *Service) QueryConfigFileContent(c context.Context, sessionID string) (content string, err error) {
  110. var (
  111. url = conf.Conf.Property.Sven.ConfigValue
  112. fileName = conf.Conf.Property.Sven.SagaConfigsParam.FileName
  113. configs *model.ConfigData
  114. )
  115. if configs, err = s.QueryAllConfigFile(c, sessionID, true); err != nil {
  116. return
  117. }
  118. for _, confValue := range configs.BuildFiles {
  119. if confValue.Name == fileName {
  120. log.Info("QueryConfigFileContent get config name: %s", fileName)
  121. id := strconv.Itoa(confValue.ID)
  122. url = fmt.Sprintf(url+"?config_id=%s", id)
  123. if content, err = s.dao.QueryConfigFileContent(c, sessionID, url); err != nil {
  124. return
  125. }
  126. }
  127. }
  128. return
  129. }
  130. // QueryProjectSagaConfig ...
  131. func (s *Service) QueryProjectSagaConfig(c context.Context, sessionID string, projectID int) (sagaConfig *model.RepoConfig, err error) {
  132. var (
  133. projectInfo *model.ProjectInfo
  134. content string
  135. )
  136. if content, err = s.QueryConfigFileContent(c, sessionID); err != nil {
  137. return
  138. }
  139. index := strings.Index(content, _sagaConfigFlag)
  140. if index < 0 {
  141. return
  142. }
  143. content = content[index:]
  144. log.Info("QueryProjectSagaConfig content: %s", content)
  145. Conf := &model.Config{}
  146. if _, err = toml.Decode(content, &Conf); err != nil {
  147. log.Error("QueryProjectSagaConfig Decode err(%+v)", err)
  148. return
  149. }
  150. if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
  151. log.Error("QueryProjectSagaConfig ProjectInfoByID err(%+v)", err)
  152. return
  153. }
  154. projectUrl := strings.Replace(projectInfo.Repo, "git-test", "git", 1)
  155. for _, r := range Conf.Property.Repos {
  156. if r.URL == projectUrl {
  157. sagaConfig = r
  158. return
  159. }
  160. }
  161. return
  162. }
  163. // UpdateConfig ...
  164. func (s *Service) UpdateConfig(c context.Context, sessionID, user, configFileName, configContent, mark string, isSaga bool) (resp *model.CommonResp, err error) {
  165. var (
  166. reqUrl = conf.Conf.Property.Sven.ConfigUpdate
  167. params = url.Values{}
  168. )
  169. if isSaga {
  170. params.Set(_svenConfigAppName, conf.Conf.Property.Sven.SagaConfigsParam.AppName)
  171. params.Set(_svenConfigEnv, conf.Conf.Property.Sven.SagaConfigsParam.Env)
  172. params.Set(_svenConfigZone, conf.Conf.Property.Sven.SagaConfigsParam.Zone)
  173. params.Set(_svenConfigTreeID, strconv.Itoa(conf.Conf.Property.Sven.SagaConfigsParam.TreeID))
  174. params.Set(_svenConfigToken, conf.Conf.Property.Sven.SagaConfigsParam.Token)
  175. } else {
  176. params.Set(_svenConfigAppName, conf.Conf.Property.Sven.ConfigsParam.AppName)
  177. params.Set(_svenConfigEnv, conf.Conf.Property.Sven.ConfigsParam.Env)
  178. params.Set(_svenConfigZone, conf.Conf.Property.Sven.ConfigsParam.Zone)
  179. params.Set(_svenConfigTreeID, strconv.Itoa(conf.Conf.Property.Sven.ConfigsParam.TreeID))
  180. params.Set(_svenConfigToken, conf.Conf.Property.Sven.ConfigsParam.Token)
  181. }
  182. data := `[{"name":"%s","comment":"%s","mark":"%s"}]`
  183. data = fmt.Sprintf(data, configFileName, configContent, mark)
  184. params.Set(_svenConfigData, data)
  185. params.Set(_svenConfigUser, user)
  186. log.Info("UpdateConfig params:%v", params)
  187. if resp, err = s.dao.RequestConfig(c, sessionID, reqUrl, params); err != nil {
  188. return
  189. }
  190. if resp.Code == ecode.OK.Code() && resp.Message == "0" {
  191. log.Info("RequestConfig success")
  192. resp = nil
  193. }
  194. return
  195. }
  196. // PublicConfig ...
  197. func (s *Service) PublicConfig(c context.Context, sessionID, user, configFileName, mark string, isSaga bool) (resp *model.CommonResp, err error) {
  198. var (
  199. reqUrl = conf.Conf.Property.Sven.TagUpdate
  200. params = url.Values{}
  201. )
  202. if isSaga {
  203. params.Set(_svenConfigAppName, conf.Conf.Property.Sven.SagaConfigsParam.AppName)
  204. params.Set(_svenConfigEnv, conf.Conf.Property.Sven.SagaConfigsParam.Env)
  205. params.Set(_svenConfigZone, conf.Conf.Property.Sven.SagaConfigsParam.Zone)
  206. params.Set(_svenConfigTreeID, strconv.Itoa(conf.Conf.Property.Sven.SagaConfigsParam.TreeID))
  207. params.Set(_svenConfigToken, conf.Conf.Property.Sven.SagaConfigsParam.Token)
  208. params.Set(_svenConfigBuild, conf.Conf.Property.Sven.SagaConfigsParam.Build)
  209. } else {
  210. params.Set(_svenConfigAppName, conf.Conf.Property.Sven.ConfigsParam.AppName)
  211. params.Set(_svenConfigEnv, conf.Conf.Property.Sven.ConfigsParam.Env)
  212. params.Set(_svenConfigZone, conf.Conf.Property.Sven.ConfigsParam.Zone)
  213. params.Set(_svenConfigTreeID, strconv.Itoa(conf.Conf.Property.Sven.ConfigsParam.TreeID))
  214. params.Set(_svenConfigToken, conf.Conf.Property.Sven.ConfigsParam.Token)
  215. params.Set(_svenConfigBuild, conf.Conf.Property.Sven.ConfigsParam.Build)
  216. }
  217. params.Set(_svenConfigForce, strconv.Itoa(conf.Conf.Property.Sven.SagaConfigsParam.Force))
  218. params.Set(_svenConfigIncrement, strconv.Itoa(conf.Conf.Property.Sven.SagaConfigsParam.Increment))
  219. params.Set(_svenConfigMark, mark)
  220. params.Set(_svenConfigUser, user)
  221. params.Set(_svenConfigNames, configFileName)
  222. params.Set(_svenConfigConfigIDs, "")
  223. if resp, err = s.dao.RequestConfig(c, sessionID, reqUrl, params); err != nil {
  224. return
  225. }
  226. if resp.Code == ecode.OK.Code() && resp.Message == "0" {
  227. log.Info("RequestConfig success")
  228. resp = nil
  229. }
  230. return
  231. }
  232. // ParseRequestConfig ...
  233. func (s *Service) ParseRequestConfig(projectInfo *model.ProjectInfo, configs []model.ConfigSagaItem) (requestConfig *model.RepoConfig, requestConfigStr string, err error) {
  234. var (
  235. configStr string
  236. content []byte
  237. )
  238. configStr = doBasicDefault(projectInfo)
  239. for _, config := range configs {
  240. rv := reflect.ValueOf(config.Value)
  241. if !rv.IsValid() {
  242. configStr, _ = doDefault(config.Name, configStr)
  243. continue
  244. }
  245. if rv.Kind() == reflect.Slice {
  246. log.Info("%s is slice", config.Name)
  247. if rv.IsNil() {
  248. configStr, _ = doDefault(config.Name, configStr)
  249. continue
  250. }
  251. if content, err = json.Marshal(config.Value); err != nil {
  252. log.Error("ParseRequestConfig err(%+v)", err)
  253. return
  254. }
  255. configTr := fmt.Sprintf(_formatStr, config.Name, string(content))
  256. configStr = configStr + configTr + _configFlag
  257. } else {
  258. configTr := fmt.Sprintf(_formatValue, config.Name, config.Value)
  259. configStr = configStr + configTr + _configFlag
  260. }
  261. }
  262. log.Info("ParseRequestConfig: %s", configStr)
  263. requestConfigStr = configStr
  264. requestConfig = &model.RepoConfig{}
  265. if _, err = toml.Decode(configStr, &requestConfig); err != nil {
  266. log.Error("ParseRequestConfig toml decode err(%+v)", err)
  267. return
  268. }
  269. return
  270. }
  271. // doBasicDefault ...
  272. func doBasicDefault(projectInfo *model.ProjectInfo) (configStr string) {
  273. var configTr string
  274. configStr = _configFlag
  275. configTr = fmt.Sprintf(_formatStrQuo, _repoURL, projectInfo.Repo)
  276. configStr = configStr + configTr + _configFlag
  277. configTr = fmt.Sprintf(_formatStrQuo, _repoGroup, projectInfo.SpaceName)
  278. configStr = configStr + configTr + _configFlag
  279. configTr = fmt.Sprintf(_formatStrQuo, _repoName, projectInfo.Name)
  280. configStr = configStr + configTr + _configFlag
  281. return configStr
  282. }
  283. // doDefault ...
  284. func doDefault(name, config string) (configStr string, err error) {
  285. var (
  286. content []byte
  287. defaultBr = []string{_defaultBranch}
  288. )
  289. configStr = config
  290. if strings.ToLower(name) == strings.ToLower(_repoLockTimeout) {
  291. configTr := fmt.Sprintf(_formatInt, name, _defaultLockTimeout)
  292. configStr = configStr + configTr + _configFlag
  293. }
  294. if strings.ToLower(name) == strings.ToLower(_repoAuthBranches) || strings.ToLower(name) == strings.ToLower(_repoTargetBranches) {
  295. if content, err = json.Marshal(defaultBr); err != nil {
  296. log.Error("Marshal err(%+v)", err)
  297. return
  298. }
  299. configTr := fmt.Sprintf(_formatStr, name, string(content))
  300. configStr = configStr + configTr + _configFlag
  301. }
  302. return
  303. }
  304. // ParseSvenConfig ...
  305. func (s *Service) ParseSvenConfig(c context.Context, sessionID, projectUrl string) (fileContent, svenConfig string, err error) {
  306. var (
  307. content string
  308. projectConfigs []string
  309. )
  310. if fileContent, err = s.QueryConfigFileContent(c, sessionID); err != nil {
  311. return
  312. }
  313. log.Info("ParseSvenConfig fileContent : %s", fileContent)
  314. index := strings.Index(fileContent, _sagaConfigFlag)
  315. if index < 0 {
  316. log.Warn("ParseSvenConfig not found any config flag: %s", projectUrl)
  317. return
  318. }
  319. content = fileContent[index:]
  320. projectConfigs = strings.Split(content, _sagaConfigFlag)
  321. for i := 0; i < len(projectConfigs); i++ {
  322. if strings.Contains(projectConfigs[i], projectUrl) {
  323. svenConfig = projectConfigs[i]
  324. return
  325. }
  326. }
  327. return
  328. }
  329. // ReplaceConfig ...
  330. func (s *Service) ReplaceConfig(c context.Context, username, sessionID string, projectInfo *model.ProjectInfo, req *model.ConfigList) (newConfig string, err error) {
  331. var (
  332. requestConfig *model.RepoConfig
  333. requestConfigStr string
  334. fileContent string
  335. svenConfig string
  336. )
  337. if requestConfig, requestConfigStr, err = s.ParseRequestConfig(projectInfo, req.Configs); err != nil {
  338. return
  339. }
  340. if fileContent, svenConfig, err = s.ParseSvenConfig(c, sessionID, projectInfo.Repo); err != nil {
  341. return
  342. }
  343. if len(svenConfig) <= 0 {
  344. return
  345. }
  346. index := strings.Index(svenConfig, "#")
  347. if index > 0 {
  348. annotate := svenConfig[index:]
  349. requestConfigStr = requestConfigStr + _configFlag + " " + annotate
  350. }
  351. log.Info("ReplaceConfig requestConfig: %v", requestConfig)
  352. log.Info("ReplaceConfig requestConfigStr: %s", requestConfigStr)
  353. log.Info("ReplaceConfig svenConfig: %s", svenConfig)
  354. newConfig = strings.Replace(fileContent, svenConfig, requestConfigStr, 1)
  355. log.Info("ReplaceConfig newConfig: %s", newConfig)
  356. return
  357. }
  358. // ReleaseSagaConfig ...
  359. func (s *Service) ReleaseSagaConfig(c context.Context, username, sessionID string, req *model.ConfigList) (resp *model.CommonResp, err error) {
  360. var (
  361. configFileName = conf.Conf.Property.Sven.SagaConfigsParam.FileName
  362. sagaConfig *model.RepoConfig
  363. projectInfo *model.ProjectInfo
  364. projectID = req.ProjectID
  365. newConfigContent string
  366. )
  367. if sagaConfig, err = s.QueryProjectSagaConfig(c, sessionID, req.ProjectID); err != nil || sagaConfig == nil {
  368. log.Error("ReleaseSagaConfig err(%+v)", err)
  369. return
  370. }
  371. if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
  372. log.Error("ProjectInfoByID err(%+v)", err)
  373. return
  374. }
  375. projectInfo.Repo = strings.Replace(projectInfo.Repo, "git-test", "git", 1)
  376. log.Info("ReleaseSagaConfig query project: %s, sagaConfig: %v", projectInfo.Name, sagaConfig)
  377. if sagaConfig.Name == projectInfo.Name {
  378. if newConfigContent, err = s.ReplaceConfig(c, username, sessionID, projectInfo, req); err != nil {
  379. return
  380. }
  381. year, month, day := time.Now().Date()
  382. monthInt := int(month)
  383. hour := time.Now().Hour()
  384. updateMark := fmt.Sprintf("%s-%d-%d-%d-%d | from saga-admin", username, year, monthInt, day, hour)
  385. newConfigContent = strconv.Quote(newConfigContent)[1 : len(strconv.Quote(newConfigContent))-1]
  386. log.Info("ReleaseSagaConfig newConfig: %s", newConfigContent)
  387. if _, err = s.UpdateConfig(c, sessionID, username, configFileName, newConfigContent, updateMark, true); err != nil {
  388. log.Error("UpdateConfig err(%+v)", err)
  389. return
  390. }
  391. if _, err = s.PublicConfig(c, sessionID, username, configFileName, updateMark, true); err != nil {
  392. log.Error("PublicConfig err(%+v)", err)
  393. return
  394. }
  395. }
  396. return
  397. }
  398. // OptionSaga ...
  399. func (s *Service) OptionSaga(c context.Context, projectID, sessionID string) (resp []*model.OptionSagaItem, err error) {
  400. var sagaConfig *model.RepoConfig
  401. projectIDInt, _ := strconv.Atoi(projectID)
  402. if sagaConfig, err = s.QueryProjectSagaConfig(c, sessionID, projectIDInt); err != nil || sagaConfig == nil {
  403. log.Error("QueryProjectSagaConfig err(%+v)", err)
  404. return
  405. }
  406. t := reflect.TypeOf(sagaConfig)
  407. if t.Kind() != reflect.Ptr {
  408. log.Info("OptionSaga the object is not a Ptr, but it is : %v", t.Kind())
  409. return
  410. }
  411. t = reflect.TypeOf(sagaConfig).Elem()
  412. if t.Kind() != reflect.Struct {
  413. log.Info("OptionSaga the object is not a struct, but it is : %v", t.Kind())
  414. return
  415. }
  416. v := reflect.ValueOf(sagaConfig).Elem()
  417. for i := 0; i < t.NumField(); i++ {
  418. f := t.Field(i)
  419. if f.Name == _repoURL || f.Name == _repoGroup || f.Name == _repoName || f.Name == _repoLanguage {
  420. continue
  421. }
  422. val := v.Field(i).Interface()
  423. log.Info("OptionSaga === %s: %v = %v", f.Name, f.Type, val)
  424. sagaItem := &model.OptionSagaItem{}
  425. sagaItem.Name = f.Name
  426. sagaItem.Value = val
  427. sagaItem.CNName = sagaConfigCnName[i]
  428. sagaItem.Remark = sagaConfigMark[i]
  429. configTr := fmt.Sprintf(`%v`, f.Type)
  430. sagaItem.Type = configTr
  431. resp = append(resp, sagaItem)
  432. }
  433. return
  434. }