Introduced ClonePrivateRepositoryWithDeploymentKey and its handler

feat1752-introduce-deploymentkeys
ssbkang 2019-07-01 23:18:04 +12:00
parent 7dd9e9e365
commit f496776490
7 changed files with 62 additions and 10 deletions

View File

@ -1,11 +1,12 @@
package git
import (
"net/url"
"strings"
"golang.org/x/crypto/ssh"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing"
gitSsh "gopkg.in/src-d/go-git.v4/plumbing/transport/ssh"
"net/url"
"strings"
)
// Service represents a service for managing Git.
@ -44,3 +45,34 @@ func cloneRepository(repositoryURL, referenceName string, destination string) er
_, err := git.PlainClone(destination, false, options)
return err
}
func (service *Service) ClonePrivateRepositoryWithDeploymentKey(repositoryURL, referenceName string, destination string, privateKeyPem []byte) error {
// url := "git@github.com:ssbkang/personal-development.git"
// directory := "personal-development"
// https://github.com/portainer/portainer-compose
signer, _ := ssh.ParsePrivateKey(privateKeyPem)
auth := &gitSsh.PublicKeys{
User: "git",
Signer: signer,
HostKeyCallbackHelper: gitSsh.HostKeyCallbackHelper{
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
},
}
repositoryURL = strings.Replace(repositoryURL, "https://", "git@", 1)
repositoryURL += ".git"
options := &git.CloneOptions{
URL: repositoryURL,
RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
Auth: auth,
}
if referenceName != "" {
options.ReferenceName = plumbing.ReferenceName(referenceName)
}
_, err := git.PlainClone(destination, false, options)
return err
}

View File

@ -87,11 +87,13 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter,
return response.JSON(w, stack)
}
// Update struc to cater for deployment key
type composeStackFromGitRepositoryPayload struct {
Name string
RepositoryURL string
RepositoryReferenceName string
RepositoryAuthentication bool
RepositoryDeploymentKey string
RepositoryUsername string
RepositoryPassword string
ComposeFilePathInRepository string
@ -145,11 +147,13 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
projectPath := handler.FileService.GetStackProjectPath(strconv.Itoa(int(stack.ID)))
stack.ProjectPath = projectPath
// Add Deployment Key
gitCloneParams := &cloneRepositoryParameters{
url: payload.RepositoryURL,
referenceName: payload.RepositoryReferenceName,
path: projectPath,
authentication: payload.RepositoryAuthentication,
deploymentKey: payload.RepositoryDeploymentKey,
username: payload.RepositoryUsername,
password: payload.RepositoryPassword,
}

View File

@ -1,10 +1,6 @@
package stacks
import (
"net/http"
"strconv"
"strings"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
@ -12,6 +8,9 @@ import (
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/http/security"
"net/http"
"strconv"
"strings"
)
type swarmStackFromFileContentPayload struct {
@ -92,6 +91,7 @@ func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r
return response.JSON(w, stack)
}
// Update struc to cater for deployment key
type swarmStackFromGitRepositoryPayload struct {
Name string
SwarmID string
@ -99,6 +99,7 @@ type swarmStackFromGitRepositoryPayload struct {
RepositoryURL string
RepositoryReferenceName string
RepositoryAuthentication bool
RepositoryDeploymentKey string
RepositoryUsername string
RepositoryPassword string
ComposeFilePathInRepository string
@ -111,12 +112,18 @@ func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) err
if govalidator.IsNull(payload.SwarmID) {
return portainer.Error("Invalid Swarm ID")
}
// Need to improve validators for SSH based URL and deployment key type
/*
if govalidator.IsNull(payload.RepositoryURL) || !govalidator.IsURL(payload.RepositoryURL) {
return portainer.Error("Invalid repository URL. Must correspond to a valid URL format")
}
if payload.RepositoryAuthentication && (govalidator.IsNull(payload.RepositoryUsername) || govalidator.IsNull(payload.RepositoryPassword)) {
return portainer.Error("Invalid repository credentials. Username and password must be specified when authentication is enabled")
if payload.RepositoryAuthentication && (govalidator.IsNull(payload.RepositoryDeploymentKey)) || (govalidator.IsNull(payload.RepositoryUsername) || govalidator.IsNull(payload.RepositoryPassword)) {
return portainer.Error("Invalid repository credentials or deploymenet key. Either Username & password or a deployment key must be specified when authentication is enabled")
}
*/
if govalidator.IsNull(payload.ComposeFilePathInRepository) {
payload.ComposeFilePathInRepository = filesystem.ComposeFileDefaultName
}
@ -160,6 +167,7 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter,
referenceName: payload.RepositoryReferenceName,
path: projectPath,
authentication: payload.RepositoryAuthentication,
deploymentKey: payload.RepositoryDeploymentKey,
username: payload.RepositoryUsername,
password: payload.RepositoryPassword,
}

View File

@ -5,13 +5,18 @@ type cloneRepositoryParameters struct {
referenceName string
path string
authentication bool
deploymentKey string
username string
password string
}
func (handler *Handler) cloneGitRepository(parameters *cloneRepositoryParameters) error {
if parameters.authentication {
if parameters.authentication && parameters.username != "" && parameters.password != "" {
return handler.GitService.ClonePrivateRepositoryWithBasicAuth(parameters.url, parameters.referenceName, parameters.path, parameters.username, parameters.password)
}
if parameters.authentication && parameters.deploymentKey != "" {
deploymentKey, _ := handler.DeploymentKeyService.DeploymentKeyByName(parameters.deploymentKey)
return handler.GitService.ClonePrivateRepositoryWithDeploymentKey(parameters.url, parameters.referenceName, parameters.path, deploymentKey.PrivateKey)
}
return handler.GitService.ClonePublicRepository(parameters.url, parameters.referenceName, parameters.path)
}

View File

@ -16,6 +16,7 @@ type Handler struct {
stackDeletionMutex *sync.Mutex
requestBouncer *security.RequestBouncer
*mux.Router
DeploymentKeyService portainer.DeploymentKeyService
FileService portainer.FileService
GitService portainer.GitService
StackService portainer.StackService

View File

@ -182,6 +182,7 @@ func (server *Server) Start() error {
stackHandler.FileService = server.FileService
stackHandler.StackService = server.StackService
stackHandler.EndpointService = server.EndpointService
stackHandler.DeploymentKeyService = server.DeploymentKeyService
stackHandler.ResourceControlService = server.ResourceControlService
stackHandler.SwarmStackManager = server.SwarmStackManager
stackHandler.ComposeStackManager = server.ComposeStackManager

View File

@ -817,6 +817,7 @@ type (
GitService interface {
ClonePublicRepository(repositoryURL, referenceName string, destination string) error
ClonePrivateRepositoryWithBasicAuth(repositoryURL, referenceName string, destination, username, password string) error
ClonePrivateRepositoryWithDeploymentKey(repositoryURL, referenceName string, destination string, privateKeyPem []byte) error
}
// JobScheduler represents a service to run jobs on a periodic basis