三方仓库如何实现Zadig流水线自动触发

频道:行业资讯 日期: 浏览:201
最近因为公司的产研调整,决定将代码仓库从本地的 Gitlab 迁移到云效的 Codeup,不是 Gitlab 不够好,而是 Codeup 在度量、安全等方面比原生的 Gitlab 要好,再则公司的产研管理也迁移到了云效,也为了统一化管理。

有同学可能会问,都用云效了,为什么不直接用它的 AppStack,还要用 Zadig?

AppStack 还处于发展阶段,还有以下问题不适合我们现阶段的需求:

AppStack 不支持管理私有云 Kubernetes 集群(没有公网入口)。

AppStack 不支持 Helm 类应用,改造工作比较大。

再则,我也是 Zadig 开源产品的忠实粉丝~~!

但是,Zadig 对非标准的代码仓库的支持力度有限,比如:

非标准代码仓库不支持列出仓库列表,需要自己手动填写

非标准代码仓库创建的流水线原生不支持 Webhook 触发

经过综合考虑,手动填写代码仓库信息以及不支持 Webhook 并不影响整体的使用,只是会影响部分项目的工作效率。

但是,为了最小程度上影响原有的产研节奏,我还是准备自己实现三方仓库的 Webhook 触发 Zadig 流水线。因为本身也不复杂。

整体思路

实现不复杂,也就是接收到 webhook 触发动作,解析内容,根据需要触发相应的流水线接口。截至目前(v1.17.0)zadig 的触发流水线接口已经可以正常使用了。

开始搬砖

封装 Zadig API

首先封装一下 Zadig 的 API。为了方便使用,之前的弄了一个 go-zadig 项目(https://github.com/joker-bai/go-zadig),这两天将其做了更新,支持最新的 1.17.0 版本的 API。

主要增加了以下内容:

复制

// 执行工作流

type ExecWorkflowTaskOptions struct {

WorkflowName string `json:"workflow_name"`

ProjectName string `json:"project_name"`

Input WorkflowInput `json:"input"`

}

type WorkflowInput struct {

TargetEnv string `json:"target_env,omitempty"`

Build ExecBuildArgs `json:"build"`

Deploy ExecDeployArgs `json:"deploy"`

}

type ExecBuildArgs struct {

Enabled bool `json:"enabled"`

ServiceList []BuildServiceInfo `json:"service_list"`

}

type BuildServiceInfo struct {

ServiceModule string `json:"service_module"`

ServiceName string `json:"service_name"`

RepoInfo []RepositoryInfo `json:"repo_info"`

Inputs []UserInput `json:"inputs"`

}

type RepositoryInfo struct {

CodehostName string `json:"codehost_name"`

RepoNamespace string `json:"repo_namespace"`

RepoName string `json:"repo_name"`

Branch string `json:"branch"`

PR int `json:"pr"`

}

type UserInput struct {

Key string `json:"key"`

Value string `json:"value"`

}

type ExecDeployArgs struct {

Enabled bool `json:"enabled"`

Source string `json:"source"`

ServiceList []DeployServiceInfo `json:"service_list"`

}

type DeployServiceInfo struct {

ServiceModule string `json:"service_module"`

ServiceName string `json:"service_name"`

Image string `json:"image"`

}

type ExecWorkflowTaskResponse struct {

ProjectName string `json:"project_name,omitempty"`

WorkflowName string `json:"workflow_name,omitempty"`

TaskID int64 `json:"task_id,omitempty"`

}

func (w *WorkflowService) ExecWorkflowTask(opt *ExecWorkflowTaskOptions, options ...RequestOptionFunc) (*ExecWorkflowTaskResponse, *Response, error) {

path := "openapi/workflows/product/task"

req, err := w.client.NewRequest(http.MethodPost, path, opt, options)

if err != nil {

return nil, nil, err

}

task := new(ExecWorkflowTaskResponse)

resp, err := w.client.Do(req, &task)

if err != nil {

return nil, resp, err

}

return task, resp, err

}

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

21.

22.

23.

24.

25.

26.

27.

28.

29.

30.

31.

32.

33.

34.

35.

36.

37.

38.

39.

40.

41.

42.

43.

44.

45.

46.

47.

48.

49.

50.

51.

52.

53.

54.

55.

56.

57.

58.

59.

60.

61.

62.

63.

64.

65.

66.

67.

68.

69.

70.

71.

这部分是执行标准工作流的接口。自定义工作流之前已经实现了,并没有什么变化。

开发 Http Server

由于 Zadig 原生不支持三方仓库的 Webhook,要实现不外乎两种:

自己修改 Zadig 源码,实现这部分功能。

找一个中间商,由它来协调。

修改源码的好处是可以不需要再单独对数据这块做太多处理,直接用现成的。但我选择了后者,主要是因为菜,源码改起来费劲。

?我使用的是 Uber 的 fx 框架。其实用什么框架不重要,本身的逻辑就很简单,我只是选了一个用起来比较简单和顺手的。

(1)定义数据结构

复制

package entity

type ZadigWorkflowTask struct {

ID int `json:"id"`

ProjectName string `json:"project_name"` // 项目名

ServiceModule string `json:"service_module"` // 服务组件名称

ServiceName string `json:"service_name"` // 服务名

CodehostName string `json:"codehost_name"` // 代码源别名

RepoNamespace string `json:"repo_namespace"` // 仓库组名

RepoName string `json:"repo_name"` // 仓库名

WorkflowType string `json:"workflow_type"` // 工作流类型: product/custom

JobName string `json:"job_name"` // 任务名 workflow_type为custom生效

JobType string `json:"job_type"` // 任务类型 workflow_type为custom生效

Registry string `json:"registry"` // 镜像仓库 workflow_type为custom生效

}

func (z *ZadigWorkflowTask) Table() string {

return "zadig_workflow_task"

}

type ZadigWorkflowName struct {

ID int `json:"id"`

WorkflowName string `json:"workflow_name"` // workflow名

Branch string `json:"branch"` // 分支

ProjectName string `json:"project_name"` // 项目名

TargetEnv string `json:"target_env"` // 目标环境

}

func (z *ZadigWorkflowName) Table() string {

return "zadig_workflow_name"

}

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

21.

22.

23.

24.

25.

26.

27.

28.

29.

30.

31.

我定义的比较简单,命名也比较随意。主要的字段就是 Zadig API 需要的字段,其他不需要的就没写了。

(2)实现 Zadig 触发标准和非标准流水线

复制

package zadig

import (

"github.com/joker-bai/go-zadig"

"joker-bai/go-webhook/config"

)

type Zadig struct {

client *zadig.Client

}

func NewZadig(cfg *config.Config) *Zadig {

client, err := zadig.NewClient(cfg.ZadigConfig.Token, zadig.WithBaseURL(cfg.ZadigConfig.URL))

if err != nil {

panic(err)

}

return &Zadig{client: client}

}

// ExecProductWorkflowTask 执行标准工作流

func (z *Zadig) ExecProductWorkflowTask(workflowName, projectName, targetEnv, serviceModule, serviceName, codehostName, repoNamespace, repoName, branch string) error {

_, _, err := z.client.Workflow.ExecWorkflowTask(&zadig.ExecWorkflowTaskOptions{

WorkflowName: workflowName,

ProjectName: projectName,

Input: zadig.WorkflowInput{

TargetEnv: targetEnv,

Build: zadig.ExecBuildArgs{

Enabled: true,

ServiceList: []zadig.BuildServiceInfo{

{

ServiceModule: serviceModule,

ServiceName: serviceName,

RepoInfo: []zadig.RepositoryInfo{

{

CodehostName: codehostName,

RepoNamespace: repoNamespace,

RepoName: repoName,

Branch: branch,

},

},

},

},

},

Deploy: zadig.ExecDeployArgs{

Enabled: true,

Source: "zadig",

},

},

})

return err

}

// ExecCustomWorkflowTask 执行自定义工作流

func (z *Zadig) ExecCustomWorkflowTask(projectName, workflowName, jobName, jobType, registry, serviceModule, serviceName, codehostName, repoNamespace, repoName, branch string) error {

_, _, err := z.client.CustomWorkflow.CreateCustomWorkflowTask(&zadig.CreateCustomWorkflowTask{

ProjectName: projectName,

WorkflowName: workflowName,

Inputs: []zadig.CreateCustomWorkflowTaskInput{

{

JobName: jobName,

JobType: jobType,

Parameters: zadig.CreateCustomWorkflowTaskParameters{

Register: registry,

ServiceList: []zadig.ServiceList{

{

ServiceModule: serviceModule,

ServiceName: serviceName,

RepoInfo: []zadig.RepoInfo{

{

CodehostName: codehostName,

RepoNamespace: repoNamespace,

RepoName: repoName,

Branch: branch,

},

},

},

},

},

},

},

})

return err

}

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

21.

22.

23.

24.

25.

26.

27.

28.

29.

30.

31.

32.

33.

34.

35.

36.

37.

38.

39.

40.

41.

42.

43.

44.

45.

46.

47.

48.

49.

50.

51.

52.

53.

54.

55.

56.

57.

58.

59.

60.

61.

62.

63.

64.

65.

66.

67.

68.

69.

70.

71.

72.

73.

74.

75.

76.

77.

78.

79.

80.

81.

82.

83.

84.

这里对工作流的 API 进行了取舍,根据实际情况选择需要的字段。

(3)实现 service 方法

复制

package service

import (

"context"

"fmt"

"github.com/sirupsen/logrus"

"go.uber.org/fx"

"joker-bai/go-webhook/config"

"joker-bai/go-webhook/infrastructure/db"

"joker-bai/go-webhook/internal/domain/entity"

"joker-bai/go-webhook/pkg/zadig"

)

// 获取并处理Codeup的Webhook

type CodeupWebhookService struct {

logger *logrus.Logger

cfg *config.Config

db *db.DataBase

}

var RegCodeupWebhookService = fx.Provide(func(logger *logrus.Logger, cfg *config.Config, db *db.DataBase) *CodeupWebhookService {

return &CodeupWebhookService{

logger: logger,

cfg: cfg,

db: db,

}

})

// ExecZadigWorkflowTask 触发执行zadig的工作流

func (c *CodeupWebhookService) ExecZadigWorkflowTask(ctx context.Context, repoName, branch string) error {

client := zadig.NewZadig(c.cfg)

// 从数据库中获取数据

dbClient := c.db.Master.WithContext(ctx)

var workflowTask entity.ZadigWorkflowTask

workflowTaskRes := dbClient.Model(&workflowTask).Where("repo_name = ?", repoName).First(&workflowTask)

if workflowTaskRes.Error != nil {

return workflowTaskRes.Error

}

// 从数据库获取项目和工作流信息

var flowName entity.ZadigWorkflowName

workflowNameRes := dbClient.Model(&flowName).Where("branch = ? and project_name = ?", repoName, workflowTask.ProjectName).First(&flowName)

if workflowNameRes.Error != nil {

return workflowNameRes.Error

}

// 判断workflow的类别

if workflowTask.WorkflowType == "product" {

return client.ExecProductWorkflowTask(

flowName.WorkflowName,

workflowTask.ProjectName,

flowName.TargetEnv,

workflowTask.ServiceModule,

workflowTask.ServiceName,

workflowTask.CodehostName,

workflowTask.RepoNamespace,

workflowTask.RepoName,

branch,

)

} else if workflowTask.WorkflowType == "custom" {

return client.ExecCustomWorkflowTask(

workflowTask.ProjectName,

flowName.WorkflowName,

workflowTask.JobName,

workflowTask.JobType,

workflowTask.Registry,

workflowTask.ServiceModule,

workflowTask.ServiceName,

workflowTask.CodehostName,

workflowTask.RepoNamespace,

workflowTask.RepoName,

branch,

)

} else {

return fmt.Errorf("未匹配workflow类型")

}

}

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

21.

22.

23.

24.

25.

26.

27.

28.

29.

30.

31.

32.

33.

34.

35.

36.

37.

38.

39.

40.

41.

42.

43.

44.

45.

46.

47.

48.

49.

50.

51.

52.

53.

54.

55.

56.

57.

58.

59.

60.

61.

62.

63.

64.

65.

66.

67.

68.

69.

70.

71.

72.

73.

74.

75.

76.

77.

78.

79.

这里从数据库获取需要的信息,然后根据不同的工作流类型执行不同的接口。

(4)实现 controller 方法

复制

package application

import (

"fmt"

"github.com/gin-gonic/gin"

"github.com/sirupsen/logrus"

"go.uber.org/fx"

"joker-bai/go-webhook/internal/core/base"

"joker-bai/go-webhook/domain/service"

"strings"

"time"

)

var regCodeupWebhookApplication = fx.Provide(NewCodeupWebhookController)

type CodeupWebhookController struct {

logger *logrus.Logger

service *service.CodeupWebhookService

}

type CodeupWebhook struct {

After string `json:"after"`

AliyunPk string `json:"aliyun_pk"`

Before string `json:"before"`

CheckoutSha string `json:"checkout_sha"`

Commits []Commits `json:"commits"`

ObjectKind string `json:"object_kind"`

ProjectID int `json:"project_id"`

Ref string `json:"ref"`

Repository Repository `json:"repository"`

TotalCommitsCount int `json:"total_commits_count"`

UserEmail string `json:"user_email"`

UserExternUID string `json:"user_extern_uid"`

UserID int `json:"user_id"`

UserName string `json:"user_name"`

}

type Author struct {

Email string `json:"email"`

Name string `json:"name"`

}

type Commits struct {

Author Author `json:"author"`

ID string `json:"id"`

Message string `json:"message"`

Timestamp time.Time `json:"timestamp"`

URL string `json:"url"`

}

type Repository struct {

Description string `json:"description"`

GitHTTPURL string `json:"git_http_url"`

GitSecondaryHTTPURL string `json:"git_secondary_http_url"`

GitSecondarySSHURL string `json:"git_secondary_ssh_url"`

GitSSHURL string `json:"git_ssh_url"`

Homepage string `json:"homepage"`

Name string `json:"name"`

URL string `json:"url"`

VisibilityLevel int `json:"visibility_level"`

}

func NewCodeupWebhookController(logger *logrus.Logger, service *service.CodeupWebhookService) *CodeupWebhookController {

return &CodeupWebhookController{

logger: logger,

service: service,

}

}

// DoCodeupWebhook 获取Codeup 代码webhook的Body

func (c *CodeupWebhookController) DoCodeupWebhook(ctx *gin.Context) {

output := base.NewResponse(ctx)

var param CodeupWebhook

err := ctx.ShouldBindJSON(?m)

if err != nil {

output.Error(10000, err.Error())

return

}

// 获取repo_name,repo_namespace,branch

repoName := param.Repository.Name

branch := strings.Split(param.Ref, "/")[2]

if err := c.service.ExecZadigWorkflowTask(ctx, repoName, branch); err != nil {

c.logger.Error(err.Error())

output.Error(502, "执行workflow失败")

}

output.Success(gin.H{

"data": "ok",

})

}

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

21.

22.

23.

24.

25.

26.

27.

28.

29.

30.

31.

32.

33.

34.

35.

36.

37.

38.

39.

40.

41.

42.

43.

44.

45.

46.

47.

48.

49.

50.

51.

52.

53.

54.

55.

56.

57.

58.

59.

60.

61.

62.

63.

64.

65.

66.

67.

68.

69.

70.

71.

72.

73.

74.

75.

76.

77.

78.

79.

80.

81.

82.

83.

84.

85.

86.

87.

88.

这部分就更简单了,从 Webhook 中获取数据,然后调 service 去执行即可。

最后就是增加路由了,这部分就不用展示了。

搬砖结束

搬砖完成过后就是对自己开发的 HTTP Server 进行验证了。

整个思路和开发的工作量都不大,上面的代码还有很多地方需要去调整的,如果有相同需求的可以自己去实现,我仅仅是做了一个 demo。

通过引入中间商的方式来实现自己的需求的优点是比较简单,不需要去看或者兼容其他的代码,只需要实现自己的逻辑,缺点就是数据这一块需要单独去处理,比较麻烦。

0 留言

评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。