Skip to content

Commit d3bfd0f

Browse files
Add gitlab and github enterprise support (#67)
New flags: repository-type: github, gitlab, or ghe (default: githab) tls-verify: true or false (default: true)
1 parent 379c24b commit d3bfd0f

File tree

10 files changed

+151
-55
lines changed

10 files changed

+151
-55
lines changed

pkg/avancement/pull_request.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
func makePullRequestInput(fromLocal bool, fromURL, toURL, branchName, prBody string) (*scm.PullRequestInput, error) {
1414
var title string
1515

16-
fromUser, toRepo, err := util.ExtractUserAndRepo(toURL)
16+
_, toRepo, err := util.ExtractUserAndRepo(toURL)
1717
if err != nil {
1818
return nil, err
1919
}
@@ -30,7 +30,7 @@ func makePullRequestInput(fromLocal bool, fromURL, toURL, branchName, prBody str
3030

3131
return &scm.PullRequestInput{
3232
Title: title,
33-
Head: fmt.Sprintf("%s:%s", fromUser, branchName),
33+
Head: branchName,
3434
Base: "master",
3535
Body: prBody,
3636
}, nil

pkg/avancement/pull_request_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func TestMakePullRequestInput(t *testing.T) {
1616

1717
want := &scm.PullRequestInput{
1818
Title: "promotion from dev-env to prod-env",
19-
Head: "project:my-test-branch",
19+
Head: "my-test-branch",
2020
Base: "master",
2121
Body: "foo bar wibble",
2222
}

pkg/avancement/service_manager.go

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313

1414
"github.com/rhd-gitops-example/services/pkg/git"
1515
"github.com/rhd-gitops-example/services/pkg/local"
16-
"github.com/rhd-gitops-example/services/pkg/util"
1716

1817
"github.com/google/uuid"
1918
)
@@ -24,11 +23,13 @@ type ServiceManager struct {
2423
clientFactory scmClientFactory
2524
repoFactory repoFactory
2625
localFactory localFactory
26+
tlsVerify bool
27+
repoType string
2728
debug bool
2829
}
2930

30-
type scmClientFactory func(token string) *scm.Client
31-
type repoFactory func(url, localPath string, debug bool) (git.Repo, error)
31+
type scmClientFactory func(token, toURL, repoType string, tlsVerify bool) *scm.Client
32+
type repoFactory func(url, localPath string, tlsVerify, debug bool) (git.Repo, error)
3233
type localFactory func(localPath string, debug bool) git.Source
3334
type serviceOpt func(*ServiceManager)
3435

@@ -40,9 +41,9 @@ func New(cacheDir string, author *git.Author, opts ...serviceOpt) *ServiceManage
4041
sm := &ServiceManager{
4142
cacheDir: cacheDir,
4243
author: author,
43-
clientFactory: git.CreateGitHubClient,
44-
repoFactory: func(url, localPath string, debug bool) (git.Repo, error) {
45-
r, err := git.NewRepository(url, localPath, debug)
44+
clientFactory: git.CreateClient,
45+
repoFactory: func(url, localPath string, tlsVerify, debug bool) (git.Repo, error) {
46+
r, err := git.NewRepository(url, localPath, tlsVerify, debug)
4647
return git.Repo(r), err
4748
},
4849
localFactory: func(localPath string, debug bool) git.Source {
@@ -64,6 +65,22 @@ func WithDebug(f bool) serviceOpt {
6465
}
6566
}
6667

68+
// WithTlsVerify is a service option that configures the ServiceManager for
69+
// TLS verify option.
70+
func WithInsecureSkipVerify(f bool) serviceOpt {
71+
return func(sm *ServiceManager) {
72+
sm.tlsVerify = !f
73+
}
74+
}
75+
76+
// WithRepType is a service option that configures the ServiceManager for
77+
// repository type option. Supported values are "github", "gitlab, and "ghe"
78+
func WithRepoType(f string) serviceOpt {
79+
return func(sm *ServiceManager) {
80+
sm.repoType = strings.ToLower(f)
81+
}
82+
}
83+
6784
// TODO: make this a command-line parameter defaulting to "master"
6885
const fromBranch = "master"
6986

@@ -150,7 +167,7 @@ func (s *ServiceManager) Promote(serviceName, fromURL, toURL, newBranchName, mes
150167
}
151168

152169
ctx := context.Background()
153-
pr, err := createPullRequest(ctx, fromURL, toURL, newBranchName, commitMsg, s.clientFactory(s.author.Token), isLocal)
170+
pr, err := createPullRequest(ctx, fromURL, toURL, newBranchName, commitMsg, s.clientFactory(s.author.Token, toURL, s.repoType, s.tlsVerify), isLocal)
154171
if err != nil {
155172
message := fmt.Sprintf("failed to create a pull-request for branch %s, error: %s", newBranchName, err)
156173
return git.GitError(message, toURL)
@@ -192,7 +209,7 @@ func (s *ServiceManager) cloneRepo(repoURL, branch string) (git.Repo, error) {
192209
if err != nil {
193210
return nil, err
194211
}
195-
repo, err := s.repoFactory(repoURL, path.Join(s.cacheDir, encode(repoURL, branch)), s.debug)
212+
repo, err := s.repoFactory(repoURL, path.Join(s.cacheDir, encode(repoURL, branch)), s.tlsVerify, s.debug)
196213
if err != nil {
197214
message := fmt.Sprintf("failed to clone repository, error is: %s", err.Error())
198215
return nil, git.GitError(message, repoURL)
@@ -210,14 +227,10 @@ func createPullRequest(ctx context.Context, fromURL, toURL, newBranchName, commi
210227
// TODO: improve this error message
211228
return nil, err
212229
}
213-
user, repo, err := util.ExtractUserAndRepo(toURL)
214-
if err != nil {
215-
// TODO: improve this error message
216-
return nil, err
217-
}
218-
// TODO: come up with a better way of generating the repository URL (this
219-
// only works for GitHub)
220-
pr, _, err := client.PullRequests.Create(ctx, fmt.Sprintf("%s/%s", user, repo), prInput)
230+
231+
u, _ := url.Parse(toURL)
232+
// take out ".git" at the end
233+
pr, _, err := client.PullRequests.Create(ctx, u.Path[1:len(u.Path)-4], prInput)
221234
return pr, err
222235
}
223236

pkg/avancement/service_manager_test.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@ const (
2222
staging = "https://example.com/testing/staging-env"
2323
)
2424

25-
func TestPromoteWithSuccessKeepCacheTrue(t *testing.T) {
26-
promoteWithSuccess(t, true, "")
25+
func TestPromoteWithSuccessKeepCacheTrueWithGHE(t *testing.T) {
26+
promoteWithSuccess(t, true, "ghe", true, "")
2727
}
2828

29-
func TestPromoteWithSuccessKeepCacheFalse(t *testing.T) {
30-
promoteWithSuccess(t, false, "")
29+
func TestPromoteWithSuccessKeepCacheFalseWithGitHub(t *testing.T) {
30+
promoteWithSuccess(t, false, "github", false, "")
3131
}
3232

3333
func TestPromoteWithSuccessCustomMsg(t *testing.T) {
3434
promoteLocalWithSuccess(t, true, "custom commit message here")
3535
}
3636

37-
func promoteWithSuccess(t *testing.T, keepCache bool, msg string) {
37+
func promoteWithSuccess(t *testing.T, keepCache bool, repoType string, tlsVerify bool, msg string) {
3838
dstBranch := "test-branch"
3939
author := &git.Author{Name: "Testing User", Email: "[email protected]", Token: "test-token"}
4040
devRepo, stagingRepo := mock.New("/dev", "master"), mock.New("/staging", "master")
@@ -43,11 +43,22 @@ func promoteWithSuccess(t *testing.T, keepCache bool, msg string) {
4343
mustAddCredentials(t, staging, author): stagingRepo,
4444
}
4545
sm := New("tmp", author)
46-
sm.clientFactory = func(s string) *scm.Client {
46+
sm.repoType = repoType
47+
sm.tlsVerify = tlsVerify
48+
sm.clientFactory = func(s, ty, r string, v bool) *scm.Client {
4749
client, _ := fakescm.NewDefault()
50+
if r != repoType {
51+
t.Fatalf("repoType doesn't match %s != %s\n", r, repoType)
52+
}
53+
if v != tlsVerify {
54+
t.Fatalf("tlsVerify doesn't match in ClientFactory %v != %v\n", v, tlsVerify)
55+
}
4856
return client
4957
}
50-
sm.repoFactory = func(url, _ string, _ bool) (git.Repo, error) {
58+
sm.repoFactory = func(url, _ string, v bool, _ bool) (git.Repo, error) {
59+
if v != tlsVerify {
60+
t.Fatalf("tlsVerify doesn't match in RepoFactory %v != %v\n", v, tlsVerify)
61+
}
5162
return git.Repo(repos[url]), nil
5263
}
5364
devRepo.AddFiles("/services/my-service/base/config/myfile.yaml")
@@ -96,11 +107,11 @@ func promoteLocalWithSuccess(t *testing.T, keepCache bool, msg string) {
96107
devRepo := NewLocal("/dev")
97108

98109
sm := New("tmp", author)
99-
sm.clientFactory = func(s string) *scm.Client {
110+
sm.clientFactory = func(s, t, r string, v bool) *scm.Client {
100111
client, _ := fakescm.NewDefault()
101112
return client
102113
}
103-
sm.repoFactory = func(url, _ string, _ bool) (git.Repo, error) {
114+
sm.repoFactory = func(url, _ string, _ bool, _ bool) (git.Repo, error) {
104115
return git.Repo(stagingRepo), nil
105116
}
106117
sm.localFactory = func(path string, _ bool) git.Source {
@@ -173,11 +184,11 @@ func TestPromoteWithCacheDeletionFailure(t *testing.T) {
173184
mustAddCredentials(t, staging, author): stagingRepo,
174185
}
175186
sm := New("tmp", author)
176-
sm.clientFactory = func(s string) *scm.Client {
187+
sm.clientFactory = func(s, t, r string, v bool) *scm.Client {
177188
client, _ := fakescm.NewDefault()
178189
return client
179190
}
180-
sm.repoFactory = func(url, _ string, _ bool) (git.Repo, error) {
191+
sm.repoFactory = func(url, _ string, _ bool, _ bool) (git.Repo, error) {
181192
return git.Repo(repos[url]), nil
182193
}
183194
devRepo.AddFiles("/services/my-service/base/config/myfile.yaml")
@@ -287,13 +298,13 @@ func TestRepositoryCloneErrorOmitsToken(t *testing.T) {
287298
dstBranch := "test-branch"
288299
author := &git.Author{Name: "Testing User", Email: "[email protected]", Token: "test-token"}
289300
client, _ := fakescm.NewDefault()
290-
fakeClientFactory := func(s string) *scm.Client {
301+
fakeClientFactory := func(s, t, r string, v bool) *scm.Client {
291302
return client
292303
}
293304
sm := New("tmp", author)
294305
sm.clientFactory = fakeClientFactory
295306

296-
sm.repoFactory = func(url, _ string, _ bool) (git.Repo, error) {
307+
sm.repoFactory = func(url, _ string, _ bool, _ bool) (git.Repo, error) {
297308
// This actually causes the error and results in trying to create a repository
298309
// which can surface the token
299310
errorMessage := fmt.Errorf("failed to clone repository %s: exit status 128", dev)

pkg/cmd/promote.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,27 @@ func makePromoteCmd() *cobra.Command {
7272
)
7373
logIfError(viper.BindPFlag(msgFlag, cmd.Flags().Lookup(msgFlag)))
7474

75+
cmd.Flags().String(
76+
repoTypeFlag,
77+
"github",
78+
"the type of repository: github, gitlab or ghe",
79+
)
80+
logIfError(viper.BindPFlag(repoTypeFlag, cmd.Flags().Lookup(repoTypeFlag)))
81+
7582
cmd.Flags().Bool(
7683
debugFlag,
7784
false,
7885
"additional debug logging output",
7986
)
8087
logIfError(viper.BindPFlag(debugFlag, cmd.Flags().Lookup(debugFlag)))
8188

89+
cmd.Flags().Bool(
90+
insecureSkipVerifyFlag,
91+
false,
92+
"Insecure skip verify TLS certificate",
93+
)
94+
logIfError(viper.BindPFlag(insecureSkipVerifyFlag, cmd.Flags().Lookup(insecureSkipVerifyFlag)))
95+
8296
cmd.Flags().Bool(
8397
keepCacheFlag,
8498
false,
@@ -98,7 +112,9 @@ func promoteAction(c *cobra.Command, args []string) error {
98112
fromRepo := viper.GetString(fromFlag)
99113
toRepo := viper.GetString(toFlag)
100114
service := viper.GetString(serviceFlag)
115+
repoType := viper.GetString(repoTypeFlag)
101116
newBranchName := viper.GetString(branchNameFlag)
117+
insecureSkipVerify := viper.GetBool(insecureSkipVerifyFlag)
102118
debug := viper.GetBool(debugFlag)
103119
keepCache := viper.GetBool(keepCacheFlag)
104120
msg := viper.GetString(msgFlag)
@@ -112,7 +128,7 @@ func promoteAction(c *cobra.Command, args []string) error {
112128
if err != nil {
113129
return fmt.Errorf("unable to establish credentials: %w", err)
114130
}
115-
return avancement.New(cacheDir, author, avancement.WithDebug(debug)).Promote(service, fromRepo, toRepo, newBranchName, msg, keepCache)
131+
return avancement.New(cacheDir, author, avancement.WithDebug(debug), avancement.WithInsecureSkipVerify(insecureSkipVerify), avancement.WithRepoType(repoType)).Promote(service, fromRepo, toRepo, newBranchName, msg, keepCache)
116132
}
117133

118134
func newAuthor() (*git.Author, error) {

pkg/cmd/root.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@ import (
1010
)
1111

1212
const (
13-
githubTokenFlag = "github-token"
14-
branchNameFlag = "branch-name"
15-
fromFlag = "from"
16-
toFlag = "to"
17-
serviceFlag = "service"
18-
cacheDirFlag = "cache-dir"
19-
msgFlag = "commit-message"
20-
nameFlag = "commit-name"
21-
emailFlag = "commit-email"
22-
debugFlag = "debug"
23-
keepCacheFlag = "keep-cache"
13+
githubTokenFlag = "github-token"
14+
branchNameFlag = "branch-name"
15+
fromFlag = "from"
16+
toFlag = "to"
17+
serviceFlag = "service"
18+
repoTypeFlag = "repository-type"
19+
cacheDirFlag = "cache-dir"
20+
msgFlag = "commit-message"
21+
nameFlag = "commit-name"
22+
emailFlag = "commit-email"
23+
insecureSkipVerifyFlag = "insecure-skip-verify"
24+
debugFlag = "debug"
25+
keepCacheFlag = "keep-cache"
2426
)
2527

2628
var rootCmd = &cobra.Command{

pkg/git/client.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,46 @@ package git
22

33
import (
44
"context"
5-
5+
"net/http"
6+
"net/url"
7+
"crypto/tls"
68
"github.com/jenkins-x/go-scm/scm"
79
"github.com/jenkins-x/go-scm/scm/driver/github"
10+
"github.com/jenkins-x/go-scm/scm/driver/gitlab"
811
"golang.org/x/oauth2"
912
)
1013

11-
// CreateGitHubClient creates and returns a go-scm GitHub client, using the provided
14+
// CreateClient creates and returns a go-scm GitHub client, using the provided
1215
// oauth2 token.
13-
// TODO: This should probably use https://github.com/jenkins-x/go-scm/tree/master/scm/factory
14-
func CreateGitHubClient(token string) *scm.Client {
15-
client := github.NewDefault()
16+
func CreateClient(token, toURL, repoType string, tlsVerify bool) *scm.Client {
17+
var client *scm.Client
18+
u, _ := url.Parse(toURL)
19+
if repoType == "gitlab" {
20+
client = gitlab.NewDefault()
21+
} else if repoType == "ghe" {
22+
client, _ = github.New(u.Scheme + "://" + u.Host + "/api/v3")
23+
} else {
24+
client = github.NewDefault()
25+
}
1626
ts := oauth2.StaticTokenSource(
1727
&oauth2.Token{AccessToken: token},
1828
)
19-
client.Client = oauth2.NewClient(context.Background(), ts)
29+
if tlsVerify {
30+
client.Client = oauth2.NewClient(context.Background(), ts)
31+
} else {
32+
client.Client = &http.Client{
33+
Transport: &oauth2.Transport{
34+
Source: ts,
35+
Base: &http.Transport{
36+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
37+
},
38+
},
39+
}
40+
}
41+
if repoType == "gitlab" {
42+
u, _ := url.Parse(toURL)
43+
client.BaseURL.Host = u.Host
44+
}
2045
return client
2146
}
47+

pkg/git/client_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package git
2+
3+
import (
4+
"testing"
5+
)
6+
func TestCreateClient(t *testing.T) {
7+
create := []struct {
8+
toUrl string
9+
repoType string
10+
wantHost string
11+
}{
12+
{"https://github.com/myorg/myrepo.git", "github", "api.github.com"},
13+
{"https://gitlab.local.com/myorg/subdir/myrepo.git", "gitlab", "gitlab.local.com"},
14+
{"https://github.local.com/myorg/myrepo.git", "ghe", "github.local.com"},
15+
}
16+
17+
for _, tt := range create {
18+
client := CreateClient("tokendata", tt.toUrl, tt.repoType, true)
19+
if tt.wantHost != client.BaseURL.Host {
20+
t.Errorf("CreateClient(%s) got host %s, want %s", tt.toUrl, client.BaseURL.Host, tt.wantHost)
21+
continue
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)