drone/app/gitspace/scm/gitness_scm.go
Dhruv Dhruv 6dbe85fbfd fix: [CDE-413]: Setting branch URL in all gitspace API response (#2881)
* 

fix: [CDE-413]: Adding spacePath to method.
* 

fix: [CDE-413]: Adding method to get branch url in the scm interface and using it to set the branch url in the gitspace config api responses. Removing the SCM interface as it has only 1 impl. Minor refactoring.
* 

fix: [CDE-413]: Adding method to get branch url in the scm interface and using it to set the branch url in the gitspace config api responses. Removing the SCM interface as it has only 1 impl. Minor refactoring.
2024-10-29 04:46:03 +00:00

263 lines
7.6 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package scm
import (
"context"
"fmt"
"io"
"net/url"
"path"
"strings"
"time"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/bootstrap"
"github.com/harness/gitness/app/jwt"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/token"
urlprovider "github.com/harness/gitness/app/url"
"github.com/harness/gitness/git"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
var _ Provider = (*GitnessSCM)(nil)
var gitspaceJWTLifetime = 720 * 24 * time.Hour
const defaultGitspacePATIdentifier = "Gitspace_Default"
type GitnessSCM struct {
git git.Interface
repoStore store.RepoStore
tokenStore store.TokenStore
principalStore store.PrincipalStore
urlProvider urlprovider.Provider
}
// ListBranches implements Provider.
func (s *GitnessSCM) ListBranches(
ctx context.Context,
filter *BranchFilter,
_ *ResolvedCredentials,
) ([]Branch, error) {
repo, err := s.repoStore.FindByRef(ctx, filter.Repository)
if err != nil {
return nil, fmt.Errorf("failed to find repo: %w", err)
}
rpcOut, err := s.git.ListBranches(ctx, &git.ListBranchesParams{
ReadParams: git.CreateReadParams(repo),
IncludeCommit: false,
Query: filter.Query,
Sort: git.BranchSortOptionDate,
Order: git.SortOrderDesc,
Page: int32(filter.Page),
PageSize: int32(filter.Size),
})
if err != nil {
return nil, err
}
branches := make([]Branch, len(rpcOut.Branches))
for i := range rpcOut.Branches {
branches[i] = mapBranch(rpcOut.Branches[i])
}
return branches, nil
}
func mapBranch(b git.Branch) Branch {
return Branch{
Name: b.Name,
SHA: b.SHA.String(),
}
}
// ListRepositories implements Provider.
func (s *GitnessSCM) ListRepositories(
ctx context.Context,
filter *RepositoryFilter,
_ *ResolvedCredentials,
) ([]Repository, error) {
repos, err := s.repoStore.List(ctx, filter.SpaceID, &types.RepoFilter{
Page: filter.Page,
Size: filter.Size,
Query: filter.Query,
Sort: enum.RepoAttrUpdated,
Order: enum.OrderDesc,
})
if err != nil {
return nil, fmt.Errorf("failed to list child repos: %w", err)
}
var reposOut []Repository
for _, repo := range repos {
// backfill URLs
repo.GitURL = s.urlProvider.GenerateGITCloneURL(ctx, repo.Path)
repo.GitSSHURL = s.urlProvider.GenerateGITCloneSSHURL(ctx, repo.Path)
repoOut, err := mapRepository(repo)
if err != nil {
return nil, fmt.Errorf("failed to get repo %q output: %w", repo.Path, err)
}
reposOut = append(reposOut, repoOut)
}
return reposOut, nil
}
func mapRepository(repo *types.Repository) (Repository, error) {
if repo == nil {
return Repository{}, fmt.Errorf("repository is null")
}
return Repository{
Name: repo.Identifier,
DefaultBranch: repo.DefaultBranch,
GitURL: repo.GitURL,
GitSSHURL: repo.GitSSHURL,
}, nil
}
func NewGitnessSCM(
repoStore store.RepoStore, git git.Interface,
tokenStore store.TokenStore,
principalStore store.PrincipalStore,
urlProvider urlprovider.Provider,
) *GitnessSCM {
return &GitnessSCM{
repoStore: repoStore,
git: git,
tokenStore: tokenStore,
principalStore: principalStore,
urlProvider: urlProvider,
}
}
func (s *GitnessSCM) ResolveCredentials(
ctx context.Context,
gitspaceConfig types.GitspaceConfig,
) (*ResolvedCredentials, error) {
repoURL, err := url.Parse(gitspaceConfig.CodeRepo.URL)
if err != nil {
return nil, fmt.Errorf("failed to parse repository URL %s: %w", gitspaceConfig.CodeRepo.URL, err)
}
repoName := strings.TrimSuffix(path.Base(repoURL.Path), ".git")
repo, err := s.repoStore.FindByRef(ctx, *gitspaceConfig.CodeRepo.Ref)
if err != nil {
return nil, fmt.Errorf("failed to find repository: %w", err)
}
cloneURL, err := url.Parse(repo.Path)
if err != nil {
return nil, fmt.Errorf("failed to parse clone url '%s': %w", cloneURL, err)
}
// Backfill clone URL
gitURL := s.urlProvider.GenerateContainerGITCloneURL(ctx, repo.Path)
resolvedCredentails := &ResolvedCredentials{Branch: gitspaceConfig.CodeRepo.Branch}
resolvedCredentails.RepoName = repoName
gitspacePrincipal := bootstrap.NewGitspaceServiceSession().Principal
user, err := findUserFromUID(ctx, s.principalStore, gitspaceConfig.GitspaceUser.Identifier)
if err != nil {
return nil, err
}
var jwtToken string
existingToken, _ := s.tokenStore.FindByIdentifier(ctx, user.ID, defaultGitspacePATIdentifier)
if existingToken != nil {
// create jwt token.
jwtToken, err = jwt.GenerateForToken(existingToken, user.ToPrincipal().Salt)
if err != nil {
return nil, fmt.Errorf("failed to create JWT token: %w", err)
}
} else {
_, jwtToken, err = token.CreatePAT(
ctx,
s.tokenStore,
&gitspacePrincipal,
user,
defaultGitspacePATIdentifier,
&gitspaceJWTLifetime)
}
if err != nil {
return nil, fmt.Errorf("failed to create JWT: %w", err)
}
modifiedURL, err := url.Parse(gitURL)
if err != nil {
return nil, fmt.Errorf("error while parsing the clone url: %s", gitURL)
}
userInfo := url.UserPassword("harness", jwtToken)
modifiedURL.User = userInfo
resolvedCredentails.CloneURL = modifiedURL.String()
credentials := &Credentials{
Email: user.Email,
Name: user.DisplayName,
Password: jwtToken,
}
resolvedCredentails.Credentials = credentials
return resolvedCredentails, nil
}
func (s *GitnessSCM) GetFileContent(
ctx context.Context,
gitspaceConfig types.GitspaceConfig,
filePath string,
_ *ResolvedCredentials,
) ([]byte, error) {
repo, err := s.repoStore.FindByRef(ctx, *gitspaceConfig.CodeRepo.Ref)
if err != nil {
return nil, fmt.Errorf("failed to find repository: %w", err)
}
// create read params once
readParams := git.CreateReadParams(repo)
treeNodeOutput, err := s.git.GetTreeNode(ctx, &git.GetTreeNodeParams{
ReadParams: readParams,
GitREF: gitspaceConfig.CodeRepo.Branch,
Path: filePath,
IncludeLatestCommit: false,
})
if err != nil {
return make([]byte, 0), nil //nolint:nilerr
}
// viewing Raw content is only supported for blob content
if treeNodeOutput.Node.Type != git.TreeNodeTypeBlob {
return nil, usererror.BadRequestf(
"Object in '%s' at '/%s' is of type '%s'. Only objects of type %s support raw viewing.",
gitspaceConfig.CodeRepo.Branch, filePath, treeNodeOutput.Node.Type, git.TreeNodeTypeBlob)
}
blobReader, err := s.git.GetBlob(ctx, &git.GetBlobParams{
ReadParams: readParams,
SHA: treeNodeOutput.Node.SHA,
SizeLimit: 0, // no size limit, we stream whatever data there is
})
if err != nil {
return nil, fmt.Errorf("failed to read blob: %w", err)
}
catFileOutput, err := io.ReadAll(blobReader.Content)
if err != nil {
return nil, fmt.Errorf("failed to read blob content: %w", err)
}
return catFileOutput, nil
}
func findUserFromUID(
ctx context.Context,
principalStore store.PrincipalStore, userUID string,
) (*types.User, error) {
return principalStore.FindUserByUID(ctx, userUID)
}
func (s *GitnessSCM) GetBranchURL(_ string, repoURL string, branch string) (string, error) {
return fmt.Sprintf("%s/files/%s", strings.TrimSuffix(repoURL, ".git"), branch), nil
}