feat: [CDE-651]: add gitlab and bitbucket on-prem (#3674)

* feat: [CDE-651]: add gitlab and bitbucket on-prem
* feat: [CDE-651]: add gitlab and bitbucket on-prem
* feat: [CDE-651]: add gitlab and bitbucket on-prem
* feat: [CDE-651]: add gitlab and bitbucket on-prem
* feat: [CDE-651]: add gitlab and bitbucket on-prem
* feat: [CDE-651]: add gitlab and bitbucket on-prem
* feat: [CDE-651]: add gitlab and bitbucket on-prem
* feat: [CDE-651]: add gitlab and bitbucket on-prem
* feat: [CDE-651]: add gitlab and bitbucket on-prem
* feat: [CDE-651]: add gitlab and bitbucket on-prem
* feat: [CDE-603]: add SCM auth providers for bitbucket and gitlab

feat: [CDE-651]: add gitlab and bitbucket on-prem
* feat: [CDE-651]: add gitlab and bitbucket on-prem
This commit is contained in:
Ansuman Satapathy 2025-04-14 09:53:57 +00:00 committed by Harness
parent 46f6366728
commit e03b053205
10 changed files with 137 additions and 59 deletions

View File

@ -240,7 +240,7 @@ func (e *EmbeddedDockerOrchestrator) startStoppedGitspace(
} }
// Set up git credentials if needed // Set up git credentials if needed
if resolvedRepoDetails.Credentials != nil { if resolvedRepoDetails.UserPasswordCredentials != nil {
if err = utils.SetupGitCredentials(ctx, exec, resolvedRepoDetails, logStreamInstance); err != nil { if err = utils.SetupGitCredentials(ctx, exec, resolvedRepoDetails, logStreamInstance); err != nil {
return err return err
} }
@ -674,7 +674,7 @@ func (e *EmbeddedDockerOrchestrator) buildSetupSteps(
exec *devcontainer.Exec, exec *devcontainer.Exec,
gitspaceLogger gitspaceTypes.GitspaceLogger, gitspaceLogger gitspaceTypes.GitspaceLogger,
) error { ) error {
if resolvedRepoDetails.ResolvedCredentials.Credentials != nil { if resolvedRepoDetails.ResolvedCredentials.UserPasswordCredentials != nil {
return utils.SetupGitCredentials(ctx, exec, resolvedRepoDetails, gitspaceLogger) return utils.SetupGitCredentials(ctx, exec, resolvedRepoDetails, gitspaceLogger)
} }
return nil return nil

View File

@ -91,9 +91,9 @@ func CloneCode(
Branch: resolvedRepoDetails.Branch, Branch: resolvedRepoDetails.Branch,
RepoName: resolvedRepoDetails.RepoName, RepoName: resolvedRepoDetails.RepoName,
} }
if resolvedRepoDetails.ResolvedCredentials.Credentials != nil { if resolvedRepoDetails.ResolvedCredentials.UserPasswordCredentials != nil {
data.Email = resolvedRepoDetails.Credentials.Email data.Email = resolvedRepoDetails.UserPasswordCredentials.Email
data.Name = resolvedRepoDetails.Credentials.Name.Value() data.Name = resolvedRepoDetails.UserPasswordCredentials.Name.Value()
} }
script, err := GenerateScriptFromTemplate( script, err := GenerateScriptFromTemplate(
templateCloneCode, data) templateCloneCode, data)

View File

@ -35,7 +35,8 @@ import (
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
var _ Provider = (*GitnessSCM)(nil) var _ ListingProvider = (*GitnessSCM)(nil)
var _ AuthAndFileContentProvider = (*GitnessSCM)(nil)
var gitspaceJWTLifetime = 720 * 24 * time.Hour var gitspaceJWTLifetime = 720 * 24 * time.Hour
@ -50,7 +51,7 @@ type GitnessSCM struct {
urlProvider urlprovider.Provider urlProvider urlprovider.Provider
} }
// ListBranches implements Provider. // ListBranches implements ListingProvider.
func (s *GitnessSCM) ListBranches( func (s *GitnessSCM) ListBranches(
ctx context.Context, ctx context.Context,
filter *BranchFilter, filter *BranchFilter,
@ -66,8 +67,8 @@ func (s *GitnessSCM) ListBranches(
Query: filter.Query, Query: filter.Query,
Sort: git.BranchSortOptionDate, Sort: git.BranchSortOptionDate,
Order: git.SortOrderDesc, Order: git.SortOrderDesc,
Page: int32(filter.Page), Page: filter.Page,
PageSize: int32(filter.Size), PageSize: filter.Size,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -87,7 +88,7 @@ func mapBranch(b git.Branch) Branch {
} }
} }
// ListRepositories implements Provider. // ListRepositories implements ListingProvider.
func (s *GitnessSCM) ListRepositories( func (s *GitnessSCM) ListRepositories(
ctx context.Context, ctx context.Context,
filter *RepositoryFilter, filter *RepositoryFilter,
@ -202,12 +203,12 @@ func (s *GitnessSCM) ResolveCredentials(
userInfo := url.UserPassword("harness", jwtToken) userInfo := url.UserPassword("harness", jwtToken)
modifiedURL.User = userInfo modifiedURL.User = userInfo
resolvedCredentails.CloneURL = types.NewMaskSecret(modifiedURL.String()) resolvedCredentails.CloneURL = types.NewMaskSecret(modifiedURL.String())
credentials := &Credentials{ credentials := &UserPasswordCredentials{
Email: user.Email, Email: user.Email,
Name: types.NewMaskSecret(user.DisplayName), Name: types.NewMaskSecret(user.DisplayName),
Password: types.NewMaskSecret(jwtToken), Password: types.NewMaskSecret(jwtToken),
} }
resolvedCredentails.Credentials = credentials resolvedCredentails.UserPasswordCredentials = credentials
return resolvedCredentails, nil return resolvedCredentails, nil
} }

View File

@ -31,7 +31,8 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
var _ Provider = (*GenericSCM)(nil) var _ ListingProvider = (*GenericSCM)(nil)
var _ AuthAndFileContentProvider = (*GenericSCM)(nil)
type GenericSCM struct { type GenericSCM struct {
} }

View File

@ -62,12 +62,12 @@ func (s *SCM) CheckValidCodeRepo(
return codeRepositoryResponse, nil return codeRepositoryResponse, nil
} }
scmProvider, err := s.getSCMProvider(codeRepositoryRequest.RepoType) authAndFileContentProvider, err := s.getSCMAuthAndFileProvider(codeRepositoryRequest.RepoType)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to resolve SCM provider: %w", err) return nil, fmt.Errorf("failed to resolve SCM provider: %w", err)
} }
resolvedCreds, err := s.resolveRepoCredentials(ctx, scmProvider, codeRepositoryRequest) resolvedCreds, err := s.resolveRepoCredentials(ctx, authAndFileContentProvider, codeRepositoryRequest)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to resolve repo credentials and URL: %w", err) return nil, fmt.Errorf("failed to resolve repo credentials and URL: %w", err)
} }
@ -83,17 +83,21 @@ func (s *SCM) GetSCMRepoDetails(
ctx context.Context, ctx context.Context,
gitspaceConfig types.GitspaceConfig, gitspaceConfig types.GitspaceConfig,
) (*ResolvedDetails, error) { ) (*ResolvedDetails, error) {
scmProvider, err := s.getSCMProvider(gitspaceConfig.CodeRepo.Type) scmAuthAndFileContentProvider, err := s.getSCMAuthAndFileProvider(gitspaceConfig.CodeRepo.Type)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to resolve SCM provider: %w", err) return nil, fmt.Errorf("failed to resolve SCM Auth and File COntent provider: %w", err)
} }
resolvedCredentials, err := scmProvider.ResolveCredentials(ctx, gitspaceConfig) resolvedCredentials, err := scmAuthAndFileContentProvider.ResolveCredentials(ctx, gitspaceConfig)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to resolve repo credentials and url: %w", err) return nil, fmt.Errorf("failed to resolve repo credentials and url: %w", err)
} }
devcontainerConfig, err := s.getDevcontainerConfig(ctx, scmProvider, gitspaceConfig, resolvedCredentials) scmAuthAndFileProvider, err := s.getSCMAuthAndFileProvider(gitspaceConfig.CodeRepo.Type)
if err != nil {
return nil, fmt.Errorf("failed to resolve SCM Auth and File content provider: %w", err)
}
devcontainerConfig, err := s.getDevcontainerConfig(ctx, scmAuthAndFileProvider, gitspaceConfig, resolvedCredentials)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read or parse devcontainer config: %w", err) return nil, fmt.Errorf("failed to read or parse devcontainer config: %w", err)
} }
@ -148,16 +152,16 @@ func (s *SCM) detectBranch(ctx context.Context, repoURL string) (string, error)
return defaultBranch, nil return defaultBranch, nil
} }
func (s *SCM) getSCMProvider(repoType enum.GitspaceCodeRepoType) (Provider, error) { func (s *SCM) getSCMAuthAndFileProvider(repoType enum.GitspaceCodeRepoType) (AuthAndFileContentProvider, error) {
if repoType == "" { if repoType == "" {
repoType = enum.CodeRepoTypeUnknown repoType = enum.CodeRepoTypeUnknown
} }
return s.scmProviderFactory.GetSCMProvider(repoType) return s.scmProviderFactory.GetSCMAuthAndFileProvider(repoType)
} }
func (s *SCM) resolveRepoCredentials( func (s *SCM) resolveRepoCredentials(
ctx context.Context, ctx context.Context,
scmProvider Provider, authAndFileContentProvider AuthAndFileContentProvider,
codeRepositoryRequest CodeRepositoryRequest, codeRepositoryRequest CodeRepositoryRequest,
) (*ResolvedCredentials, error) { ) (*ResolvedCredentials, error) {
codeRepo := types.CodeRepo{URL: codeRepositoryRequest.URL} codeRepo := types.CodeRepo{URL: codeRepositoryRequest.URL}
@ -167,18 +171,19 @@ func (s *SCM) resolveRepoCredentials(
SpacePath: codeRepositoryRequest.SpacePath, SpacePath: codeRepositoryRequest.SpacePath,
GitspaceUser: gitspaceUser, GitspaceUser: gitspaceUser,
} }
return scmProvider.ResolveCredentials(ctx, gitspaceConfig) return authAndFileContentProvider.ResolveCredentials(ctx, gitspaceConfig)
} }
func (s *SCM) getDevcontainerConfig( func (s *SCM) getDevcontainerConfig(
ctx context.Context, ctx context.Context,
scmProvider Provider, scmAuthAndFileContentProvider AuthAndFileContentProvider,
gitspaceConfig types.GitspaceConfig, gitspaceConfig types.GitspaceConfig,
resolvedCredentials *ResolvedCredentials, resolvedCredentials *ResolvedCredentials,
) (types.DevcontainerConfig, error) { ) (types.DevcontainerConfig, error) {
config := types.DevcontainerConfig{} config := types.DevcontainerConfig{}
filePath := devcontainerDefaultPath filePath := devcontainerDefaultPath
catFileOutputBytes, err := scmProvider.GetFileContent(ctx, gitspaceConfig, filePath, resolvedCredentials) catFileOutputBytes, err := scmAuthAndFileContentProvider.GetFileContent(
ctx, gitspaceConfig, filePath, resolvedCredentials)
if err != nil { if err != nil {
return config, fmt.Errorf("failed to read devcontainer file: %w", err) return config, fmt.Errorf("failed to read devcontainer file: %w", err)
} }

View File

@ -0,0 +1,31 @@
// 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"
"github.com/harness/gitness/types"
)
type AuthAndFileContentProvider interface {
GetFileContent(
ctx context.Context,
gitspaceConfig types.GitspaceConfig,
filePath string,
credentials *ResolvedCredentials,
) ([]byte, error)
ResolveCredentials(ctx context.Context, gitspaceConfig types.GitspaceConfig) (*ResolvedCredentials, error)
}

View File

@ -21,22 +21,41 @@ import (
) )
type Factory struct { type Factory struct {
providers map[enum.GitspaceCodeRepoType]Provider listingProviders map[enum.GitspaceCodeRepoType]ListingProvider
authAndFileProviders map[enum.GitspaceCodeRepoType]AuthAndFileContentProvider
} }
func NewFactoryWithProviders(providers map[enum.GitspaceCodeRepoType]Provider) Factory { func NewFactoryWithProviders(
return Factory{providers: providers} providers map[enum.GitspaceCodeRepoType]ListingProvider,
authProviders map[enum.GitspaceCodeRepoType]AuthAndFileContentProvider) Factory {
return Factory{listingProviders: providers,
authAndFileProviders: authProviders}
} }
func NewFactory(gitnessProvider *GitnessSCM, genericSCM *GenericSCM) Factory { func NewFactory(gitnessProvider *GitnessSCM, genericSCM *GenericSCM) Factory {
providers := make(map[enum.GitspaceCodeRepoType]Provider) listingProviders := make(map[enum.GitspaceCodeRepoType]ListingProvider)
providers[enum.CodeRepoTypeGitness] = gitnessProvider listingProviders[enum.CodeRepoTypeGitness] = gitnessProvider
providers[enum.CodeRepoTypeUnknown] = genericSCM listingProviders[enum.CodeRepoTypeUnknown] = genericSCM
return Factory{providers: providers} authAndFileContentProviders := make(map[enum.GitspaceCodeRepoType]AuthAndFileContentProvider)
authAndFileContentProviders[enum.CodeRepoTypeGitness] = gitnessProvider
authAndFileContentProviders[enum.CodeRepoTypeUnknown] = genericSCM
return Factory{
listingProviders: listingProviders,
authAndFileProviders: authAndFileContentProviders,
}
} }
func (f *Factory) GetSCMProvider(providerType enum.GitspaceCodeRepoType) (Provider, error) { func (f *Factory) GetSCMProvider(providerType enum.GitspaceCodeRepoType) (ListingProvider, error) {
val := f.providers[providerType] val := f.listingProviders[providerType]
if val == nil {
return nil, fmt.Errorf("unknown scm provider type: %s", providerType)
}
return val, nil
}
func (f *Factory) GetSCMAuthAndFileProvider(providerType enum.GitspaceCodeRepoType) (
AuthAndFileContentProvider, error) {
val := f.authAndFileProviders[providerType]
if val == nil { if val == nil {
return nil, fmt.Errorf("unknown scm provider type: %s", providerType) return nil, fmt.Errorf("unknown scm provider type: %s", providerType)
} }

View File

@ -16,20 +16,9 @@ package scm
import ( import (
"context" "context"
"github.com/harness/gitness/types"
) )
type Provider interface { type ListingProvider interface {
ResolveCredentials(ctx context.Context, gitspaceConfig types.GitspaceConfig) (*ResolvedCredentials, error)
GetFileContent(
ctx context.Context,
gitspaceConfig types.GitspaceConfig,
filePath string,
credentials *ResolvedCredentials,
) ([]byte, error)
ListRepositories( ListRepositories(
ctx context.Context, ctx context.Context,
filter *RepositoryFilter, filter *RepositoryFilter,

View File

@ -33,26 +33,50 @@ type CodeRepositoryResponse struct {
CodeRepoIsPrivate bool `json:"is_private"` CodeRepoIsPrivate bool `json:"is_private"`
} }
// CredentialType represents the type of credential.
type CredentialType string
const (
CredentialTypeUserPassword CredentialType = "user_password"
CredentialTypeOAuthTokenRef CredentialType = "oauth_token_ref" // #nosec G101
)
type ( type (
ResolvedDetails struct { ResolvedDetails struct {
ResolvedCredentials ResolvedCredentials
DevcontainerConfig types.DevcontainerConfig DevcontainerConfig types.DevcontainerConfig
} }
// Credentials contains login and initialization information used // UserPasswordCredentials contains login and initialization information used
// by an automated login process. // by an automated login process.
Credentials struct { UserPasswordCredentials struct {
Email string Email string
Name types.MaskSecret Name types.MaskSecret
Password types.MaskSecret Password types.MaskSecret
} }
OAuth2ClientRefCredentials struct {
ClientID string
ClientSecretRef string
GrantType string
RedirectURI string
Code string
}
OAuth2TokenRefCredentials struct {
UserPasswordCredentials
RefreshTokenRef string
AccessTokenRef string
}
ResolvedCredentials struct { ResolvedCredentials struct {
Branch string Branch string
// CloneURL contains credentials for private repositories in url prefix // CloneURL contains credentials for private repositories in url prefix
CloneURL types.MaskSecret CloneURL types.MaskSecret
Credentials *Credentials UserPasswordCredentials *UserPasswordCredentials
RepoName string OAuth2TokenRefCredentials *OAuth2TokenRefCredentials
RepoName string
CredentialType CredentialType
} }
RepositoryFilter struct { RepositoryFilter struct {
@ -67,8 +91,8 @@ type (
SpaceID int64 `json:"space_id"` SpaceID int64 `json:"space_id"`
Repository string `json:"repo"` Repository string `json:"repo"`
Query string `json:"query"` Query string `json:"query"`
Page int `json:"page"` Page int32 `json:"page"`
Size int `json:"size"` Size int32 `json:"size"`
RepoURL string `json:"repo_url"` RepoURL string `json:"repo_url"`
} }

View File

@ -25,13 +25,21 @@ var codeRepoTypes = []GitspaceCodeRepoType{
CodeRepoTypeBitbucket, CodeRepoTypeBitbucket,
CodeRepoTypeUnknown, CodeRepoTypeUnknown,
CodeRepoTypeGitness, CodeRepoTypeGitness,
CodeRepoTypeGitlabOnPrem,
CodeRepoTypeBitbucketServer,
} }
const ( const (
CodeRepoTypeGithub GitspaceCodeRepoType = "github" CodeRepoTypeGithub GitspaceCodeRepoType = "github"
CodeRepoTypeGitlab GitspaceCodeRepoType = "gitlab" CodeRepoTypeGitlab GitspaceCodeRepoType = "gitlab"
CodeRepoTypeGitness GitspaceCodeRepoType = "gitness" CodeRepoTypeGitness GitspaceCodeRepoType = "gitness"
CodeRepoTypeHarnessCode GitspaceCodeRepoType = "harness_code" CodeRepoTypeHarnessCode GitspaceCodeRepoType = "harness_code"
CodeRepoTypeBitbucket GitspaceCodeRepoType = "bitbucket" CodeRepoTypeBitbucket GitspaceCodeRepoType = "bitbucket"
CodeRepoTypeUnknown GitspaceCodeRepoType = "unknown" CodeRepoTypeUnknown GitspaceCodeRepoType = "unknown"
CodeRepoTypeGitlabOnPrem GitspaceCodeRepoType = "gitlab_on_prem"
CodeRepoTypeBitbucketServer GitspaceCodeRepoType = "bitbucket_server"
) )
func (p GitspaceCodeRepoType) IsOnPrem() bool {
return p == CodeRepoTypeGitlabOnPrem || p == CodeRepoTypeBitbucketServer
}