Introduced ClonePrivateRepositoryWithDeploymentKey and its handler

pull/2972/head
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 package git
import ( import (
"net/url" "golang.org/x/crypto/ssh"
"strings"
"gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing" "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. // 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) _, err := git.PlainClone(destination, false, options)
return err 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) return response.JSON(w, stack)
} }
// Update struc to cater for deployment key
type composeStackFromGitRepositoryPayload struct { type composeStackFromGitRepositoryPayload struct {
Name string Name string
RepositoryURL string RepositoryURL string
RepositoryReferenceName string RepositoryReferenceName string
RepositoryAuthentication bool RepositoryAuthentication bool
RepositoryDeploymentKey string
RepositoryUsername string RepositoryUsername string
RepositoryPassword string RepositoryPassword string
ComposeFilePathInRepository string ComposeFilePathInRepository string
@ -145,11 +147,13 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
projectPath := handler.FileService.GetStackProjectPath(strconv.Itoa(int(stack.ID))) projectPath := handler.FileService.GetStackProjectPath(strconv.Itoa(int(stack.ID)))
stack.ProjectPath = projectPath stack.ProjectPath = projectPath
// Add Deployment Key
gitCloneParams := &cloneRepositoryParameters{ gitCloneParams := &cloneRepositoryParameters{
url: payload.RepositoryURL, url: payload.RepositoryURL,
referenceName: payload.RepositoryReferenceName, referenceName: payload.RepositoryReferenceName,
path: projectPath, path: projectPath,
authentication: payload.RepositoryAuthentication, authentication: payload.RepositoryAuthentication,
deploymentKey: payload.RepositoryDeploymentKey,
username: payload.RepositoryUsername, username: payload.RepositoryUsername,
password: payload.RepositoryPassword, password: payload.RepositoryPassword,
} }

View File

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

View File

@ -5,13 +5,18 @@ type cloneRepositoryParameters struct {
referenceName string referenceName string
path string path string
authentication bool authentication bool
deploymentKey string
username string username string
password string password string
} }
func (handler *Handler) cloneGitRepository(parameters *cloneRepositoryParameters) error { 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) 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) return handler.GitService.ClonePublicRepository(parameters.url, parameters.referenceName, parameters.path)
} }

View File

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

View File

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

View File

@ -817,6 +817,7 @@ type (
GitService interface { GitService interface {
ClonePublicRepository(repositoryURL, referenceName string, destination string) error ClonePublicRepository(repositoryURL, referenceName string, destination string) error
ClonePrivateRepositoryWithBasicAuth(repositoryURL, referenceName string, destination, username, password 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 // JobScheduler represents a service to run jobs on a periodic basis