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
if resolvedRepoDetails.Credentials != nil {
if resolvedRepoDetails.UserPasswordCredentials != nil {
if err = utils.SetupGitCredentials(ctx, exec, resolvedRepoDetails, logStreamInstance); err != nil {
return err
}
@ -674,7 +674,7 @@ func (e *EmbeddedDockerOrchestrator) buildSetupSteps(
exec *devcontainer.Exec,
gitspaceLogger gitspaceTypes.GitspaceLogger,
) error {
if resolvedRepoDetails.ResolvedCredentials.Credentials != nil {
if resolvedRepoDetails.ResolvedCredentials.UserPasswordCredentials != nil {
return utils.SetupGitCredentials(ctx, exec, resolvedRepoDetails, gitspaceLogger)
}
return nil

View File

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

View File

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

View File

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

View File

@ -62,12 +62,12 @@ func (s *SCM) CheckValidCodeRepo(
return codeRepositoryResponse, nil
}
scmProvider, err := s.getSCMProvider(codeRepositoryRequest.RepoType)
authAndFileContentProvider, err := s.getSCMAuthAndFileProvider(codeRepositoryRequest.RepoType)
if err != nil {
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 {
return nil, fmt.Errorf("failed to resolve repo credentials and URL: %w", err)
}
@ -83,17 +83,21 @@ func (s *SCM) GetSCMRepoDetails(
ctx context.Context,
gitspaceConfig types.GitspaceConfig,
) (*ResolvedDetails, error) {
scmProvider, err := s.getSCMProvider(gitspaceConfig.CodeRepo.Type)
scmAuthAndFileContentProvider, err := s.getSCMAuthAndFileProvider(gitspaceConfig.CodeRepo.Type)
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 {
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 {
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
}
func (s *SCM) getSCMProvider(repoType enum.GitspaceCodeRepoType) (Provider, error) {
func (s *SCM) getSCMAuthAndFileProvider(repoType enum.GitspaceCodeRepoType) (AuthAndFileContentProvider, error) {
if repoType == "" {
repoType = enum.CodeRepoTypeUnknown
}
return s.scmProviderFactory.GetSCMProvider(repoType)
return s.scmProviderFactory.GetSCMAuthAndFileProvider(repoType)
}
func (s *SCM) resolveRepoCredentials(
ctx context.Context,
scmProvider Provider,
authAndFileContentProvider AuthAndFileContentProvider,
codeRepositoryRequest CodeRepositoryRequest,
) (*ResolvedCredentials, error) {
codeRepo := types.CodeRepo{URL: codeRepositoryRequest.URL}
@ -167,18 +171,19 @@ func (s *SCM) resolveRepoCredentials(
SpacePath: codeRepositoryRequest.SpacePath,
GitspaceUser: gitspaceUser,
}
return scmProvider.ResolveCredentials(ctx, gitspaceConfig)
return authAndFileContentProvider.ResolveCredentials(ctx, gitspaceConfig)
}
func (s *SCM) getDevcontainerConfig(
ctx context.Context,
scmProvider Provider,
scmAuthAndFileContentProvider AuthAndFileContentProvider,
gitspaceConfig types.GitspaceConfig,
resolvedCredentials *ResolvedCredentials,
) (types.DevcontainerConfig, error) {
config := types.DevcontainerConfig{}
filePath := devcontainerDefaultPath
catFileOutputBytes, err := scmProvider.GetFileContent(ctx, gitspaceConfig, filePath, resolvedCredentials)
catFileOutputBytes, err := scmAuthAndFileContentProvider.GetFileContent(
ctx, gitspaceConfig, filePath, resolvedCredentials)
if err != nil {
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 {
providers map[enum.GitspaceCodeRepoType]Provider
listingProviders map[enum.GitspaceCodeRepoType]ListingProvider
authAndFileProviders map[enum.GitspaceCodeRepoType]AuthAndFileContentProvider
}
func NewFactoryWithProviders(providers map[enum.GitspaceCodeRepoType]Provider) Factory {
return Factory{providers: providers}
func NewFactoryWithProviders(
providers map[enum.GitspaceCodeRepoType]ListingProvider,
authProviders map[enum.GitspaceCodeRepoType]AuthAndFileContentProvider) Factory {
return Factory{listingProviders: providers,
authAndFileProviders: authProviders}
}
func NewFactory(gitnessProvider *GitnessSCM, genericSCM *GenericSCM) Factory {
providers := make(map[enum.GitspaceCodeRepoType]Provider)
providers[enum.CodeRepoTypeGitness] = gitnessProvider
providers[enum.CodeRepoTypeUnknown] = genericSCM
return Factory{providers: providers}
listingProviders := make(map[enum.GitspaceCodeRepoType]ListingProvider)
listingProviders[enum.CodeRepoTypeGitness] = gitnessProvider
listingProviders[enum.CodeRepoTypeUnknown] = genericSCM
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) {
val := f.providers[providerType]
func (f *Factory) GetSCMProvider(providerType enum.GitspaceCodeRepoType) (ListingProvider, error) {
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 {
return nil, fmt.Errorf("unknown scm provider type: %s", providerType)
}

View File

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

View File

@ -33,26 +33,50 @@ type CodeRepositoryResponse struct {
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 (
ResolvedDetails struct {
ResolvedCredentials
DevcontainerConfig types.DevcontainerConfig
}
// Credentials contains login and initialization information used
// UserPasswordCredentials contains login and initialization information used
// by an automated login process.
Credentials struct {
UserPasswordCredentials struct {
Email string
Name 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 {
Branch string
// CloneURL contains credentials for private repositories in url prefix
CloneURL types.MaskSecret
Credentials *Credentials
RepoName string
CloneURL types.MaskSecret
UserPasswordCredentials *UserPasswordCredentials
OAuth2TokenRefCredentials *OAuth2TokenRefCredentials
RepoName string
CredentialType CredentialType
}
RepositoryFilter struct {
@ -67,8 +91,8 @@ type (
SpaceID int64 `json:"space_id"`
Repository string `json:"repo"`
Query string `json:"query"`
Page int `json:"page"`
Size int `json:"size"`
Page int32 `json:"page"`
Size int32 `json:"size"`
RepoURL string `json:"repo_url"`
}

View File

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