Skip to content

Commit 5a1920f

Browse files
authored
Merge pull request #2946 from actiontech/feat_git_branch
feat: 快捷审核git仓库审核,支持指定仓库分支
2 parents c351a1a + f0327d9 commit 5a1920f

File tree

9 files changed

+415
-33
lines changed

9 files changed

+415
-33
lines changed

sqle/api/app.go

+1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ func StartApi(net *gracenet.Net, exitChan chan struct{}, config *config.SqleOpti
149149
v1Router.PATCH("/configurations/coding", v1.UpdateCodingConfigurationV1, sqleMiddleware.OpGlobalAllowed())
150150
v1Router.GET("/configurations/coding", v1.GetCodingConfigurationV1, sqleMiddleware.ViewGlobalAllowed())
151151
v1Router.POST("/configurations/coding/test", v1.TestCodingConfigV1, sqleMiddleware.OpGlobalAllowed())
152+
v1Router.POST("/configurations/git/test", v1.TestGitConnectionV1, sqleMiddleware.OpGlobalAllowed())
152153

153154
// statistic
154155
v1Router.GET("/statistic/instances/type_percent", v1.GetInstancesTypePercentV1, sqleMiddleware.ViewGlobalAllowed())

sqle/api/controller/v1/configuration.go

+25
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,31 @@ func TestCodingConfigV1(c echo.Context) error {
416416
return testCodingAuditConfigV1(c)
417417
}
418418

419+
// TestGitConnectionV1
420+
// @Summary 测试Git联通性
421+
// @Description test git connection
422+
// @Accept json
423+
// @Id TestGitConnectionV1
424+
// @Tags configuration
425+
// @Security ApiKeyAuth
426+
// @Param req body v1.TestGitConnectionReqV1 true "test git configuration req"
427+
// @Success 200 {object} v1.TestGitConnectionResV1
428+
// @router /v1/configurations/git/test [post]
429+
func TestGitConnectionV1(c echo.Context) error {
430+
return testGitConnectionV1(c)
431+
}
432+
433+
type TestGitConnectionReqV1 struct {
434+
GitHttpUrl string `json:"git_http_url" form:"git_http_url" valid:"required"`
435+
GitUserName string `json:"git_user_name" form:"git_user_name" valid:"required"`
436+
GitUserPassword string `json:"git_user_password" form:"git_user_password" valid:"required"`
437+
}
438+
439+
type TestGitConnectionResV1 struct {
440+
controller.BaseRes
441+
Data TestGitConnectionResDataV1 `json:"data"`
442+
}
443+
419444
type ScheduleTaskDefaultOption struct {
420445
DefaultSelector string `json:"default_selector" enums:"wechat,feishu"`
421446
}

sqle/api/controller/v1/configuration_ce.go

+79-3
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ package v1
55

66
import (
77
e "errors"
8-
9-
"github.com/actiontech/sqle/sqle/errors"
10-
118
"github.com/actiontech/sqle/sqle/api/controller"
9+
"github.com/actiontech/sqle/sqle/errors"
10+
"github.com/actiontech/sqle/sqle/utils"
11+
"github.com/go-git/go-git/v5/plumbing"
12+
"github.com/go-git/go-git/v5/plumbing/storer"
1213
"github.com/labstack/echo/v4"
14+
"net/http"
1315
)
1416

1517
var (
@@ -70,3 +72,77 @@ func testCodingAuditConfigV1(c echo.Context) error {
7072
func getScheduledTaskDefaultOptionV1(c echo.Context) error {
7173
return controller.JSONBaseErrorReq(c, errCommunityEditionDoesNotSupportScheduledNotify)
7274
}
75+
76+
func testGitConnectionV1(c echo.Context) error {
77+
request := new(TestGitConnectionReqV1)
78+
if err := controller.BindAndValidateReq(c, request); err != nil {
79+
return controller.JSONBaseErrorReq(c, err)
80+
}
81+
repository, _, cleanup, err := utils.CloneGitRepository(c.Request().Context(), request.GitHttpUrl, request.GitUserName, request.GitUserPassword)
82+
if err != nil {
83+
return c.JSON(http.StatusOK, &TestGitConnectionResV1{
84+
BaseRes: controller.NewBaseReq(nil),
85+
Data: TestGitConnectionResDataV1{
86+
IsConnectedSuccess: false,
87+
ErrorMessage: err.Error(),
88+
},
89+
})
90+
}
91+
defer func() {
92+
cleanupError := cleanup()
93+
if cleanupError != nil {
94+
c.Logger().Errorf("cleanup git repository failed, err: %v", cleanupError)
95+
}
96+
}()
97+
references, err := repository.References()
98+
if err != nil {
99+
return c.JSON(http.StatusOK, &TestGitConnectionResV1{
100+
BaseRes: controller.NewBaseReq(nil),
101+
Data: TestGitConnectionResDataV1{
102+
IsConnectedSuccess: false,
103+
ErrorMessage: err.Error(),
104+
},
105+
})
106+
}
107+
branches, err := getBranches(references)
108+
return c.JSON(http.StatusOK, &TestGitConnectionResV1{
109+
BaseRes: controller.NewBaseReq(nil),
110+
Data: TestGitConnectionResDataV1{
111+
IsConnectedSuccess: true,
112+
Branches: branches,
113+
},
114+
})
115+
}
116+
117+
func getBranches(references storer.ReferenceIter) ([]string, error) {
118+
branches := make([]string, 0)
119+
err := references.ForEach(func(ref *plumbing.Reference) error {
120+
if ref.Type() == plumbing.HashReference {
121+
branches = append(branches, ref.Name().Short())
122+
}
123+
return nil
124+
})
125+
if err != nil {
126+
return branches, err
127+
}
128+
if len(branches) < 2 {
129+
return branches, nil
130+
}
131+
// 第一个元素确认了默认分支名,需要把可以checkout的默认分支提到第一个元素
132+
defaultBranch := "origin/" + branches[0]
133+
defaultBranchIndex := -1
134+
for i, branch := range branches {
135+
if branch == defaultBranch {
136+
defaultBranchIndex = i
137+
break
138+
}
139+
}
140+
if defaultBranchIndex == -1 {
141+
return branches, nil
142+
}
143+
//1. 根据第一个元素,找到其余元素中的默认分支
144+
//2. 如果找到,将找到的默认分支名移到第一个元素,并且删除原来的第一个元素。
145+
branches[0], branches[defaultBranchIndex] = branches[defaultBranchIndex], branches[0]
146+
branches = append(branches[:defaultBranchIndex], branches[defaultBranchIndex+1:]...)
147+
return branches, nil
148+
}

sqle/api/controller/v1/sql_audit_record.go

+29-27
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
e "errors"
88
"fmt"
9+
"github.com/go-git/go-git/v5/plumbing"
910
"io"
1011
"io/fs"
1112
"net/http"
@@ -27,8 +28,6 @@ import (
2728
"github.com/actiontech/sqle/sqle/server"
2829
"github.com/actiontech/sqle/sqle/utils"
2930
goGit "github.com/go-git/go-git/v5"
30-
goGitTransport "github.com/go-git/go-git/v5/plumbing/transport/http"
31-
3231
"github.com/labstack/echo/v4"
3332
)
3433

@@ -63,7 +62,8 @@ var maxZipFileSize int64 = 1024 * 1024 * 10
6362
// @Description 4. file[input_zip_file]: it is ZIP file that sql will be parsed from xml or sql file inside it.
6463
// @Description 5. formData[git_http_url]:the url which scheme is http(s) and end with .git.
6564
// @Description 6. formData[git_user_name]:The name of the user who owns the repository read access.
66-
// @Description 7. formData[git_user_password]:The password corresponding to git_user_name.
65+
// @Description 7. formData[git_branch_name]:The name of the repository branch.
66+
// @Description 8. formData[git_user_password]:The password corresponding to git_user_name.
6767
// @Accept mpfd
6868
// @Produce json
6969
// @Tags sql_audit_record
@@ -79,6 +79,7 @@ var maxZipFileSize int64 = 1024 * 1024 * 10
7979
// @Param input_zip_file formData file false "input ZIP file"
8080
// @Param git_http_url formData string false "git repository url"
8181
// @Param git_user_name formData string false "the name of user to clone the repository"
82+
// @Param git_branch_name formData string false "the name of repository branch"
8283
// @Param git_user_password formData string false "the password corresponding to git_user_name"
8384
// @Success 200 {object} v1.CreateSQLAuditRecordResV1
8485
// @router /v1/projects/{project_name}/sql_audit_records [post]
@@ -188,6 +189,12 @@ func CreateSQLAuditRecord(c echo.Context) error {
188189
})
189190
}
190191

192+
type TestGitConnectionResDataV1 struct {
193+
IsConnectedSuccess bool `json:"is_connected_success"`
194+
Branches []string `json:"branches"`
195+
ErrorMessage string `json:"error_message,omitempty"`
196+
}
197+
191198
type getSQLFromFileResp struct {
192199
SourceType string
193200
SQLsFromFormData string
@@ -433,39 +440,34 @@ func parseXMLsWithFilePath(xmlContents []xmlParser.XmlFile) ([]SQLFromXML, error
433440

434441
// todo 此处跳过了不支持的编码格式文件
435442
func getSqlsFromGit(c echo.Context) (sqlsFromSQLFiles, sqlsFromJavaFiles []SQLsFromSQLFile, sqlsFromXMLs []SQLFromXML, exist bool, err error) {
436-
// make a temp dir and clean up befor return
437-
dir, err := os.MkdirTemp("./", "git-repo-")
443+
// clone from git
444+
repository, directory, cleanup, err := utils.CloneGitRepository(c.Request().Context(), c.FormValue(GitHttpURL), c.FormValue(GitUserName), c.FormValue(GitPassword))
438445
if err != nil {
439446
return nil, nil, nil, false, err
440447
}
441-
defer os.RemoveAll(dir)
442-
// read http url from form and check if it's a git url
443-
url := c.FormValue(GitHttpURL)
444-
if !utils.IsGitHttpURL(url) {
445-
return nil, nil, nil, false, errors.New(errors.DataInvalid, fmt.Errorf("url is not a git url"))
446-
}
447-
cloneOpts := &goGit.CloneOptions{
448-
URL: url,
449-
}
450-
// public repository do not require an user name and password
451-
userName := c.FormValue(GitUserName)
452-
password := c.FormValue(GitPassword)
453-
if userName != "" {
454-
cloneOpts.Auth = &goGitTransport.BasicAuth{
455-
Username: userName,
456-
Password: password,
457-
}
458-
}
459-
// clone from git
460-
_, err = goGit.PlainCloneContext(c.Request().Context(), dir, false, cloneOpts)
448+
defer func() {
449+
cleanupError := cleanup()
450+
c.Logger().Errorf("cleanup git repository failed, err: %v", cleanupError)
451+
}()
452+
workTree, err := repository.Worktree()
461453
if err != nil {
462454
return nil, nil, nil, false, err
463455
}
456+
branch := c.FormValue(GitBranchName)
457+
if branch != "" {
458+
err = workTree.Checkout(&goGit.CheckoutOptions{
459+
Branch: plumbing.NewRemoteReferenceName("", branch),
460+
Create: false,
461+
})
462+
if err != nil {
463+
return nil, nil, nil, false, err
464+
}
465+
}
464466
l := log.NewEntry().WithField("function", "getSqlsFromGit")
465467
var xmlContents []xmlParser.XmlFile
466468
// traverse the repository, parse and put SQL into sqlBuffer
467-
err = filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
468-
gitPath := strings.TrimPrefix(path, strings.TrimPrefix(dir, "./"))
469+
err = filepath.Walk(directory, func(path string, info fs.FileInfo, err error) error {
470+
gitPath := strings.TrimPrefix(path, strings.TrimPrefix(directory, "./"))
469471
if !info.IsDir() {
470472
var sqlBuffer strings.Builder
471473
switch {

sqle/api/controller/v1/task.go

+1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ const (
111111
InputZipFileName = "input_zip_file"
112112
InputFileFromGit = "input_file_from_git"
113113
GitHttpURL = "git_http_url"
114+
GitBranchName = "git_branch_name"
114115
GitUserName = "git_user_name"
115116
GitPassword = "git_user_password"
116117
ZIPFileExtension = ".zip"

sqle/docs/docs.go

+92-1
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,43 @@ var doc = `{
477477
}
478478
}
479479
},
480+
"/v1/configurations/git/test": {
481+
"post": {
482+
"security": [
483+
{
484+
"ApiKeyAuth": []
485+
}
486+
],
487+
"description": "test git connection",
488+
"consumes": [
489+
"application/json"
490+
],
491+
"tags": [
492+
"configuration"
493+
],
494+
"summary": "测试Git联通性",
495+
"operationId": "TestGitConnectionV1",
496+
"parameters": [
497+
{
498+
"description": "test git configuration req",
499+
"name": "req",
500+
"in": "body",
501+
"required": true,
502+
"schema": {
503+
"$ref": "#/definitions/v1.TestGitConnectionReqV1"
504+
}
505+
}
506+
],
507+
"responses": {
508+
"200": {
509+
"description": "OK",
510+
"schema": {
511+
"$ref": "#/definitions/v1.TestGitConnectionResV1"
512+
}
513+
}
514+
}
515+
}
516+
},
480517
"/v1/configurations/license": {
481518
"get": {
482519
"security": [
@@ -4815,7 +4852,7 @@ var doc = `{
48154852
"ApiKeyAuth": []
48164853
}
48174854
],
4818-
"description": "SQL audit\n1. formData[sql]: sql content;\n2. file[input_sql_file]: it is a sql file;\n3. file[input_mybatis_xml_file]: it is mybatis xml file, sql will be parsed from it.\n4. file[input_zip_file]: it is ZIP file that sql will be parsed from xml or sql file inside it.\n5. formData[git_http_url]:the url which scheme is http(s) and end with .git.\n6. formData[git_user_name]:The name of the user who owns the repository read access.\n7. formData[git_user_password]:The password corresponding to git_user_name.",
4855+
"description": "SQL audit\n1. formData[sql]: sql content;\n2. file[input_sql_file]: it is a sql file;\n3. file[input_mybatis_xml_file]: it is mybatis xml file, sql will be parsed from it.\n4. file[input_zip_file]: it is ZIP file that sql will be parsed from xml or sql file inside it.\n5. formData[git_http_url]:the url which scheme is http(s) and end with .git.\n6. formData[git_user_name]:The name of the user who owns the repository read access.\n7. formData[git_branch_name]:The name of the repository branch.\n8. formData[git_user_password]:The password corresponding to git_user_name.",
48194856
"consumes": [
48204857
"multipart/form-data"
48214858
],
@@ -4895,6 +4932,12 @@ var doc = `{
48954932
"name": "git_user_name",
48964933
"in": "formData"
48974934
},
4935+
{
4936+
"type": "string",
4937+
"description": "the name of repository branch",
4938+
"name": "git_branch_name",
4939+
"in": "formData"
4940+
},
48984941
{
48994942
"type": "string",
49004943
"description": "the password corresponding to git_user_name",
@@ -18485,6 +18528,54 @@ var doc = `{
1848518528
}
1848618529
}
1848718530
},
18531+
"v1.TestGitConnectionReqV1": {
18532+
"type": "object",
18533+
"properties": {
18534+
"git_http_url": {
18535+
"type": "string"
18536+
},
18537+
"git_user_name": {
18538+
"type": "string"
18539+
},
18540+
"git_user_password": {
18541+
"type": "string"
18542+
}
18543+
}
18544+
},
18545+
"v1.TestGitConnectionResDataV1": {
18546+
"type": "object",
18547+
"properties": {
18548+
"branches": {
18549+
"type": "array",
18550+
"items": {
18551+
"type": "string"
18552+
}
18553+
},
18554+
"error_message": {
18555+
"type": "string"
18556+
},
18557+
"is_connected_success": {
18558+
"type": "boolean"
18559+
}
18560+
}
18561+
},
18562+
"v1.TestGitConnectionResV1": {
18563+
"type": "object",
18564+
"properties": {
18565+
"code": {
18566+
"type": "integer",
18567+
"example": 0
18568+
},
18569+
"data": {
18570+
"type": "object",
18571+
"$ref": "#/definitions/v1.TestGitConnectionResDataV1"
18572+
},
18573+
"message": {
18574+
"type": "string",
18575+
"example": "ok"
18576+
}
18577+
}
18578+
},
1848818579
"v1.TestWechatConfigResDataV1": {
1848918580
"type": "object",
1849018581
"properties": {

0 commit comments

Comments
 (0)