github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/source/common/common.go (about)

     1  package common
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"net/http"
     7  	"net/url"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/cockroachdb/errors"
    13  	"github.com/projecteru2/core/log"
    14  	"github.com/projecteru2/core/types"
    15  
    16  	gogit "github.com/go-git/go-git/v5"
    17  	"github.com/go-git/go-git/v5/plumbing"
    18  	gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
    19  	"golang.org/x/crypto/ssh"
    20  )
    21  
    22  // GitScm is gitlab or github source code manager
    23  type GitScm struct {
    24  	http.Client
    25  	Config      types.GitConfig
    26  	AuthHeaders map[string]string
    27  
    28  	keyBytes []byte
    29  }
    30  
    31  // NewGitScm .
    32  func NewGitScm(config types.GitConfig, authHeaders map[string]string) (*GitScm, error) {
    33  	b, err := os.ReadFile(config.PrivateKey)
    34  	return &GitScm{
    35  		Config:      config,
    36  		AuthHeaders: authHeaders,
    37  		keyBytes:    b,
    38  	}, err
    39  }
    40  
    41  // SourceCode clone code from repository into path, by revision
    42  func (g *GitScm) SourceCode(ctx context.Context, repository, path, revision string, submodule bool) error {
    43  	var repo *gogit.Repository
    44  	var err error
    45  	ctx, cancel := context.WithTimeout(ctx, g.Config.CloneTimeout)
    46  	defer cancel()
    47  	opts := &gogit.CloneOptions{
    48  		URL:      repository,
    49  		Progress: io.Discard,
    50  	}
    51  	logger := log.WithFunc("source.common.SourceCode")
    52  
    53  	switch {
    54  	case strings.Contains(repository, "https://"):
    55  		repo, err = gogit.PlainCloneContext(ctx, path, false, opts)
    56  	case strings.Contains(repository, "git@") || strings.Contains(repository, "gitlab@"):
    57  		signer, signErr := ssh.ParsePrivateKey(g.keyBytes)
    58  		if signErr != nil {
    59  			return signErr
    60  		}
    61  		splitRepo := strings.Split(repository, "@")
    62  		user, parseErr := url.Parse(splitRepo[0])
    63  		if parseErr != nil {
    64  			return parseErr
    65  		}
    66  		// TODO check if it ok?
    67  		// gitssh.SetConfigHostKeyFields( // nolint
    68  		//	&ssh.ClientConfig{
    69  		//		HostKeyCallback: ssh.InsecureIgnoreHostKey()}, // nolint
    70  		//	user.Host)
    71  
    72  		auth := &gitssh.PublicKeys{
    73  			User:   user.Host + user.Path,
    74  			Signer: signer,
    75  		}
    76  		opts.Auth = auth
    77  		repo, err = gogit.PlainCloneContext(ctx, path, false, opts)
    78  	default:
    79  		return types.ErrInvaildSCMType
    80  	}
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	w, err := repo.Worktree()
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	hash, err := repo.ResolveRevision(plumbing.Revision(revision))
    91  	if err != nil {
    92  		return err
    93  	}
    94  
    95  	if err = w.Checkout(&gogit.CheckoutOptions{Hash: *hash}); err != nil {
    96  		return err
    97  	}
    98  
    99  	logger.Infof(ctx, "Fetch repo %s", repository)
   100  	logger.Infof(ctx, "Checkout to commit %s", hash)
   101  
   102  	// Prepare submodules
   103  	if submodule {
   104  		s, err := w.Submodules()
   105  		if err != nil {
   106  			return err
   107  		}
   108  		return s.Update(&gogit.SubmoduleUpdateOptions{Init: true, Auth: opts.Auth})
   109  	}
   110  	return err
   111  }
   112  
   113  // Artifact download the artifact to the path, then unzip it
   114  func (g *GitScm) Artifact(ctx context.Context, artifact, path string) error {
   115  	req, err := http.NewRequest(http.MethodGet, artifact, nil)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	for k, v := range g.AuthHeaders {
   121  		req.Header.Add(k, v)
   122  	}
   123  
   124  	log.WithFunc("source.common.Artifact").Infof(ctx, "Downloading artifacts from %q", artifact)
   125  	resp, err := g.Do(req)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	defer resp.Body.Close()
   130  	if resp.StatusCode != 200 {
   131  		return errors.Wrapf(types.ErrDownloadArtifactsFailed, "code: %d", resp.StatusCode)
   132  	}
   133  
   134  	// extract files from zipfile
   135  	return unzipFile(resp.Body, path)
   136  }
   137  
   138  // Security remove the .git folder
   139  func (g *GitScm) Security(path string) error {
   140  	return os.RemoveAll(filepath.Join(path, ".git"))
   141  }