Skip to content

Commit

Permalink
Initial implementation of SCM-Manager as scm_provider and pullrequest…
Browse files Browse the repository at this point in the history
…_generator for applicationsets

Signed-off-by: Eduard Heimbuch <[email protected]>
  • Loading branch information
Eduard Heimbuch authored and sdorra committed Aug 8, 2024
1 parent 80e8596 commit a0f170f
Show file tree
Hide file tree
Showing 20 changed files with 2,982 additions and 828 deletions.
8 changes: 8 additions & 0 deletions applicationset/generators/pull_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,14 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
}
return pullrequest.NewAzureDevOpsService(ctx, token, providerConfig.API, providerConfig.Organization, providerConfig.Project, providerConfig.Repo, providerConfig.Labels)
}
if generatorConfig.ScmManager != nil {
providerConfig := generatorConfig.ScmManager
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewScmManagerService(ctx, token, providerConfig.API, providerConfig.Namespace, providerConfig.Name, providerConfig.Insecure)
}
return nil, fmt.Errorf("no Pull Request provider implementation configured")
}

Expand Down
9 changes: 9 additions & 0 deletions applicationset/generators/scm_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,15 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
if awsErr != nil {
return nil, fmt.Errorf("error initializing AWS codecommit service: %w", awsErr)
}
} else if providerConfig.ScmManager != nil {
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.ScmManager.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching SCM-Manager token: %v", err)
}
provider, err = scm_provider.NewScmManagerProvider(ctx, token, providerConfig.ScmManager.API, providerConfig.ScmManager.AllBranches, providerConfig.ScmManager.Insecure)
if err != nil {
return nil, fmt.Errorf("error initializing SCM-Manager provider: %v", err)
}
} else {
return nil, fmt.Errorf("no SCM provider implementation configured")
}
Expand Down
71 changes: 71 additions & 0 deletions applicationset/services/pull_request/scm-manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package pull_request

import (
"context"
"crypto/tls"
"net/http"
"net/http/cookiejar"
"os"
"strconv"

scmm "github.com/scm-manager/goscm"
)

type ScmManagerService struct {
client *scmm.Client
namespace string
name string
}

var _ PullRequestService = (*ScmManagerService)(nil)

func NewScmManagerService(ctx context.Context, token, url, namespace, name string, insecure bool) (PullRequestService, error) {
if token == "" {
token = os.Getenv("SCMM_TOKEN")
}
httpClient := &http.Client{}
if insecure {
cookieJar, _ := cookiejar.New(nil)

httpClient = &http.Client{
Jar: cookieJar,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}
}
client, err := scmm.NewClient(url, token)
if err != nil {
return nil, err
}
client.SetHttpClient(httpClient)
return &ScmManagerService{
client: client,
namespace: namespace,
name: name,
}, nil
}

func (g *ScmManagerService) List(ctx context.Context) ([]*PullRequest, error) {
prs, err := g.client.ListPullRequests(g.namespace, g.name, g.client.NewPullRequestListFilter())
if err != nil {
return nil, err
}
list := []*PullRequest{}
for _, pr := range prs.Embedded.PullRequests {
changeset, err := g.client.GetHeadChangesetForBranch(g.namespace, g.name, pr.Source)
if err != nil {
return nil, err
}
prId, err := strconv.Atoi(pr.Id)
if err != nil {
return nil, err
}
list = append(list, &PullRequest{
Number: prId,
Branch: pr.Source,
HeadSHA: changeset.Id,
Labels: make([]string, 0),
})
}
return list, nil
}
107 changes: 107 additions & 0 deletions applicationset/services/pull_request/scm-manager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package pull_request

import (
"context"
"fmt"
"github.com/stretchr/testify/assert"
"io"
"net/http"
"net/http/httptest"
"testing"
)

func scmmMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Println(r.RequestURI)
switch r.RequestURI {
case "/api/v2/pull-requests/test-argocd/pr-test?status=OPEN&pageSize=10":
_, err := io.WriteString(w, `{
"page": 0,
"pageTotal": 1,
"_embedded": {
"pullRequests": [
{
"id": "1",
"author": {
"id": "eheimbuch",
"displayName": "Eduard Heimbuch",
"mail": "[email protected]"
},
"reviser": {
"id": null,
"displayName": null
},
"closeDate": null,
"source": "test_pr",
"target": "main",
"title": "New feature xyz",
"description": "Awesome!",
"creationDate": "2023-01-23T12:58:56.770Z",
"lastModified": null,
"status": "OPEN",
"reviewer": [],
"tasks": {
"todo": 0,
"done": 0
},
"sourceRevision": null,
"targetRevision": null,
"markedAsReviewed": [],
"emergencyMerged": false,
"ignoredMergeObstacles": null
}
]
}
}`)
if err != nil {
t.Fail()
}
case "/api/v2/repositories/test-argocd/pr-test/branches/test_pr/changesets?&pageSize=1":
_, err := io.WriteString(w, `{
"page": 0,
"pageTotal": 1,
"_embedded": {
"changesets": [
{
"id": "b4ed814b1afe810c4902bc5590c7b09531296679",
"author": {
"mail": "[email protected]",
"name": "Eduard Heimbuch"
},
"date": "2023-07-03T08:53:15Z",
"description": "test url",
"contributors": [
{
"type": "Pushed-by",
"person": {
"mail": "[email protected]",
"name": "Eduard Heimbuch"
}
}
]
}
]
}
}`)
if err != nil {
t.Fail()
}
}
}
}

func TestScmManagerPrList(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
scmmMockHandler(t)(w, r)
}))
defer ts.Close()
host, err := NewScmManagerService(context.Background(), "", ts.URL, "test-argocd", "pr-test", false)
assert.Nil(t, err)
prs, err := host.List(context.Background())
assert.Nil(t, err)
assert.Equal(t, len(prs), 1)
assert.Equal(t, prs[0].Number, 1)
assert.Equal(t, prs[0].Branch, "test_pr")
assert.Equal(t, prs[0].HeadSHA, "b4ed814b1afe810c4902bc5590c7b09531296679")
}
151 changes: 151 additions & 0 deletions applicationset/services/scm_provider/scm-manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package scm_provider

import (
"context"
"crypto/tls"
"errors"
"fmt"
"net/http"
"net/http/cookiejar"
"os"

scmm "github.com/scm-manager/goscm"
)

type ScmManagerProvider struct {
client *scmm.Client
allBranches bool
}

var _ SCMProviderService = &ScmManagerProvider{}

func NewScmManagerProvider(ctx context.Context, token, url string, allBranches, insecure bool) (*ScmManagerProvider, error) {
if token == "" {
token = os.Getenv("SCMM_TOKEN")
}
httpClient := &http.Client{}
if insecure {
cookieJar, _ := cookiejar.New(nil)

httpClient = &http.Client{
Jar: cookieJar,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}
}
client, err := scmm.NewClient(url, token)
if err != nil {
return nil, fmt.Errorf("error creating a new SCM-Manager client: %w", err)
}
client.SetHttpClient(httpClient)
return &ScmManagerProvider{
client: client,
allBranches: allBranches,
}, nil
}

func (g *ScmManagerProvider) GetBranches(ctx context.Context, repo *Repository) ([]*Repository, error) {
scmmRepo, err := g.client.GetRepo(repo.Organization, repo.Repository)
if err != nil {
return nil, err
}

if !g.allBranches {
defaultBranch, err := g.client.GetDefaultBranch(repo.Organization, repo.Repository)
if err != nil {
return nil, err
}

return []*Repository{
{
Organization: repo.Organization,
Repository: repo.Repository,
Branch: defaultBranch.Name,
URL: repo.URL,
SHA: defaultBranch.Revision,
Labels: make([]string, 0),
RepositoryId: scmmRepo.Namespace + "/" + scmmRepo.Name,
},
}, nil
}
repos := []*Repository{}
branches, err := g.client.ListRepoBranches(repo.Organization, repo.Repository)
if err != nil {
return nil, err
}
for _, branch := range branches.Embedded.Branches {
repos = append(repos, &Repository{
Organization: scmmRepo.Namespace,
Repository: scmmRepo.Name,
Branch: branch.Name,
URL: scmmRepo.Links.ProtocolUrl[0].Href,
SHA: branch.Revision,
Labels: make([]string, 0),
RepositoryId: scmmRepo.Namespace + "/" + scmmRepo.Name,
})
}
return repos, nil
}

func (g *ScmManagerProvider) ListRepos(ctx context.Context, cloneProtocol string) ([]*Repository, error) {
repos := []*Repository{}
filter := g.client.NewRepoListFilter()
filter.Limit = 9999
scmmRepos, err := g.client.ListRepos(filter)
if err != nil {
return nil, err
}
for _, scmmRepo := range scmmRepos.Embedded.Repositories {
var url string
switch cloneProtocol {
// Default to SSH if unspecified (i.e. if ""). SSH Plugin needs to be installed
case "", "ssh":
url = getProtocolUrlByName(scmmRepo.Links.ProtocolUrl, "ssh")
case "https":
url = getProtocolUrlByName(scmmRepo.Links.ProtocolUrl, "http")
default:
return nil, fmt.Errorf("unknown clone protocol %v", cloneProtocol)
}

if url == "" {
return nil, errors.New("could not find valid repository protocol url")
}

defaultBranch, err := g.client.GetDefaultBranch(scmmRepo.Namespace, scmmRepo.Name)
if err != nil {
return nil, err
}

repos = append(repos, &Repository{
Organization: scmmRepo.Namespace,
Repository: scmmRepo.Name,
Branch: defaultBranch.Name,
URL: url,
RepositoryId: scmmRepo.Namespace + "/" + scmmRepo.Name,
})
}
return repos, nil
}

func getProtocolUrlByName(urls []scmm.ProtocolUrl, name string) string {
for _, url := range urls {
if url.Name == name {
return url.Href
}
}
return ""
}

func (g *ScmManagerProvider) RepoHasPath(ctx context.Context, repo *Repository, path string) (bool, error) {
_, resp, err := g.client.GetContent(repo.Organization, repo.Repository, repo.Branch, path)
if resp != nil && resp.StatusCode == 404 {
return false, nil
}
if fmt.Sprint(err) == "expect file, got directory" {
return true, nil
}
if err != nil {
return false, err
}
return true, nil
}
Loading

0 comments on commit a0f170f

Please sign in to comment.