mirror of https://github.com/portainer/portainer
feat(gitops): allow to skip tls verification [EE-5023] (#8679)
parent
ab1a8c1d6a
commit
99331a81d4
|
@ -15,8 +15,6 @@ import (
|
||||||
"github.com/portainer/portainer/api/crypto"
|
"github.com/portainer/portainer/api/crypto"
|
||||||
gittypes "github.com/portainer/portainer/api/git/types"
|
gittypes "github.com/portainer/portainer/api/git/types"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport/client"
|
|
||||||
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -51,21 +49,22 @@ type azureItem struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type azureClient struct {
|
type azureClient struct {
|
||||||
client *http.Client
|
|
||||||
baseUrl string
|
baseUrl string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAzureClient() *azureClient {
|
func NewAzureClient() *azureClient {
|
||||||
httpsCli := newHttpClientForAzure()
|
|
||||||
return &azureClient{
|
return &azureClient{
|
||||||
client: httpsCli,
|
|
||||||
baseUrl: "https://dev.azure.com",
|
baseUrl: "https://dev.azure.com",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHttpClientForAzure() *http.Client {
|
func newHttpClientForAzure(insecureSkipVerify bool) *http.Client {
|
||||||
tlsConfig := crypto.CreateTLSConfiguration()
|
tlsConfig := crypto.CreateTLSConfiguration()
|
||||||
|
|
||||||
|
if insecureSkipVerify {
|
||||||
|
tlsConfig.InsecureSkipVerify = true
|
||||||
|
}
|
||||||
|
|
||||||
httpsCli := &http.Client{
|
httpsCli := &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
TLSClientConfig: tlsConfig,
|
TLSClientConfig: tlsConfig,
|
||||||
|
@ -74,7 +73,6 @@ func newHttpClientForAzure() *http.Client {
|
||||||
Timeout: 300 * time.Second,
|
Timeout: 300 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
client.InstallProtocol("https", githttp.NewClient(httpsCli))
|
|
||||||
return httpsCli
|
return httpsCli
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +104,7 @@ func (a *azureClient) downloadZipFromAzureDevOps(ctx context.Context, opt cloneO
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.WithMessage(err, "failed to create temp file")
|
return "", errors.WithMessage(err, "failed to create temp file")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer zipFile.Close()
|
defer zipFile.Close()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", downloadUrl, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", downloadUrl, nil)
|
||||||
|
@ -119,10 +118,14 @@ func (a *azureClient) downloadZipFromAzureDevOps(ctx context.Context, opt cloneO
|
||||||
return "", errors.WithMessage(err, "failed to create a new HTTP request")
|
return "", errors.WithMessage(err, "failed to create a new HTTP request")
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := a.client.Do(req)
|
client := newHttpClientForAzure(opt.tlsSkipVerify)
|
||||||
|
defer client.CloseIdleConnections()
|
||||||
|
|
||||||
|
res, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.WithMessage(err, "failed to make an HTTP request")
|
return "", errors.WithMessage(err, "failed to make an HTTP request")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
|
@ -166,7 +169,10 @@ func (a *azureClient) getRootItem(ctx context.Context, opt fetchOption) (*azureI
|
||||||
return nil, errors.WithMessage(err, "failed to create a new HTTP request")
|
return nil, errors.WithMessage(err, "failed to create a new HTTP request")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := a.client.Do(req)
|
client := newHttpClientForAzure(opt.tlsSkipVerify)
|
||||||
|
defer client.CloseIdleConnections()
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithMessage(err, "failed to make an HTTP request")
|
return nil, errors.WithMessage(err, "failed to make an HTTP request")
|
||||||
}
|
}
|
||||||
|
@ -399,7 +405,10 @@ func (a *azureClient) listRefs(ctx context.Context, opt baseOption) ([]string, e
|
||||||
return nil, errors.WithMessage(err, "failed to create a new HTTP request")
|
return nil, errors.WithMessage(err, "failed to create a new HTTP request")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := a.client.Do(req)
|
client := newHttpClientForAzure(opt.tlsSkipVerify)
|
||||||
|
defer client.CloseIdleConnections()
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithMessage(err, "failed to make an HTTP request")
|
return nil, errors.WithMessage(err, "failed to make an HTTP request")
|
||||||
}
|
}
|
||||||
|
@ -456,7 +465,10 @@ func (a *azureClient) listFiles(ctx context.Context, opt fetchOption) ([]string,
|
||||||
return nil, errors.WithMessage(err, "failed to create a new HTTP request")
|
return nil, errors.WithMessage(err, "failed to create a new HTTP request")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := a.client.Do(req)
|
client := newHttpClientForAzure(opt.tlsSkipVerify)
|
||||||
|
defer client.CloseIdleConnections()
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithMessage(err, "failed to make an HTTP request")
|
return nil, errors.WithMessage(err, "failed to make an HTTP request")
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ 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, "", "")
|
err := service.CloneRepository(dst, repositoryUrl, tt.args.referenceName, "", "", 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"))
|
||||||
})
|
})
|
||||||
|
@ -74,7 +74,7 @@ func TestService_ClonePrivateRepository_Azure(t *testing.T) {
|
||||||
|
|
||||||
dst := t.TempDir()
|
dst := t.TempDir()
|
||||||
|
|
||||||
err := service.CloneRepository(dst, privateAzureRepoURL, "refs/heads/main", "", pat)
|
err := service.CloneRepository(dst, privateAzureRepoURL, "refs/heads/main", "", pat, 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"))
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ 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)
|
id, err := service.LatestCommitID(privateAzureRepoURL, "refs/heads/main", "", pat, 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")
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ 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)
|
refs, err := service.ListRefs(privateAzureRepoURL, username, accessToken, false, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.GreaterOrEqual(t, len(refs), 1)
|
assert.GreaterOrEqual(t, len(refs), 1)
|
||||||
}
|
}
|
||||||
|
@ -109,8 +109,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(), REPOSITORY_CACHE_SIZE, 200*time.Millisecond)
|
service := newService(context.TODO(), REPOSITORY_CACHE_SIZE, 200*time.Millisecond)
|
||||||
|
|
||||||
go service.ListRefs(privateAzureRepoURL, username, accessToken, false)
|
go service.ListRefs(privateAzureRepoURL, username, accessToken, false, false)
|
||||||
service.ListRefs(privateAzureRepoURL, username, accessToken, false)
|
service.ListRefs(privateAzureRepoURL, username, accessToken, false, false)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
|
@ -248,7 +248,7 @@ 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, tt.extensions)
|
paths, err := service.ListFiles(tt.args.repositoryUrl, tt.args.referenceName, tt.args.username, tt.args.password, 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 {
|
||||||
|
@ -271,8 +271,8 @@ func TestService_ListFiles_Azure_Concurrently(t *testing.T) {
|
||||||
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
||||||
service := newService(context.TODO(), REPOSITORY_CACHE_SIZE, 200*time.Millisecond)
|
service := newService(context.TODO(), REPOSITORY_CACHE_SIZE, 200*time.Millisecond)
|
||||||
|
|
||||||
go service.ListFiles(privateAzureRepoURL, "refs/heads/main", username, accessToken, false, []string{})
|
go service.ListFiles(privateAzureRepoURL, "refs/heads/main", username, accessToken, false, []string{}, false)
|
||||||
service.ListFiles(privateAzureRepoURL, "refs/heads/main", username, accessToken, false, []string{})
|
service.ListFiles(privateAzureRepoURL, "refs/heads/main", username, accessToken, false, []string{}, false)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,7 +292,6 @@ func Test_azureDownloader_downloadZipFromAzureDevOps(t *testing.T) {
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
a := &azureClient{
|
a := &azureClient{
|
||||||
client: server.Client(),
|
|
||||||
baseUrl: server.URL,
|
baseUrl: server.URL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,7 +328,6 @@ func Test_azureDownloader_latestCommitID(t *testing.T) {
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
a := &azureClient{
|
a := &azureClient{
|
||||||
client: server.Client(),
|
|
||||||
baseUrl: server.URL,
|
baseUrl: server.URL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,6 +440,7 @@ func Test_listRefs_azure(t *testing.T) {
|
||||||
|
|
||||||
accessToken := getRequiredValue(t, "AZURE_DEVOPS_PAT")
|
accessToken := getRequiredValue(t, "AZURE_DEVOPS_PAT")
|
||||||
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
username := getRequiredValue(t, "AZURE_DEVOPS_USERNAME")
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args baseOption
|
args baseOption
|
||||||
|
|
|
@ -20,6 +20,8 @@ type CloneOptions struct {
|
||||||
ReferenceName string
|
ReferenceName string
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
|
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
||||||
|
TLSSkipVerify bool `example:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func CloneWithBackup(gitService portainer.GitService, fileService portainer.FileService, options CloneOptions) (clean func(), err error) {
|
func CloneWithBackup(gitService portainer.GitService, fileService portainer.FileService, options CloneOptions) (clean func(), err error) {
|
||||||
|
@ -43,7 +45,7 @@ func CloneWithBackup(gitService portainer.GitService, fileService portainer.File
|
||||||
|
|
||||||
cleanUp = true
|
cleanUp = true
|
||||||
|
|
||||||
err = gitService.CloneRepository(options.ProjectPath, options.URL, options.ReferenceName, options.Username, options.Password)
|
err = gitService.CloneRepository(options.ProjectPath, options.URL, options.ReferenceName, options.Username, options.Password, options.TLSSkipVerify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanUp = false
|
cleanUp = false
|
||||||
restoreError := filesystem.MoveDirectory(backupProjectPath, options.ProjectPath)
|
restoreError := filesystem.MoveDirectory(backupProjectPath, options.ProjectPath)
|
||||||
|
|
|
@ -30,6 +30,7 @@ func (c *gitClient) download(ctx context.Context, dst string, opt cloneOption) e
|
||||||
gitOptions := git.CloneOptions{
|
gitOptions := git.CloneOptions{
|
||||||
URL: opt.repositoryUrl,
|
URL: opt.repositoryUrl,
|
||||||
Depth: opt.depth,
|
Depth: opt.depth,
|
||||||
|
InsecureSkipTLS: opt.tlsSkipVerify,
|
||||||
Auth: getAuth(opt.username, opt.password),
|
Auth: getAuth(opt.username, opt.password),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +62,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.username, opt.password),
|
||||||
|
InsecureSkipTLS: opt.tlsSkipVerify,
|
||||||
}
|
}
|
||||||
|
|
||||||
refs, err := remote.List(listOptions)
|
refs, err := remote.List(listOptions)
|
||||||
|
@ -111,6 +113,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.username, opt.password),
|
||||||
|
InsecureSkipTLS: opt.tlsSkipVerify,
|
||||||
}
|
}
|
||||||
|
|
||||||
refs, err := rem.List(listOptions)
|
refs, err := rem.List(listOptions)
|
||||||
|
@ -138,6 +141,7 @@ func (c *gitClient) listFiles(ctx context.Context, opt fetchOption) ([]string, e
|
||||||
SingleBranch: true,
|
SingleBranch: true,
|
||||||
ReferenceName: plumbing.ReferenceName(opt.referenceName),
|
ReferenceName: plumbing.ReferenceName(opt.referenceName),
|
||||||
Auth: getAuth(opt.username, opt.password),
|
Auth: getAuth(opt.username, opt.password),
|
||||||
|
InsecureSkipTLS: opt.tlsSkipVerify,
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, err := git.Clone(memory.NewStorage(), nil, cloneOption)
|
repo, err := git.Clone(memory.NewStorage(), nil, cloneOption)
|
||||||
|
|
|
@ -24,7 +24,7 @@ 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)
|
err := service.CloneRepository(dst, repositoryUrl, "refs/heads/main", username, accessToken, 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 +37,7 @@ 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)
|
id, err := service.LatestCommitID(repositoryUrl, "refs/heads/main", username, accessToken, 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 +50,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)
|
refs, err := service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.GreaterOrEqual(t, len(refs), 1)
|
assert.GreaterOrEqual(t, len(refs), 1)
|
||||||
}
|
}
|
||||||
|
@ -63,8 +63,8 @@ func TestService_ListRefs_Github_Concurrently(t *testing.T) {
|
||||||
service := newService(context.TODO(), REPOSITORY_CACHE_SIZE, 200*time.Millisecond)
|
service := newService(context.TODO(), REPOSITORY_CACHE_SIZE, 200*time.Millisecond)
|
||||||
|
|
||||||
repositoryUrl := privateGitRepoURL
|
repositoryUrl := privateGitRepoURL
|
||||||
go service.ListRefs(repositoryUrl, username, accessToken, false)
|
go service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
||||||
service.ListRefs(repositoryUrl, username, accessToken, false)
|
service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ 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, tt.extensions)
|
paths, err := service.ListFiles(tt.args.repositoryUrl, tt.args.referenceName, tt.args.username, tt.args.password, 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 +226,8 @@ func TestService_ListFiles_Github_Concurrently(t *testing.T) {
|
||||||
username := getRequiredValue(t, "GITHUB_USERNAME")
|
username := getRequiredValue(t, "GITHUB_USERNAME")
|
||||||
service := newService(context.TODO(), REPOSITORY_CACHE_SIZE, 200*time.Millisecond)
|
service := newService(context.TODO(), REPOSITORY_CACHE_SIZE, 200*time.Millisecond)
|
||||||
|
|
||||||
go service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, []string{})
|
go service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, []string{}, false)
|
||||||
service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, []string{})
|
service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, []string{}, false)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
|
@ -240,8 +240,8 @@ 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)
|
service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
||||||
service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, []string{})
|
service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, 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 +261,8 @@ 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)
|
service.ListRefs(repositoryUrl, username, accessToken, false, false)
|
||||||
service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, false, []string{})
|
service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, 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 +293,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)
|
refs, err := service.ListRefs(repositoryUrl, username, accessToken, 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())
|
||||||
|
|
||||||
refs, err = service.ListRefs(repositoryUrl, username, "fake-token", false)
|
_, err = service.ListRefs(repositoryUrl, username, "fake-token", 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 +311,26 @@ 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)
|
refs, err := service.ListRefs(repositoryUrl, username, accessToken, 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, []string{})
|
files, err := service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, 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, []string{})
|
files, err = service.ListFiles(repositoryUrl, "refs/heads/test", username, accessToken, 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())
|
||||||
|
|
||||||
refs, err = service.ListRefs(repositoryUrl, username, "fake-token", false)
|
_, err = service.ListRefs(repositoryUrl, username, "fake-token", false, false)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, 1, service.repoRefCache.Len())
|
assert.Equal(t, 1, service.repoRefCache.Len())
|
||||||
|
|
||||||
refs, err = service.ListRefs(repositoryUrl, username, "fake-token", true)
|
_, err = service.ListRefs(repositoryUrl, username, "fake-token", 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 +344,12 @@ 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, []string{})
|
files, err := service.ListFiles(repositoryUrl, "refs/heads/main", username, accessToken, 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/main", username, "fake-token", true, []string{})
|
_, err = service.ListFiles(repositoryUrl, "refs/heads/main", username, "fake-token", 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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, "", "")
|
err := service.CloneRepository(dir, repositoryURL, referenceName, "", "", 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, "", "")
|
err := service.CloneRepository(dir, repositoryURL, referenceName, "", "", 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, "", "")
|
id, err := service.LatestCommitID(repositoryURL, referenceName, "", "", false)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "68dcaa7bd452494043c64252ab90db0f98ecf8d2", id)
|
assert.Equal(t, "68dcaa7bd452494043c64252ab90db0f98ecf8d2", id)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -20,6 +21,7 @@ type baseOption struct {
|
||||||
repositoryUrl string
|
repositoryUrl string
|
||||||
username string
|
username string
|
||||||
password string
|
password string
|
||||||
|
tlsSkipVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchOption allows to specify the reference name of the target repository
|
// fetchOption allows to specify the reference name of the target repository
|
||||||
|
@ -119,13 +121,14 @@ 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) error {
|
func (service *Service) CloneRepository(destination, repositoryURL, referenceName, username, password string, 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,
|
||||||
|
tlsSkipVerify: tlsSkipVerify,
|
||||||
},
|
},
|
||||||
referenceName: referenceName,
|
referenceName: referenceName,
|
||||||
},
|
},
|
||||||
|
@ -144,12 +147,13 @@ 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) (string, error) {
|
func (service *Service) LatestCommitID(repositoryURL, referenceName, username, password string, tlsSkipVerify bool) (string, error) {
|
||||||
options := fetchOption{
|
options := fetchOption{
|
||||||
baseOption: baseOption{
|
baseOption: baseOption{
|
||||||
repositoryUrl: repositoryURL,
|
repositoryUrl: repositoryURL,
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
|
tlsSkipVerify: tlsSkipVerify,
|
||||||
},
|
},
|
||||||
referenceName: referenceName,
|
referenceName: referenceName,
|
||||||
}
|
}
|
||||||
|
@ -162,8 +166,8 @@ 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) ([]string, error) {
|
func (service *Service) ListRefs(repositoryURL, username, password string, hardRefresh bool, tlsSkipVerify bool) ([]string, error) {
|
||||||
refCacheKey := generateCacheKey(repositoryURL, password)
|
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
|
||||||
service.repoRefCache.Remove(refCacheKey)
|
service.repoRefCache.Remove(refCacheKey)
|
||||||
|
@ -193,6 +197,7 @@ func (service *Service) ListRefs(repositoryURL, username, password string, hardR
|
||||||
repositoryUrl: repositoryURL,
|
repositoryUrl: repositoryURL,
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
|
tlsSkipVerify: tlsSkipVerify,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -219,8 +224,8 @@ func (service *Service) ListRefs(repositoryURL, username, password string, hardR
|
||||||
|
|
||||||
// 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, hardRefresh bool, includedExts []string) ([]string, error) {
|
func (service *Service) ListFiles(repositoryURL, referenceName, username, password string, hardRefresh bool, includedExts []string, tlsSkipVerify bool) ([]string, error) {
|
||||||
repoKey := generateCacheKey(repositoryURL, referenceName)
|
repoKey := generateCacheKey(repositoryURL, referenceName, 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
|
||||||
|
@ -246,6 +251,7 @@ func (service *Service) ListFiles(repositoryURL, referenceName, username, passwo
|
||||||
repositoryUrl: repositoryURL,
|
repositoryUrl: repositoryURL,
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
|
tlsSkipVerify: tlsSkipVerify,
|
||||||
},
|
},
|
||||||
referenceName: referenceName,
|
referenceName: referenceName,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ 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")
|
||||||
)
|
)
|
||||||
|
|
||||||
// RepoConfig represents a configuration for a repo
|
// RepoConfig represents a configuration for a repo
|
||||||
|
@ -19,6 +19,8 @@ type RepoConfig struct {
|
||||||
Authentication *GitAuthentication
|
Authentication *GitAuthentication
|
||||||
// Repository hash
|
// Repository hash
|
||||||
ConfigHash string `example:"bc4c183d756879ea4d173315338110b31004b8e0"`
|
ConfigHash string `example:"bc4c183d756879ea4d173315338110b31004b8e0"`
|
||||||
|
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
||||||
|
TLSSkipVerify bool `example:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GitAuthentication struct {
|
type GitAuthentication struct {
|
||||||
|
|
|
@ -6,14 +6,13 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/dataservices"
|
|
||||||
"github.com/portainer/portainer/api/git"
|
"github.com/portainer/portainer/api/git"
|
||||||
gittypes "github.com/portainer/portainer/api/git/types"
|
gittypes "github.com/portainer/portainer/api/git/types"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpdateGitObject updates a git object based on its config
|
// UpdateGitObject updates a git object based on its config
|
||||||
func UpdateGitObject(gitService portainer.GitService, dataStore dataservices.DataStore, objId string, gitConfig *gittypes.RepoConfig, autoUpdateConfig *portainer.AutoUpdateSettings, projectPath string) (bool, string, error) {
|
func UpdateGitObject(gitService portainer.GitService, objId string, gitConfig *gittypes.RepoConfig, forceUpdate bool, projectPath string) (bool, string, error) {
|
||||||
if gitConfig == nil {
|
if gitConfig == nil {
|
||||||
return false, "", nil
|
return false, "", nil
|
||||||
}
|
}
|
||||||
|
@ -29,13 +28,13 @@ func UpdateGitObject(gitService portainer.GitService, dataStore dataservices.Dat
|
||||||
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)
|
newHash, err := gitService.LatestCommitID(gitConfig.URL, gitConfig.ReferenceName, username, password, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
hashChanged := !strings.EqualFold(newHash, string(gitConfig.ConfigHash))
|
hashChanged := !strings.EqualFold(newHash, gitConfig.ConfigHash)
|
||||||
forceUpdate := autoUpdateConfig != nil && autoUpdateConfig.ForceUpdate
|
|
||||||
if !hashChanged && !forceUpdate {
|
if !hashChanged && !forceUpdate {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("hash", newHash).
|
Str("hash", newHash).
|
||||||
|
@ -51,6 +50,7 @@ func UpdateGitObject(gitService portainer.GitService, dataStore dataservices.Dat
|
||||||
url: gitConfig.URL,
|
url: gitConfig.URL,
|
||||||
ref: gitConfig.ReferenceName,
|
ref: gitConfig.ReferenceName,
|
||||||
toDir: projectPath,
|
toDir: projectPath,
|
||||||
|
tlsSkipVerify: gitConfig.TLSSkipVerify,
|
||||||
}
|
}
|
||||||
if gitConfig.Authentication != nil {
|
if gitConfig.Authentication != nil {
|
||||||
cloneParams.auth = &gitAuth{
|
cloneParams.auth = &gitAuth{
|
||||||
|
@ -78,6 +78,8 @@ type cloneRepositoryParameters struct {
|
||||||
ref string
|
ref string
|
||||||
toDir string
|
toDir string
|
||||||
auth *gitAuth
|
auth *gitAuth
|
||||||
|
// tlsSkipVerify skips SSL verification when cloning the Git repository
|
||||||
|
tlsSkipVerify bool `example:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type gitAuth struct {
|
type gitAuth struct {
|
||||||
|
@ -87,8 +89,8 @@ type gitAuth struct {
|
||||||
|
|
||||||
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)
|
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, "", "")
|
return gitService.CloneRepository(cloneParams.toDir, cloneParams.url, cloneParams.ref, "", "", cloneParams.tlsSkipVerify)
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,6 +213,8 @@ type customTemplateFromGitRepositoryPayload struct {
|
||||||
ComposeFilePathInRepository string `example:"docker-compose.yml" default:"docker-compose.yml"`
|
ComposeFilePathInRepository string `example:"docker-compose.yml" default:"docker-compose.yml"`
|
||||||
// Definitions of variables in the stack file
|
// Definitions of variables in the stack file
|
||||||
Variables []portainer.CustomTemplateVariableDefinition
|
Variables []portainer.CustomTemplateVariableDefinition
|
||||||
|
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
||||||
|
TLSSkipVerify bool `example:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *customTemplateFromGitRepositoryPayload) Validate(r *http.Request) error {
|
func (payload *customTemplateFromGitRepositoryPayload) Validate(r *http.Request) error {
|
||||||
|
@ -279,7 +281,7 @@ func (handler *Handler) createCustomTemplateFromGitRepository(r *http.Request) (
|
||||||
repositoryPassword = ""
|
repositoryPassword = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.GitService.CloneRepository(projectPath, payload.RepositoryURL, payload.RepositoryReferenceName, repositoryUsername, repositoryPassword)
|
err = handler.GitService.CloneRepository(projectPath, payload.RepositoryURL, payload.RepositoryReferenceName, repositoryUsername, repositoryPassword, payload.TLSSkipVerify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gittypes.ErrAuthenticationFailure {
|
if err == gittypes.ErrAuthenticationFailure {
|
||||||
return nil, fmt.Errorf("invalid git credential")
|
return nil, fmt.Errorf("invalid git credential")
|
||||||
|
|
|
@ -201,6 +201,8 @@ type swarmStackFromGitRepositoryPayload struct {
|
||||||
Registries []portainer.RegistryID
|
Registries []portainer.RegistryID
|
||||||
// Uses the manifest's namespaces instead of the default one
|
// Uses the manifest's namespaces instead of the default one
|
||||||
UseManifestNamespaces bool
|
UseManifestNamespaces bool
|
||||||
|
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
||||||
|
TLSSkipVerify bool `example:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) error {
|
func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) error {
|
||||||
|
@ -247,6 +249,7 @@ func (handler *Handler) createSwarmStackFromGitRepository(r *http.Request, dryru
|
||||||
URL: payload.RepositoryURL,
|
URL: payload.RepositoryURL,
|
||||||
ReferenceName: payload.RepositoryReferenceName,
|
ReferenceName: payload.RepositoryReferenceName,
|
||||||
ConfigFilePath: payload.FilePathInRepository,
|
ConfigFilePath: payload.FilePathInRepository,
|
||||||
|
TLSSkipVerify: payload.TLSSkipVerify,
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.RepositoryAuthentication {
|
if payload.RepositoryAuthentication {
|
||||||
|
@ -345,7 +348,7 @@ func (handler *Handler) storeManifestFromGitRepository(stackFolder string, relat
|
||||||
repositoryPassword = repositoryConfig.Authentication.Password
|
repositoryPassword = repositoryConfig.Authentication.Password
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.GitService.CloneRepository(projectPath, repositoryConfig.URL, repositoryConfig.ReferenceName, repositoryUsername, repositoryPassword)
|
err = handler.GitService.CloneRepository(projectPath, repositoryConfig.URL, repositoryConfig.ReferenceName, repositoryUsername, repositoryPassword, repositoryConfig.TLSSkipVerify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,32 +18,12 @@ import (
|
||||||
"github.com/portainer/portainer/api/filesystem"
|
"github.com/portainer/portainer/api/filesystem"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
"github.com/portainer/portainer/api/internal/edge/edgestacks"
|
"github.com/portainer/portainer/api/internal/edge/edgestacks"
|
||||||
|
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||||
"github.com/portainer/portainer/api/jwt"
|
"github.com/portainer/portainer/api/jwt"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type gitService struct {
|
|
||||||
cloneErr error
|
|
||||||
id string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *gitService) CloneRepository(destination, repositoryURL, referenceName, username, password string) error {
|
|
||||||
return g.cloneErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *gitService) LatestCommitID(repositoryURL, referenceName, username, password string) (string, error) {
|
|
||||||
return g.id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *gitService) ListRefs(repositoryURL, username, password string, hardRefresh bool) ([]string, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *gitService) ListFiles(repositoryURL, referenceName, username, password string, hardRefresh bool, includedExts []string) ([]string, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
func setupHandler(t *testing.T) (*Handler, string, func()) {
|
func setupHandler(t *testing.T) (*Handler, string, func()) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
@ -98,7 +78,7 @@ func setupHandler(t *testing.T) (*Handler, string, func()) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.GitService = &gitService{errors.New("Clone error"), "git-service-id"}
|
handler.GitService = testhelpers.NewGitService(errors.New("Clone error"), "git-service-id")
|
||||||
|
|
||||||
return handler, rawAPIKey, storeTeardown
|
return handler, rawAPIKey, storeTeardown
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,9 +162,11 @@ type composeStackFromGitRepositoryPayload struct {
|
||||||
Env []portainer.Pair
|
Env []portainer.Pair
|
||||||
// Whether the stack is from a app template
|
// Whether the stack is from a app template
|
||||||
FromAppTemplate bool `example:"false"`
|
FromAppTemplate bool `example:"false"`
|
||||||
|
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
||||||
|
TLSSkipVerify bool `example:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func createStackPayloadFromComposeGitPayload(name, repoUrl, repoReference, repoUsername, repoPassword string, repoAuthentication bool, composeFile string, additionalFiles []string, autoUpdate *portainer.AutoUpdateSettings, env []portainer.Pair, fromAppTemplate bool) stackbuilders.StackPayload {
|
func createStackPayloadFromComposeGitPayload(name, repoUrl, repoReference, repoUsername, repoPassword string, repoAuthentication bool, composeFile string, additionalFiles []string, autoUpdate *portainer.AutoUpdateSettings, env []portainer.Pair, fromAppTemplate bool, repoSkipSSLVerify bool) stackbuilders.StackPayload {
|
||||||
return stackbuilders.StackPayload{
|
return stackbuilders.StackPayload{
|
||||||
Name: name,
|
Name: name,
|
||||||
RepositoryConfigPayload: stackbuilders.RepositoryConfigPayload{
|
RepositoryConfigPayload: stackbuilders.RepositoryConfigPayload{
|
||||||
|
@ -173,6 +175,7 @@ func createStackPayloadFromComposeGitPayload(name, repoUrl, repoReference, repoU
|
||||||
Authentication: repoAuthentication,
|
Authentication: repoAuthentication,
|
||||||
Username: repoUsername,
|
Username: repoUsername,
|
||||||
Password: repoPassword,
|
Password: repoPassword,
|
||||||
|
TLSSkipVerify: repoSkipSSLVerify,
|
||||||
},
|
},
|
||||||
ComposeFile: composeFile,
|
ComposeFile: composeFile,
|
||||||
AdditionalFiles: additionalFiles,
|
AdditionalFiles: additionalFiles,
|
||||||
|
@ -258,7 +261,9 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
|
||||||
payload.AdditionalFiles,
|
payload.AdditionalFiles,
|
||||||
payload.AutoUpdate,
|
payload.AutoUpdate,
|
||||||
payload.Env,
|
payload.Env,
|
||||||
payload.FromAppTemplate)
|
payload.FromAppTemplate,
|
||||||
|
payload.TLSSkipVerify,
|
||||||
|
)
|
||||||
|
|
||||||
composeStackBuilder := stackbuilders.CreateComposeStackGitBuilder(securityContext,
|
composeStackBuilder := stackbuilders.CreateComposeStackGitBuilder(securityContext,
|
||||||
handler.DataStore,
|
handler.DataStore,
|
||||||
|
|
|
@ -46,9 +46,11 @@ type kubernetesGitDeploymentPayload struct {
|
||||||
ManifestFile string
|
ManifestFile string
|
||||||
AdditionalFiles []string
|
AdditionalFiles []string
|
||||||
AutoUpdate *portainer.AutoUpdateSettings
|
AutoUpdate *portainer.AutoUpdateSettings
|
||||||
|
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
||||||
|
TLSSkipVerify bool `example:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func createStackPayloadFromK8sGitPayload(name, repoUrl, repoReference, repoUsername, repoPassword string, repoAuthentication, composeFormat bool, namespace, manifest string, additionalFiles []string, autoUpdate *portainer.AutoUpdateSettings) stackbuilders.StackPayload {
|
func createStackPayloadFromK8sGitPayload(name, repoUrl, repoReference, repoUsername, repoPassword string, repoAuthentication, composeFormat bool, namespace, manifest string, additionalFiles []string, autoUpdate *portainer.AutoUpdateSettings, repoSkipSSLVerify bool) stackbuilders.StackPayload {
|
||||||
return stackbuilders.StackPayload{
|
return stackbuilders.StackPayload{
|
||||||
StackName: name,
|
StackName: name,
|
||||||
RepositoryConfigPayload: stackbuilders.RepositoryConfigPayload{
|
RepositoryConfigPayload: stackbuilders.RepositoryConfigPayload{
|
||||||
|
@ -57,6 +59,7 @@ func createStackPayloadFromK8sGitPayload(name, repoUrl, repoReference, repoUsern
|
||||||
Authentication: repoAuthentication,
|
Authentication: repoAuthentication,
|
||||||
Username: repoUsername,
|
Username: repoUsername,
|
||||||
Password: repoPassword,
|
Password: repoPassword,
|
||||||
|
TLSSkipVerify: repoSkipSSLVerify,
|
||||||
},
|
},
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
ComposeFormat: composeFormat,
|
ComposeFormat: composeFormat,
|
||||||
|
@ -203,7 +206,9 @@ func (handler *Handler) createKubernetesStackFromGitRepository(w http.ResponseWr
|
||||||
payload.Namespace,
|
payload.Namespace,
|
||||||
payload.ManifestFile,
|
payload.ManifestFile,
|
||||||
payload.AdditionalFiles,
|
payload.AdditionalFiles,
|
||||||
payload.AutoUpdate)
|
payload.AutoUpdate,
|
||||||
|
payload.TLSSkipVerify,
|
||||||
|
)
|
||||||
|
|
||||||
k8sStackBuilder := stackbuilders.CreateKubernetesStackGitBuilder(handler.DataStore,
|
k8sStackBuilder := stackbuilders.CreateKubernetesStackGitBuilder(handler.DataStore,
|
||||||
handler.FileService,
|
handler.FileService,
|
||||||
|
|
|
@ -117,6 +117,8 @@ type swarmStackFromGitRepositoryPayload struct {
|
||||||
AdditionalFiles []string `example:"[nz.compose.yml, uat.compose.yml]"`
|
AdditionalFiles []string `example:"[nz.compose.yml, uat.compose.yml]"`
|
||||||
// Optional auto update configuration
|
// Optional auto update configuration
|
||||||
AutoUpdate *portainer.AutoUpdateSettings
|
AutoUpdate *portainer.AutoUpdateSettings
|
||||||
|
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
||||||
|
TLSSkipVerify bool `example:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) error {
|
func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) error {
|
||||||
|
@ -138,7 +140,7 @@ func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createStackPayloadFromSwarmGitPayload(name, swarmID, repoUrl, repoReference, repoUsername, repoPassword string, repoAuthentication bool, composeFile string, additionalFiles []string, autoUpdate *portainer.AutoUpdateSettings, env []portainer.Pair, fromAppTemplate bool) stackbuilders.StackPayload {
|
func createStackPayloadFromSwarmGitPayload(name, swarmID, repoUrl, repoReference, repoUsername, repoPassword string, repoAuthentication bool, composeFile string, additionalFiles []string, autoUpdate *portainer.AutoUpdateSettings, env []portainer.Pair, fromAppTemplate bool, repoSkipSSLVerify bool) stackbuilders.StackPayload {
|
||||||
return stackbuilders.StackPayload{
|
return stackbuilders.StackPayload{
|
||||||
Name: name,
|
Name: name,
|
||||||
SwarmID: swarmID,
|
SwarmID: swarmID,
|
||||||
|
@ -201,7 +203,9 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter,
|
||||||
payload.AdditionalFiles,
|
payload.AdditionalFiles,
|
||||||
payload.AutoUpdate,
|
payload.AutoUpdate,
|
||||||
payload.Env,
|
payload.Env,
|
||||||
payload.FromAppTemplate)
|
payload.FromAppTemplate,
|
||||||
|
payload.TLSSkipVerify,
|
||||||
|
)
|
||||||
|
|
||||||
swarmStackBuilder := stackbuilders.CreateSwarmStackGitBuilder(securityContext,
|
swarmStackBuilder := stackbuilders.CreateSwarmStackGitBuilder(securityContext,
|
||||||
handler.DataStore,
|
handler.DataStore,
|
||||||
|
|
|
@ -158,7 +158,7 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
|
||||||
Username: payload.RepositoryUsername,
|
Username: payload.RepositoryUsername,
|
||||||
Password: password,
|
Password: password,
|
||||||
}
|
}
|
||||||
_, err = handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, stack.GitConfig.Authentication.Username, stack.GitConfig.Authentication.Password)
|
_, err = handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, stack.GitConfig.Authentication.Username, stack.GitConfig.Authentication.Password, stack.GitConfig.TLSSkipVerify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError("Unable to fetch git repository", err)
|
return httperror.InternalServerError("Unable to fetch git repository", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,16 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
||||||
repositoryUsername = payload.RepositoryUsername
|
repositoryUsername = payload.RepositoryUsername
|
||||||
}
|
}
|
||||||
|
|
||||||
clean, err := git.CloneWithBackup(handler.GitService, handler.FileService, git.CloneOptions{ProjectPath: stack.ProjectPath, URL: stack.GitConfig.URL, ReferenceName: stack.GitConfig.ReferenceName, Username: repositoryUsername, Password: repositoryPassword})
|
cloneOptions := git.CloneOptions{
|
||||||
|
ProjectPath: stack.ProjectPath,
|
||||||
|
URL: stack.GitConfig.URL,
|
||||||
|
ReferenceName: stack.GitConfig.ReferenceName,
|
||||||
|
Username: repositoryUsername,
|
||||||
|
Password: repositoryPassword,
|
||||||
|
TLSSkipVerify: stack.GitConfig.TLSSkipVerify,
|
||||||
|
}
|
||||||
|
|
||||||
|
clean, err := git.CloneWithBackup(handler.GitService, handler.FileService, cloneOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError("Unable to clone git repository directory", err)
|
return httperror.InternalServerError("Unable to clone git repository directory", err)
|
||||||
}
|
}
|
||||||
|
@ -157,7 +166,7 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
||||||
return httpErr
|
return httpErr
|
||||||
}
|
}
|
||||||
|
|
||||||
newHash, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, repositoryUsername, repositoryPassword)
|
newHash, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, repositoryUsername, repositoryPassword, 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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ func (handler *Handler) updateKubernetesStack(r *http.Request, stack *portainer.
|
||||||
Username: payload.RepositoryUsername,
|
Username: payload.RepositoryUsername,
|
||||||
Password: password,
|
Password: password,
|
||||||
}
|
}
|
||||||
_, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, stack.GitConfig.Authentication.Username, stack.GitConfig.Authentication.Password)
|
_, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, stack.GitConfig.Authentication.Username, stack.GitConfig.Authentication.Password, stack.GitConfig.TLSSkipVerify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError("Unable to fetch git repository", err)
|
return httperror.InternalServerError("Unable to fetch git repository", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ func (handler *Handler) templateFile(w http.ResponseWriter, r *http.Request) *ht
|
||||||
|
|
||||||
defer handler.cleanUp(projectPath)
|
defer handler.cleanUp(projectPath)
|
||||||
|
|
||||||
err = handler.GitService.CloneRepository(projectPath, payload.RepositoryURL, "", "", "")
|
err = handler.GitService.CloneRepository(projectPath, payload.RepositoryURL, "", "", "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError("Unable to clone git repository", err)
|
return httperror.InternalServerError("Unable to clone git repository", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -395,7 +395,7 @@ func (transport *Transport) updateDefaultGitBranch(request *http.Request) error
|
||||||
remote := request.URL.Query().Get("remote")
|
remote := request.URL.Query().Get("remote")
|
||||||
if strings.HasSuffix(remote, ".git") {
|
if strings.HasSuffix(remote, ".git") {
|
||||||
repositoryURL := remote[:len(remote)-4]
|
repositoryURL := remote[:len(remote)-4]
|
||||||
latestCommitID, err := transport.gitService.LatestCommitID(repositoryURL, "", "", "")
|
latestCommitID, err := transport.gitService.LatestCommitID(repositoryURL, "", "", "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,16 @@
|
||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
type noopGitService struct{}
|
|
||||||
|
|
||||||
func (s *noopGitService) CloneRepository(destination string, repositoryURL, referenceName, username, password string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (s *noopGitService) LatestCommitID(repositoryURL, referenceName, username, password string) (string, error) {
|
|
||||||
return "my-latest-commit-id", nil
|
|
||||||
}
|
|
||||||
func (g *noopGitService) ListRefs(repositoryURL, username, password string, hardRefresh bool) ([]string, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
func (g *noopGitService) ListFiles(repositoryURL, referenceName, username, password string, hardRefresh bool, includedExts []string) ([]string, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTransport_updateDefaultGitBranch(t *testing.T) {
|
func TestTransport_updateDefaultGitBranch(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
gitService portainer.GitService
|
gitService portainer.GitService
|
||||||
|
@ -33,8 +20,10 @@ func TestTransport_updateDefaultGitBranch(t *testing.T) {
|
||||||
request *http.Request
|
request *http.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commitId := "my-latest-commit-id"
|
||||||
|
|
||||||
defaultFields := fields{
|
defaultFields := fields{
|
||||||
gitService: &noopGitService{},
|
gitService: testhelpers.NewGitService(nil, commitId),
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -51,7 +40,7 @@ func TestTransport_updateDefaultGitBranch(t *testing.T) {
|
||||||
request: httptest.NewRequest(http.MethodPost, "http://unixsocket/build?dockerfile=Dockerfile&remote=https://my-host.com/my-user/my-repo.git&t=my-image", nil),
|
request: httptest.NewRequest(http.MethodPost, "http://unixsocket/build?dockerfile=Dockerfile&remote=https://my-host.com/my-user/my-repo.git&t=my-image", nil),
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
expectedQuery: "dockerfile=Dockerfile&remote=https%3A%2F%2Fmy-host.com%2Fmy-user%2Fmy-repo.git%23my-latest-commit-id&t=my-image",
|
expectedQuery: fmt.Sprintf("dockerfile=Dockerfile&remote=https%%3A%%2F%%2Fmy-host.com%%2Fmy-user%%2Fmy-repo.git%%23%s&t=my-image", commitId),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "not append commit ID",
|
name: "not append commit ID",
|
||||||
|
|
|
@ -1,12 +1,32 @@
|
||||||
package testhelpers
|
package testhelpers
|
||||||
|
|
||||||
type gitService struct{}
|
import portainer "github.com/portainer/portainer/api"
|
||||||
|
|
||||||
|
type gitService struct {
|
||||||
|
cloneErr error
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
// NewGitService creates new mock for portainer.GitService.
|
// NewGitService creates new mock for portainer.GitService.
|
||||||
func NewGitService() *gitService {
|
func NewGitService(cloneErr error, id string) portainer.GitService {
|
||||||
return &gitService{}
|
return &gitService{
|
||||||
|
cloneErr: cloneErr,
|
||||||
|
id: id,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *gitService) CloneRepository(destination string, repositoryURL, referenceName string, username, password string) error {
|
func (g *gitService) CloneRepository(destination, repositoryURL, referenceName, username, password string, tlsSkipVerify bool) error {
|
||||||
return nil
|
return g.cloneErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gitService) LatestCommitID(repositoryURL, referenceName, username, password string, tlsSkipVerify bool) (string, error) {
|
||||||
|
return g.id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gitService) ListRefs(repositoryURL, username, password string, hardRefresh bool, tlsSkipVerify bool) ([]string, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gitService) ListFiles(repositoryURL, referenceName, username, password string, hardRefresh bool, includedExts []string, tlsSkipVerify bool) ([]string, error) {
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1393,10 +1393,10 @@ 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) error
|
CloneRepository(destination string, repositoryURL, referenceName, username, password string, tlsSkipVerify bool) error
|
||||||
LatestCommitID(repositoryURL, referenceName, username, password string) (string, error)
|
LatestCommitID(repositoryURL, referenceName, username, password string, tlsSkipVerify bool) (string, error)
|
||||||
ListRefs(repositoryURL, username, password string, hardRefresh bool) ([]string, error)
|
ListRefs(repositoryURL, username, password string, hardRefresh bool, tlsSkipVerify bool) ([]string, error)
|
||||||
ListFiles(repositoryURL, referenceName, username, password string, hardRefresh bool, includeExts []string) ([]string, error)
|
ListFiles(repositoryURL, referenceName, username, password string, hardRefresh bool, includeExts []string, tlsSkipVerify bool) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenAMTService represents a service for managing OpenAMT
|
// OpenAMTService represents a service for managing OpenAMT
|
||||||
|
|
|
@ -53,14 +53,14 @@ func RedeployWhenChanged(stackID portainer.StackID, deployer StackDeployer, data
|
||||||
Str("author", author).
|
Str("author", author).
|
||||||
Str("stack", stack.Name).
|
Str("stack", stack.Name).
|
||||||
Int("endpoint_id", int(stack.EndpointID)).
|
Int("endpoint_id", int(stack.EndpointID)).
|
||||||
Msg("cannot autoupdate a stack, stack author user is missing")
|
Msg("cannot auto update a stack, stack author user is missing")
|
||||||
|
|
||||||
return &StackAuthorMissingErr{int(stack.ID), author}
|
return &StackAuthorMissingErr{int(stack.ID), author}
|
||||||
}
|
}
|
||||||
|
|
||||||
var gitCommitChangedOrForceUpdate bool
|
var gitCommitChangedOrForceUpdate bool
|
||||||
if !stack.FromAppTemplate {
|
if !stack.FromAppTemplate {
|
||||||
updated, newHash, err := update.UpdateGitObject(gitService, datastore, fmt.Sprintf("stack:%d", stackID), stack.GitConfig, stack.AutoUpdate, stack.ProjectPath)
|
updated, newHash, err := update.UpdateGitObject(gitService, fmt.Sprintf("stack:%d", stackID), stack.GitConfig, false, stack.ProjectPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ func RedeployWhenChanged(stackID portainer.StackID, deployer StackDeployer, data
|
||||||
|
|
||||||
err := deployer.DeployKubernetesStack(stack, endpoint, user)
|
err := deployer.DeployKubernetesStack(stack, endpoint, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithMessagef(err, "failed to deploy a kubternetes app stack %v", stackID)
|
return errors.WithMessagef(err, "failed to deploy a kubernetes app stack %v", stackID)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return errors.Errorf("cannot update stack, type %v is unsupported", stack.Type)
|
return errors.Errorf("cannot update stack, type %v is unsupported", stack.Type)
|
||||||
|
|
|
@ -6,33 +6,13 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/portainer/portainer/api/datastore"
|
"github.com/portainer/portainer/api/datastore"
|
||||||
|
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
gittypes "github.com/portainer/portainer/api/git/types"
|
gittypes "github.com/portainer/portainer/api/git/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
type gitService struct {
|
|
||||||
cloneErr error
|
|
||||||
id string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *gitService) CloneRepository(destination, repositoryURL, referenceName, username, password string) error {
|
|
||||||
return g.cloneErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *gitService) LatestCommitID(repositoryURL, referenceName, username, password string) (string, error) {
|
|
||||||
return g.id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *gitService) ListRefs(repositoryURL, username, password string, hardRefresh bool) ([]string, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *gitService) ListFiles(repositoryURL, referenceName, username, password string, hardRefresh bool, includedExts []string) ([]string, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type noopDeployer struct{}
|
type noopDeployer struct{}
|
||||||
|
|
||||||
func (s *noopDeployer) DeploySwarmStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, prune bool, pullImage bool) error {
|
func (s *noopDeployer) DeploySwarmStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, prune bool, pullImage bool) error {
|
||||||
|
@ -67,7 +47,7 @@ func Test_redeployWhenChanged_DoesNothingWhenNotAGitBasedStack(t *testing.T) {
|
||||||
err = store.Stack().Create(&portainer.Stack{ID: 1, CreatedBy: "admin"})
|
err = store.Stack().Create(&portainer.Stack{ID: 1, CreatedBy: "admin"})
|
||||||
assert.NoError(t, err, "failed to create a test stack")
|
assert.NoError(t, err, "failed to create a test stack")
|
||||||
|
|
||||||
err = RedeployWhenChanged(1, nil, store, &gitService{nil, ""})
|
err = RedeployWhenChanged(1, nil, store, testhelpers.NewGitService(nil, ""))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +77,7 @@ func Test_redeployWhenChanged_DoesNothingWhenNoGitChanges(t *testing.T) {
|
||||||
}})
|
}})
|
||||||
assert.NoError(t, err, "failed to create a test stack")
|
assert.NoError(t, err, "failed to create a test stack")
|
||||||
|
|
||||||
err = RedeployWhenChanged(1, nil, store, &gitService{nil, "oldHash"})
|
err = RedeployWhenChanged(1, nil, store, testhelpers.NewGitService(nil, "oldHash"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +105,7 @@ func Test_redeployWhenChanged_FailsWhenCannotClone(t *testing.T) {
|
||||||
}})
|
}})
|
||||||
assert.NoError(t, err, "failed to create a test stack")
|
assert.NoError(t, err, "failed to create a test stack")
|
||||||
|
|
||||||
err = RedeployWhenChanged(1, nil, store, &gitService{cloneErr, "newHash"})
|
err = RedeployWhenChanged(1, nil, store, testhelpers.NewGitService(cloneErr, "newHash"))
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.ErrorIs(t, err, cloneErr, "should failed to clone but didn't, check test setup")
|
assert.ErrorIs(t, err, cloneErr, "should failed to clone but didn't, check test setup")
|
||||||
}
|
}
|
||||||
|
@ -162,7 +142,7 @@ func Test_redeployWhenChanged(t *testing.T) {
|
||||||
stack.Type = portainer.DockerComposeStack
|
stack.Type = portainer.DockerComposeStack
|
||||||
store.Stack().UpdateStack(stack.ID, &stack)
|
store.Stack().UpdateStack(stack.ID, &stack)
|
||||||
|
|
||||||
err = RedeployWhenChanged(1, &noopDeployer{}, store, &gitService{nil, "newHash"})
|
err = RedeployWhenChanged(1, &noopDeployer{}, store, testhelpers.NewGitService(nil, "newHash"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -170,7 +150,7 @@ func Test_redeployWhenChanged(t *testing.T) {
|
||||||
stack.Type = portainer.DockerSwarmStack
|
stack.Type = portainer.DockerSwarmStack
|
||||||
store.Stack().UpdateStack(stack.ID, &stack)
|
store.Stack().UpdateStack(stack.ID, &stack)
|
||||||
|
|
||||||
err = RedeployWhenChanged(1, &noopDeployer{}, store, &gitService{nil, "newHash"})
|
err = RedeployWhenChanged(1, &noopDeployer{}, store, testhelpers.NewGitService(nil, "newHash"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -178,7 +158,7 @@ func Test_redeployWhenChanged(t *testing.T) {
|
||||||
stack.Type = portainer.KubernetesStack
|
stack.Type = portainer.KubernetesStack
|
||||||
store.Stack().UpdateStack(stack.ID, &stack)
|
store.Stack().UpdateStack(stack.ID, &stack)
|
||||||
|
|
||||||
err = RedeployWhenChanged(1, &noopDeployer{}, store, &gitService{nil, "newHash"})
|
err = RedeployWhenChanged(1, &noopDeployer{}, store, testhelpers.NewGitService(nil, "newHash"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,8 @@ func (b *GitMethodStackBuilder) SetGitRepository(payload *StackPayload) GitMetho
|
||||||
|
|
||||||
repoConfig.URL = payload.URL
|
repoConfig.URL = payload.URL
|
||||||
repoConfig.ReferenceName = payload.ReferenceName
|
repoConfig.ReferenceName = payload.ReferenceName
|
||||||
|
repoConfig.TLSSkipVerify = payload.TLSSkipVerify
|
||||||
|
|
||||||
repoConfig.ConfigFilePath = payload.ComposeFile
|
repoConfig.ConfigFilePath = payload.ComposeFile
|
||||||
if payload.ComposeFile == "" {
|
if payload.ComposeFile == "" {
|
||||||
repoConfig.ConfigFilePath = filesystem.ComposeFileDefaultName
|
repoConfig.ConfigFilePath = filesystem.ComposeFileDefaultName
|
||||||
|
|
|
@ -52,4 +52,6 @@ type RepositoryConfigPayload struct {
|
||||||
// Password used in basic authentication. Required when RepositoryAuthentication is true
|
// Password used in basic authentication. Required when RepositoryAuthentication is true
|
||||||
// and RepositoryGitCredentialID is 0
|
// and RepositoryGitCredentialID is 0
|
||||||
Password string `example:"myGitPassword"`
|
Password string `example:"myGitPassword"`
|
||||||
|
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
||||||
|
TLSSkipVerify bool `example:"false"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ func DownloadGitRepository(stackID portainer.StackID, config gittypes.RepoConfig
|
||||||
stackFolder := fmt.Sprintf("%d", stackID)
|
stackFolder := fmt.Sprintf("%d", stackID)
|
||||||
projectPath := fileService.GetStackProjectPath(stackFolder)
|
projectPath := fileService.GetStackProjectPath(stackFolder)
|
||||||
|
|
||||||
err := gitService.CloneRepository(projectPath, config.URL, config.ReferenceName, username, password)
|
err := gitService.CloneRepository(projectPath, config.URL, config.ReferenceName, username, password, config.TLSSkipVerify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gittypes.ErrAuthenticationFailure {
|
if err == gittypes.ErrAuthenticationFailure {
|
||||||
newErr := ErrInvalidGitCredential
|
newErr := ErrInvalidGitCredential
|
||||||
|
@ -38,7 +38,7 @@ func DownloadGitRepository(stackID portainer.StackID, config gittypes.RepoConfig
|
||||||
return "", newErr
|
return "", newErr
|
||||||
}
|
}
|
||||||
|
|
||||||
commitID, err := gitService.LatestCommitID(config.URL, config.ReferenceName, username, password)
|
commitID, err := gitService.LatestCommitID(config.URL, config.ReferenceName, username, password, 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
|
||||||
|
|
|
@ -56,6 +56,7 @@ angular.module('portainer.edge').factory('EdgeStackService', function EdgeStackS
|
||||||
RepositoryAuthentication: repositoryOptions.RepositoryAuthentication,
|
RepositoryAuthentication: repositoryOptions.RepositoryAuthentication,
|
||||||
RepositoryUsername: repositoryOptions.RepositoryUsername,
|
RepositoryUsername: repositoryOptions.RepositoryUsername,
|
||||||
RepositoryPassword: repositoryOptions.RepositoryPassword,
|
RepositoryPassword: repositoryOptions.RepositoryPassword,
|
||||||
|
TLSSkipVerify: repositoryOptions.TLSSkipVerify,
|
||||||
}
|
}
|
||||||
).$promise;
|
).$promise;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ export default class CreateEdgeStackViewController {
|
||||||
Groups: [],
|
Groups: [],
|
||||||
DeploymentType: 0,
|
DeploymentType: 0,
|
||||||
UseManifestNamespaces: false,
|
UseManifestNamespaces: false,
|
||||||
|
TLSSkipVerify: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.EditorType = EditorType;
|
this.EditorType = EditorType;
|
||||||
|
@ -215,6 +216,7 @@ export default class CreateEdgeStackViewController {
|
||||||
RepositoryAuthentication: this.formValues.RepositoryAuthentication,
|
RepositoryAuthentication: this.formValues.RepositoryAuthentication,
|
||||||
RepositoryUsername: this.formValues.RepositoryUsername,
|
RepositoryUsername: this.formValues.RepositoryUsername,
|
||||||
RepositoryPassword: this.formValues.RepositoryPassword,
|
RepositoryPassword: this.formValues.RepositoryPassword,
|
||||||
|
TLSSkipVerify: this.formValues.TLSSkipVerify,
|
||||||
};
|
};
|
||||||
return this.EdgeStackService.createStackFromGitRepository(
|
return this.EdgeStackService.createStackFromGitRepository(
|
||||||
{
|
{
|
||||||
|
|
|
@ -59,6 +59,7 @@ class KubernetesDeployController {
|
||||||
ComposeFilePathInRepository: '',
|
ComposeFilePathInRepository: '',
|
||||||
Variables: {},
|
Variables: {},
|
||||||
AutoUpdate: parseAutoUpdateResponse(),
|
AutoUpdate: parseAutoUpdateResponse(),
|
||||||
|
TLSSkipVerify: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.ManifestDeployTypes = KubernetesDeployManifestTypes;
|
this.ManifestDeployTypes = KubernetesDeployManifestTypes;
|
||||||
|
@ -248,6 +249,7 @@ class KubernetesDeployController {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (method === KubernetesDeployRequestMethods.REPOSITORY) {
|
if (method === KubernetesDeployRequestMethods.REPOSITORY) {
|
||||||
|
payload.TLSSkipVerify = this.formValues.TLSSkipVerify;
|
||||||
payload.RepositoryURL = this.formValues.RepositoryURL;
|
payload.RepositoryURL = this.formValues.RepositoryURL;
|
||||||
payload.RepositoryReferenceName = this.formValues.RepositoryReferenceName;
|
payload.RepositoryReferenceName = this.formValues.RepositoryReferenceName;
|
||||||
payload.RepositoryAuthentication = this.formValues.RepositoryAuthentication ? true : false;
|
payload.RepositoryAuthentication = this.formValues.RepositoryAuthentication ? true : false;
|
||||||
|
|
|
@ -355,6 +355,7 @@ angular.module('portainer.app').factory('StackService', [
|
||||||
RepositoryPassword: repositoryOptions.RepositoryPassword,
|
RepositoryPassword: repositoryOptions.RepositoryPassword,
|
||||||
Env: env,
|
Env: env,
|
||||||
FromAppTemplate: repositoryOptions.FromAppTemplate,
|
FromAppTemplate: repositoryOptions.FromAppTemplate,
|
||||||
|
TLSSkipVerify: repositoryOptions.TLSSkipVerify,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (repositoryOptions.AutoUpdate) {
|
if (repositoryOptions.AutoUpdate) {
|
||||||
|
@ -382,6 +383,7 @@ angular.module('portainer.app').factory('StackService', [
|
||||||
RepositoryPassword: repositoryOptions.RepositoryPassword,
|
RepositoryPassword: repositoryOptions.RepositoryPassword,
|
||||||
Env: env,
|
Env: env,
|
||||||
FromAppTemplate: repositoryOptions.FromAppTemplate,
|
FromAppTemplate: repositoryOptions.FromAppTemplate,
|
||||||
|
TLSSkipVerify: repositoryOptions.TLSSkipVerify,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (repositoryOptions.AutoUpdate) {
|
if (repositoryOptions.AutoUpdate) {
|
||||||
|
|
|
@ -44,6 +44,7 @@ class CreateCustomTemplateViewController {
|
||||||
Type: 1,
|
Type: 1,
|
||||||
AccessControlData: new AccessControlFormData(),
|
AccessControlData: new AccessControlFormData(),
|
||||||
Variables: [],
|
Variables: [],
|
||||||
|
TLSSkipVerify: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
|
|
@ -57,6 +57,7 @@ angular
|
||||||
EnableWebhook: false,
|
EnableWebhook: false,
|
||||||
Variables: {},
|
Variables: {},
|
||||||
AutoUpdate: parseAutoUpdateResponse(),
|
AutoUpdate: parseAutoUpdateResponse(),
|
||||||
|
TLSSkipVerify: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
|
@ -175,6 +176,7 @@ angular
|
||||||
RepositoryUsername: $scope.formValues.RepositoryUsername,
|
RepositoryUsername: $scope.formValues.RepositoryUsername,
|
||||||
RepositoryPassword: $scope.formValues.RepositoryPassword,
|
RepositoryPassword: $scope.formValues.RepositoryPassword,
|
||||||
AutoUpdate: transformAutoUpdateViewModel($scope.formValues.AutoUpdate, $scope.state.webhookId),
|
AutoUpdate: transformAutoUpdateViewModel($scope.formValues.AutoUpdate, $scope.state.webhookId),
|
||||||
|
TLSSkipVerify: $scope.formValues.TLSSkipVerify,
|
||||||
};
|
};
|
||||||
|
|
||||||
return StackService.createSwarmStackFromGitRepository(name, repositoryOptions, env, endpointId);
|
return StackService.createSwarmStackFromGitRepository(name, repositoryOptions, env, endpointId);
|
||||||
|
@ -201,6 +203,7 @@ angular
|
||||||
RepositoryUsername: $scope.formValues.RepositoryUsername,
|
RepositoryUsername: $scope.formValues.RepositoryUsername,
|
||||||
RepositoryPassword: $scope.formValues.RepositoryPassword,
|
RepositoryPassword: $scope.formValues.RepositoryPassword,
|
||||||
AutoUpdate: transformAutoUpdateViewModel($scope.formValues.AutoUpdate, $scope.state.webhookId),
|
AutoUpdate: transformAutoUpdateViewModel($scope.formValues.AutoUpdate, $scope.state.webhookId),
|
||||||
|
TLSSkipVerify: $scope.formValues.TLSSkipVerify,
|
||||||
};
|
};
|
||||||
|
|
||||||
return StackService.createComposeStackFromGitRepository(name, repositoryOptions, env, endpointId);
|
return StackService.createComposeStackFromGitRepository(name, repositoryOptions, env, endpointId);
|
||||||
|
|
|
@ -35,6 +35,7 @@ export function PathSelector({
|
||||||
repository: model.RepositoryURL,
|
repository: model.RepositoryURL,
|
||||||
keyword: searchTerm,
|
keyword: searchTerm,
|
||||||
reference: model.RepositoryReferenceName,
|
reference: model.RepositoryReferenceName,
|
||||||
|
tlsSkipVerify: model.TLSSkipVerify,
|
||||||
...creds,
|
...creds,
|
||||||
};
|
};
|
||||||
const enabled = Boolean(
|
const enabled = Boolean(
|
||||||
|
|
|
@ -70,6 +70,7 @@ export function Primary({
|
||||||
ComposeFilePathInRepository: '',
|
ComposeFilePathInRepository: '',
|
||||||
NewCredentialName: '',
|
NewCredentialName: '',
|
||||||
SaveCredential: false,
|
SaveCredential: false,
|
||||||
|
TLSSkipVerify: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { TimeWindowDisplay } from '@/react/portainer/gitops/TimeWindowDisplay';
|
||||||
|
|
||||||
import { FormSection } from '@@/form-components/FormSection';
|
import { FormSection } from '@@/form-components/FormSection';
|
||||||
import { validateForm } from '@@/form-components/validate-form';
|
import { validateForm } from '@@/form-components/validate-form';
|
||||||
|
import { SwitchField } from '@@/form-components/SwitchField';
|
||||||
|
|
||||||
import { GitCredential } from '../account/git-credentials/types';
|
import { GitCredential } from '../account/git-credentials/types';
|
||||||
|
|
||||||
|
@ -104,6 +105,19 @@ export function GitForm({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<TimeWindowDisplay />
|
<TimeWindowDisplay />
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<SwitchField
|
||||||
|
label="Skip TLS Verification"
|
||||||
|
checked={value.TLSSkipVerify}
|
||||||
|
onChange={(value) => handleChange({ TLSSkipVerify: value })}
|
||||||
|
name="TLSSkipVerify"
|
||||||
|
tooltip="Enabling this will allow skipping TLS validation for any self-signed certificate."
|
||||||
|
labelClass="col-sm-3 col-lg-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -127,7 +141,18 @@ export function buildGitValidationSchema(
|
||||||
): SchemaOf<GitFormModel> {
|
): SchemaOf<GitFormModel> {
|
||||||
return object({
|
return object({
|
||||||
RepositoryURL: string()
|
RepositoryURL: string()
|
||||||
.url('Invalid Url')
|
.test('valid URL', 'The URL must be a valid URL', (value) => {
|
||||||
|
if (!value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(value);
|
||||||
|
return !!url.hostname;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
.required('Repository URL is required'),
|
.required('Repository URL is required'),
|
||||||
RepositoryReferenceName: refFieldValidation(),
|
RepositoryReferenceName: refFieldValidation(),
|
||||||
ComposeFilePathInRepository: string().required(
|
ComposeFilePathInRepository: string().required(
|
||||||
|
@ -136,5 +161,6 @@ export function buildGitValidationSchema(
|
||||||
AdditionalFiles: array(string().required('Path is required')).default([]),
|
AdditionalFiles: array(string().required('Path is required')).default([]),
|
||||||
RepositoryURLValid: boolean().default(false),
|
RepositoryURLValid: boolean().default(false),
|
||||||
AutoUpdate: autoUpdateValidation().nullable(),
|
AutoUpdate: autoUpdateValidation().nullable(),
|
||||||
|
TLSSkipVerify: boolean().default(false),
|
||||||
}).concat(gitAuthValidation(gitCredentials));
|
}).concat(gitAuthValidation(gitCredentials));
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,14 +40,18 @@ export function GitFormUrlField({
|
||||||
|
|
||||||
const creds = getAuthentication(model);
|
const creds = getAuthentication(model);
|
||||||
const [force, setForce] = useState(false);
|
const [force, setForce] = useState(false);
|
||||||
const repoStatusQuery = useCheckRepo(value, creds, force, {
|
const repoStatusQuery = useCheckRepo(
|
||||||
|
value,
|
||||||
|
{ creds, force, tlsSkipVerify: model.TLSSkipVerify },
|
||||||
|
{
|
||||||
onSettled(isValid) {
|
onSettled(isValid) {
|
||||||
onChangeRepositoryValid(!!isValid);
|
onChangeRepositoryValid(!!isValid);
|
||||||
setForce(false);
|
setForce(false);
|
||||||
},
|
},
|
||||||
// disabled check on CE since it's not supported
|
// disabled check on CE since it's not supported
|
||||||
enabled: isBE,
|
enabled: isBE,
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const [debouncedValue, debouncedOnChange] = useDebounce(value, onChange);
|
const [debouncedValue, debouncedOnChange] = useDebounce(value, onChange);
|
||||||
|
|
||||||
|
@ -115,7 +119,7 @@ export function useUrlValidation(force: boolean) {
|
||||||
const model = context.parent as GitFormModel;
|
const model = context.parent as GitFormModel;
|
||||||
|
|
||||||
const creds = getAuthentication(model);
|
const creds = getAuthentication(model);
|
||||||
return checkRepo(url, creds, force);
|
return checkRepo(url, { creds, force });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ export function RefSelector({
|
||||||
const payload = {
|
const payload = {
|
||||||
repository: model.RepositoryURL,
|
repository: model.RepositoryURL,
|
||||||
stackId,
|
stackId,
|
||||||
|
tlsSkipVerify: model.TLSSkipVerify,
|
||||||
...creds,
|
...creds,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,4 +2,5 @@ import { GitCredentialsModel } from '../types';
|
||||||
|
|
||||||
export interface RefFieldModel extends GitCredentialsModel {
|
export interface RefFieldModel extends GitCredentialsModel {
|
||||||
RepositoryURL: string;
|
RepositoryURL: string;
|
||||||
|
TLSSkipVerify?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,23 @@ interface Creds {
|
||||||
password?: string;
|
password?: string;
|
||||||
gitCredentialId?: number;
|
gitCredentialId?: number;
|
||||||
}
|
}
|
||||||
|
interface CheckRepoOptions {
|
||||||
|
creds?: Creds;
|
||||||
|
force?: boolean;
|
||||||
|
tlsSkipVerify?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export function useCheckRepo(
|
export function useCheckRepo(
|
||||||
url: string,
|
url: string,
|
||||||
creds: Creds,
|
options: CheckRepoOptions,
|
||||||
force: boolean,
|
|
||||||
{
|
{
|
||||||
enabled,
|
enabled,
|
||||||
onSettled,
|
onSettled,
|
||||||
}: { enabled?: boolean; onSettled?(isValid?: boolean): void } = {}
|
}: { enabled?: boolean; onSettled?(isValid?: boolean): void } = {}
|
||||||
) {
|
) {
|
||||||
return useQuery(
|
return useQuery(
|
||||||
['git_repo_valid', url, creds, force],
|
['git_repo_valid', url, options],
|
||||||
() => checkRepo(url, creds, force),
|
() => checkRepo(url, options),
|
||||||
{
|
{
|
||||||
enabled: !!url && enabled,
|
enabled: !!url && enabled,
|
||||||
onSettled,
|
onSettled,
|
||||||
|
@ -31,13 +35,12 @@ export function useCheckRepo(
|
||||||
|
|
||||||
export async function checkRepo(
|
export async function checkRepo(
|
||||||
repository: string,
|
repository: string,
|
||||||
creds: Creds,
|
{ force, ...options }: CheckRepoOptions
|
||||||
force: boolean
|
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await axios.post<string[]>(
|
await axios.post<string[]>(
|
||||||
'/gitops/repo/refs',
|
'/gitops/repo/refs',
|
||||||
{ repository, ...creds },
|
{ repository, tlsSkipVerify: options.tlsSkipVerify, ...options.creds },
|
||||||
force ? { params: { force } } : {}
|
force ? { params: { force } } : {}
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
|
@ -45,11 +48,12 @@ export async function checkRepo(
|
||||||
throw parseAxiosError(error as Error, '', (axiosError: AxiosError) => {
|
throw parseAxiosError(error as Error, '', (axiosError: AxiosError) => {
|
||||||
let details = axiosError.response?.data.details;
|
let details = axiosError.response?.data.details;
|
||||||
|
|
||||||
|
const { creds = {} } = options;
|
||||||
// If no credentials were provided alter error from git to indicate repository is not found or is private
|
// If no credentials were provided alter error from git to indicate repository is not found or is private
|
||||||
if (
|
if (
|
||||||
!(creds.username && creds.password) &&
|
!(creds.username && creds.password) &&
|
||||||
details ===
|
details ===
|
||||||
'Authentication failed, please ensure that the git credentials are correct.'
|
'authentication failed, please ensure that the git credentials are correct'
|
||||||
) {
|
) {
|
||||||
details =
|
details =
|
||||||
'Git repository could not be found or is private, please ensure that the URL is correct or credentials are provided.';
|
'Git repository could not be found or is private, please ensure that the URL is correct or credentials are provided.';
|
||||||
|
|
|
@ -6,6 +6,7 @@ interface RefsPayload {
|
||||||
repository: string;
|
repository: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
|
tlsSkipVerify?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGitRefs<T = string[]>(
|
export function useGitRefs<T = string[]>(
|
||||||
|
|
|
@ -60,6 +60,7 @@ export interface GitFormModel extends GitAuthModel {
|
||||||
|
|
||||||
SaveCredential?: boolean;
|
SaveCredential?: boolean;
|
||||||
NewCredentialName?: string;
|
NewCredentialName?: string;
|
||||||
|
TLSSkipVerify: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto update
|
* Auto update
|
||||||
|
|
Loading…
Reference in New Issue