2023-09-23 17:12:35 +02:00
|
|
|
package db_lib
|
2023-02-26 07:22:47 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
"github.com/ansible-semaphore/semaphore/db"
|
2024-04-12 12:32:54 +02:00
|
|
|
"github.com/ansible-semaphore/semaphore/pkg/task_logger"
|
2023-02-26 07:22:47 +01:00
|
|
|
"github.com/ansible-semaphore/semaphore/util"
|
|
|
|
"github.com/go-git/go-git/v5"
|
|
|
|
"github.com/go-git/go-git/v5/config"
|
|
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
|
|
"github.com/go-git/go-git/v5/plumbing/transport"
|
2023-02-26 07:58:21 +01:00
|
|
|
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
2023-02-26 07:22:47 +01:00
|
|
|
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
|
|
|
"github.com/go-git/go-git/v5/storage/memory"
|
2023-03-12 17:01:47 +01:00
|
|
|
|
|
|
|
ssh2 "golang.org/x/crypto/ssh"
|
2023-02-26 07:22:47 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type GoGitClient struct{}
|
|
|
|
|
2023-03-10 20:42:01 +01:00
|
|
|
type ProgressWrapper struct {
|
2024-04-12 12:32:54 +02:00
|
|
|
Logger task_logger.Logger
|
2023-02-26 07:22:47 +01:00
|
|
|
}
|
|
|
|
|
2023-03-10 20:42:01 +01:00
|
|
|
func (t ProgressWrapper) Write(p []byte) (n int, err error) {
|
2023-02-26 07:22:47 +01:00
|
|
|
t.Logger.Log(string(p))
|
|
|
|
return len(p), nil
|
|
|
|
}
|
|
|
|
|
2023-02-26 10:27:11 +01:00
|
|
|
func getAuthMethod(r GitRepository) (transport.AuthMethod, error) {
|
2023-02-26 07:22:47 +01:00
|
|
|
if r.Repository.SSHKey.Type == db.AccessKeySSH {
|
2023-04-26 16:31:13 +02:00
|
|
|
var sshKeyBuff = r.Repository.SSHKey.SshKey.PrivateKey
|
2023-02-26 07:22:47 +01:00
|
|
|
|
2023-04-26 16:31:13 +02:00
|
|
|
if r.Repository.SSHKey.SshKey.Login == "" {
|
2023-04-26 16:33:30 +02:00
|
|
|
r.Repository.SSHKey.SshKey.Login = "git"
|
2023-04-26 16:31:13 +02:00
|
|
|
}
|
2023-03-12 17:01:47 +01:00
|
|
|
|
2023-04-26 16:31:13 +02:00
|
|
|
publicKey, sshErr := ssh.NewPublicKeys(r.Repository.SSHKey.SshKey.Login, []byte(sshKeyBuff), r.Repository.SSHKey.SshKey.Passphrase)
|
2023-04-24 22:26:31 +02:00
|
|
|
|
2023-04-26 16:31:13 +02:00
|
|
|
if sshErr != nil {
|
2023-04-26 16:33:30 +02:00
|
|
|
r.Logger.Log("Unable to creating ssh auth method")
|
|
|
|
return nil, sshErr
|
2023-04-26 16:31:13 +02:00
|
|
|
}
|
|
|
|
publicKey.HostKeyCallback = ssh2.InsecureIgnoreHostKey()
|
2023-04-24 22:26:31 +02:00
|
|
|
|
2023-04-26 16:31:13 +02:00
|
|
|
return publicKey, sshErr
|
2023-02-26 07:22:47 +01:00
|
|
|
} else if r.Repository.SSHKey.Type == db.AccessKeyLoginPassword {
|
2023-04-26 16:31:13 +02:00
|
|
|
password := &http.BasicAuth{
|
2023-04-26 16:33:30 +02:00
|
|
|
Username: r.Repository.SSHKey.LoginPassword.Login,
|
|
|
|
Password: r.Repository.SSHKey.LoginPassword.Password,
|
2023-04-26 16:31:13 +02:00
|
|
|
}
|
2023-02-26 07:22:47 +01:00
|
|
|
|
2023-04-26 16:31:13 +02:00
|
|
|
return password, nil
|
2023-02-26 07:22:47 +01:00
|
|
|
} else if r.Repository.SSHKey.Type == db.AccessKeyNone {
|
2023-04-26 16:33:30 +02:00
|
|
|
return nil, nil
|
2023-02-26 07:22:47 +01:00
|
|
|
} else {
|
2023-09-23 17:12:35 +02:00
|
|
|
return nil, errors.New("unsupported auth method")
|
2023-02-26 07:22:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-26 10:27:11 +01:00
|
|
|
func openRepository(r GitRepository, targetDir GitRepositoryDirType) (*git.Repository, error) {
|
2023-02-26 07:22:47 +01:00
|
|
|
|
|
|
|
var dir string
|
|
|
|
|
|
|
|
switch targetDir {
|
2024-05-22 19:00:28 +02:00
|
|
|
case GitRepositoryTmpPath:
|
2023-02-26 07:22:47 +01:00
|
|
|
dir = util.Config.TmpPath
|
2024-05-22 19:00:28 +02:00
|
|
|
case GitRepositoryFullPath:
|
2023-02-26 07:22:47 +01:00
|
|
|
dir = r.GetFullPath()
|
|
|
|
default:
|
|
|
|
panic("unknown Repository directory type")
|
|
|
|
}
|
|
|
|
|
|
|
|
return git.PlainOpen(dir)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c GoGitClient) Clone(r GitRepository) error {
|
|
|
|
r.Logger.Log("Cloning Repository " + r.Repository.GitURL)
|
|
|
|
|
2023-02-26 10:27:11 +01:00
|
|
|
authMethod, authErr := getAuthMethod(r)
|
2023-02-26 07:22:47 +01:00
|
|
|
|
|
|
|
if authErr != nil {
|
|
|
|
return authErr
|
|
|
|
}
|
|
|
|
|
|
|
|
cloneOpt := &git.CloneOptions{
|
|
|
|
URL: r.Repository.GetGitURL(),
|
2023-03-10 20:42:01 +01:00
|
|
|
Progress: ProgressWrapper{r.Logger},
|
2023-02-26 07:22:47 +01:00
|
|
|
RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
|
|
|
|
ReferenceName: plumbing.NewBranchReferenceName(r.Repository.GitBranch),
|
|
|
|
Auth: authMethod,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := git.PlainClone(r.GetFullPath(), false, cloneOpt)
|
|
|
|
if err != nil {
|
|
|
|
r.Logger.Log("Unable to clone repository: " + err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c GoGitClient) Pull(r GitRepository) error {
|
|
|
|
r.Logger.Log("Updating Repository " + r.Repository.GitURL)
|
|
|
|
|
2024-05-22 19:00:28 +02:00
|
|
|
rep, err := openRepository(r, GitRepositoryFullPath)
|
2023-02-26 07:22:47 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
wt, err := rep.Worktree()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-02-26 10:27:11 +01:00
|
|
|
authMethod, authErr := getAuthMethod(r)
|
|
|
|
if authErr != nil {
|
|
|
|
return authErr
|
|
|
|
}
|
|
|
|
|
2023-02-26 07:22:47 +01:00
|
|
|
// Pull the latest changes from the origin remote and merge into the current branch
|
2023-10-26 13:22:31 +02:00
|
|
|
err = wt.Pull(&git.PullOptions{RemoteName: "origin",
|
|
|
|
Auth: authMethod,
|
|
|
|
RecurseSubmodules: git.DefaultSubmoduleRecursionDepth})
|
2023-02-26 10:27:11 +01:00
|
|
|
if err != nil && err != git.NoErrAlreadyUpToDate {
|
2023-02-26 07:22:47 +01:00
|
|
|
r.Logger.Log("Unable to pull latest changes")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c GoGitClient) Checkout(r GitRepository, target string) error {
|
|
|
|
r.Logger.Log("Checkout repository to " + target)
|
|
|
|
|
2024-05-22 19:00:28 +02:00
|
|
|
rep, err := openRepository(r, GitRepositoryFullPath)
|
2023-02-26 07:22:47 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
wt, err := rep.Worktree()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = wt.Checkout(&git.CheckoutOptions{
|
|
|
|
Hash: plumbing.NewHash(target),
|
|
|
|
})
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c GoGitClient) CanBePulled(r GitRepository) bool {
|
|
|
|
|
2024-05-22 19:00:28 +02:00
|
|
|
rep, err := openRepository(r, GitRepositoryFullPath)
|
2023-02-26 07:22:47 +01:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-02-26 10:27:11 +01:00
|
|
|
authMethod, err := getAuthMethod(r)
|
2023-02-26 07:22:47 +01:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-02-26 09:21:45 +01:00
|
|
|
err = rep.Fetch(&git.FetchOptions{
|
|
|
|
Auth: authMethod,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil && err != git.NoErrAlreadyUpToDate {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-02-26 07:22:47 +01:00
|
|
|
head, err := rep.Head()
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
headCommit, err := rep.CommitObject(head.Hash())
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
hash, err := rep.ResolveRevision(plumbing.Revision("origin/" + r.Repository.GitBranch))
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
lastCommit, err := rep.CommitObject(*hash)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
isAncestor, err := headCommit.IsAncestor(lastCommit)
|
|
|
|
return isAncestor && err == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c GoGitClient) GetLastCommitMessage(r GitRepository) (msg string, err error) {
|
|
|
|
r.Logger.Log("Get current commit message")
|
|
|
|
|
2024-05-22 19:00:28 +02:00
|
|
|
rep, err := openRepository(r, GitRepositoryFullPath)
|
2023-02-26 07:22:47 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
headRef, err := rep.Head()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
headCommit, err := rep.CommitObject(headRef.Hash())
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-02-26 09:36:46 +01:00
|
|
|
msg = headCommit.Message
|
2023-02-26 07:22:47 +01:00
|
|
|
if len(msg) > 100 {
|
|
|
|
msg = msg[0:100]
|
|
|
|
}
|
|
|
|
|
2023-02-26 10:27:11 +01:00
|
|
|
r.Logger.Log("Message: " + msg)
|
|
|
|
|
2023-02-26 07:22:47 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c GoGitClient) GetLastCommitHash(r GitRepository) (hash string, err error) {
|
|
|
|
r.Logger.Log("Get current commit hash")
|
|
|
|
|
2024-05-22 19:00:28 +02:00
|
|
|
rep, err := openRepository(r, GitRepositoryFullPath)
|
2023-02-26 07:22:47 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
headRef, err := rep.Head()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
hash = headRef.Hash().String()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c GoGitClient) GetLastRemoteCommitHash(r GitRepository) (hash string, err error) {
|
|
|
|
|
|
|
|
rem := git.NewRemote(memory.NewStorage(), &config.RemoteConfig{
|
|
|
|
Name: "origin",
|
|
|
|
URLs: []string{r.Repository.GitURL},
|
|
|
|
})
|
|
|
|
|
2023-02-26 10:27:11 +01:00
|
|
|
auth, err := getAuthMethod(r)
|
2023-02-26 07:22:47 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
refs, err := rem.List(&git.ListOptions{
|
|
|
|
Auth: auth,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var lastRemoteRef *plumbing.Reference
|
|
|
|
|
|
|
|
for _, rf := range refs {
|
|
|
|
|
|
|
|
if rf.Name().Short() == r.Repository.GitBranch {
|
|
|
|
lastRemoteRef = rf
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if lastRemoteRef != nil {
|
|
|
|
hash = lastRemoteRef.Hash().String()
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|