Skip to content

Commit eb7fac7

Browse files
FEATURE: Azure gitops support (#458)
* git azure service added * wip * wip * wip * azure devops support dev * git provider type correction * error handling correction * error handling for branch creation * branch error check fix * add git binary * add git binary * local tested * fix azure gitops startup * git clone fix * log corection * review fix * for loop simplification * db migration added * gitops config default handling * slow git clone handling Co-authored-by: Vikram Singh <[email protected]>
1 parent aa79ebe commit eb7fac7

File tree

35 files changed

+14510
-68
lines changed

35 files changed

+14510
-68
lines changed

Dockerfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ RUN GOOS=linux make
1111

1212
FROM alpine:3.9
1313
RUN apk add --no-cache ca-certificates
14-
RUN echo pwd
14+
RUN apk update
15+
RUN apk add git
1516
COPY --from=build-env /go/src/github.com/devtron-labs/devtron/devtron .
1617
COPY --from=build-env /go/src/github.com/devtron-labs/devtron/auth_model.conf .
1718
COPY --from=build-env /go/src/github.com/devtron-labs/devtron/vendor/github.com/argoproj/argo-cd/assets/ /go/src/github.com/devtron-labs/devtron/vendor/github.com/argoproj/argo-cd/assets
1819
COPY --from=build-env /go/src/github.com/devtron-labs/devtron/scripts/devtron-reference-helm-charts scripts/devtron-reference-helm-charts
1920
COPY --from=build-env /go/src/github.com/devtron-labs/devtron/scripts/argo-assets/APPLICATION_TEMPLATE.JSON scripts/argo-assets/APPLICATION_TEMPLATE.JSON
2021

22+
COPY ./git-ask-pass.sh /git-ask-pass.sh
23+
RUN chmod +x /git-ask-pass.sh
24+
2125
CMD ["./devtron"]

Gopkg.lock

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,7 @@ required = [
187187
[[constraint]]
188188
name = "github.com/igm/sockjs-go"
189189
version = "3.0.0"
190+
191+
[[constraint]]
192+
branch = "dev"
193+
name = "github.com/microsoft/azure-devops-go-api"

Wire.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@ func InitializeApp() (*App, error) {
646646
wire.Bind(new(router.CommonRouter), new(*router.CommonRouterImpl)),
647647
restHandler.NewCommonRestHanlderImpl,
648648
wire.Bind(new(restHandler.CommonRestHanlder), new(*restHandler.CommonRestHanlderImpl)),
649+
util.NewGitCliUtil,
649650
)
650651
return &App{}, nil
651652
}

git-ask-pass.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/sh
2+
# This script is used as the command supplied to GIT_ASKPASS as a way to supply username/password
3+
# credentials to git, without having to use git credentials helpers, or having on-disk config.
4+
case "$1" in
5+
Username*) echo "${GIT_USERNAME}" ;;
6+
Password*) echo "${GIT_PASSWORD}" ;;
7+
esac

internal/sql/repository/GitOpsConfigRepository.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type GitOpsConfig struct {
4646
Token string `sql:"token"`
4747
GitLabGroupId string `sql:"gitlab_group_id"`
4848
GitHubOrgId string `sql:"github_org_id"`
49+
AzureProject string `sql:"azure_project"`
4950
Host string `sql:"host"`
5051
Active bool `sql:"active,notnull"`
5152
models.AuditLog

internal/util/GitCliUtil.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package util
2+
3+
import (
4+
"fmt"
5+
"go.uber.org/zap"
6+
"gopkg.in/src-d/go-git.v4"
7+
"gopkg.in/src-d/go-git.v4/config"
8+
"os"
9+
"os/exec"
10+
"strings"
11+
)
12+
13+
type GitCliUtil struct {
14+
logger *zap.SugaredLogger
15+
}
16+
17+
func NewGitCliUtil(logger *zap.SugaredLogger) *GitCliUtil {
18+
return &GitCliUtil{
19+
logger: logger,
20+
}
21+
}
22+
23+
const GIT_ASK_PASS = "/git-ask-pass.sh"
24+
25+
func (impl *GitCliUtil) Fetch(rootDir string, username string, password string) (response, errMsg string, err error) {
26+
impl.logger.Debugw("git fetch ", "location", rootDir)
27+
cmd := exec.Command("git", "-C", rootDir, "fetch", "origin", "--tags", "--force")
28+
output, errMsg, err := impl.runCommandWithCred(cmd, username, password)
29+
impl.logger.Debugw("fetch output", "root", rootDir, "opt", output, "errMsg", errMsg, "error", err)
30+
return output, errMsg, err
31+
}
32+
33+
func (impl *GitCliUtil) Pull(rootDir string, username string, password string, branch string) (response, errMsg string, err error) {
34+
impl.logger.Debugw("git pull ", "location", rootDir)
35+
cmd := exec.Command("git", "-C", rootDir, "pull", "origin", branch, "--force")
36+
output, errMsg, err := impl.runCommandWithCred(cmd, username, password)
37+
impl.logger.Debugw("pull output", "root", rootDir, "opt", output, "errMsg", errMsg, "error", err)
38+
return output, errMsg, err
39+
}
40+
func (impl *GitCliUtil) Checkout(rootDir string, branch string) (response, errMsg string, err error) {
41+
impl.logger.Debugw("git checkout ", "location", rootDir)
42+
cmd := exec.Command("git", "-C", rootDir, "checkout", branch, "--force")
43+
output, errMsg, err := impl.runCommand(cmd)
44+
impl.logger.Debugw("checkout output", "root", rootDir, "opt", output, "errMsg", errMsg, "error", err)
45+
return output, errMsg, err
46+
}
47+
48+
func (impl *GitCliUtil) runCommandWithCred(cmd *exec.Cmd, userName, password string) (response, errMsg string, err error) {
49+
cmd.Env = append(os.Environ(),
50+
fmt.Sprintf("GIT_ASKPASS=%s", GIT_ASK_PASS),
51+
fmt.Sprintf("GIT_USERNAME=%s", userName),
52+
fmt.Sprintf("GIT_PASSWORD=%s", password),
53+
)
54+
return impl.runCommand(cmd)
55+
}
56+
57+
func (impl *GitCliUtil) runCommand(cmd *exec.Cmd) (response, errMsg string, err error) {
58+
cmd.Env = append(cmd.Env, "HOME=/dev/null")
59+
outBytes, err := cmd.CombinedOutput()
60+
if err != nil {
61+
exErr, ok := err.(*exec.ExitError)
62+
if !ok {
63+
return "", "", err
64+
}
65+
errOutput := string(exErr.Stderr)
66+
return "", errOutput, err
67+
}
68+
output := string(outBytes)
69+
output = strings.TrimSpace(output)
70+
return output, "", nil
71+
}
72+
73+
func (impl *GitCliUtil) Init(rootDir string, remoteUrl string, isBare bool) error {
74+
//-----------------
75+
err := os.RemoveAll(rootDir)
76+
if err != nil {
77+
impl.logger.Errorw("error in cleaning rootDir", "err", err)
78+
return err
79+
}
80+
err = os.MkdirAll(rootDir, 0755)
81+
if err != nil {
82+
return err
83+
}
84+
repo, err := git.PlainInit(rootDir, isBare)
85+
if err != nil {
86+
return err
87+
}
88+
_, err = repo.CreateRemote(&config.RemoteConfig{
89+
Name: git.DefaultRemoteName,
90+
URLs: []string{remoteUrl},
91+
})
92+
return err
93+
}
94+
95+
func (impl *GitCliUtil) Clone(rootDir string, remoteUrl string, username string, password string) (response, errMsg string, err error) {
96+
err = impl.Init(rootDir, remoteUrl, false)
97+
if err != nil {
98+
return "", "", err
99+
}
100+
response, errMsg, err = impl.Fetch(rootDir, username, password)
101+
if err == nil && errMsg == "" {
102+
response, errMsg, err = impl.Pull(rootDir, username, password, "master")
103+
}
104+
return response, errMsg, err
105+
}

internal/util/GitService.go

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"golang.org/x/oauth2"
2929
"gopkg.in/src-d/go-git.v4"
3030
"gopkg.in/src-d/go-git.v4/plumbing/object"
31-
"gopkg.in/src-d/go-git.v4/plumbing/transport"
3231
"gopkg.in/src-d/go-git.v4/plumbing/transport/http"
3332
"io/ioutil"
3433
"net/url"
@@ -49,6 +48,7 @@ type GitFactory struct {
4948
GitWorkingDir string
5049
logger *zap.SugaredLogger
5150
gitOpsRepository repository.GitOpsConfigRepository
51+
gitCliUtil *GitCliUtil
5252
}
5353

5454
func (factory *GitFactory) Reload() error {
@@ -57,7 +57,7 @@ func (factory *GitFactory) Reload() error {
5757
if err != nil {
5858
return err
5959
}
60-
gitService := NewGitServiceImpl(cfg, logger)
60+
gitService := NewGitServiceImpl(cfg, logger, factory.gitCliUtil)
6161
factory.gitService = gitService
6262
client, err := NewGitLabClient(cfg, logger, gitService)
6363
if err != nil {
@@ -68,12 +68,12 @@ func (factory *GitFactory) Reload() error {
6868
return nil
6969
}
7070

71-
func NewGitFactory(logger *zap.SugaredLogger, gitOpsRepository repository.GitOpsConfigRepository) (*GitFactory, error) {
71+
func NewGitFactory(logger *zap.SugaredLogger, gitOpsRepository repository.GitOpsConfigRepository, gitCliUtil *GitCliUtil) (*GitFactory, error) {
7272
cfg, err := GetGitConfig(gitOpsRepository)
7373
if err != nil {
7474
return nil, err
7575
}
76-
gitService := NewGitServiceImpl(cfg, logger)
76+
gitService := NewGitServiceImpl(cfg, logger, gitCliUtil)
7777
client, err := NewGitLabClient(cfg, logger, gitService)
7878
if err != nil {
7979
return nil, err
@@ -84,18 +84,21 @@ func NewGitFactory(logger *zap.SugaredLogger, gitOpsRepository repository.GitOps
8484
gitService: gitService,
8585
gitOpsRepository: gitOpsRepository,
8686
GitWorkingDir: cfg.GitWorkingDir,
87+
gitCliUtil: gitCliUtil,
8788
}, nil
8889
}
8990

9091
type GitConfig struct {
91-
GitlabGroupId string //local
92-
GitlabGroupPath string //local
93-
GitToken string `env:"GIT_TOKEN" ` //not null // public
94-
GitUserName string `env:"GIT_USERNAME" ` //not null // public
95-
GitWorkingDir string `env:"GIT_WORKING_DIRECTORY" envDefault:"/tmp/gitops/"` //working directory for git. might use pvc
92+
GitlabGroupId string //local
93+
GitlabGroupPath string //local
94+
GitToken string //not null // public
95+
GitUserName string //not null // public
96+
GitWorkingDir string //working directory for git. might use pvc
9697
GithubOrganization string
97-
GitProvider string `env:"GIT_PROVIDER" envDefault:"GITHUB"` // SUPPORTED VALUES GITHUB, GITLAB
98-
GitHost string `env:"GIT_HOST" envDefault:""`
98+
GitProvider string // SUPPORTED VALUES GITHUB, GITLAB
99+
GitHost string
100+
AzureToken string
101+
AzureProject string
99102
}
100103

101104
func GetGitConfig(gitOpsRepository repository.GitOpsConfigRepository) (*GitConfig, error) {
@@ -121,6 +124,8 @@ func GetGitConfig(gitOpsRepository repository.GitOpsConfigRepository) (*GitConfi
121124
GithubOrganization: gitOpsConfig.GitHubOrgId,
122125
GitProvider: gitOpsConfig.Provider,
123126
GitHost: gitOpsConfig.Host,
127+
AzureToken: gitOpsConfig.Token,
128+
AzureProject: gitOpsConfig.AzureProject,
124129
}
125130
return cfg, err
126131
}
@@ -190,9 +195,15 @@ func NewGitLabClient(config *GitConfig, logger *zap.SugaredLogger, gitService Gi
190195
logger: logger,
191196
gitService: gitService,
192197
}, nil
193-
} else {
198+
} else if config.GitProvider == "GITHUB" {
194199
gitHubClient := NewGithubClient(config.GitToken, config.GithubOrganization, logger, gitService)
195200
return gitHubClient, nil
201+
} else if config.GitProvider == "AZURE_DEVOPS" {
202+
gitAzureClient := NewGitAzureClient(config.AzureToken, config.GitHost, config.AzureProject, logger, gitService)
203+
return gitAzureClient, nil
204+
} else {
205+
logger.Errorw("no gitops config provided, gitops will not work ")
206+
return nil, nil
196207
}
197208
}
198209

@@ -373,17 +384,19 @@ type GitService interface {
373384
Pull(repoRoot string) (err error)
374385
}
375386
type GitServiceImpl struct {
376-
Auth transport.AuthMethod
377-
config *GitConfig
378-
logger *zap.SugaredLogger
387+
Auth *http.BasicAuth
388+
config *GitConfig
389+
logger *zap.SugaredLogger
390+
gitCliUtil *GitCliUtil
379391
}
380392

381-
func NewGitServiceImpl(config *GitConfig, logger *zap.SugaredLogger) *GitServiceImpl {
393+
func NewGitServiceImpl(config *GitConfig, logger *zap.SugaredLogger, GitCliUtil *GitCliUtil) *GitServiceImpl {
382394
auth := &http.BasicAuth{Password: config.GitToken, Username: config.GitUserName}
383395
return &GitServiceImpl{
384-
Auth: auth,
385-
logger: logger,
386-
config: config,
396+
Auth: auth,
397+
logger: logger,
398+
config: config,
399+
gitCliUtil: GitCliUtil,
387400
}
388401
}
389402

@@ -395,14 +408,14 @@ func (impl GitServiceImpl) GetCloneDirectory(targetDir string) (clonedDir string
395408
func (impl GitServiceImpl) Clone(url, targetDir string) (clonedDir string, err error) {
396409
impl.logger.Debugw("git checkout ", "url", url, "dir", targetDir)
397410
clonedDir = filepath.Join(impl.config.GitWorkingDir, targetDir)
398-
_, err = git.PlainClone(clonedDir, false, &git.CloneOptions{
399-
URL: url,
400-
Auth: impl.Auth,
401-
})
411+
_, errorMsg, err := impl.gitCliUtil.Clone(clonedDir, url, impl.Auth.Username, impl.Auth.Password)
402412
if err != nil {
403-
impl.logger.Errorw("error in git checkout ", "url", url, "targetDir", targetDir, "err", err)
413+
impl.logger.Errorw("error in git checkout", "url", url, "targetDir", targetDir, "err", err)
404414
return "", err
405415
}
416+
if errorMsg != "" {
417+
return "", fmt.Errorf(errorMsg)
418+
}
406419
return clonedDir, nil
407420
}
408421

0 commit comments

Comments
 (0)