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) {
|
||||
dst := t.TempDir()
|
||||
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.FileExists(t, filepath.Join(dst, "README.md"))
|
||||
})
|
||||
|
@ -73,7 +81,15 @@ func TestService_ClonePrivateRepository_Azure(t *testing.T) {
|
|||
|
||||
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.FileExists(t, filepath.Join(dst, "README.md"))
|
||||
}
|
||||
|
@ -84,7 +100,14 @@ func TestService_LatestCommitID_Azure(t *testing.T) {
|
|||
pat := getRequiredValue(t, "AZURE_DEVOPS_PAT")
|
||||
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.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")
|
||||
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.GreaterOrEqual(t, len(refs), 1)
|
||||
}
|
||||
|
@ -108,8 +138,8 @@ func TestService_ListRefs_Azure_Concurrently(t *testing.T) {
|
|||
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
||||
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
||||
|
||||
go service.ListRefs(privateAzureRepoURL, username, accessToken, false, false)
|
||||
service.ListRefs(privateAzureRepoURL, username, accessToken, false, false)
|
||||
go service.ListRefs(privateAzureRepoURL, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
service.ListRefs(privateAzureRepoURL, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
@ -247,7 +277,17 @@ func TestService_ListFiles_Azure(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
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 {
|
||||
assert.Error(t, err)
|
||||
if tt.expect.err != nil {
|
||||
|
@ -270,8 +310,28 @@ func TestService_ListFiles_Azure_Concurrently(t *testing.T) {
|
|||
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
||||
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
||||
|
||||
go service.ListFiles(privateAzureRepoURL, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
||||
service.ListFiles(privateAzureRepoURL, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
||||
go service.ListFiles(
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ type CloneOptions struct {
|
|||
ReferenceName string
|
||||
Username string
|
||||
Password string
|
||||
AuthType gittypes.GitCredentialAuthType
|
||||
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
||||
TLSSkipVerify bool `example:"false"`
|
||||
}
|
||||
|
@ -42,7 +43,15 @@ func CloneWithBackup(gitService portainer.GitService, fileService portainer.File
|
|||
|
||||
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
|
||||
if err := filesystem.MoveDirectory(backupProjectPath, options.ProjectPath, false); err != nil {
|
||||
log.Warn().Err(err).Msg("failed restoring backup folder")
|
||||
|
|
|
@ -7,12 +7,14 @@ import (
|
|||
"strings"
|
||||
|
||||
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/config"
|
||||
"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/object"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -33,7 +35,7 @@ func (c *gitClient) download(ctx context.Context, dst string, opt cloneOption) e
|
|||
URL: opt.repositoryUrl,
|
||||
Depth: opt.depth,
|
||||
InsecureSkipTLS: opt.tlsSkipVerify,
|
||||
Auth: getAuth(opt.username, opt.password),
|
||||
Auth: getAuth(opt.authType, opt.username, opt.password),
|
||||
Tags: git.NoTags,
|
||||
}
|
||||
|
||||
|
@ -51,7 +53,10 @@ func (c *gitClient) download(ctx context.Context, dst string, opt cloneOption) e
|
|||
}
|
||||
|
||||
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
|
||||
|
@ -64,7 +69,7 @@ func (c *gitClient) latestCommitID(ctx context.Context, opt fetchOption) (string
|
|||
})
|
||||
|
||||
listOptions := &git.ListOptions{
|
||||
Auth: getAuth(opt.username, opt.password),
|
||||
Auth: getAuth(opt.authType, opt.username, opt.password),
|
||||
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)
|
||||
}
|
||||
|
||||
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 username == "" {
|
||||
username = "token"
|
||||
|
@ -108,6 +129,15 @@ func getAuth(username, password string) *githttp.BasicAuth {
|
|||
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) {
|
||||
rem := git.NewRemote(memory.NewStorage(), &config.RemoteConfig{
|
||||
Name: "origin",
|
||||
|
@ -115,7 +145,7 @@ func (c *gitClient) listRefs(ctx context.Context, opt baseOption) ([]string, err
|
|||
})
|
||||
|
||||
listOptions := &git.ListOptions{
|
||||
Auth: getAuth(opt.username, opt.password),
|
||||
Auth: getAuth(opt.authType, opt.username, opt.password),
|
||||
InsecureSkipTLS: opt.tlsSkipVerify,
|
||||
}
|
||||
|
||||
|
@ -143,7 +173,7 @@ func (c *gitClient) listFiles(ctx context.Context, opt fetchOption) ([]string, e
|
|||
Depth: 1,
|
||||
SingleBranch: true,
|
||||
ReferenceName: plumbing.ReferenceName(opt.referenceName),
|
||||
Auth: getAuth(opt.username, opt.password),
|
||||
Auth: getAuth(opt.authType, opt.username, opt.password),
|
||||
InsecureSkipTLS: opt.tlsSkipVerify,
|
||||
Tags: git.NoTags,
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package git
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -24,7 +26,15 @@ func TestService_ClonePrivateRepository_GitHub(t *testing.T) {
|
|||
dst := t.TempDir()
|
||||
|
||||
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.FileExists(t, filepath.Join(dst, "README.md"))
|
||||
}
|
||||
|
@ -37,7 +47,14 @@ func TestService_LatestCommitID_GitHub(t *testing.T) {
|
|||
service := newService(context.TODO(), 0, 0)
|
||||
|
||||
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.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)
|
||||
|
||||
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.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)
|
||||
|
||||
repositoryUrl := privateGitRepoURL
|
||||
go service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
||||
service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
||||
go service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, false)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
@ -202,7 +219,17 @@ func TestService_ListFiles_GitHub(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
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 {
|
||||
assert.Error(t, err)
|
||||
if tt.expect.err != nil {
|
||||
|
@ -226,8 +253,28 @@ func TestService_ListFiles_Github_Concurrently(t *testing.T) {
|
|||
username := getRequiredValue(t, "GITHUB_USERNAME")
|
||||
service := newService(context.TODO(), repositoryCacheSize, 200*time.Millisecond)
|
||||
|
||||
go service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
||||
service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
||||
go service.ListFiles(
|
||||
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)
|
||||
}
|
||||
|
@ -240,8 +287,18 @@ func TestService_purgeCache_Github(t *testing.T) {
|
|||
username := getRequiredValue(t, "GITHUB_USERNAME")
|
||||
service := NewService(context.TODO())
|
||||
|
||||
service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
||||
service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
||||
service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, 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.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
|
||||
service := newService(context.TODO(), 2, 40*timeout)
|
||||
|
||||
service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
||||
service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, false, []string{}, false)
|
||||
service.ListRefs(repositoryUrl, username, accessToken, gittypes.GitCredentialAuthType_Basic, false, 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.repoFileCache.Len())
|
||||
|
||||
|
@ -293,12 +360,12 @@ func TestService_HardRefresh_ListRefs_GitHub(t *testing.T) {
|
|||
service := newService(context.TODO(), 2, 0)
|
||||
|
||||
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.GreaterOrEqual(t, len(refs), 1)
|
||||
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.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)
|
||||
|
||||
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.GreaterOrEqual(t, len(refs), 1)
|
||||
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.GreaterOrEqual(t, len(files), 1)
|
||||
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.GreaterOrEqual(t, len(files), 1)
|
||||
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.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.Equal(t, 1, service.repoRefCache.Len())
|
||||
// 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")
|
||||
username := getRequiredValue(t, "GITHUB_USERNAME")
|
||||
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.GreaterOrEqual(t, len(files), 1)
|
||||
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.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()
|
||||
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.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()
|
||||
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.NoDirExists(t, filepath.Join(dir, ".git"))
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ func Test_latestCommitID(t *testing.T) {
|
|||
repositoryURL := setup(t)
|
||||
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.Equal(t, "68dcaa7bd452494043c64252ab90db0f98ecf8d2", id)
|
||||
|
@ -95,7 +95,7 @@ func Test_ListRefs(t *testing.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.Equal(t, []string{"refs/heads/main"}, fs)
|
||||
|
@ -107,7 +107,17 @@ func Test_ListFiles(t *testing.T) {
|
|||
repositoryURL := setup(t)
|
||||
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.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",
|
||||
args: fetchOption{
|
||||
baseOption: baseOption{
|
||||
repositoryUrl: privateGitRepoURL + "fake",
|
||||
repositoryUrl: privateGitRepoURL,
|
||||
username: "",
|
||||
password: "",
|
||||
},
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
gittypes "github.com/portainer/portainer/api/git/types"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
@ -22,6 +23,7 @@ type baseOption struct {
|
|||
repositoryUrl string
|
||||
username string
|
||||
password string
|
||||
authType gittypes.GitCredentialAuthType
|
||||
tlsSkipVerify bool
|
||||
}
|
||||
|
||||
|
@ -123,13 +125,22 @@ func (service *Service) timerHasStopped() bool {
|
|||
|
||||
// CloneRepository clones a git repository using the specified URL in the specified
|
||||
// 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{
|
||||
fetchOption: fetchOption{
|
||||
baseOption: baseOption{
|
||||
repositoryUrl: repositoryURL,
|
||||
username: username,
|
||||
password: password,
|
||||
authType: authType,
|
||||
tlsSkipVerify: tlsSkipVerify,
|
||||
},
|
||||
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
|
||||
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{
|
||||
baseOption: baseOption{
|
||||
repositoryUrl: repositoryURL,
|
||||
username: username,
|
||||
password: password,
|
||||
authType: authType,
|
||||
tlsSkipVerify: tlsSkipVerify,
|
||||
},
|
||||
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
|
||||
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))
|
||||
if service.cacheEnabled && hardRefresh {
|
||||
// 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,
|
||||
username: username,
|
||||
password: password,
|
||||
authType: authType,
|
||||
tlsSkipVerify: tlsSkipVerify,
|
||||
}
|
||||
|
||||
|
@ -215,18 +242,62 @@ var singleflightGroup = &singleflight.Group{}
|
|||
|
||||
// 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
|
||||
func (service *Service) ListFiles(repositoryURL, referenceName, username, password string, dirOnly, hardRefresh bool, includedExts []string, tlsSkipVerify bool) ([]string, error) {
|
||||
repoKey := generateCacheKey(repositoryURL, referenceName, username, password, strconv.FormatBool(tlsSkipVerify), strconv.FormatBool(dirOnly))
|
||||
func (service *Service) ListFiles(
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
func (service *Service) listFiles(repositoryURL, referenceName, username, password string, dirOnly, hardRefresh bool, tlsSkipVerify bool) ([]string, error) {
|
||||
repoKey := generateCacheKey(repositoryURL, referenceName, username, password, strconv.FormatBool(tlsSkipVerify), strconv.FormatBool(dirOnly))
|
||||
func (service *Service) listFiles(
|
||||
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 {
|
||||
// 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,
|
||||
username: username,
|
||||
password: password,
|
||||
authType: authType,
|
||||
tlsSkipVerify: tlsSkipVerify,
|
||||
},
|
||||
referenceName: referenceName,
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
package gittypes
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
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")
|
||||
)
|
||||
|
||||
type GitCredentialAuthType int
|
||||
|
||||
const (
|
||||
GitCredentialAuthType_Basic GitCredentialAuthType = iota
|
||||
GitCredentialAuthType_Token
|
||||
)
|
||||
|
||||
// RepoConfig represents a configuration for a repo
|
||||
type RepoConfig struct {
|
||||
// The repo url
|
||||
|
@ -24,10 +33,11 @@ type RepoConfig struct {
|
|||
}
|
||||
|
||||
type GitAuthentication struct {
|
||||
Username string
|
||||
Password string
|
||||
Username string
|
||||
Password string
|
||||
AuthorizationType GitCredentialAuthType
|
||||
// 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
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
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{
|
||||
username: username,
|
||||
password: password,
|
||||
authType: gitConfig.Authentication.AuthorizationType,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,14 +97,31 @@ type cloneRepositoryParameters struct {
|
|||
}
|
||||
|
||||
type gitAuth struct {
|
||||
authType gittypes.GitCredentialAuthType
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
func cloneGitRepository(gitService portainer.GitService, cloneParams *cloneRepositoryParameters) error {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -56,11 +71,26 @@ type InvalidTestGitService struct {
|
|||
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")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -37,14 +37,16 @@ type customTemplateUpdatePayload struct {
|
|||
RepositoryURL string `example:"https://github.com/openfaas/faas" validate:"required"`
|
||||
// Reference name of a Git repository hosting the Stack file
|
||||
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"`
|
||||
// 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"`
|
||||
// Password used in basic authentication. Required when RepositoryAuthentication is true
|
||||
// and RepositoryGitCredentialID is 0
|
||||
// Password used in basic authentication or token used in token authentication.
|
||||
// Required when RepositoryAuthentication is true and RepositoryGitCredentialID is 0
|
||||
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
|
||||
// is true and RepositoryUsername/RepositoryPassword are not provided
|
||||
RepositoryGitCredentialID int `example:"0"`
|
||||
|
@ -182,12 +184,15 @@ func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Requ
|
|||
|
||||
repositoryUsername := ""
|
||||
repositoryPassword := ""
|
||||
repositoryAuthType := gittypes.GitCredentialAuthType_Basic
|
||||
if payload.RepositoryAuthentication {
|
||||
repositoryUsername = payload.RepositoryUsername
|
||||
repositoryPassword = payload.RepositoryPassword
|
||||
repositoryAuthType = payload.RepositoryAuthorizationType
|
||||
gitConfig.Authentication = &gittypes.GitAuthentication{
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: payload.RepositoryPassword,
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: payload.RepositoryPassword,
|
||||
AuthorizationType: payload.RepositoryAuthorizationType,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,6 +202,7 @@ func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Requ
|
|||
ReferenceName: gitConfig.ReferenceName,
|
||||
Username: repositoryUsername,
|
||||
Password: repositoryPassword,
|
||||
AuthType: repositoryAuthType,
|
||||
TLSSkipVerify: gitConfig.TLSSkipVerify,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -205,7 +211,14 @@ func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Requ
|
|||
|
||||
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 {
|
||||
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"`
|
||||
// Password used in basic authentication. Required when RepositoryAuthentication is true.
|
||||
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
|
||||
FilePathInRepository string `example:"docker-compose.yml" default:"docker-compose.yml"`
|
||||
// List of identifiers of EdgeGroups
|
||||
|
@ -125,8 +127,9 @@ func (handler *Handler) createEdgeStackFromGitRepository(r *http.Request, tx dat
|
|||
|
||||
if payload.RepositoryAuthentication {
|
||||
repoConfig.Authentication = &gittypes.GitAuthentication{
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: payload.RepositoryPassword,
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: payload.RepositoryPassword,
|
||||
AuthorizationType: payload.RepositoryAuthorizationType,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,12 +148,22 @@ func (handler *Handler) storeManifestFromGitRepository(tx dataservices.DataStore
|
|||
projectPath = handler.FileService.GetEdgeStackProjectPath(stackFolder)
|
||||
repositoryUsername := ""
|
||||
repositoryPassword := ""
|
||||
repositoryAuthType := gittypes.GitCredentialAuthType_Basic
|
||||
if repositoryConfig.Authentication != nil && repositoryConfig.Authentication.Password != "" {
|
||||
repositoryUsername = repositoryConfig.Authentication.Username
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -17,10 +17,11 @@ type fileResponse struct {
|
|||
}
|
||||
|
||||
type repositoryFilePreviewPayload struct {
|
||||
Repository string `json:"repository" example:"https://github.com/openfaas/faas" validate:"required"`
|
||||
Reference string `json:"reference" example:"refs/heads/master"`
|
||||
Username string `json:"username" example:"myGitUsername"`
|
||||
Password string `json:"password" example:"myGitPassword"`
|
||||
Repository string `json:"repository" example:"https://github.com/openfaas/faas" validate:"required"`
|
||||
Reference string `json:"reference" example:"refs/heads/master"`
|
||||
Username string `json:"username" example:"myGitUsername"`
|
||||
Password string `json:"password" example:"myGitPassword"`
|
||||
AuthorizationType gittypes.GitCredentialAuthType `json:"authorizationType"`
|
||||
// Path to file whose content will be read
|
||||
TargetFile string `json:"targetFile" example:"docker-compose.yml"`
|
||||
// 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)
|
||||
}
|
||||
|
||||
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 errors.Is(err, gittypes.ErrAuthenticationFailure) {
|
||||
return httperror.BadRequest("Invalid git credential", err)
|
||||
|
|
|
@ -19,14 +19,15 @@ import (
|
|||
)
|
||||
|
||||
type stackGitUpdatePayload struct {
|
||||
AutoUpdate *portainer.AutoUpdateSettings
|
||||
Env []portainer.Pair
|
||||
Prune bool
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
TLSSkipVerify bool
|
||||
AutoUpdate *portainer.AutoUpdateSettings
|
||||
Env []portainer.Pair
|
||||
Prune bool
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
RepositoryAuthorizationType gittypes.GitCredentialAuthType
|
||||
TLSSkipVerify bool
|
||||
}
|
||||
|
||||
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{
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: password,
|
||||
Username: payload.RepositoryUsername,
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/git"
|
||||
gittypes "github.com/portainer/portainer/api/git/types"
|
||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
k "github.com/portainer/portainer/api/kubernetes"
|
||||
|
@ -19,12 +20,13 @@ import (
|
|||
)
|
||||
|
||||
type stackGitRedployPayload struct {
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
Env []portainer.Pair
|
||||
Prune bool
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
RepositoryAuthorizationType gittypes.GitCredentialAuthType
|
||||
Env []portainer.Pair
|
||||
Prune bool
|
||||
// Force a pulling to current image with the original tag though the image is already the latest
|
||||
PullImage bool `example:"false"`
|
||||
|
||||
|
@ -135,13 +137,16 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
repositoryUsername := ""
|
||||
repositoryPassword := ""
|
||||
repositoryAuthType := gittypes.GitCredentialAuthType_Basic
|
||||
if payload.RepositoryAuthentication {
|
||||
repositoryPassword = payload.RepositoryPassword
|
||||
repositoryAuthType = payload.RepositoryAuthorizationType
|
||||
|
||||
// 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
|
||||
if repositoryPassword == "" && stack.GitConfig != nil && stack.GitConfig.Authentication != nil {
|
||||
repositoryPassword = stack.GitConfig.Authentication.Password
|
||||
repositoryAuthType = stack.GitConfig.Authentication.AuthorizationType
|
||||
}
|
||||
repositoryUsername = payload.RepositoryUsername
|
||||
}
|
||||
|
@ -152,6 +157,7 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
|||
ReferenceName: stack.GitConfig.ReferenceName,
|
||||
Username: repositoryUsername,
|
||||
Password: repositoryPassword,
|
||||
AuthType: repositoryAuthType,
|
||||
TLSSkipVerify: stack.GitConfig.TLSSkipVerify,
|
||||
}
|
||||
|
||||
|
@ -166,7 +172,7 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
|||
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 {
|
||||
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 {
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
AutoUpdate *portainer.AutoUpdateSettings
|
||||
TLSSkipVerify bool
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
RepositoryAuthorizationType gittypes.GitCredentialAuthType
|
||||
AutoUpdate *portainer.AutoUpdateSettings
|
||||
TLSSkipVerify bool
|
||||
}
|
||||
|
||||
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{
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: password,
|
||||
Username: payload.RepositoryUsername,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"slices"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
gittypes "github.com/portainer/portainer/api/git/types"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"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)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"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/security"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
|
@ -418,7 +419,14 @@ func (transport *Transport) updateDefaultGitBranch(request *http.Request) error
|
|||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1538,10 +1538,42 @@ type (
|
|||
|
||||
// GitService represents a service for managing Git
|
||||
GitService interface {
|
||||
CloneRepository(destination string, repositoryURL, referenceName, username, password string, tlsSkipVerify bool) error
|
||||
LatestCommitID(repositoryURL, referenceName, username, password string, tlsSkipVerify bool) (string, error)
|
||||
ListRefs(repositoryURL, username, password string, hardRefresh bool, tlsSkipVerify bool) ([]string, error)
|
||||
ListFiles(repositoryURL, referenceName, username, password string, dirOnly, hardRefresh bool, includeExts []string, tlsSkipVerify bool) ([]string, error)
|
||||
CloneRepository(
|
||||
destination string,
|
||||
repositoryURL,
|
||||
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
|
||||
|
|
|
@ -19,13 +19,23 @@ var (
|
|||
func DownloadGitRepository(config gittypes.RepoConfig, gitService portainer.GitService, getProjectPath func() string) (string, error) {
|
||||
username := ""
|
||||
password := ""
|
||||
authType := gittypes.GitCredentialAuthType_Basic
|
||||
if config.Authentication != nil {
|
||||
username = config.Authentication.Username
|
||||
password = config.Authentication.Password
|
||||
authType = config.Authentication.AuthorizationType
|
||||
}
|
||||
|
||||
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 errors.Is(err, gittypes.ErrAuthenticationFailure) {
|
||||
newErr := git.ErrInvalidGitCredential
|
||||
|
@ -36,7 +46,14 @@ func DownloadGitRepository(config gittypes.RepoConfig, gitService portainer.GitS
|
|||
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 {
|
||||
newErr := fmt.Errorf("unable to fetch git repository id: %w", err)
|
||||
return "", newErr
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue