From afeee531523a509973ab9f2f01b71173bf758b77 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Wed, 22 May 2024 20:11:02 +0200 Subject: [PATCH] Implement GetTaskURI for GitLab provider This will be using the token when fetching tasks if the fetched URL is on the same host of where the Repository URL is. Signed-off-by: Chmouel Boudjnah --- docs/content/docs/guide/resolver.md | 26 +++++++-- pkg/provider/gitlab/gitlab.go | 7 +-- pkg/provider/gitlab/task.go | 86 +++++++++++++++++++++++++++++ pkg/provider/gitlab/task_test.go | 57 +++++++++++++++++++ 4 files changed, 164 insertions(+), 12 deletions(-) create mode 100644 pkg/provider/gitlab/task.go create mode 100644 pkg/provider/gitlab/task_test.go diff --git a/docs/content/docs/guide/resolver.md b/docs/content/docs/guide/resolver.md index ce763b10d..20dc69f6e 100644 --- a/docs/content/docs/guide/resolver.md +++ b/docs/content/docs/guide/resolver.md @@ -139,13 +139,15 @@ will fetch the task directly from that remote URL : pipelinesascode.tekton.dev/task: "[https://remote.url/task.yaml]" ``` -### Remote HTTP URL from a private GitHub repository +### Remote HTTP URL from a private repository -If you are using `GitHub` and If the remote task URL uses the same host as where -the repository CRD is, Pipelines-as-Code will use the GitHub token and fetch the URL using the -GitHub API. +If you are using the `GitHub` or the `GitLab` provider and If the remote task +URL uses the same host as where the repository CRD is, Pipelines-as-Code will +use the provided token to fetch the URL using the GitHub or GitLab API. -For example if you have a repository URL looking like this : +#### GitHub + +When using the GitHub provider if you have a repository URL looking like this : @@ -153,7 +155,7 @@ and the remote HTTP URLs is a referenced GitHub "blob" URL: -if the remote HTTP URL has a slash (/) in the branch name you will need to HTML +If the remote HTTP URL has a slash (/) in the branch name you will need to HTML encode with the `%2F` character, example: @@ -169,6 +171,18 @@ There is settings you can set in the Pipelines-as-Code `Configmap` to control th `secret-github-app-token-scoped` and `secret-github-app-scope-extra-repos` settings in the [settings documentation](/docs/install/settings). +#### GitLab + +This same applies to `GitLab` URL as directly copied from `GitLab` UI like this: + + + +or `GitLab` raw URL like this: + + + +The GitLab token as provider in the Repository CR will be used to fetch the file. + ### Tasks or Pipelines inside the repository Additionally, you can as well have a reference to a task or pipeline from a YAML file inside diff --git a/pkg/provider/gitlab/gitlab.go b/pkg/provider/gitlab/gitlab.go index b7e649fdd..555b2b5dd 100644 --- a/pkg/provider/gitlab/gitlab.go +++ b/pkg/provider/gitlab/gitlab.go @@ -63,11 +63,6 @@ func (v *Provider) SetPacInfo(pacInfo *info.PacOpts) { v.pacInfo = pacInfo } -// GetTaskURI TODO: Implement me. -func (v *Provider) GetTaskURI(_ context.Context, _ *info.Event, _ string) (bool, string, error) { - return false, "", nil -} - // CheckPolicyAllowing TODO: Implement ME. func (v *Provider) CheckPolicyAllowing(_ context.Context, _ *info.Event, _ []string) (bool, string) { return false, "" @@ -253,7 +248,7 @@ func (v *Provider) GetTektonDir(_ context.Context, event *info.Event, path, prov } objects, resp, err := v.Client.Repositories.ListTree(v.sourceProjectID, opt) - if resp != nil && resp.Response.StatusCode == http.StatusNotFound { + if resp != nil && resp.StatusCode == http.StatusNotFound { return "", nil } if err != nil { diff --git a/pkg/provider/gitlab/task.go b/pkg/provider/gitlab/task.go new file mode 100644 index 000000000..63223832b --- /dev/null +++ b/pkg/provider/gitlab/task.go @@ -0,0 +1,86 @@ +package gitlab + +import ( + "context" + "fmt" + "net/url" + "regexp" + + "github.com/openshift-pipelines/pipelines-as-code/pkg/params/info" + "github.com/openshift-pipelines/pipelines-as-code/pkg/provider" +) + +type gitLabInfo struct { + Host string + GroupOrUser string + Repository string + Revision string + FilePath string +} + +// extractGitLabInfo generated with chatGPT https://chatgpt.com/share/e3c06a7e-3f16-4891-85c7-832b3e7f25c5 +func extractGitLabInfo(gitlabURL string) (*gitLabInfo, error) { + parsedURL, err := url.Parse(gitlabURL) + if err != nil { + return nil, err + } + + // Regular expression to match the specific GitLab URL pattern + re := regexp.MustCompile(`^/([^/]+(?:/[^/]+)*)/([^/]+)/-/blob/([^/]+)(/.*)?|^/([^/]+(?:/[^/]+)*)/([^/]+)/-/raw/([^/]+)(/.*)?`) + matches := re.FindStringSubmatch(parsedURL.Path) + + if len(matches) == 0 { + return nil, fmt.Errorf("URL does not match the expected GitLab pattern") + } + + groupOrUser := "" + repoName := "" + revision := "" + filePath := "" + + if matches[1] != "" { // For /blob/ URLs + groupOrUser = matches[1] + repoName = matches[2] + revision = matches[3] + if len(matches) >= 5 && matches[4] != "" { + filePath = matches[4][1:] // Remove initial slash + } + } else if matches[5] != "" { // For /raw/ URLs + groupOrUser = matches[5] + repoName = matches[6] + revision = matches[7] + if len(matches) >= 9 && matches[8] != "" { + filePath = matches[8][1:] // Remove initial slash + } + } + + return &gitLabInfo{ + Host: parsedURL.Host, + GroupOrUser: groupOrUser, + Repository: repoName, + Revision: revision, + FilePath: filePath, + }, nil +} + +// GetTaskURI if we are getting a URL from the same URL where the provider is, +// it means we can try to get the file with the provider token. +func (v *Provider) GetTaskURI(ctx context.Context, event *info.Event, uri string) (bool, string, error) { + if ret := provider.CompareHostOfURLS(uri, event.URL); !ret { + return false, "", nil + } + extracted, err := extractGitLabInfo(uri) + if err != nil { + return false, "", err + } + + nEvent := info.NewEvent() + nEvent.Organization = extracted.GroupOrUser + nEvent.Repository = extracted.Repository + nEvent.BaseBranch = extracted.Revision + ret, err := v.GetFileInsideRepo(ctx, nEvent, extracted.FilePath, extracted.Revision) + if err != nil { + return false, "", err + } + return true, ret, nil +} diff --git a/pkg/provider/gitlab/task_test.go b/pkg/provider/gitlab/task_test.go new file mode 100644 index 000000000..82a43c47f --- /dev/null +++ b/pkg/provider/gitlab/task_test.go @@ -0,0 +1,57 @@ +package gitlab + +import ( + "testing" + + "gotest.tools/v3/assert" +) + +func TestExtractGitLabInfo(t *testing.T) { + tests := []struct { + url string + name string + expected *gitLabInfo + }{ + { + name: "custom host", + url: "https://gitlab.chmouel.com/group/subgroup/repo/-/blob/main/README.md?ref_type=heads", + expected: &gitLabInfo{ + Host: "gitlab.chmouel.com", + GroupOrUser: "group/subgroup", + Repository: "repo", + Revision: "main", + FilePath: "README.md", + }, + }, + { + name: "org repo", + url: "https://gitlab.com/org/repo/-/blob/main/README.md", + expected: &gitLabInfo{ + Host: "gitlab.com", + GroupOrUser: "org", + Repository: "repo", + Revision: "main", + FilePath: "README.md", + }, + }, + { + name: "long group and subgroups", + url: "https://gitlab.com/gitlab-com/partners/alliance/corp/sandbox/another/foo-foo/-/raw/main/hello.txt?ref_type=heads", + expected: &gitLabInfo{ + Host: "gitlab.com", + GroupOrUser: "gitlab-com/partners/alliance/corp/sandbox/another", + Repository: "foo-foo", + Revision: "main", + FilePath: "hello.txt", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + info, err := extractGitLabInfo(tt.url) + assert.NilError(t, err) + assert.DeepEqual(t, info, tt.expected) + }) + } +}