Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit

Permalink
feat: add gitness provider (daytonaio#689)
Browse files Browse the repository at this point in the history
Signed-off-by: rutik7066 <rutikthakre@gmail.com>
  • Loading branch information
Rutik7066 authored Jun 21, 2024
1 parent 88d7d2f commit 9682edf
Show file tree
Hide file tree
Showing 8 changed files with 828 additions and 3 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Set up a development environment on any infrastructure, with a single command.
* __Configuration File Support__: Initially support for [dev container](https://containers.dev/), ability to expand to DevFile, Nix & Flox (Contributions welcome here!).
* __Prebuilds System__: Drastically improve environment setup times (Contributions welcome here!).
* __IDE Support__ : Seamlessly supports [VS Code](https://github.com/microsoft/vscode) & [JetBrains](https://www.jetbrains.com/remote-development/gateway/) locally, ready to use without configuration. Includes a built-in Web IDE for added convenience.
* __Git Provider Integration__: GitHub, GitLab, Bitbucket & Gitea can be connected, allowing easy repo branch or PR pull and commit back from the workspaces.
* __Git Provider Integration__: GitHub, GitLab, Bitbucket, Gitea & Gitness can be connected, allowing easy repo branch or PR pull and commit back from the workspaces.
* __Multiple Project Workspace__: Support for multiple project repositories in the same workspace, making it easy to develop using a micro-service architecture.
* __Reverse Proxy Integration__: Enable collaboration and streamline feedback loops by leveraging reverse proxy functionality. Access preview ports and the Web IDE seamlessly, even behind firewalls.
* __Extensibility__: Enable extensibility with plugin or provider development. Moreover, in any dynamic language, not just Go(Contributions welcome here!).
Expand Down Expand Up @@ -159,7 +159,7 @@ This initiates the Daytona Server in daemon mode. Use the command:
daytona server
```
__2. Add Your Git Provider of Choice:__
Daytona supports GitHub, GitLab, Bitbucket and Gitea. To add them to your profile, use the command:
Daytona supports GitHub, GitLab, Bitbucket, Gitea and Gitness. To add them to your profile, use the command:
```bash
daytona git-providers add

Expand Down
5 changes: 5 additions & 0 deletions cmd/daytona/config/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func GetSupportedGitProviders() []GitProvider {
{"bitbucket", "Bitbucket"},
{"codeberg", "Codeberg"},
{"gitea", "Gitea"},
{"gitness", "Gitness"},
}
}

Expand All @@ -69,6 +70,8 @@ func GetDocsLinkFromGitProvider(providerId string) string {
return "https://docs.codeberg.org/advanced/access-token/"
case "gitea":
return "https://docs.gitea.com/1.21/development/api-usage#generating-and-listing-api-tokens"
case "gitness":
return "https://docs.gitness.com/administration/user-management#generate-user-token"
default:
return ""
}
Expand All @@ -90,6 +93,8 @@ func GetScopesFromGitProvider(providerId string) string {
fallthrough
case "gitea":
return "read:organization,write:repository,read:user"
case "gitness":
return "/"
default:
return ""
}
Expand Down
295 changes: 295 additions & 0 deletions pkg/gitnessclient/gitness_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
// Copyright 2024 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0

package gitnessclient

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strings"
)

const personalNamespaceId = "<PERSONAL>"

type GitnessClient struct {
token string
BaseURL *url.URL
}

func NewGitnessClient(token string, baseUrl *url.URL) *GitnessClient {
return &GitnessClient{
token: token,
BaseURL: baseUrl,
}
}

func (g *GitnessClient) performRequest(method, requestURL string) ([]byte, error) {
req, err := http.NewRequestWithContext(context.Background(), method, requestURL, nil)
if err != nil {
return nil, err
}

req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+g.token)

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, err
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

return body, nil
}

func (g *GitnessClient) GetSpaceAdmin(spaceName string) (*SpaceMemberResponse, error) {
spacesURL, err := g.BaseURL.Parse(fmt.Sprintf("/api/v1/spaces/%s/members", spaceName))
if err != nil {
return nil, err
}

body, err := g.performRequest("GET", spacesURL.String())
if err != nil {
return nil, err
}

var apiMemberships []SpaceMemberResponse
if err := json.Unmarshal(body, &apiMemberships); err != nil {
return nil, err
}
var admin *SpaceMemberResponse
for _, member := range apiMemberships {
if member.Role == "space_owner" {
admin = &member
break
}
}
if admin == nil {
return nil, err
}

return admin, nil
}

func (g *GitnessClient) GetSpaces() ([]MembershipResponse, error) {
spacesURL, err := g.BaseURL.Parse("/api/v1/user/memberships")
if err != nil {
return nil, err
}
values := url.Values{}
values.Add("order", "asc")
values.Add("sort", "identifier")
values.Add("page", "1")
values.Add("limit", "100")
apiUrl := spacesURL.String() + "?" + values.Encode()

body, err := g.performRequest("GET", apiUrl)
if err != nil {
return nil, err
}

var apiMemberships []MembershipResponse
if err := json.Unmarshal(body, &apiMemberships); err != nil {
return nil, err
}

return apiMemberships, nil
}

func (g *GitnessClient) GetUser() (*UserResponse, error) {
userURL, err := g.BaseURL.Parse("/api/v1/user")
if err != nil {
return nil, err
}

body, err := g.performRequest("GET", userURL.String())
if err != nil {
return nil, err
}

var apiUser UserResponse
if err := json.Unmarshal(body, &apiUser); err != nil {
return nil, err
}

return &apiUser, nil
}

func (g *GitnessClient) GetRepositories(namespace string) ([]Repository, error) {
space := ""
if namespace == personalNamespaceId {
user, err := g.GetUser()
if err != nil {
return nil, err
}
space = user.UID
} else {
space = namespace
}

reposURL, err := g.BaseURL.Parse(fmt.Sprintf("/api/v1/spaces/%s/+/repos", space))
if err != nil {
return nil, err
}

body, err := g.performRequest("GET", reposURL.String())
if err != nil {
return nil, err
}

var apiRepos []Repository
if err := json.Unmarshal(body, &apiRepos); err != nil {
return nil, err
}

return apiRepos, nil
}

func (g *GitnessClient) GetRepoBranches(repositoryId string, namespaceId string) ([]*RepoBranch, error) {
branchesURL, err := g.BaseURL.Parse(fmt.Sprintf("/api/v1/repos/%s/branches", url.PathEscape(namespaceId+"/"+repositoryId)))
if err != nil {
return nil, err
}

body, err := g.performRequest("GET", branchesURL.String())
if err != nil {
return nil, err
}

var branches []*RepoBranch
if err := json.Unmarshal(body, &branches); err != nil {
return nil, err
}

return branches, nil
}

func (g *GitnessClient) GetRepoPRs(repositoryId string, namespaceId string) ([]*PR, error) {
prsURL, err := g.BaseURL.Parse(fmt.Sprintf("/api/v1/repos/%s/pullreq", url.PathEscape(namespaceId+"/"+repositoryId)))
if err != nil {
return nil, err
}
values := url.Values{}
values.Add("state", "open")
apiUrl := prsURL.String() + "?" + values.Encode()

body, err := g.performRequest("GET", apiUrl)
if err != nil {
return nil, err
}

var apiPRs []*PR
if err := json.Unmarshal(body, &apiPRs); err != nil {
return nil, err
}

return apiPRs, nil
}

func (g *GitnessClient) GetLastCommitSha(repoURL string, branch *string) (string, error) {
ref, err := g.GetRepoRef(repoURL)
if err != nil {
return "", err
}
api, err := g.BaseURL.Parse(fmt.Sprintf("/api/v1/repos/%s/commits", url.PathEscape(*ref)))
if err != nil {
return "", fmt.Errorf("failed to parse url : %w", err)
}

apiURL := ""
if branch != nil {
v := url.Values{}
v.Add("git_ref", *branch)
apiURL = api.String() + "?" + v.Encode()
} else {
apiURL = api.String()
}

body, err := g.performRequest("GET", apiURL)
if err != nil {
return "", fmt.Errorf("error while making request: %s", err.Error())
}

lastCommit, err := getLastCommit(body)
if err != nil {
return "", fmt.Errorf("error while fetching last commit from list: %s", err.Error())
}

return lastCommit.Sha, nil
}

func (g *GitnessClient) GetRepoRef(url string) (*string, error) {
repoUrl := strings.TrimSuffix(url, ".git")
parts := strings.Split(repoUrl, "/")
if len(parts) < 5 {
return nil, fmt.Errorf("failed to parse repository reference: invalid url passed")
}
var path string
if parts[3] == "git" && len(parts) >= 6 {
path = fmt.Sprintf("%s/%s", parts[4], parts[5])
} else {
path = fmt.Sprintf("%s/%s", parts[3], parts[4])
}
return &path, nil
}

func getLastCommit(jsonData []byte) (Commit, error) {
var commitsResponse CommitsResponse
err := json.Unmarshal(jsonData, &commitsResponse)
if err != nil {
return Commit{}, err
}

sort.Slice(commitsResponse.Commits, func(i, j int) bool {
return commitsResponse.Commits[i].Committer.When.Before(commitsResponse.Commits[j].Committer.When)
})

if len(commitsResponse.Commits) == 0 {
return Commit{}, fmt.Errorf("no commits found")
}

return commitsResponse.Commits[len(commitsResponse.Commits)-1], nil
}

func (g *GitnessClient) GetPr(repoURL string, prNumber uint32) (*PullRequest, error) {
repoRef, err := g.GetRepoRef(repoURL)
if err != nil {
return nil, err
}
apiUrl, err := g.BaseURL.Parse(fmt.Sprintf("/api/v1/repos/%s/pullreq/%d", url.PathEscape(*repoRef), prNumber))
if err != nil {
return nil, err
}

body, err := g.performRequest("GET", apiUrl.String())
if err != nil {
return nil, err
}

var pr PullRequest
err = json.Unmarshal(body, &pr)
if err != nil {
return nil, err
}
refPart := strings.Split(*repoRef, "/")
pr.GitUrl = GetCloneUrl(g.BaseURL.Scheme, g.BaseURL.Host, refPart[0], refPart[1])
return &pr, nil
}

func GetCloneUrl(protocol, host, owner, repo string) string {
return fmt.Sprintf("%s://%s/git/%s/%s.git", protocol, host, owner, repo)
}
Loading

0 comments on commit 9682edf

Please sign in to comment.