mirror of
https://github.com/portainer/portainer.git
synced 2025-07-23 15:29:42 +02:00
feat(git): support bearer token auth for git [BE-11770] (#879)
This commit is contained in:
parent
55cc250d2e
commit
caf382b64c
20 changed files with 670 additions and 117 deletions
|
@ -58,7 +58,15 @@ func TestService_ClonePublicRepository_Azure(t *testing.T) {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
dst := t.TempDir()
|
dst := t.TempDir()
|
||||||
repositoryUrl := fmt.Sprintf(tt.args.repositoryURLFormat, tt.args.password)
|
repositoryUrl := fmt.Sprintf(tt.args.repositoryURLFormat, tt.args.password)
|
||||||
err := service.CloneRepository(dst, repositoryUrl, tt.args.referenceName, "", "", false)
|
err := service.CloneRepository(
|
||||||
|
dst,
|
||||||
|
repositoryUrl,
|
||||||
|
tt.args.referenceName,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.FileExists(t, filepath.Join(dst, "README.md"))
|
assert.FileExists(t, filepath.Join(dst, "README.md"))
|
||||||
})
|
})
|
||||||
|
@ -73,7 +81,15 @@ func TestService_ClonePrivateRepository_Azure(t *testing.T) {
|
||||||
|
|
||||||
dst := t.TempDir()
|
dst := t.TempDir()
|
||||||
|
|
||||||
err := service.CloneRepository(dst, privateAzureRepoURL, "refs/heads/main", "", pat, false)
|
err := service.CloneRepository(
|
||||||
|
dst,
|
||||||
|
privateAzureRepoURL,
|
||||||
|
"refs/heads/main",
|
||||||
|
"",
|
||||||
|
pat,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.FileExists(t, filepath.Join(dst, "README.md"))
|
assert.FileExists(t, filepath.Join(dst, "README.md"))
|
||||||
}
|
}
|
||||||
|
@ -84,7 +100,14 @@ func TestService_LatestCommitID_Azure(t *testing.T) {
|
||||||
pat := getRequiredValue(t, "AZURE_DEVOPS_PAT")
|
pat := getRequiredValue(t, "AZURE_DEVOPS_PAT")
|
||||||
service := NewService(context.TODO())
|
service := NewService(context.TODO())
|
||||||
|
|
||||||
id, err := service.LatestCommitID(privateAzureRepoURL, "refs/heads/main", "", pat, false)
|
id, err := service.LatestCommitID(
|
||||||
|
privateAzureRepoURL,
|
||||||
|
"refs/heads/main",
|
||||||
|
"",
|
||||||
|
pat,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEmpty(t, id, "cannot guarantee commit id, but it should be not empty")
|
assert.NotEmpty(t, id, "cannot guarantee commit id, but it should be not empty")
|
||||||
}
|
}
|
||||||
|
@ -96,7 +119,14 @@ func TestService_ListRefs_Azure(t *testing.T) {
|
||||||
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
||||||
service := NewService(context.TODO())
|
service := NewService(context.TODO())
|
||||||
|
|
||||||
refs, err := service.ListRefs(privateAzureRepoURL, username, accessToken, false, false)
|
refs, err := service.ListRefs(
|
||||||
|
privateAzureRepoURL,
|
||||||
|
username,
|
||||||
|
accessToken,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.GreaterOrEqual(t, len(refs), 1)
|
assert.GreaterOrEqual(t, len(refs), 1)
|
||||||
}
|
}
|
||||||
|
@ -108,8 +138,8 @@ func TestService_ListRefs_Azure_Concurrently(t *testing.T) {
|
||||||
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
||||||
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
||||||
|
|
||||||
go service.ListRefs(privateAzureRepoURL, username, accessToken, false, false)
|
go service.ListRefs(privateAzureRepoURL, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||||
service.ListRefs(privateAzureRepoURL, username, accessToken, false, false)
|
service.ListRefs(privateAzureRepoURL, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
|
@ -247,7 +277,17 @@ func TestService_ListFiles_Azure(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
paths, err := service.ListFiles(tt.args.repositoryUrl, tt.args.referenceName, tt.args.username, tt.args.password, false, false, tt.extensions, false)
|
paths, err := service.ListFiles(
|
||||||
|
tt.args.repositoryUrl,
|
||||||
|
tt.args.referenceName,
|
||||||
|
tt.args.username,
|
||||||
|
tt.args.password,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
tt.extensions,
|
||||||
|
false,
|
||||||
|
)
|
||||||
if tt.expect.shouldFail {
|
if tt.expect.shouldFail {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
if tt.expect.err != nil {
|
if tt.expect.err != nil {
|
||||||
|
@ -270,8 +310,28 @@ func TestService_ListFiles_Azure_Concurrently(t *testing.T) {
|
||||||
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
||||||
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
||||||
|
|
||||||
go service.ListFiles(privateAzureRepoURL, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
go service.ListFiles(
|
||||||
service.ListFiles(privateAzureRepoURL, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
privateAzureRepoURL,
|
||||||
|
"refs/heads/main",
|
||||||
|
username,
|
||||||
|
accessToken,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
[]string{},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
service.ListFiles(
|
||||||
|
privateAzureRepoURL,
|
||||||
|
"refs/heads/main",
|
||||||
|
username,
|
||||||
|
accessToken,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
[]string{},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ type CloneOptions struct {
|
||||||
ReferenceName string
|
ReferenceName string
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
|
AuthType gittypes.GitCredentialAuthType
|
||||||
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
||||||
TLSSkipVerify bool `example:"false"`
|
TLSSkipVerify bool `example:"false"`
|
||||||
}
|
}
|
||||||
|
@ -42,7 +43,15 @@ func CloneWithBackup(gitService portainer.GitService, fileService portainer.File
|
||||||
|
|
||||||
cleanUp = true
|
cleanUp = true
|
||||||
|
|
||||||
if err := gitService.CloneRepository(options.ProjectPath, options.URL, options.ReferenceName, options.Username, options.Password, options.TLSSkipVerify); err != nil {
|
if err := gitService.CloneRepository(
|
||||||
|
options.ProjectPath,
|
||||||
|
options.URL,
|
||||||
|
options.ReferenceName,
|
||||||
|
options.Username,
|
||||||
|
options.Password,
|
||||||
|
options.AuthType,
|
||||||
|
options.TLSSkipVerify,
|
||||||
|
); err != nil {
|
||||||
cleanUp = false
|
cleanUp = false
|
||||||
if err := filesystem.MoveDirectory(backupProjectPath, options.ProjectPath, false); err != nil {
|
if err := filesystem.MoveDirectory(backupProjectPath, options.ProjectPath, false); err != nil {
|
||||||
log.Warn().Err(err).Msg("failed restoring backup folder")
|
log.Warn().Err(err).Msg("failed restoring backup folder")
|
||||||
|
|
|
@ -7,12 +7,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
gittypes "github.com/portainer/portainer/api/git/types"
|
gittypes "github.com/portainer/portainer/api/git/types"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/config"
|
"github.com/go-git/go-git/v5/config"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/go-git/go-git/v5/plumbing/filemode"
|
"github.com/go-git/go-git/v5/plumbing/filemode"
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||||
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
|
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||||
"github.com/go-git/go-git/v5/storage/memory"
|
"github.com/go-git/go-git/v5/storage/memory"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -33,7 +35,7 @@ func (c *gitClient) download(ctx context.Context, dst string, opt cloneOption) e
|
||||||
URL: opt.repositoryUrl,
|
URL: opt.repositoryUrl,
|
||||||
Depth: opt.depth,
|
Depth: opt.depth,
|
||||||
InsecureSkipTLS: opt.tlsSkipVerify,
|
InsecureSkipTLS: opt.tlsSkipVerify,
|
||||||
Auth: getAuth(opt.username, opt.password),
|
Auth: getAuth(opt.authType, opt.username, opt.password),
|
||||||
Tags: git.NoTags,
|
Tags: git.NoTags,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +53,10 @@ func (c *gitClient) download(ctx context.Context, dst string, opt cloneOption) e
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.preserveGitDirectory {
|
if !c.preserveGitDirectory {
|
||||||
os.RemoveAll(filepath.Join(dst, ".git"))
|
err := os.RemoveAll(filepath.Join(dst, ".git"))
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("failed to remove .git directory")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -64,7 +69,7 @@ func (c *gitClient) latestCommitID(ctx context.Context, opt fetchOption) (string
|
||||||
})
|
})
|
||||||
|
|
||||||
listOptions := &git.ListOptions{
|
listOptions := &git.ListOptions{
|
||||||
Auth: getAuth(opt.username, opt.password),
|
Auth: getAuth(opt.authType, opt.username, opt.password),
|
||||||
InsecureSkipTLS: opt.tlsSkipVerify,
|
InsecureSkipTLS: opt.tlsSkipVerify,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +99,23 @@ func (c *gitClient) latestCommitID(ctx context.Context, opt fetchOption) (string
|
||||||
return "", errors.Errorf("could not find ref %q in the repository", opt.referenceName)
|
return "", errors.Errorf("could not find ref %q in the repository", opt.referenceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAuth(username, password string) *githttp.BasicAuth {
|
func getAuth(authType gittypes.GitCredentialAuthType, username, password string) transport.AuthMethod {
|
||||||
|
if password == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch authType {
|
||||||
|
case gittypes.GitCredentialAuthType_Basic:
|
||||||
|
return getBasicAuth(username, password)
|
||||||
|
case gittypes.GitCredentialAuthType_Token:
|
||||||
|
return getTokenAuth(password)
|
||||||
|
default:
|
||||||
|
log.Warn().Msg("unknown git credentials authorization type, defaulting to None")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBasicAuth(username, password string) *githttp.BasicAuth {
|
||||||
if password != "" {
|
if password != "" {
|
||||||
if username == "" {
|
if username == "" {
|
||||||
username = "token"
|
username = "token"
|
||||||
|
@ -108,6 +129,15 @@ func getAuth(username, password string) *githttp.BasicAuth {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTokenAuth(token string) *githttp.TokenAuth {
|
||||||
|
if token != "" {
|
||||||
|
return &githttp.TokenAuth{
|
||||||
|
Token: token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *gitClient) listRefs(ctx context.Context, opt baseOption) ([]string, error) {
|
func (c *gitClient) listRefs(ctx context.Context, opt baseOption) ([]string, error) {
|
||||||
rem := git.NewRemote(memory.NewStorage(), &config.RemoteConfig{
|
rem := git.NewRemote(memory.NewStorage(), &config.RemoteConfig{
|
||||||
Name: "origin",
|
Name: "origin",
|
||||||
|
@ -115,7 +145,7 @@ func (c *gitClient) listRefs(ctx context.Context, opt baseOption) ([]string, err
|
||||||
})
|
})
|
||||||
|
|
||||||
listOptions := &git.ListOptions{
|
listOptions := &git.ListOptions{
|
||||||
Auth: getAuth(opt.username, opt.password),
|
Auth: getAuth(opt.authType, opt.username, opt.password),
|
||||||
InsecureSkipTLS: opt.tlsSkipVerify,
|
InsecureSkipTLS: opt.tlsSkipVerify,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +173,7 @@ func (c *gitClient) listFiles(ctx context.Context, opt fetchOption) ([]string, e
|
||||||
Depth: 1,
|
Depth: 1,
|
||||||
SingleBranch: true,
|
SingleBranch: true,
|
||||||
ReferenceName: plumbing.ReferenceName(opt.referenceName),
|
ReferenceName: plumbing.ReferenceName(opt.referenceName),
|
||||||
Auth: getAuth(opt.username, opt.password),
|
Auth: getAuth(opt.authType, opt.username, opt.password),
|
||||||
InsecureSkipTLS: opt.tlsSkipVerify,
|
InsecureSkipTLS: opt.tlsSkipVerify,
|
||||||
Tags: git.NoTags,
|
Tags: git.NoTags,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -24,7 +26,15 @@ func TestService_ClonePrivateRepository_GitHub(t *testing.T) {
|
||||||
dst := t.TempDir()
|
dst := t.TempDir()
|
||||||
|
|
||||||
repositoryUrl := privateGitRepoURL
|
repositoryUrl := privateGitRepoURL
|
||||||
err := service.CloneRepository(dst, repositoryUrl, "refs/heads/main", username, accessToken, false)
|
err := service.CloneRepository(
|
||||||
|
dst,
|
||||||
|
repositoryUrl,
|
||||||
|
"refs/heads/main",
|
||||||
|
username,
|
||||||
|
accessToken,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.FileExists(t, filepath.Join(dst, "README.md"))
|
assert.FileExists(t, filepath.Join(dst, "README.md"))
|
||||||
}
|
}
|
||||||
|
@ -37,7 +47,14 @@ func TestService_LatestCommitID_GitHub(t *testing.T) {
|
||||||
service := newService(context.TODO(), 0, 0)
|
service := newService(context.TODO(), 0, 0)
|
||||||
|
|
||||||
repositoryUrl := privateGitRepoURL
|
repositoryUrl := privateGitRepoURL
|
||||||
id, err := service.LatestCommitID(repositoryUrl, "refs/heads/main", username, accessToken, false)
|
id, err := service.LatestCommitID(
|
||||||
|
repositoryUrl,
|
||||||
|
"refs/heads/main",
|
||||||
|
username,
|
||||||
|
accessToken,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEmpty(t, id, "cannot guarantee commit id, but it should be not empty")
|
assert.NotEmpty(t, id, "cannot guarantee commit id, but it should be not empty")
|
||||||
}
|
}
|
||||||
|
@ -50,7 +67,7 @@ func TestService_ListRefs_GitHub(t *testing.T) {
|
||||||
service := newService(context.TODO(), 0, 0)
|
service := newService(context.TODO(), 0, 0)
|
||||||
|
|
||||||
repositoryUrl := privateGitRepoURL
|
repositoryUrl := privateGitRepoURL
|
||||||
refs, err := service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
refs, err := service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.GreaterOrEqual(t, len(refs), 1)
|
assert.GreaterOrEqual(t, len(refs), 1)
|
||||||
}
|
}
|
||||||
|
@ -63,8 +80,8 @@ func TestService_ListRefs_Github_Concurrently(t *testing.T) {
|
||||||
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
||||||
|
|
||||||
repositoryUrl := privateGitRepoURL
|
repositoryUrl := privateGitRepoURL
|
||||||
go service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
go service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||||
service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
|
@ -202,7 +219,17 @@ func TestService_ListFiles_GitHub(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
paths, err := service.ListFiles(tt.args.repositoryUrl, tt.args.referenceName, tt.args.username, tt.args.password, false, false, tt.extensions, false)
|
paths, err := service.ListFiles(
|
||||||
|
tt.args.repositoryUrl,
|
||||||
|
tt.args.referenceName,
|
||||||
|
tt.args.username,
|
||||||
|
tt.args.password,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
tt.extensions,
|
||||||
|
false,
|
||||||
|
)
|
||||||
if tt.expect.shouldFail {
|
if tt.expect.shouldFail {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
if tt.expect.err != nil {
|
if tt.expect.err != nil {
|
||||||
|
@ -226,8 +253,28 @@ func TestService_ListFiles_Github_Concurrently(t *testing.T) {
|
||||||
username := getRequiredValue(t, "GITHUB_USERNAME")
|
username := getRequiredValue(t, "GITHUB_USERNAME")
|
||||||
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
||||||
|
|
||||||
go service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
go service.ListFiles(
|
||||||
service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
repositoryUrl,
|
||||||
|
"refs/heads/main",
|
||||||
|
username,
|
||||||
|
accessToken,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
[]string{},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
service.ListFiles(
|
||||||
|
repositoryUrl,
|
||||||
|
"refs/heads/main",
|
||||||
|
username,
|
||||||
|
accessToken,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
[]string{},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
|
@ -240,8 +287,18 @@ func TestService_purgeCache_Github(t *testing.T) {
|
||||||
username := getRequiredValue(t, "GITHUB_USERNAME")
|
username := getRequiredValue(t, "GITHUB_USERNAME")
|
||||||
service := NewService(context.TODO())
|
service := NewService(context.TODO())
|
||||||
|
|
||||||
service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||||
service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
service.ListFiles(
|
||||||
|
repositoryUrl,
|
||||||
|
"refs/heads/main",
|
||||||
|
username,
|
||||||
|
accessToken,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
[]string{},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
assert.Equal(t, 1, service.repoRefCache.Len())
|
assert.Equal(t, 1, service.repoRefCache.Len())
|
||||||
assert.Equal(t, 1, service.repoFileCache.Len())
|
assert.Equal(t, 1, service.repoFileCache.Len())
|
||||||
|
@ -261,8 +318,18 @@ func TestService_purgeCacheByTTL_Github(t *testing.T) {
|
||||||
// 40*timeout is designed for giving enough time for ListRefs and ListFiles to cache the result
|
// 40*timeout is designed for giving enough time for ListRefs and ListFiles to cache the result
|
||||||
service := newService(context.TODO(), 2, 40*timeout)
|
service := newService(context.TODO(), 2, 40*timeout)
|
||||||
|
|
||||||
service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||||
service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
service.ListFiles(
|
||||||
|
repositoryUrl,
|
||||||
|
"refs/heads/main",
|
||||||
|
username,
|
||||||
|
accessToken,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
[]string{},
|
||||||
|
false,
|
||||||
|
)
|
||||||
assert.Equal(t, 1, service.repoRefCache.Len())
|
assert.Equal(t, 1, service.repoRefCache.Len())
|
||||||
assert.Equal(t, 1, service.repoFileCache.Len())
|
assert.Equal(t, 1, service.repoFileCache.Len())
|
||||||
|
|
||||||
|
@ -293,12 +360,12 @@ func TestService_HardRefresh_ListRefs_GitHub(t *testing.T) {
|
||||||
service := newService(context.TODO(), 2, 0)
|
service := newService(context.TODO(), 2, 0)
|
||||||
|
|
||||||
repositoryUrl := privateGitRepoURL
|
repositoryUrl := privateGitRepoURL
|
||||||
refs, err := service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
refs, err := service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.GreaterOrEqual(t, len(refs), 1)
|
assert.GreaterOrEqual(t, len(refs), 1)
|
||||||
assert.Equal(t, 1, service.repoRefCache.Len())
|
assert.Equal(t, 1, service.repoRefCache.Len())
|
||||||
|
|
||||||
_, err = service.ListRefs(repositoryUrl, username, "fake-token", false, false)
|
_, err = service.ListRefs(repositoryUrl, username, "fake-token", gittypes.GitCredentialAuthType_Basic, false, false)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, 1, service.repoRefCache.Len())
|
assert.Equal(t, 1, service.repoRefCache.Len())
|
||||||
}
|
}
|
||||||
|
@ -311,26 +378,46 @@ func TestService_HardRefresh_ListRefs_And_RemoveAllCaches_GitHub(t *testing.T) {
|
||||||
service := newService(context.TODO(), 2, 0)
|
service := newService(context.TODO(), 2, 0)
|
||||||
|
|
||||||
repositoryUrl := privateGitRepoURL
|
repositoryUrl := privateGitRepoURL
|
||||||
refs, err := service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
refs, err := service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.GreaterOrEqual(t, len(refs), 1)
|
assert.GreaterOrEqual(t, len(refs), 1)
|
||||||
assert.Equal(t, 1, service.repoRefCache.Len())
|
assert.Equal(t, 1, service.repoRefCache.Len())
|
||||||
|
|
||||||
files, err := service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
files, err := service.ListFiles(
|
||||||
|
repositoryUrl,
|
||||||
|
"refs/heads/main",
|
||||||
|
username,
|
||||||
|
accessToken,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
[]string{},
|
||||||
|
false,
|
||||||
|
)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.GreaterOrEqual(t, len(files), 1)
|
assert.GreaterOrEqual(t, len(files), 1)
|
||||||
assert.Equal(t, 1, service.repoFileCache.Len())
|
assert.Equal(t, 1, service.repoFileCache.Len())
|
||||||
|
|
||||||
files, err = service.ListFiles(repositoryUrl, "refs/heads/test", username, accessToken, false, false, []string{}, false)
|
files, err = service.ListFiles(
|
||||||
|
repositoryUrl,
|
||||||
|
"refs/heads/test",
|
||||||
|
username,
|
||||||
|
accessToken,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
[]string{},
|
||||||
|
false,
|
||||||
|
)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.GreaterOrEqual(t, len(files), 1)
|
assert.GreaterOrEqual(t, len(files), 1)
|
||||||
assert.Equal(t, 2, service.repoFileCache.Len())
|
assert.Equal(t, 2, service.repoFileCache.Len())
|
||||||
|
|
||||||
_, err = service.ListRefs(repositoryUrl, username, "fake-token", false, false)
|
_, err = service.ListRefs(repositoryUrl, username, "fake-token", gittypes.GitCredentialAuthType_Basic, false, false)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, 1, service.repoRefCache.Len())
|
assert.Equal(t, 1, service.repoRefCache.Len())
|
||||||
|
|
||||||
_, err = service.ListRefs(repositoryUrl, username, "fake-token", true, false)
|
_, err = service.ListRefs(repositoryUrl, username, "fake-token", gittypes.GitCredentialAuthType_Basic, true, false)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, 1, service.repoRefCache.Len())
|
assert.Equal(t, 1, service.repoRefCache.Len())
|
||||||
// The relevant file caches should be removed too
|
// The relevant file caches should be removed too
|
||||||
|
@ -344,12 +431,72 @@ func TestService_HardRefresh_ListFiles_GitHub(t *testing.T) {
|
||||||
accessToken := getRequiredValue(t, "GITHUB_PAT")
|
accessToken := getRequiredValue(t, "GITHUB_PAT")
|
||||||
username := getRequiredValue(t, "GITHUB_USERNAME")
|
username := getRequiredValue(t, "GITHUB_USERNAME")
|
||||||
repositoryUrl := privateGitRepoURL
|
repositoryUrl := privateGitRepoURL
|
||||||
files, err := service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
files, err := service.ListFiles(
|
||||||
|
repositoryUrl,
|
||||||
|
"refs/heads/main",
|
||||||
|
username,
|
||||||
|
accessToken,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
[]string{},
|
||||||
|
false,
|
||||||
|
)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.GreaterOrEqual(t, len(files), 1)
|
assert.GreaterOrEqual(t, len(files), 1)
|
||||||
assert.Equal(t, 1, service.repoFileCache.Len())
|
assert.Equal(t, 1, service.repoFileCache.Len())
|
||||||
|
|
||||||
_, err = service.ListFiles(repositoryUrl, "refs/heads/main", username, "fake-token", false, true, []string{}, false)
|
_, err = service.ListFiles(
|
||||||
|
repositoryUrl,
|
||||||
|
"refs/heads/main",
|
||||||
|
username,
|
||||||
|
"fake-token",
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
[]string{},
|
||||||
|
false,
|
||||||
|
)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, 0, service.repoFileCache.Len())
|
assert.Equal(t, 0, service.repoFileCache.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestService_CloneRepository_TokenAuth(t *testing.T) {
|
||||||
|
ensureIntegrationTest(t)
|
||||||
|
|
||||||
|
service := newService(context.TODO(), 2, 0)
|
||||||
|
var requests []*http.Request
|
||||||
|
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
requests = append(requests, r)
|
||||||
|
}))
|
||||||
|
accessToken := "test_access_token"
|
||||||
|
username := "test_username"
|
||||||
|
repositoryUrl := testServer.URL
|
||||||
|
|
||||||
|
// Since we aren't hitting a real git server we ignore the error
|
||||||
|
_ = service.CloneRepository(
|
||||||
|
"test_dir",
|
||||||
|
repositoryUrl,
|
||||||
|
"refs/heads/main",
|
||||||
|
username,
|
||||||
|
accessToken,
|
||||||
|
gittypes.GitCredentialAuthType_Token,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
|
testServer.Close()
|
||||||
|
|
||||||
|
if len(requests) != 1 {
|
||||||
|
t.Fatalf("expected 1 request sent but got %d", len(requests))
|
||||||
|
}
|
||||||
|
|
||||||
|
gotAuthHeader := requests[0].Header.Get("Authorization")
|
||||||
|
if gotAuthHeader == "" {
|
||||||
|
t.Fatal("no Authorization header in git request")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedAuthHeader := "Bearer test_access_token"
|
||||||
|
if gotAuthHeader != expectedAuthHeader {
|
||||||
|
t.Fatalf("expected Authorization header %q but got %q", expectedAuthHeader, gotAuthHeader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func Test_ClonePublicRepository_Shallow(t *testing.T) {
|
||||||
|
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
t.Logf("Cloning into %s", dir)
|
t.Logf("Cloning into %s", dir)
|
||||||
err := service.CloneRepository(dir, repositoryURL, referenceName, "", "", false)
|
err := service.CloneRepository(dir, repositoryURL, referenceName, "", "", gittypes.GitCredentialAuthType_Basic, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, 1, getCommitHistoryLength(t, err, dir), "cloned repo has incorrect depth")
|
assert.Equal(t, 1, getCommitHistoryLength(t, err, dir), "cloned repo has incorrect depth")
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ func Test_ClonePublicRepository_NoGitDirectory(t *testing.T) {
|
||||||
|
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
t.Logf("Cloning into %s", dir)
|
t.Logf("Cloning into %s", dir)
|
||||||
err := service.CloneRepository(dir, repositoryURL, referenceName, "", "", false)
|
err := service.CloneRepository(dir, repositoryURL, referenceName, "", "", gittypes.GitCredentialAuthType_Basic, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NoDirExists(t, filepath.Join(dir, ".git"))
|
assert.NoDirExists(t, filepath.Join(dir, ".git"))
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ func Test_latestCommitID(t *testing.T) {
|
||||||
repositoryURL := setup(t)
|
repositoryURL := setup(t)
|
||||||
referenceName := "refs/heads/main"
|
referenceName := "refs/heads/main"
|
||||||
|
|
||||||
id, err := service.LatestCommitID(repositoryURL, referenceName, "", "", false)
|
id, err := service.LatestCommitID(repositoryURL, referenceName, "", "", gittypes.GitCredentialAuthType_Basic, false)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "68dcaa7bd452494043c64252ab90db0f98ecf8d2", id)
|
assert.Equal(t, "68dcaa7bd452494043c64252ab90db0f98ecf8d2", id)
|
||||||
|
@ -95,7 +95,7 @@ func Test_ListRefs(t *testing.T) {
|
||||||
|
|
||||||
repositoryURL := setup(t)
|
repositoryURL := setup(t)
|
||||||
|
|
||||||
fs, err := service.ListRefs(repositoryURL, "", "", false, false)
|
fs, err := service.ListRefs(repositoryURL, "", "", gittypes.GitCredentialAuthType_Basic, false, false)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []string{"refs/heads/main"}, fs)
|
assert.Equal(t, []string{"refs/heads/main"}, fs)
|
||||||
|
@ -107,7 +107,17 @@ func Test_ListFiles(t *testing.T) {
|
||||||
repositoryURL := setup(t)
|
repositoryURL := setup(t)
|
||||||
referenceName := "refs/heads/main"
|
referenceName := "refs/heads/main"
|
||||||
|
|
||||||
fs, err := service.ListFiles(repositoryURL, referenceName, "", "", false, false, []string{".yml"}, false)
|
fs, err := service.ListFiles(
|
||||||
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
[]string{".yml"},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []string{"docker-compose.yml"}, fs)
|
assert.Equal(t, []string{"docker-compose.yml"}, fs)
|
||||||
|
@ -255,7 +265,7 @@ func Test_listFilesPrivateRepository(t *testing.T) {
|
||||||
name: "list tree with real repository and head ref but no credential",
|
name: "list tree with real repository and head ref but no credential",
|
||||||
args: fetchOption{
|
args: fetchOption{
|
||||||
baseOption: baseOption{
|
baseOption: baseOption{
|
||||||
repositoryUrl: privateGitRepoURL + "fake",
|
repositoryUrl: privateGitRepoURL,
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
lru "github.com/hashicorp/golang-lru"
|
lru "github.com/hashicorp/golang-lru"
|
||||||
|
gittypes "github.com/portainer/portainer/api/git/types"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
)
|
)
|
||||||
|
@ -22,6 +23,7 @@ type baseOption struct {
|
||||||
repositoryUrl string
|
repositoryUrl string
|
||||||
username string
|
username string
|
||||||
password string
|
password string
|
||||||
|
authType gittypes.GitCredentialAuthType
|
||||||
tlsSkipVerify bool
|
tlsSkipVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,13 +125,22 @@ func (service *Service) timerHasStopped() bool {
|
||||||
|
|
||||||
// CloneRepository clones a git repository using the specified URL in the specified
|
// CloneRepository clones a git repository using the specified URL in the specified
|
||||||
// destination folder.
|
// destination folder.
|
||||||
func (service *Service) CloneRepository(destination, repositoryURL, referenceName, username, password string, tlsSkipVerify bool) error {
|
func (service *Service) CloneRepository(
|
||||||
|
destination,
|
||||||
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) error {
|
||||||
options := cloneOption{
|
options := cloneOption{
|
||||||
fetchOption: fetchOption{
|
fetchOption: fetchOption{
|
||||||
baseOption: baseOption{
|
baseOption: baseOption{
|
||||||
repositoryUrl: repositoryURL,
|
repositoryUrl: repositoryURL,
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
|
authType: authType,
|
||||||
tlsSkipVerify: tlsSkipVerify,
|
tlsSkipVerify: tlsSkipVerify,
|
||||||
},
|
},
|
||||||
referenceName: referenceName,
|
referenceName: referenceName,
|
||||||
|
@ -155,12 +166,20 @@ func (service *Service) cloneRepository(destination string, options cloneOption)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LatestCommitID returns SHA1 of the latest commit of the specified reference
|
// LatestCommitID returns SHA1 of the latest commit of the specified reference
|
||||||
func (service *Service) LatestCommitID(repositoryURL, referenceName, username, password string, tlsSkipVerify bool) (string, error) {
|
func (service *Service) LatestCommitID(
|
||||||
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) (string, error) {
|
||||||
options := fetchOption{
|
options := fetchOption{
|
||||||
baseOption: baseOption{
|
baseOption: baseOption{
|
||||||
repositoryUrl: repositoryURL,
|
repositoryUrl: repositoryURL,
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
|
authType: authType,
|
||||||
tlsSkipVerify: tlsSkipVerify,
|
tlsSkipVerify: tlsSkipVerify,
|
||||||
},
|
},
|
||||||
referenceName: referenceName,
|
referenceName: referenceName,
|
||||||
|
@ -170,7 +189,14 @@ func (service *Service) LatestCommitID(repositoryURL, referenceName, username, p
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListRefs will list target repository's references without cloning the repository
|
// ListRefs will list target repository's references without cloning the repository
|
||||||
func (service *Service) ListRefs(repositoryURL, username, password string, hardRefresh bool, tlsSkipVerify bool) ([]string, error) {
|
func (service *Service) ListRefs(
|
||||||
|
repositoryURL,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
hardRefresh bool,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) ([]string, error) {
|
||||||
refCacheKey := generateCacheKey(repositoryURL, username, password, strconv.FormatBool(tlsSkipVerify))
|
refCacheKey := generateCacheKey(repositoryURL, username, password, strconv.FormatBool(tlsSkipVerify))
|
||||||
if service.cacheEnabled && hardRefresh {
|
if service.cacheEnabled && hardRefresh {
|
||||||
// Should remove the cache explicitly, so that the following normal list can show the correct result
|
// Should remove the cache explicitly, so that the following normal list can show the correct result
|
||||||
|
@ -196,6 +222,7 @@ func (service *Service) ListRefs(repositoryURL, username, password string, hardR
|
||||||
repositoryUrl: repositoryURL,
|
repositoryUrl: repositoryURL,
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
|
authType: authType,
|
||||||
tlsSkipVerify: tlsSkipVerify,
|
tlsSkipVerify: tlsSkipVerify,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,18 +242,62 @@ var singleflightGroup = &singleflight.Group{}
|
||||||
|
|
||||||
// ListFiles will list all the files of the target repository with specific extensions.
|
// ListFiles will list all the files of the target repository with specific extensions.
|
||||||
// If extension is not provided, it will list all the files under the target repository
|
// If extension is not provided, it will list all the files under the target repository
|
||||||
func (service *Service) ListFiles(repositoryURL, referenceName, username, password string, dirOnly, hardRefresh bool, includedExts []string, tlsSkipVerify bool) ([]string, error) {
|
func (service *Service) ListFiles(
|
||||||
repoKey := generateCacheKey(repositoryURL, referenceName, username, password, strconv.FormatBool(tlsSkipVerify), strconv.FormatBool(dirOnly))
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
dirOnly,
|
||||||
|
hardRefresh bool,
|
||||||
|
includedExts []string,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) ([]string, error) {
|
||||||
|
repoKey := generateCacheKey(
|
||||||
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
strconv.FormatBool(tlsSkipVerify),
|
||||||
|
strconv.Itoa(int(authType)),
|
||||||
|
strconv.FormatBool(dirOnly),
|
||||||
|
)
|
||||||
|
|
||||||
fs, err, _ := singleflightGroup.Do(repoKey, func() (any, error) {
|
fs, err, _ := singleflightGroup.Do(repoKey, func() (any, error) {
|
||||||
return service.listFiles(repositoryURL, referenceName, username, password, dirOnly, hardRefresh, tlsSkipVerify)
|
return service.listFiles(
|
||||||
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
authType,
|
||||||
|
dirOnly,
|
||||||
|
hardRefresh,
|
||||||
|
tlsSkipVerify,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return filterFiles(fs.([]string), includedExts), err
|
return filterFiles(fs.([]string), includedExts), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *Service) listFiles(repositoryURL, referenceName, username, password string, dirOnly, hardRefresh bool, tlsSkipVerify bool) ([]string, error) {
|
func (service *Service) listFiles(
|
||||||
repoKey := generateCacheKey(repositoryURL, referenceName, username, password, strconv.FormatBool(tlsSkipVerify), strconv.FormatBool(dirOnly))
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
dirOnly,
|
||||||
|
hardRefresh bool,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) ([]string, error) {
|
||||||
|
repoKey := generateCacheKey(
|
||||||
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
strconv.FormatBool(tlsSkipVerify),
|
||||||
|
strconv.Itoa(int(authType)),
|
||||||
|
strconv.FormatBool(dirOnly),
|
||||||
|
)
|
||||||
|
|
||||||
if service.cacheEnabled && hardRefresh {
|
if service.cacheEnabled && hardRefresh {
|
||||||
// Should remove the cache explicitly, so that the following normal list can show the correct result
|
// Should remove the cache explicitly, so that the following normal list can show the correct result
|
||||||
|
@ -247,6 +318,7 @@ func (service *Service) listFiles(repositoryURL, referenceName, username, passwo
|
||||||
repositoryUrl: repositoryURL,
|
repositoryUrl: repositoryURL,
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
|
authType: authType,
|
||||||
tlsSkipVerify: tlsSkipVerify,
|
tlsSkipVerify: tlsSkipVerify,
|
||||||
},
|
},
|
||||||
referenceName: referenceName,
|
referenceName: referenceName,
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
package gittypes
|
package gittypes
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrIncorrectRepositoryURL = errors.New("git repository could not be found, please ensure that the URL is correct")
|
ErrIncorrectRepositoryURL = errors.New("git repository could not be found, please ensure that the URL is correct")
|
||||||
ErrAuthenticationFailure = errors.New("authentication failed, please ensure that the git credentials are correct")
|
ErrAuthenticationFailure = errors.New("authentication failed, please ensure that the git credentials are correct")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type GitCredentialAuthType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
GitCredentialAuthType_Basic GitCredentialAuthType = iota
|
||||||
|
GitCredentialAuthType_Token
|
||||||
|
)
|
||||||
|
|
||||||
// RepoConfig represents a configuration for a repo
|
// RepoConfig represents a configuration for a repo
|
||||||
type RepoConfig struct {
|
type RepoConfig struct {
|
||||||
// The repo url
|
// The repo url
|
||||||
|
@ -24,10 +33,11 @@ type RepoConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type GitAuthentication struct {
|
type GitAuthentication struct {
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
|
AuthorizationType GitCredentialAuthType
|
||||||
// Git credentials identifier when the value is not 0
|
// Git credentials identifier when the value is not 0
|
||||||
// When the value is 0, Username and Password are set without using saved credential
|
// When the value is 0, Username, Password, and Authtype are set without using saved credential
|
||||||
// This is introduced since 2.15.0
|
// This is introduced since 2.15.0
|
||||||
GitCredentialID int `example:"0"`
|
GitCredentialID int `example:"0"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,14 @@ func UpdateGitObject(gitService portainer.GitService, objId string, gitConfig *g
|
||||||
return false, "", errors.WithMessagef(err, "failed to get credentials for %v", objId)
|
return false, "", errors.WithMessagef(err, "failed to get credentials for %v", objId)
|
||||||
}
|
}
|
||||||
|
|
||||||
newHash, err := gitService.LatestCommitID(gitConfig.URL, gitConfig.ReferenceName, username, password, gitConfig.TLSSkipVerify)
|
newHash, err := gitService.LatestCommitID(
|
||||||
|
gitConfig.URL,
|
||||||
|
gitConfig.ReferenceName,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
gitConfig.TLSSkipVerify,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", errors.WithMessagef(err, "failed to fetch latest commit id of %v", objId)
|
return false, "", errors.WithMessagef(err, "failed to fetch latest commit id of %v", objId)
|
||||||
}
|
}
|
||||||
|
@ -62,6 +69,7 @@ func UpdateGitObject(gitService portainer.GitService, objId string, gitConfig *g
|
||||||
cloneParams.auth = &gitAuth{
|
cloneParams.auth = &gitAuth{
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
|
authType: gitConfig.Authentication.AuthorizationType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,14 +97,31 @@ type cloneRepositoryParameters struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type gitAuth struct {
|
type gitAuth struct {
|
||||||
|
authType gittypes.GitCredentialAuthType
|
||||||
username string
|
username string
|
||||||
password string
|
password string
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneGitRepository(gitService portainer.GitService, cloneParams *cloneRepositoryParameters) error {
|
func cloneGitRepository(gitService portainer.GitService, cloneParams *cloneRepositoryParameters) error {
|
||||||
if cloneParams.auth != nil {
|
if cloneParams.auth != nil {
|
||||||
return gitService.CloneRepository(cloneParams.toDir, cloneParams.url, cloneParams.ref, cloneParams.auth.username, cloneParams.auth.password, cloneParams.tlsSkipVerify)
|
return gitService.CloneRepository(
|
||||||
|
cloneParams.toDir,
|
||||||
|
cloneParams.url,
|
||||||
|
cloneParams.ref,
|
||||||
|
cloneParams.auth.username,
|
||||||
|
cloneParams.auth.password,
|
||||||
|
cloneParams.auth.authType,
|
||||||
|
cloneParams.tlsSkipVerify,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return gitService.CloneRepository(cloneParams.toDir, cloneParams.url, cloneParams.ref, "", "", cloneParams.tlsSkipVerify)
|
return gitService.CloneRepository(
|
||||||
|
cloneParams.toDir,
|
||||||
|
cloneParams.url,
|
||||||
|
cloneParams.ref,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
cloneParams.tlsSkipVerify,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,13 +33,28 @@ type TestGitService struct {
|
||||||
targetFilePath string
|
targetFilePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *TestGitService) CloneRepository(destination string, repositoryURL, referenceName string, username, password string, tlsSkipVerify bool) error {
|
func (g *TestGitService) CloneRepository(
|
||||||
|
destination string,
|
||||||
|
repositoryURL,
|
||||||
|
referenceName string,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) error {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
return createTestFile(g.targetFilePath)
|
return createTestFile(g.targetFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *TestGitService) LatestCommitID(repositoryURL, referenceName, username, password string, tlsSkipVerify bool) (string, error) {
|
func (g *TestGitService) LatestCommitID(
|
||||||
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) (string, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,11 +71,26 @@ type InvalidTestGitService struct {
|
||||||
targetFilePath string
|
targetFilePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *InvalidTestGitService) CloneRepository(dest, repoUrl, refName, username, password string, tlsSkipVerify bool) error {
|
func (g *InvalidTestGitService) CloneRepository(
|
||||||
|
dest,
|
||||||
|
repoUrl,
|
||||||
|
refName,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) error {
|
||||||
return errors.New("simulate network error")
|
return errors.New("simulate network error")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *InvalidTestGitService) LatestCommitID(repositoryURL, referenceName, username, password string, tlsSkipVerify bool) (string, error) {
|
func (g *InvalidTestGitService) LatestCommitID(
|
||||||
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) (string, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,14 +37,16 @@ type customTemplateUpdatePayload struct {
|
||||||
RepositoryURL string `example:"https://github.com/openfaas/faas" validate:"required"`
|
RepositoryURL string `example:"https://github.com/openfaas/faas" validate:"required"`
|
||||||
// Reference name of a Git repository hosting the Stack file
|
// Reference name of a Git repository hosting the Stack file
|
||||||
RepositoryReferenceName string `example:"refs/heads/master"`
|
RepositoryReferenceName string `example:"refs/heads/master"`
|
||||||
// Use basic authentication to clone the Git repository
|
// Use authentication to clone the Git repository
|
||||||
RepositoryAuthentication bool `example:"true"`
|
RepositoryAuthentication bool `example:"true"`
|
||||||
// Username used in basic authentication. Required when RepositoryAuthentication is true
|
// Username used in basic authentication. Required when RepositoryAuthentication is true
|
||||||
// and RepositoryGitCredentialID is 0
|
// and RepositoryGitCredentialID is 0. Ignored if RepositoryAuthType is token
|
||||||
RepositoryUsername string `example:"myGitUsername"`
|
RepositoryUsername string `example:"myGitUsername"`
|
||||||
// Password used in basic authentication. Required when RepositoryAuthentication is true
|
// Password used in basic authentication or token used in token authentication.
|
||||||
// and RepositoryGitCredentialID is 0
|
// Required when RepositoryAuthentication is true and RepositoryGitCredentialID is 0
|
||||||
RepositoryPassword string `example:"myGitPassword"`
|
RepositoryPassword string `example:"myGitPassword"`
|
||||||
|
// RepositoryAuthorizationType is the authorization type to use
|
||||||
|
RepositoryAuthorizationType gittypes.GitCredentialAuthType `example:"0"`
|
||||||
// GitCredentialID used to identify the bound git credential. Required when RepositoryAuthentication
|
// GitCredentialID used to identify the bound git credential. Required when RepositoryAuthentication
|
||||||
// is true and RepositoryUsername/RepositoryPassword are not provided
|
// is true and RepositoryUsername/RepositoryPassword are not provided
|
||||||
RepositoryGitCredentialID int `example:"0"`
|
RepositoryGitCredentialID int `example:"0"`
|
||||||
|
@ -182,12 +184,15 @@ func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Requ
|
||||||
|
|
||||||
repositoryUsername := ""
|
repositoryUsername := ""
|
||||||
repositoryPassword := ""
|
repositoryPassword := ""
|
||||||
|
repositoryAuthType := gittypes.GitCredentialAuthType_Basic
|
||||||
if payload.RepositoryAuthentication {
|
if payload.RepositoryAuthentication {
|
||||||
repositoryUsername = payload.RepositoryUsername
|
repositoryUsername = payload.RepositoryUsername
|
||||||
repositoryPassword = payload.RepositoryPassword
|
repositoryPassword = payload.RepositoryPassword
|
||||||
|
repositoryAuthType = payload.RepositoryAuthorizationType
|
||||||
gitConfig.Authentication = &gittypes.GitAuthentication{
|
gitConfig.Authentication = &gittypes.GitAuthentication{
|
||||||
Username: payload.RepositoryUsername,
|
Username: payload.RepositoryUsername,
|
||||||
Password: payload.RepositoryPassword,
|
Password: payload.RepositoryPassword,
|
||||||
|
AuthorizationType: payload.RepositoryAuthorizationType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,6 +202,7 @@ func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Requ
|
||||||
ReferenceName: gitConfig.ReferenceName,
|
ReferenceName: gitConfig.ReferenceName,
|
||||||
Username: repositoryUsername,
|
Username: repositoryUsername,
|
||||||
Password: repositoryPassword,
|
Password: repositoryPassword,
|
||||||
|
AuthType: repositoryAuthType,
|
||||||
TLSSkipVerify: gitConfig.TLSSkipVerify,
|
TLSSkipVerify: gitConfig.TLSSkipVerify,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -205,7 +211,14 @@ func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Requ
|
||||||
|
|
||||||
defer cleanBackup()
|
defer cleanBackup()
|
||||||
|
|
||||||
commitHash, err := handler.GitService.LatestCommitID(gitConfig.URL, gitConfig.ReferenceName, repositoryUsername, repositoryPassword, gitConfig.TLSSkipVerify)
|
commitHash, err := handler.GitService.LatestCommitID(
|
||||||
|
gitConfig.URL,
|
||||||
|
gitConfig.ReferenceName,
|
||||||
|
repositoryUsername,
|
||||||
|
repositoryPassword,
|
||||||
|
repositoryAuthType,
|
||||||
|
gitConfig.TLSSkipVerify,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError("Unable get latest commit id", fmt.Errorf("failed to fetch latest commit id of the template %v: %w", customTemplate.ID, err))
|
return httperror.InternalServerError("Unable get latest commit id", fmt.Errorf("failed to fetch latest commit id of the template %v: %w", customTemplate.ID, err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,8 @@ type edgeStackFromGitRepositoryPayload struct {
|
||||||
RepositoryUsername string `example:"myGitUsername"`
|
RepositoryUsername string `example:"myGitUsername"`
|
||||||
// Password used in basic authentication. Required when RepositoryAuthentication is true.
|
// Password used in basic authentication. Required when RepositoryAuthentication is true.
|
||||||
RepositoryPassword string `example:"myGitPassword"`
|
RepositoryPassword string `example:"myGitPassword"`
|
||||||
|
// RepositoryAuthorizationType is the authorization type to use
|
||||||
|
RepositoryAuthorizationType gittypes.GitCredentialAuthType `example:"0"`
|
||||||
// Path to the Stack file inside the Git repository
|
// Path to the Stack file inside the Git repository
|
||||||
FilePathInRepository string `example:"docker-compose.yml" default:"docker-compose.yml"`
|
FilePathInRepository string `example:"docker-compose.yml" default:"docker-compose.yml"`
|
||||||
// List of identifiers of EdgeGroups
|
// List of identifiers of EdgeGroups
|
||||||
|
@ -125,8 +127,9 @@ func (handler *Handler) createEdgeStackFromGitRepository(r *http.Request, tx dat
|
||||||
|
|
||||||
if payload.RepositoryAuthentication {
|
if payload.RepositoryAuthentication {
|
||||||
repoConfig.Authentication = &gittypes.GitAuthentication{
|
repoConfig.Authentication = &gittypes.GitAuthentication{
|
||||||
Username: payload.RepositoryUsername,
|
Username: payload.RepositoryUsername,
|
||||||
Password: payload.RepositoryPassword,
|
Password: payload.RepositoryPassword,
|
||||||
|
AuthorizationType: payload.RepositoryAuthorizationType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,12 +148,22 @@ func (handler *Handler) storeManifestFromGitRepository(tx dataservices.DataStore
|
||||||
projectPath = handler.FileService.GetEdgeStackProjectPath(stackFolder)
|
projectPath = handler.FileService.GetEdgeStackProjectPath(stackFolder)
|
||||||
repositoryUsername := ""
|
repositoryUsername := ""
|
||||||
repositoryPassword := ""
|
repositoryPassword := ""
|
||||||
|
repositoryAuthType := gittypes.GitCredentialAuthType_Basic
|
||||||
if repositoryConfig.Authentication != nil && repositoryConfig.Authentication.Password != "" {
|
if repositoryConfig.Authentication != nil && repositoryConfig.Authentication.Password != "" {
|
||||||
repositoryUsername = repositoryConfig.Authentication.Username
|
repositoryUsername = repositoryConfig.Authentication.Username
|
||||||
repositoryPassword = repositoryConfig.Authentication.Password
|
repositoryPassword = repositoryConfig.Authentication.Password
|
||||||
|
repositoryAuthType = repositoryConfig.Authentication.AuthorizationType
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := handler.GitService.CloneRepository(projectPath, repositoryConfig.URL, repositoryConfig.ReferenceName, repositoryUsername, repositoryPassword, repositoryConfig.TLSSkipVerify); err != nil {
|
if err := handler.GitService.CloneRepository(
|
||||||
|
projectPath,
|
||||||
|
repositoryConfig.URL,
|
||||||
|
repositoryConfig.ReferenceName,
|
||||||
|
repositoryUsername,
|
||||||
|
repositoryPassword,
|
||||||
|
repositoryAuthType,
|
||||||
|
repositoryConfig.TLSSkipVerify,
|
||||||
|
); err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,11 @@ type fileResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type repositoryFilePreviewPayload struct {
|
type repositoryFilePreviewPayload struct {
|
||||||
Repository string `json:"repository" example:"https://github.com/openfaas/faas" validate:"required"`
|
Repository string `json:"repository" example:"https://github.com/openfaas/faas" validate:"required"`
|
||||||
Reference string `json:"reference" example:"refs/heads/master"`
|
Reference string `json:"reference" example:"refs/heads/master"`
|
||||||
Username string `json:"username" example:"myGitUsername"`
|
Username string `json:"username" example:"myGitUsername"`
|
||||||
Password string `json:"password" example:"myGitPassword"`
|
Password string `json:"password" example:"myGitPassword"`
|
||||||
|
AuthorizationType gittypes.GitCredentialAuthType `json:"authorizationType"`
|
||||||
// Path to file whose content will be read
|
// Path to file whose content will be read
|
||||||
TargetFile string `json:"targetFile" example:"docker-compose.yml"`
|
TargetFile string `json:"targetFile" example:"docker-compose.yml"`
|
||||||
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
||||||
|
@ -68,7 +69,15 @@ func (handler *Handler) gitOperationRepoFilePreview(w http.ResponseWriter, r *ht
|
||||||
return httperror.InternalServerError("Unable to create temporary folder", err)
|
return httperror.InternalServerError("Unable to create temporary folder", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.gitService.CloneRepository(projectPath, payload.Repository, payload.Reference, payload.Username, payload.Password, payload.TLSSkipVerify)
|
err = handler.gitService.CloneRepository(
|
||||||
|
projectPath,
|
||||||
|
payload.Repository,
|
||||||
|
payload.Reference,
|
||||||
|
payload.Username,
|
||||||
|
payload.Password,
|
||||||
|
payload.AuthorizationType,
|
||||||
|
payload.TLSSkipVerify,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gittypes.ErrAuthenticationFailure) {
|
if errors.Is(err, gittypes.ErrAuthenticationFailure) {
|
||||||
return httperror.BadRequest("Invalid git credential", err)
|
return httperror.BadRequest("Invalid git credential", err)
|
||||||
|
|
|
@ -19,14 +19,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type stackGitUpdatePayload struct {
|
type stackGitUpdatePayload struct {
|
||||||
AutoUpdate *portainer.AutoUpdateSettings
|
AutoUpdate *portainer.AutoUpdateSettings
|
||||||
Env []portainer.Pair
|
Env []portainer.Pair
|
||||||
Prune bool
|
Prune bool
|
||||||
RepositoryReferenceName string
|
RepositoryReferenceName string
|
||||||
RepositoryAuthentication bool
|
RepositoryAuthentication bool
|
||||||
RepositoryUsername string
|
RepositoryUsername string
|
||||||
RepositoryPassword string
|
RepositoryPassword string
|
||||||
TLSSkipVerify bool
|
RepositoryAuthorizationType gittypes.GitCredentialAuthType
|
||||||
|
TLSSkipVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *stackGitUpdatePayload) Validate(r *http.Request) error {
|
func (payload *stackGitUpdatePayload) Validate(r *http.Request) error {
|
||||||
|
@ -151,11 +152,19 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
|
||||||
}
|
}
|
||||||
|
|
||||||
stack.GitConfig.Authentication = &gittypes.GitAuthentication{
|
stack.GitConfig.Authentication = &gittypes.GitAuthentication{
|
||||||
Username: payload.RepositoryUsername,
|
Username: payload.RepositoryUsername,
|
||||||
Password: password,
|
Password: password,
|
||||||
|
AuthorizationType: payload.RepositoryAuthorizationType,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, stack.GitConfig.Authentication.Username, stack.GitConfig.Authentication.Password, stack.GitConfig.TLSSkipVerify); err != nil {
|
if _, err := handler.GitService.LatestCommitID(
|
||||||
|
stack.GitConfig.URL,
|
||||||
|
stack.GitConfig.ReferenceName,
|
||||||
|
stack.GitConfig.Authentication.Username,
|
||||||
|
stack.GitConfig.Authentication.Password,
|
||||||
|
stack.GitConfig.Authentication.AuthorizationType,
|
||||||
|
stack.GitConfig.TLSSkipVerify,
|
||||||
|
); err != nil {
|
||||||
return httperror.InternalServerError("Unable to fetch git repository", err)
|
return httperror.InternalServerError("Unable to fetch git repository", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/git"
|
"github.com/portainer/portainer/api/git"
|
||||||
|
gittypes "github.com/portainer/portainer/api/git/types"
|
||||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
k "github.com/portainer/portainer/api/kubernetes"
|
k "github.com/portainer/portainer/api/kubernetes"
|
||||||
|
@ -19,12 +20,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type stackGitRedployPayload struct {
|
type stackGitRedployPayload struct {
|
||||||
RepositoryReferenceName string
|
RepositoryReferenceName string
|
||||||
RepositoryAuthentication bool
|
RepositoryAuthentication bool
|
||||||
RepositoryUsername string
|
RepositoryUsername string
|
||||||
RepositoryPassword string
|
RepositoryPassword string
|
||||||
Env []portainer.Pair
|
RepositoryAuthorizationType gittypes.GitCredentialAuthType
|
||||||
Prune bool
|
Env []portainer.Pair
|
||||||
|
Prune bool
|
||||||
// Force a pulling to current image with the original tag though the image is already the latest
|
// Force a pulling to current image with the original tag though the image is already the latest
|
||||||
PullImage bool `example:"false"`
|
PullImage bool `example:"false"`
|
||||||
|
|
||||||
|
@ -135,13 +137,16 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
repositoryUsername := ""
|
repositoryUsername := ""
|
||||||
repositoryPassword := ""
|
repositoryPassword := ""
|
||||||
|
repositoryAuthType := gittypes.GitCredentialAuthType_Basic
|
||||||
if payload.RepositoryAuthentication {
|
if payload.RepositoryAuthentication {
|
||||||
repositoryPassword = payload.RepositoryPassword
|
repositoryPassword = payload.RepositoryPassword
|
||||||
|
repositoryAuthType = payload.RepositoryAuthorizationType
|
||||||
|
|
||||||
// When the existing stack is using the custom username/password and the password is not updated,
|
// When the existing stack is using the custom username/password and the password is not updated,
|
||||||
// the stack should keep using the saved username/password
|
// the stack should keep using the saved username/password
|
||||||
if repositoryPassword == "" && stack.GitConfig != nil && stack.GitConfig.Authentication != nil {
|
if repositoryPassword == "" && stack.GitConfig != nil && stack.GitConfig.Authentication != nil {
|
||||||
repositoryPassword = stack.GitConfig.Authentication.Password
|
repositoryPassword = stack.GitConfig.Authentication.Password
|
||||||
|
repositoryAuthType = stack.GitConfig.Authentication.AuthorizationType
|
||||||
}
|
}
|
||||||
repositoryUsername = payload.RepositoryUsername
|
repositoryUsername = payload.RepositoryUsername
|
||||||
}
|
}
|
||||||
|
@ -152,6 +157,7 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
||||||
ReferenceName: stack.GitConfig.ReferenceName,
|
ReferenceName: stack.GitConfig.ReferenceName,
|
||||||
Username: repositoryUsername,
|
Username: repositoryUsername,
|
||||||
Password: repositoryPassword,
|
Password: repositoryPassword,
|
||||||
|
AuthType: repositoryAuthType,
|
||||||
TLSSkipVerify: stack.GitConfig.TLSSkipVerify,
|
TLSSkipVerify: stack.GitConfig.TLSSkipVerify,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +172,7 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
newHash, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, repositoryUsername, repositoryPassword, stack.GitConfig.TLSSkipVerify)
|
newHash, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, repositoryUsername, repositoryPassword, repositoryAuthType, stack.GitConfig.TLSSkipVerify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError("Unable get latest commit id", errors.WithMessagef(err, "failed to fetch latest commit id of the stack %v", stack.ID))
|
return httperror.InternalServerError("Unable get latest commit id", errors.WithMessagef(err, "failed to fetch latest commit id of the stack %v", stack.ID))
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,13 @@ type kubernetesFileStackUpdatePayload struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type kubernetesGitStackUpdatePayload struct {
|
type kubernetesGitStackUpdatePayload struct {
|
||||||
RepositoryReferenceName string
|
RepositoryReferenceName string
|
||||||
RepositoryAuthentication bool
|
RepositoryAuthentication bool
|
||||||
RepositoryUsername string
|
RepositoryUsername string
|
||||||
RepositoryPassword string
|
RepositoryPassword string
|
||||||
AutoUpdate *portainer.AutoUpdateSettings
|
RepositoryAuthorizationType gittypes.GitCredentialAuthType
|
||||||
TLSSkipVerify bool
|
AutoUpdate *portainer.AutoUpdateSettings
|
||||||
|
TLSSkipVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *kubernetesFileStackUpdatePayload) Validate(r *http.Request) error {
|
func (payload *kubernetesFileStackUpdatePayload) Validate(r *http.Request) error {
|
||||||
|
@ -76,11 +77,19 @@ func (handler *Handler) updateKubernetesStack(r *http.Request, stack *portainer.
|
||||||
}
|
}
|
||||||
|
|
||||||
stack.GitConfig.Authentication = &gittypes.GitAuthentication{
|
stack.GitConfig.Authentication = &gittypes.GitAuthentication{
|
||||||
Username: payload.RepositoryUsername,
|
Username: payload.RepositoryUsername,
|
||||||
Password: password,
|
Password: password,
|
||||||
|
AuthorizationType: payload.RepositoryAuthorizationType,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, stack.GitConfig.Authentication.Username, stack.GitConfig.Authentication.Password, stack.GitConfig.TLSSkipVerify); err != nil {
|
if _, err := handler.GitService.LatestCommitID(
|
||||||
|
stack.GitConfig.URL,
|
||||||
|
stack.GitConfig.ReferenceName,
|
||||||
|
stack.GitConfig.Authentication.Username,
|
||||||
|
stack.GitConfig.Authentication.Password,
|
||||||
|
stack.GitConfig.Authentication.AuthorizationType,
|
||||||
|
stack.GitConfig.TLSSkipVerify,
|
||||||
|
); err != nil {
|
||||||
return httperror.InternalServerError("Unable to fetch git repository", err)
|
return httperror.InternalServerError("Unable to fetch git repository", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
gittypes "github.com/portainer/portainer/api/git/types"
|
||||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||||
|
@ -71,7 +72,15 @@ func (handler *Handler) templateFile(w http.ResponseWriter, r *http.Request) *ht
|
||||||
|
|
||||||
defer handler.cleanUp(projectPath)
|
defer handler.cleanUp(projectPath)
|
||||||
|
|
||||||
if err := handler.GitService.CloneRepository(projectPath, template.Repository.URL, "", "", "", false); err != nil {
|
if err := handler.GitService.CloneRepository(
|
||||||
|
projectPath,
|
||||||
|
template.Repository.URL,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
); err != nil {
|
||||||
return httperror.InternalServerError("Unable to clone git repository", err)
|
return httperror.InternalServerError("Unable to clone git repository", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/dataservices"
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
|
gittypes "github.com/portainer/portainer/api/git/types"
|
||||||
"github.com/portainer/portainer/api/http/proxy/factory/utils"
|
"github.com/portainer/portainer/api/http/proxy/factory/utils"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
"github.com/portainer/portainer/api/internal/authorization"
|
"github.com/portainer/portainer/api/internal/authorization"
|
||||||
|
@ -418,7 +419,14 @@ func (transport *Transport) updateDefaultGitBranch(request *http.Request) error
|
||||||
}
|
}
|
||||||
|
|
||||||
repositoryURL := remote[:len(remote)-4]
|
repositoryURL := remote[:len(remote)-4]
|
||||||
latestCommitID, err := transport.gitService.LatestCommitID(repositoryURL, "", "", "", false)
|
latestCommitID, err := transport.gitService.LatestCommitID(
|
||||||
|
repositoryURL,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
gittypes.GitCredentialAuthType_Basic,
|
||||||
|
false,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package testhelpers
|
package testhelpers
|
||||||
|
|
||||||
import portainer "github.com/portainer/portainer/api"
|
import (
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
gittypes "github.com/portainer/portainer/api/git/types"
|
||||||
|
)
|
||||||
|
|
||||||
type gitService struct {
|
type gitService struct {
|
||||||
cloneErr error
|
cloneErr error
|
||||||
|
@ -15,18 +18,50 @@ func NewGitService(cloneErr error, id string) portainer.GitService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gitService) CloneRepository(destination, repositoryURL, referenceName, username, password string, tlsSkipVerify bool) error {
|
func (g *gitService) CloneRepository(
|
||||||
|
destination,
|
||||||
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) error {
|
||||||
return g.cloneErr
|
return g.cloneErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gitService) LatestCommitID(repositoryURL, referenceName, username, password string, tlsSkipVerify bool) (string, error) {
|
func (g *gitService) LatestCommitID(
|
||||||
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) (string, error) {
|
||||||
return g.id, nil
|
return g.id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gitService) ListRefs(repositoryURL, username, password string, hardRefresh bool, tlsSkipVerify bool) ([]string, error) {
|
func (g *gitService) ListRefs(
|
||||||
|
repositoryURL,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
hardRefresh bool,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) ([]string, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gitService) ListFiles(repositoryURL, referenceName, username, password string, dirOnly, hardRefresh bool, includedExts []string, tlsSkipVerify bool) ([]string, error) {
|
func (g *gitService) ListFiles(
|
||||||
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
dirOnly,
|
||||||
|
hardRefresh bool,
|
||||||
|
includedExts []string,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) ([]string, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1538,10 +1538,42 @@ type (
|
||||||
|
|
||||||
// GitService represents a service for managing Git
|
// GitService represents a service for managing Git
|
||||||
GitService interface {
|
GitService interface {
|
||||||
CloneRepository(destination string, repositoryURL, referenceName, username, password string, tlsSkipVerify bool) error
|
CloneRepository(
|
||||||
LatestCommitID(repositoryURL, referenceName, username, password string, tlsSkipVerify bool) (string, error)
|
destination string,
|
||||||
ListRefs(repositoryURL, username, password string, hardRefresh bool, tlsSkipVerify bool) ([]string, error)
|
repositoryURL,
|
||||||
ListFiles(repositoryURL, referenceName, username, password string, dirOnly, hardRefresh bool, includeExts []string, tlsSkipVerify bool) ([]string, error)
|
referenceName,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) error
|
||||||
|
LatestCommitID(
|
||||||
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) (string, error)
|
||||||
|
ListRefs(
|
||||||
|
repositoryURL,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
hardRefresh bool,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) ([]string, error)
|
||||||
|
ListFiles(
|
||||||
|
repositoryURL,
|
||||||
|
referenceName,
|
||||||
|
username,
|
||||||
|
password string,
|
||||||
|
authType gittypes.GitCredentialAuthType,
|
||||||
|
dirOnly,
|
||||||
|
hardRefresh bool,
|
||||||
|
includeExts []string,
|
||||||
|
tlsSkipVerify bool,
|
||||||
|
) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenAMTService represents a service for managing OpenAMT
|
// OpenAMTService represents a service for managing OpenAMT
|
||||||
|
|
|
@ -19,13 +19,23 @@ var (
|
||||||
func DownloadGitRepository(config gittypes.RepoConfig, gitService portainer.GitService, getProjectPath func() string) (string, error) {
|
func DownloadGitRepository(config gittypes.RepoConfig, gitService portainer.GitService, getProjectPath func() string) (string, error) {
|
||||||
username := ""
|
username := ""
|
||||||
password := ""
|
password := ""
|
||||||
|
authType := gittypes.GitCredentialAuthType_Basic
|
||||||
if config.Authentication != nil {
|
if config.Authentication != nil {
|
||||||
username = config.Authentication.Username
|
username = config.Authentication.Username
|
||||||
password = config.Authentication.Password
|
password = config.Authentication.Password
|
||||||
|
authType = config.Authentication.AuthorizationType
|
||||||
}
|
}
|
||||||
|
|
||||||
projectPath := getProjectPath()
|
projectPath := getProjectPath()
|
||||||
err := gitService.CloneRepository(projectPath, config.URL, config.ReferenceName, username, password, config.TLSSkipVerify)
|
err := gitService.CloneRepository(
|
||||||
|
projectPath,
|
||||||
|
config.URL,
|
||||||
|
config.ReferenceName,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
authType,
|
||||||
|
config.TLSSkipVerify,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gittypes.ErrAuthenticationFailure) {
|
if errors.Is(err, gittypes.ErrAuthenticationFailure) {
|
||||||
newErr := git.ErrInvalidGitCredential
|
newErr := git.ErrInvalidGitCredential
|
||||||
|
@ -36,7 +46,14 @@ func DownloadGitRepository(config gittypes.RepoConfig, gitService portainer.GitS
|
||||||
return "", newErr
|
return "", newErr
|
||||||
}
|
}
|
||||||
|
|
||||||
commitID, err := gitService.LatestCommitID(config.URL, config.ReferenceName, username, password, config.TLSSkipVerify)
|
commitID, err := gitService.LatestCommitID(
|
||||||
|
config.URL,
|
||||||
|
config.ReferenceName,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
authType,
|
||||||
|
config.TLSSkipVerify,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
newErr := fmt.Errorf("unable to fetch git repository id: %w", err)
|
newErr := fmt.Errorf("unable to fetch git repository id: %w", err)
|
||||||
return "", newErr
|
return "", newErr
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue