mirror of https://github.com/harness/drone.git
feat: [CDE-195]: use dependency-injection for scm providers for gitspaces. (#2308)
* feat: [CDE-195]: use dependency-injection for scm providers for gitspaces. * feat: [CDE-195]: use dependency-injection for scm providers for gitspaces. * feat: [CDE-195]: use dependency-injection for scm providers for gitspaces.pull/3545/head
parent
f7928f1f77
commit
42af216dc7
|
@ -83,7 +83,7 @@ func (o orchestrator) StartGitspace(
|
|||
|
||||
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeFetchDevcontainerStart)
|
||||
|
||||
scmResolvedDetails, err := o.scm.Resolve(ctx, gitspaceConfig)
|
||||
scmResolvedDetails, err := o.scm.GetSCMRepoDetails(ctx, gitspaceConfig)
|
||||
if err != nil {
|
||||
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeFetchDevcontainerFailed)
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
// 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
|
||||
|
||||
type CodeRepositoryRequest struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type CodeRepositoryResponse struct {
|
||||
URL string `json:"url"`
|
||||
Branch string `json:"branch,omitempty"`
|
||||
CodeRepoIsPrivate bool `json:"is_private"`
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
// 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"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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.CodeRepoURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse repository URL %s: %w", gitspaceConfig.CodeRepoURL, err)
|
||||
}
|
||||
repoName := strings.TrimSuffix(path.Base(repoURL.Path), ".git")
|
||||
repo, err := s.repoStore.FindByRef(ctx, gitspaceConfig.CodeRepoURL)
|
||||
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.Branch, CloneURL: gitURL}
|
||||
resolvedCredentails.RepoName = repoName
|
||||
gitspacePrincipal := bootstrap.NewGitspaceServiceSession().Principal
|
||||
user, err := findUserFromUID(ctx, s.principalStore, gitspaceConfig.UserID)
|
||||
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)
|
||||
}
|
||||
credentials := &Credentials{
|
||||
Password: jwtToken,
|
||||
Email: user.Email,
|
||||
Name: user.DisplayName,
|
||||
}
|
||||
resolvedCredentails.Credentials = credentials
|
||||
return resolvedCredentails, nil
|
||||
}
|
||||
|
||||
func (s GitnessSCM) GetFileContent(ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
filePath string,
|
||||
) ([]byte, error) {
|
||||
repo, err := s.repoStore.FindByRef(ctx, gitspaceConfig.CodeRepoURL)
|
||||
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.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.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)
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
// 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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/harness/gitness/git/command"
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type GenericSCM struct {
|
||||
}
|
||||
|
||||
func NewGenericSCM() *GenericSCM {
|
||||
return &GenericSCM{}
|
||||
}
|
||||
|
||||
func (s GenericSCM) GetFileContent(ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
filePath string,
|
||||
) ([]byte, error) {
|
||||
gitWorkingDirectory := "/tmp/git/"
|
||||
cloneDir := gitWorkingDirectory + uuid.New().String()
|
||||
err := os.MkdirAll(cloneDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating directory %s: %w", cloneDir, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = os.RemoveAll(cloneDir)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Warn().Err(err).Msg("Unable to remove working directory")
|
||||
}
|
||||
}()
|
||||
|
||||
log.Info().Msg("Cloning the repository...")
|
||||
cmd := command.New("clone",
|
||||
command.WithFlag("--branch", gitspaceConfig.Branch),
|
||||
command.WithFlag("--no-checkout"),
|
||||
command.WithFlag("--depth", "1"),
|
||||
command.WithArg(gitspaceConfig.CodeRepoURL),
|
||||
command.WithArg(cloneDir),
|
||||
)
|
||||
if err := cmd.Run(ctx, command.WithDir(cloneDir)); err != nil {
|
||||
return nil, fmt.Errorf("failed to clone repository %s: %w", gitspaceConfig.CodeRepoURL, err)
|
||||
}
|
||||
|
||||
var lsTreeOutput bytes.Buffer
|
||||
lsTreeCmd := command.New("ls-tree",
|
||||
command.WithArg("HEAD"),
|
||||
command.WithArg(filePath),
|
||||
)
|
||||
|
||||
if err := lsTreeCmd.Run(ctx, command.WithDir(cloneDir), command.WithStdout(&lsTreeOutput)); err != nil {
|
||||
return nil, fmt.Errorf("failed to list files in repository %s: %w", cloneDir, err)
|
||||
}
|
||||
|
||||
if lsTreeOutput.Len() == 0 {
|
||||
log.Info().Msg("File not found, returning empty devcontainerConfig")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
fields := strings.Fields(lsTreeOutput.String())
|
||||
blobSHA := fields[2]
|
||||
|
||||
var catFileOutput bytes.Buffer
|
||||
catFileCmd := command.New("cat-file", command.WithFlag("-p"), command.WithArg(blobSHA))
|
||||
err = catFileCmd.Run(
|
||||
ctx,
|
||||
command.WithDir(cloneDir),
|
||||
command.WithStderr(io.Discard),
|
||||
command.WithStdout(&catFileOutput),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read devcontainer file from path %s: %w", filePath, err)
|
||||
}
|
||||
return catFileOutput.Bytes(), nil
|
||||
}
|
||||
|
||||
func (s GenericSCM) ResolveCredentials(
|
||||
_ context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
) (*ResolvedCredentials, error) {
|
||||
var resolvedDetails = &ResolvedDetails{}
|
||||
resolvedDetails.Branch = gitspaceConfig.Branch
|
||||
resolvedDetails.CloneURL = gitspaceConfig.CodeRepoURL
|
||||
repoURL, err := url.Parse(gitspaceConfig.CodeRepoURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse repository URL %s: %w", gitspaceConfig.CodeRepoURL, err)
|
||||
}
|
||||
repoName := strings.TrimSuffix(path.Base(repoURL.Path), ".git")
|
||||
resolvedDetails.RepoName = repoName
|
||||
return nil, err
|
||||
}
|
|
@ -20,42 +20,25 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"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/git/command"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoDefaultBranch = errors.New("no default branch")
|
||||
)
|
||||
|
||||
var gitspaceJWTLifetime = 720 * 24 * time.Hour
|
||||
const devcontainerDefaultPath = ".devcontainer/devcontainer.json"
|
||||
|
||||
const defaultGitspacePATIdentifier = "Gitspace_Default"
|
||||
|
||||
var _ SCM = (*genericSCM)(nil)
|
||||
var _ SCM = (*scm)(nil)
|
||||
|
||||
type SCM interface {
|
||||
// RepoNameAndDevcontainerConfig fetches repository name & devcontainer config file from the given repo and branch.
|
||||
Resolve(
|
||||
// GetSCMRepoDetails fetches repository name, credentials & devcontainer config file from the given repo and branch.
|
||||
GetSCMRepoDetails(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
) (*ResolvedDetails, error)
|
||||
|
@ -65,28 +48,15 @@ type SCM interface {
|
|||
CheckValidCodeRepo(ctx context.Context, request CodeRepositoryRequest) (*CodeRepositoryResponse, error)
|
||||
}
|
||||
|
||||
type genericSCM struct {
|
||||
git git.Interface
|
||||
repoStore store.RepoStore
|
||||
tokenStore store.TokenStore
|
||||
principalStore store.PrincipalStore
|
||||
urlProvider urlprovider.Provider
|
||||
type scm struct {
|
||||
scmProviderFactory Factory
|
||||
}
|
||||
|
||||
func NewSCM(repoStore store.RepoStore, git git.Interface,
|
||||
tokenStore store.TokenStore,
|
||||
principalStore store.PrincipalStore,
|
||||
urlProvider urlprovider.Provider) SCM {
|
||||
return &genericSCM{
|
||||
repoStore: repoStore,
|
||||
git: git,
|
||||
tokenStore: tokenStore,
|
||||
principalStore: principalStore,
|
||||
urlProvider: urlProvider,
|
||||
}
|
||||
func NewSCM(factory Factory) SCM {
|
||||
return &scm{scmProviderFactory: factory}
|
||||
}
|
||||
|
||||
func (s genericSCM) CheckValidCodeRepo(
|
||||
func (s scm) CheckValidCodeRepo(
|
||||
ctx context.Context,
|
||||
request CodeRepositoryRequest,
|
||||
) (*CodeRepositoryResponse, error) {
|
||||
|
@ -113,162 +83,40 @@ func (s genericSCM) CheckValidCodeRepo(
|
|||
return codeRepositoryResponse, nil
|
||||
}
|
||||
|
||||
func (s genericSCM) Resolve(
|
||||
func (s scm) GetSCMRepoDetails(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
) (*ResolvedDetails, error) {
|
||||
resolvedDetails := &ResolvedDetails{Branch: gitspaceConfig.Branch, CloneURL: gitspaceConfig.CodeRepoURL}
|
||||
repoURL, err := url.Parse(gitspaceConfig.CodeRepoURL)
|
||||
filePath := devcontainerDefaultPath
|
||||
scmProvider, err := s.scmProviderFactory.GetSCMProvider(gitspaceConfig.CodeRepoType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse repository URL %s: %w", gitspaceConfig.CodeRepoURL, err)
|
||||
return nil, fmt.Errorf("failed to resolve scm provider: %w", err)
|
||||
}
|
||||
repoName := strings.TrimSuffix(path.Base(repoURL.Path), ".git")
|
||||
resolvedDetails.RepoName = repoName
|
||||
gitWorkingDirectory := "/tmp/git/"
|
||||
|
||||
cloneDir := gitWorkingDirectory + uuid.New().String()
|
||||
|
||||
err = os.MkdirAll(cloneDir, os.ModePerm)
|
||||
resolvedCredentials, err := scmProvider.ResolveCredentials(ctx, gitspaceConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating directory %s: %w", cloneDir, err)
|
||||
return nil, fmt.Errorf("failed to resolve repo credentials and url: %w", err)
|
||||
}
|
||||
var resolvedDetails = &ResolvedDetails{
|
||||
ResolvedCredentials: resolvedCredentials,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = os.RemoveAll(cloneDir)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Warn().Err(err).Msg("Unable to remove working directory")
|
||||
}
|
||||
}()
|
||||
|
||||
filePath := ".devcontainer/devcontainer.json"
|
||||
var catFileOutputBytes []byte
|
||||
switch gitspaceConfig.CodeRepoType { //nolint:exhaustive
|
||||
case enum.CodeRepoTypeGitness:
|
||||
repo, err := s.repoStore.FindByRef(ctx, gitspaceConfig.CodeRepoURL)
|
||||
|
||||
// Backfill clone URL
|
||||
repo.GitURL = s.urlProvider.GenerateContainerGITCloneURL(ctx, repo.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find repository: %w", err)
|
||||
}
|
||||
resolvedDetails.CloneURL = repo.GitURL
|
||||
catFileOutputBytes, err = s.getDevContainerConfigInternal(ctx, gitspaceConfig, filePath, repo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read devcontainer file : %w", err)
|
||||
}
|
||||
netrc, err := s.gitnessCredentials(ctx, repo, gitspaceConfig.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve repo credentials: %w", err)
|
||||
}
|
||||
resolvedDetails.Credentials = netrc
|
||||
default:
|
||||
catFileOutputBytes, err = s.getDevContainerConfigPublic(ctx, gitspaceConfig, cloneDir, filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
catFileOutputBytes, err := scmProvider.GetFileContent(ctx, gitspaceConfig, filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read devcontainer file : %w", err)
|
||||
}
|
||||
if len(catFileOutputBytes) == 0 {
|
||||
resolvedDetails.DevcontainerConfig = &types.DevcontainerConfig{}
|
||||
return resolvedDetails, nil
|
||||
} else {
|
||||
sanitizedJSON := removeComments(catFileOutputBytes)
|
||||
var config *types.DevcontainerConfig
|
||||
if err = json.Unmarshal(sanitizedJSON, &config); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse devcontainer json: %w", err)
|
||||
}
|
||||
resolvedDetails.DevcontainerConfig = config
|
||||
}
|
||||
sanitizedJSON := removeComments(catFileOutputBytes)
|
||||
var config *types.DevcontainerConfig
|
||||
err = json.Unmarshal(sanitizedJSON, &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse devcontainer json: %w", err)
|
||||
}
|
||||
resolvedDetails.DevcontainerConfig = config
|
||||
return resolvedDetails, nil
|
||||
}
|
||||
|
||||
func (s genericSCM) getDevContainerConfigInternal(ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
filePath string,
|
||||
repo *types.Repository,
|
||||
) ([]byte, error) {
|
||||
// create read params once
|
||||
readParams := git.CreateReadParams(repo)
|
||||
treeNodeOutput, err := s.git.GetTreeNode(ctx, &git.GetTreeNodeParams{
|
||||
ReadParams: readParams,
|
||||
GitREF: gitspaceConfig.Branch,
|
||||
Path: filePath,
|
||||
IncludeLatestCommit: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read tree node: %w", err)
|
||||
}
|
||||
|
||||
// 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.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 (s genericSCM) getDevContainerConfigPublic(ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
cloneDir string,
|
||||
filePath string,
|
||||
) ([]byte, error) {
|
||||
log.Info().Msg("Cloning the repository...")
|
||||
cmd := command.New("clone",
|
||||
command.WithFlag("--branch", gitspaceConfig.Branch),
|
||||
command.WithFlag("--no-checkout"),
|
||||
command.WithFlag("--depth", "1"),
|
||||
command.WithArg(gitspaceConfig.CodeRepoURL),
|
||||
command.WithArg(cloneDir),
|
||||
)
|
||||
if err := cmd.Run(ctx, command.WithDir(cloneDir)); err != nil {
|
||||
return nil, fmt.Errorf("failed to clone repository %s: %w", gitspaceConfig.CodeRepoURL, err)
|
||||
}
|
||||
|
||||
var lsTreeOutput bytes.Buffer
|
||||
lsTreeCmd := command.New("ls-tree",
|
||||
command.WithArg("HEAD"),
|
||||
command.WithArg(filePath),
|
||||
)
|
||||
|
||||
if err := lsTreeCmd.Run(ctx, command.WithDir(cloneDir), command.WithStdout(&lsTreeOutput)); err != nil {
|
||||
return nil, fmt.Errorf("failed to list files in repository %s: %w", cloneDir, err)
|
||||
}
|
||||
|
||||
if lsTreeOutput.Len() == 0 {
|
||||
log.Info().Msg("File not found, returning empty devcontainerConfig")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
fields := strings.Fields(lsTreeOutput.String())
|
||||
blobSHA := fields[2]
|
||||
|
||||
var catFileOutput bytes.Buffer
|
||||
catFileCmd := command.New("cat-file", command.WithFlag("-p"), command.WithArg(blobSHA))
|
||||
err := catFileCmd.Run(
|
||||
ctx,
|
||||
command.WithDir(cloneDir),
|
||||
command.WithStderr(io.Discard),
|
||||
command.WithStdout(&catFileOutput),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read devcontainer file from path %s: %w", filePath, err)
|
||||
}
|
||||
return catFileOutput.Bytes(), nil
|
||||
}
|
||||
|
||||
func removeComments(input []byte) []byte {
|
||||
blockCommentRegex := regexp.MustCompile(`(?s)/\*.*?\*/`)
|
||||
input = blockCommentRegex.ReplaceAll(input, nil)
|
||||
|
@ -301,52 +149,3 @@ func validateURL(request CodeRepositoryRequest) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findUserFromUID(ctx context.Context,
|
||||
principalStore store.PrincipalStore, userUID string,
|
||||
) (*types.User, error) {
|
||||
return principalStore.FindUserByUID(ctx, userUID)
|
||||
}
|
||||
|
||||
func (s genericSCM) gitnessCredentials(
|
||||
ctx context.Context,
|
||||
repo *types.Repository,
|
||||
userUID string,
|
||||
) (*Credentials, error) {
|
||||
gitspacePrincipal := bootstrap.NewGitspaceServiceSession().Principal
|
||||
user, err := findUserFromUID(ctx, s.principalStore, userUID)
|
||||
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)
|
||||
}
|
||||
|
||||
cloneURL, err := url.Parse(repo.GitURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse clone url '%s': %w", cloneURL, err)
|
||||
}
|
||||
|
||||
return &Credentials{
|
||||
Password: jwtToken,
|
||||
Email: user.Email,
|
||||
Name: user.DisplayName,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
// 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"
|
||||
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
ResolveCredentials(ctx context.Context, gitspaceConfig *types.GitspaceConfig) (*ResolvedCredentials, error)
|
||||
GetFileContent(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
filePath string,
|
||||
) ([]byte, error)
|
||||
}
|
||||
|
||||
type Factory struct {
|
||||
providers map[enum.GitspaceCodeRepoType]Provider
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
|
||||
func (f *Factory) GetSCMProvider(providerType enum.GitspaceCodeRepoType) (Provider, error) {
|
||||
val := f.providers[providerType]
|
||||
if val == nil {
|
||||
return nil, fmt.Errorf("unknown scm provider type: %s", providerType)
|
||||
}
|
||||
return val, nil
|
||||
}
|
|
@ -16,13 +16,20 @@ package scm
|
|||
|
||||
import "github.com/harness/gitness/types"
|
||||
|
||||
type CodeRepositoryRequest struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type CodeRepositoryResponse struct {
|
||||
URL string `json:"url"`
|
||||
Branch string `json:"branch,omitempty"`
|
||||
CodeRepoIsPrivate bool `json:"is_private"`
|
||||
}
|
||||
|
||||
type (
|
||||
ResolvedDetails struct {
|
||||
Branch string
|
||||
CloneURL string
|
||||
Credentials *Credentials
|
||||
*ResolvedCredentials
|
||||
DevcontainerConfig *types.DevcontainerConfig
|
||||
RepoName string
|
||||
}
|
||||
|
||||
// Credentials contains login and initialization information used
|
||||
|
@ -32,4 +39,11 @@ type (
|
|||
Name string
|
||||
Password string
|
||||
}
|
||||
|
||||
ResolvedCredentials struct {
|
||||
Branch string
|
||||
CloneURL string
|
||||
Credentials *Credentials
|
||||
RepoName string
|
||||
}
|
||||
)
|
||||
|
|
|
@ -24,14 +24,26 @@ import (
|
|||
|
||||
// WireSet provides a wire set for this package.
|
||||
var WireSet = wire.NewSet(
|
||||
ProvideSCM,
|
||||
ProvideGitnessSCM, ProvideGenericSCM, ProvideFactory, ProvideSCM,
|
||||
)
|
||||
|
||||
func ProvideSCM(repoStore store.RepoStore,
|
||||
func ProvideGitnessSCM(repoStore store.RepoStore,
|
||||
rpcClient git.Interface,
|
||||
tokenStore store.TokenStore,
|
||||
principalStore store.PrincipalStore,
|
||||
urlProvider urlprovider.Provider,
|
||||
) SCM {
|
||||
return NewSCM(repoStore, rpcClient, tokenStore, principalStore, urlProvider)
|
||||
) *GitnessSCM {
|
||||
return NewGitnessSCM(repoStore, rpcClient, tokenStore, principalStore, urlProvider)
|
||||
}
|
||||
|
||||
func ProvideGenericSCM() *GenericSCM {
|
||||
return NewGenericSCM()
|
||||
}
|
||||
|
||||
func ProvideFactory(gitness *GitnessSCM, genericSCM *GenericSCM) Factory {
|
||||
return NewFactory(gitness, genericSCM)
|
||||
}
|
||||
|
||||
func ProvideSCM(factory Factory) SCM {
|
||||
return NewSCM(factory)
|
||||
}
|
||||
|
|
|
@ -343,7 +343,10 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scmSCM := scm.ProvideSCM(repoStore, gitInterface, tokenStore, principalStore, provider)
|
||||
gitnessSCM := scm.ProvideGitnessSCM(repoStore, gitInterface, tokenStore, principalStore, provider)
|
||||
genericSCM := scm.ProvideGenericSCM()
|
||||
scmFactory := scm.ProvideFactory(gitnessSCM, genericSCM)
|
||||
scmSCM := scm.ProvideSCM(scmFactory)
|
||||
infraProvisioner := infrastructure.ProvideInfraProvisionerService(infraProviderConfigStore, infraProviderResourceStore, factory)
|
||||
statefulLogger := logutil.ProvideStatefulLogger(logStream)
|
||||
containerOrchestrator := container.ProvideEmbeddedDockerOrchestrator(dockerClientFactory, statefulLogger)
|
||||
|
|
Loading…
Reference in New Issue