Skip to content

Commit

Permalink
feat: add gogs git provider (#1255)
Browse files Browse the repository at this point in the history
Signed-off-by: tarunrajput <[email protected]>
  • Loading branch information
tarunrajput authored Oct 21, 2024
1 parent 5ce468c commit 80718f8
Show file tree
Hide file tree
Showing 10 changed files with 563 additions and 8 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,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, Bitbucket Server, Gitea, Gitness, Azure DevOps & AWS CodeCommit can be connected, allowing easy repo branch or PR pull and commit back from the workspaces.
* __Git Provider Integration__: GitHub, GitLab, Bitbucket, Bitbucket Server, Gitea, Gitness, Azure DevOps, AWS CodeCommit & Gogs 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 @@ -165,7 +165,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, Bitbucket Server, Gitea, Gitness, AWS CodeCommit and Azure DevOps. To add them to your profile, use the command:
Daytona supports GitHub, GitLab, Bitbucket, Bitbucket Server, Gitea, Gitness, AWS CodeCommit, Azure DevOps and Gogs. 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 @@ -57,6 +57,7 @@ func GetSupportedGitProviders() []GitProvider {
{"gitness", "Gitness"},
{"azure-devops", "Azure DevOps"},
{"aws-codecommit", "AWS CodeCommit"},
{"gogs", "Gogs"},
}
}

Expand Down Expand Up @@ -84,6 +85,8 @@ func GetDocsLinkFromGitProvider(providerId string) string {
return "https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows#create-a-pat"
case "aws-codecommit":
return "https://docs.aws.amazon.com/codecommit/latest/userguide/setting-up-gc.html and to configure AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY & AWS_DEFAULT_REGION read https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html"
case "gogs":
return "https://www.daytona.io/docs/configuration/git-providers/#gogs"
default:
return ""
}
Expand Down Expand Up @@ -130,6 +133,8 @@ func GetRequiredScopesFromGitProviderId(providerId string) string {
return "Code (Status, Read & Write); User Profile (Read); Project and Team (Read)"
case "aws-codecommit":
return "/"
case "gogs":
fallthrough
default:
return ""
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ require (
github.com/go-git/go-git/v5 v5.12.1-0.20240617075238-c127d1b35535
github.com/go-playground/validator/v10 v10.19.0
github.com/go-playground/webhooks/v6 v6.4.0
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.1
github.com/hashicorp/go-hclog v1.6.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,8 @@ github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/workspace/util/repository_wizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (

func isGitProviderWithUnsupportedPagination(providerId string) bool {
switch providerId {
case "azure-devops", "bitbucket", "gitness", "aws-codecommit":
case "azure-devops", "bitbucket", "gitness", "aws-codecommit", "gogs":
return true
default:
return false
Expand Down
287 changes: 287 additions & 0 deletions pkg/gitprovider/gogs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
// Copyright 2024 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0

package gitprovider

import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"

gogs "github.com/gogs/go-gogs-client"
)

type GogsGitProvider struct {
*AbstractGitProvider

token string
baseApiUrl string
}

func NewGogsGitProvider(token string, baseApiUrl string) *GogsGitProvider {
provider := &GogsGitProvider{
token: token,
baseApiUrl: baseApiUrl,
AbstractGitProvider: &AbstractGitProvider{},
}
provider.AbstractGitProvider.GitProvider = provider

return provider
}

func (g *GogsGitProvider) getApiClient() *gogs.Client {
return gogs.NewClient(g.baseApiUrl, g.token)
}

func (g *GogsGitProvider) CanHandle(repoUrl string) (bool, error) {
staticContext, err := g.ParseStaticGitContext(repoUrl)
if err != nil {
return false, err
}

return strings.Contains(g.baseApiUrl, staticContext.Source), nil
}

func (g *GogsGitProvider) GetNamespaces(options ListOptions) ([]*GitNamespace, error) {
client := g.getApiClient()
var namespaces []*GitNamespace
user, err := g.GetUser()
if err != nil {
return nil, err
}
orgs, err := client.ListMyOrgs()
if err != nil {
return nil, fmt.Errorf("failed to fetch Namespace : %w", err)
}

namespaces = append([]*GitNamespace{{Id: personalNamespaceId, Name: user.Username}}, namespaces...)
for _, org := range orgs {
namespaces = append(namespaces, &GitNamespace{Id: org.UserName, Name: org.UserName})
}
return namespaces, nil
}

func (g *GogsGitProvider) GetUser() (*GitUser, error) {
client := g.getApiClient()
user, err := client.GetSelfInfo()
if err != nil {
return nil, fmt.Errorf("failed to fetch User : %w", err)
}
fullName := user.FullName
userName := user.UserName
if fullName == "" {
fullName = userName
}
return &GitUser{
Id: strconv.FormatInt(user.ID, 10),
Username: user.UserName,
Name: fullName,
Email: user.Email,
}, nil
}

func (g *GogsGitProvider) GetRepositories(namespace string, options ListOptions) ([]*GitRepository, error) {
client := g.getApiClient()
var repoList []*gogs.Repository
if namespace == personalNamespaceId {
repos, err := client.ListMyRepos()
if err != nil {
return nil, fmt.Errorf("failed to fetch Repositories : %w", err)
}
repoList = repos
} else {
repos, err := client.ListOrgRepos(namespace)
if err != nil {
return nil, fmt.Errorf("failed to fetch Repositories : %w", err)
}
repoList = repos
}
repos := []*GitRepository{}
for _, repo := range repoList {
u, err := url.Parse(repo.HTMLURL)
if err != nil {
return nil, err
}

repos = append(repos, &GitRepository{
Id: repo.Name,
Name: repo.Name,
Url: repo.HTMLURL,
Branch: repo.DefaultBranch,
Owner: repo.Owner.UserName,
Source: u.Host,
})
}

return repos, nil
}

func (g *GogsGitProvider) GetRepoBranches(repositoryId string, namespaceId string, options ListOptions) ([]*GitBranch, error) {
client := g.getApiClient()
if namespaceId == personalNamespaceId {
user, err := g.GetUser()
if err != nil {
return nil, err
}
namespaceId = user.Username
}
var branches []*GitBranch

repoBranches, err := client.ListRepoBranches(namespaceId, repositoryId)
if err != nil {
return nil, fmt.Errorf("failed to fetch Branches: %w", err)
}
for _, branch := range repoBranches {
responseBranch := &GitBranch{
Name: branch.Name,
}
if branch.Commit != nil {
responseBranch.Sha = branch.Commit.ID
}
branches = append(branches, responseBranch)
}

return branches, nil
}

func (g *GogsGitProvider) GetDefaultBranch(staticContext *StaticGitContext) (*string, error) {
client := g.getApiClient()
repo, err := client.GetRepo(staticContext.Owner, staticContext.Name)
if err != nil {
return nil, err
}

return &repo.DefaultBranch, nil
}

func (g *GogsGitProvider) GetRepoPRs(repositoryId string, namespaceId string, options ListOptions) ([]*GitPullRequest, error) {
// Gogs does not have any API endpoint to fetch PRs
return []*GitPullRequest{}, nil
}

func (g *GogsGitProvider) GetBranchByCommit(staticContext *StaticGitContext) (string, error) {
client := g.getApiClient()
repoBranches, err := client.ListRepoBranches(staticContext.Owner, staticContext.Name)
if err != nil {
return "", fmt.Errorf("failed to get branch by commit: %w", err)
}

var branchName string
for _, branch := range repoBranches {
commitId := branch.Commit.ID
if *staticContext.Sha == commitId {
branchName = branch.Name
break
}

for commitId != "" {
commit, err := client.GetSingleCommit(staticContext.Owner, staticContext.Name, commitId)
if err != nil {
continue
}

if *staticContext.Sha == commit.SHA {
branchName = branch.Name
break
}
if len(commit.Parents) > 0 {
commitId = commit.Parents[0].SHA
if *staticContext.Sha == commitId {
branchName = branch.Name
break
}
} else {
commitId = ""
}
}

if branchName != "" {
break
}
}

if branchName == "" {
return "", fmt.Errorf("status code: %d branch not found for SHA: %s", http.StatusNotFound, *staticContext.Sha)
}
return branchName, nil
}

func (g *GogsGitProvider) GetLastCommitSha(staticContext *StaticGitContext) (string, error) {
client := g.getApiClient()
branchName := ""
if staticContext.Branch != nil {
branchName = *staticContext.Branch
}
branch, err := client.GetRepoBranch(staticContext.Owner, staticContext.Name, branchName)
if err != nil {
return "", fmt.Errorf("failed to get last commit SHA: %w", err)
}
return branch.Commit.ID, nil
}

func (g *GogsGitProvider) ParseStaticGitContext(repoUrl string) (*StaticGitContext, error) {
staticContext, err := g.AbstractGitProvider.ParseStaticGitContext(repoUrl)
if err != nil {
return nil, err
}

if staticContext.Path == nil {
return staticContext, nil
}

parts := strings.Split(*staticContext.Path, "/")
switch {
case len(parts) >= 2 && parts[0] == "pulls":
prNumber, err := strconv.Atoi(parts[1])
if err != nil {
return nil, err
}
prUint := uint32(prNumber)
staticContext.PrNumber = &prUint
staticContext.Path = nil
case len(parts) >= 2 && parts[0] == "src":
staticContext.Branch = &parts[1]
if len(parts) > 2 {
branchPath := strings.Join(parts[2:], "/")
staticContext.Path = &branchPath
} else {
staticContext.Path = nil
}
case len(parts) >= 2 && parts[0] == "commits":
staticContext.Branch = &parts[1]
staticContext.Path = nil
case len(parts) >= 2 && parts[0] == "commit":
staticContext.Sha = &parts[1]
staticContext.Branch = staticContext.Sha
staticContext.Path = nil
}

return staticContext, nil
}

func (g *GogsGitProvider) GetPrContext(staticContext *StaticGitContext) (*StaticGitContext, error) {
// Gogs does not have any API endpoint to fetch PRs
return nil, fmt.Errorf("creating workspaces from Pull Requests is not supported for Gogs")
}

func (g *GogsGitProvider) GetUrlFromContext(repoContext *GetRepositoryContext) string {
url := strings.TrimSuffix(repoContext.Url, ".git")

if repoContext.Branch != nil && *repoContext.Branch != "" {
if repoContext.Sha != nil && *repoContext.Sha == *repoContext.Branch {
url += "/commit/" + *repoContext.Branch
} else {
url += "/src/" + *repoContext.Branch
}

if repoContext.Path != nil && *repoContext.Path != "" {
url += "/" + *repoContext.Path
}
} else if repoContext.Path != nil {
url += "/src/main/" + *repoContext.Path
}

return url
}
Loading

0 comments on commit 80718f8

Please sign in to comment.