From 9871bef92f90903858f1eae8931ed4504b8c4861 Mon Sep 17 00:00:00 2001 From: David Wertenteil Date: Wed, 8 Jun 2022 18:25:42 +0300 Subject: [PATCH] support azure --- azureparser/v1/datastructures.go | 15 ++++ azureparser/v1/parser.go | 118 +++++++++++++++++++++++++++++++ azureparser/v1/parser_test.go | 63 +++++++++++++++++ githubparser/v1/parser.go | 5 +- githubparser/v1/parser_test.go | 1 + init.go | 22 +++++- init_test.go | 61 ++++++++++++++-- interface.go | 13 ++-- 8 files changed, 283 insertions(+), 15 deletions(-) create mode 100644 azureparser/v1/datastructures.go create mode 100644 azureparser/v1/parser.go create mode 100644 azureparser/v1/parser_test.go diff --git a/azureparser/v1/datastructures.go b/azureparser/v1/datastructures.go new file mode 100644 index 0000000..7d4d9f4 --- /dev/null +++ b/azureparser/v1/datastructures.go @@ -0,0 +1,15 @@ +package v1 + +type AzureURL struct { + host string // default is github + owner string // repo owner + repo string // repo name + project string + branch string + tag string + path string + token string // github token + isFile bool // is the URL is pointing to a file or not + username string + password string +} diff --git a/azureparser/v1/parser.go b/azureparser/v1/parser.go new file mode 100644 index 0000000..887eed5 --- /dev/null +++ b/azureparser/v1/parser.go @@ -0,0 +1,118 @@ +package v1 + +import ( + "fmt" + "net/url" + "os" + "strings" + + giturl "github.com/whilp/git-urls" +) + +const HOST = "azure.com" +const HOST_DEV = "dev.azure.com" +const HOST_PROD = "prod.azure.com" + +// NewGitHubParser empty instance of a github parser +func NewAzureParser() *AzureURL { + + return &AzureURL{ + token: os.Getenv("AZURE_TOKEN"), + } +} + +// NewGitHubParserWithURL parsed instance of a github parser +func NewAzureParserWithURL(fullURL string) (*AzureURL, error) { + az := NewAzureParser() + + if err := az.Parse(fullURL); err != nil { + return az, err + } + + return az, nil +} + +func (az *AzureURL) GetURL() *url.URL { + return &url.URL{ + Scheme: "https", + Host: az.host, + Path: fmt.Sprintf("%s/%s/_git/%s", az.GetOwnerName(), az.GetProjectName(), az.GetRepoName()), + } +} + +func IsHostAzure(host string) bool { return strings.HasSuffix(host, HOST) } + +func (az *AzureURL) GetProvider() string { return "azure" } +func (az *AzureURL) GetHostName() string { return az.host } +func (az *AzureURL) GetProjectName() string { return az.project } +func (az *AzureURL) GetBranchName() string { return az.branch } +func (az *AzureURL) GetTag() string { return az.tag } +func (az *AzureURL) GetOwnerName() string { return az.owner } +func (az *AzureURL) GetRepoName() string { return az.repo } +func (az *AzureURL) GetPath() string { return az.path } +func (az *AzureURL) GetToken() string { return az.token } + +func (az *AzureURL) SetOwnerName(o string) { az.owner = o } +func (az *AzureURL) SetProjectName(project string) { az.project = project } +func (az *AzureURL) SetRepoName(r string) { az.repo = r } +func (az *AzureURL) SetBranchName(branch string) { az.branch = branch } +func (az *AzureURL) SetTag(tag string) { az.tag = tag } +func (az *AzureURL) SetPath(p string) { az.path = p } +func (az *AzureURL) SetToken(token string) { az.token = token } + +// Parse URL +func (az *AzureURL) Parse(fullURL string) error { + parsedURL, err := giturl.Parse(fullURL) + if err != nil { + return err + } + az.host = parsedURL.Host + + if strings.HasPrefix(az.host, "ssh") { + az.host = strings.TrimPrefix(az.host, "ssh.") + return az.parseHostSSH(parsedURL) + } + return az.parseHostHTTP(parsedURL) +} + +func (az *AzureURL) parseHostSSH(parsedURL *url.URL) error { + splittedRepo := strings.FieldsFunc(parsedURL.Path, func(c rune) bool { return c == '/' }) // trim empty fields from returned slice + + if len(splittedRepo) < 3 || len(splittedRepo) > 5 { + return fmt.Errorf("expecting v/// in url path, received: '%s'", parsedURL.Path) + } + + index := 0 + if len(splittedRepo) == 4 { + index++ + } + az.owner = splittedRepo[index] + az.project = splittedRepo[index+1] + az.repo = splittedRepo[index+2] + + return nil +} +func (az *AzureURL) parseHostHTTP(parsedURL *url.URL) error { + splittedRepo := strings.FieldsFunc(parsedURL.Path, func(c rune) bool { return c == '/' }) // trim empty fields from returned slice + if len(splittedRepo) < 4 || splittedRepo[2] != "_git" { + return fmt.Errorf("expecting //_git/ in url path, received: '%s'", parsedURL.Path) + } + az.owner = splittedRepo[0] + az.project = splittedRepo[1] + az.repo = splittedRepo[3] + + if v := parsedURL.Query().Get("version"); v != "" { + if strings.HasPrefix(v, "GB") { + az.branch = strings.TrimPrefix(v, "GB") + } + if strings.HasPrefix(v, "GT") { + az.tag = strings.TrimPrefix(v, "GT") + } + } + + if v := parsedURL.Query().Get("path"); v != "" { + az.path = v + } + + return nil +} diff --git a/azureparser/v1/parser_test.go b/azureparser/v1/parser_test.go new file mode 100644 index 0000000..53351f4 --- /dev/null +++ b/azureparser/v1/parser_test.go @@ -0,0 +1,63 @@ +package v1 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + urlA = "https://dev.azure.com/dwertent/ks-testing-public/_git/ks-testing-public" + urlB = "https://dev.azure.com/dwertent/ks-testing-public/_git/ks-testing-public?path=/rules-tests/alert-rw-hostpath/deployment/expected.json" + urlC = "https://dev.azure.com/dwertent/ks-testing-public/_git/ks-testing-public?path=/scripts&version=GBdev&_a=contents" + urlD = "https://dev.azure.com/dwertent/ks-testing-public/_git/ks-testing-public?path=/scripts&version=GTv1.0.1&_a=contents" +) + +func TestNewGitHubParserWithURL(t *testing.T) { + { + az, err := NewAzureParserWithURL(urlA) + assert.NoError(t, err) + assert.Equal(t, "dev.azure.com", az.GetHostName()) + assert.Equal(t, "azure", az.GetProvider()) + assert.Equal(t, "dwertent", az.GetOwnerName()) + assert.Equal(t, "ks-testing-public", az.GetRepoName()) + assert.Equal(t, urlA, az.GetURL().String()) + assert.Equal(t, "", az.GetBranchName()) + assert.Equal(t, "", az.GetPath()) + } + { + az, err := NewAzureParserWithURL(urlB) + assert.NoError(t, err) + assert.Equal(t, "dev.azure.com", az.GetHostName()) + assert.Equal(t, "azure", az.GetProvider()) + assert.Equal(t, "dwertent", az.GetOwnerName()) + assert.Equal(t, "ks-testing-public", az.GetRepoName()) + assert.Equal(t, urlA, az.GetURL().String()) + assert.Equal(t, "", az.GetBranchName()) + assert.Equal(t, "/rules-tests/alert-rw-hostpath/deployment/expected.json", az.GetPath()) + } + { + az, err := NewAzureParserWithURL(urlC) + assert.NoError(t, err) + assert.Equal(t, "dev.azure.com", az.GetHostName()) + assert.Equal(t, "azure", az.GetProvider()) + assert.Equal(t, "dwertent", az.GetOwnerName()) + assert.Equal(t, "ks-testing-public", az.GetRepoName()) + assert.Equal(t, urlA, az.GetURL().String()) + assert.Equal(t, "dev", az.GetBranchName()) + assert.Equal(t, "", az.GetTag()) + assert.Equal(t, "/scripts", az.GetPath()) + } + { + az, err := NewAzureParserWithURL(urlD) + assert.NoError(t, err) + assert.Equal(t, "dev.azure.com", az.GetHostName()) + assert.Equal(t, "azure", az.GetProvider()) + assert.Equal(t, "dwertent", az.GetOwnerName()) + assert.Equal(t, "ks-testing-public", az.GetRepoName()) + assert.Equal(t, urlA, az.GetURL().String()) + assert.Equal(t, "v1.0.1", az.GetTag()) + assert.Equal(t, "", az.GetBranchName()) + assert.Equal(t, "/scripts", az.GetPath()) + } +} diff --git a/githubparser/v1/parser.go b/githubparser/v1/parser.go index 7e799d4..9fafd3c 100644 --- a/githubparser/v1/parser.go +++ b/githubparser/v1/parser.go @@ -38,6 +38,9 @@ func (gh *GitHubURL) GetURL() *url.URL { Path: fmt.Sprintf("%s/%s", gh.GetOwnerName(), gh.GetRepoName()), } } +func IsHostGitHub(host string) bool { + return host == githubapi.DEFAULT_HOST || host == githubapi.RAW_HOST +} func (gh *GitHubURL) GetProvider() string { return "github" } func (gh *GitHubURL) GetHostName() string { return gh.host } @@ -66,7 +69,7 @@ func (gh *GitHubURL) Parse(fullURL string) error { index := 0 - splittedRepo := strings.FieldsFunc(parsedURL.Path, func(c rune) bool { return c == '/' }) + splittedRepo := strings.FieldsFunc(parsedURL.Path, func(c rune) bool { return c == '/' }) // trim empty fields from returned slice if len(splittedRepo) < 2 { return fmt.Errorf("expecting / in url path, received: '%s'", parsedURL.Path) } diff --git a/githubparser/v1/parser_test.go b/githubparser/v1/parser_test.go index f313089..1409d75 100644 --- a/githubparser/v1/parser_test.go +++ b/githubparser/v1/parser_test.go @@ -20,6 +20,7 @@ func TestNewGitHubParserWithURL(t *testing.T) { gh, err := NewGitHubParserWithURL(urlA) assert.NoError(t, err) assert.Equal(t, "github.com", gh.GetHostName()) + assert.Equal(t, "github", gh.GetProvider()) assert.Equal(t, "armosec", gh.GetOwnerName()) assert.Equal(t, "go-git-url", gh.GetRepoName()) assert.Equal(t, urlA, gh.GetURL().String()) diff --git a/init.go b/init.go index eb1634c..65f33b7 100644 --- a/init.go +++ b/init.go @@ -2,9 +2,11 @@ package giturl import ( "fmt" - "net/url" + + giturl "github.com/whilp/git-urls" "github.com/armosec/go-git-url/apis/githubapi" + azureparserv1 "github.com/armosec/go-git-url/azureparser/v1" githubparserv1 "github.com/armosec/go-git-url/githubparser/v1" ) @@ -15,6 +17,22 @@ func NewGitURL(fullURL string) (IGitURL, error) { return nil, err } + if githubparserv1.IsHostGitHub(hostUrl) { + return githubparserv1.NewGitHubParserWithURL(fullURL) + } + if azureparserv1.IsHostAzure(hostUrl) { + return azureparserv1.NewAzureParserWithURL(fullURL) + } + return nil, fmt.Errorf("repository host '%s' not supported", hostUrl) +} + +// NewGitAPI get instance of git api +func NewGitAPI(fullURL string) (IGitAPI, error) { + hostUrl, err := getHost(fullURL) + if err != nil { + return nil, err + } + switch hostUrl { case githubapi.DEFAULT_HOST, githubapi.RAW_HOST: return githubparserv1.NewGitHubParserWithURL(fullURL) @@ -24,7 +42,7 @@ func NewGitURL(fullURL string) (IGitURL, error) { } func getHost(fullURL string) (string, error) { - parsedURL, err := url.Parse(fullURL) + parsedURL, err := giturl.Parse(fullURL) if err != nil { return "", err } diff --git a/init_test.go b/init_test.go index f0c7b60..5bff07b 100644 --- a/init_test.go +++ b/init_test.go @@ -7,11 +7,58 @@ import ( ) func TestNewGitURL(t *testing.T) { + { // parse github + const githubURL = "https://github.com/armosec/go-git-url" + gh, err := NewGitURL(githubURL) + assert.NoError(t, err) + assert.Equal(t, "github", gh.GetProvider()) + assert.Equal(t, "armosec", gh.GetOwnerName()) + assert.Equal(t, "go-git-url", gh.GetRepoName()) + assert.Equal(t, "", gh.GetBranchName()) + assert.Equal(t, githubURL, gh.GetURL().String()) + } + { // parse github + const githubURL = "git@github.com:armosec/go-git-url.git" + gh, err := NewGitURL(githubURL) + assert.NoError(t, err) + assert.Equal(t, "github", gh.GetProvider()) + assert.Equal(t, "armosec", gh.GetOwnerName()) + assert.Equal(t, "go-git-url", gh.GetRepoName()) + assert.Equal(t, "", gh.GetBranchName()) + assert.Equal(t, "https://github.com/armosec/go-git-url", gh.GetURL().String()) + } + { // parse azure + const azureURL = "https://dev.azure.com/dwertent/ks-testing-public/_git/ks-testing-public" + az, err := NewGitURL(azureURL) + assert.NoError(t, err) + assert.NoError(t, err) + assert.Equal(t, "azure", az.GetProvider()) + assert.Equal(t, "dwertent", az.GetOwnerName()) + assert.Equal(t, "ks-testing-public", az.GetRepoName()) + assert.Equal(t, "", az.GetBranchName()) + assert.Equal(t, "", az.GetPath()) + assert.Equal(t, azureURL, az.GetURL().String()) + } + { // parse azure + const azureURL = "git@ssh.dev.azure.com:v3/dwertent/ks-testing-public/ks-testing-public" + az, err := NewGitURL(azureURL) + assert.NoError(t, err) + assert.NoError(t, err) + assert.Equal(t, "azure", az.GetProvider()) + assert.Equal(t, "dwertent", az.GetOwnerName()) + assert.Equal(t, "ks-testing-public", az.GetRepoName()) + assert.Equal(t, "", az.GetBranchName()) + assert.Equal(t, "", az.GetPath()) + assert.Equal(t, "https://dev.azure.com/dwertent/ks-testing-public/_git/ks-testing-public", az.GetURL().String()) + } + +} +func TestNewGitAPI(t *testing.T) { fileText := "https://raw.githubusercontent.com/armosec/go-git-url/master/files/file0.text" - var gitURL IGitURL + var gitURL IGitAPI var err error { - gitURL, err = NewGitURL("https://github.com/armosec/go-git-url") + gitURL, err = NewGitAPI("https://github.com/armosec/go-git-url") assert.NoError(t, err) files, err := gitURL.ListFilesNamesWithExtension([]string{"yaml", "json"}) @@ -20,7 +67,7 @@ func TestNewGitURL(t *testing.T) { } { - gitURL, err = NewGitURL("https://github.com/armosec/go-git-url") + gitURL, err = NewGitAPI("https://github.com/armosec/go-git-url") assert.NoError(t, err) files, errM := gitURL.DownloadFilesWithExtension([]string{"text"}) @@ -31,7 +78,7 @@ func TestNewGitURL(t *testing.T) { } { - gitURL, err = NewGitURL(fileText) + gitURL, err = NewGitAPI(fileText) assert.NoError(t, err) files, errM := gitURL.DownloadFilesWithExtension([]string{"text"}) @@ -41,7 +88,7 @@ func TestNewGitURL(t *testing.T) { } { - gitURL, err = NewGitURL(fileText) + gitURL, err = NewGitAPI(fileText) assert.NoError(t, err) files, errM := gitURL.DownloadAllFiles() @@ -51,7 +98,7 @@ func TestNewGitURL(t *testing.T) { } { - gitURL, err := NewGitURL("https://github.com/armosec/go-git-url/tree/master/files") + gitURL, err := NewGitAPI("https://github.com/armosec/go-git-url/tree/master/files") assert.NoError(t, err) files, errM := gitURL.DownloadFilesWithExtension([]string{"text"}) @@ -62,7 +109,7 @@ func TestNewGitURL(t *testing.T) { } { - gitURL, err = NewGitURL("https://github.com/armosec/go-git-url/blob/master/files/file0.text") + gitURL, err = NewGitAPI("https://github.com/armosec/go-git-url/blob/master/files/file0.text") assert.NoError(t, err) files, errM := gitURL.DownloadFilesWithExtension([]string{"text"}) diff --git a/interface.go b/interface.go index 3d79afb..378c45f 100644 --- a/interface.go +++ b/interface.go @@ -11,24 +11,27 @@ type IGitURL interface { SetBranchName(string) SetOwnerName(string) SetPath(string) - SetToken(string) SetRepoName(string) GetProvider() string GetBranchName() string GetOwnerName() string GetPath() string - GetToken() string GetRepoName() string // parse url Parse(fullURL string) error - // set default branch name using the providers git API - SetDefaultBranchName() error - // GetURL git url GetURL() *url.URL +} + +type IGitAPI interface { + GetToken() string + SetToken(string) + + // set default branch name using the providers git API + SetDefaultBranchName() error // ListFilesNamesWithExtension list all files in path with the desired extension. if empty will list all (including directories) ListFilesNamesWithExtension(extensions []string) ([]string, error)