mirror of https://github.com/portainer/portainer
feat(stack): Introduce SSH Key Based Deployment
parent
5d169ad827
commit
ef800d5a7d
|
@ -47,10 +47,6 @@ func cloneRepository(repositoryURL, referenceName string, destination string) er
|
|||
}
|
||||
|
||||
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",
|
||||
|
|
|
@ -36,8 +36,6 @@ func (handler *Handler) deploymentkeyCreate(w http.ResponseWriter, r *http.Reque
|
|||
return &httperror.HandlerError{http.StatusConflict, "A deploymentkey for this resource already exists", portainer.ErrDeploymentkeyAlreadyExists}
|
||||
}
|
||||
|
||||
// Add a function to call and create public key and private key
|
||||
|
||||
private, public, err := handler.SignatureService.GenerateDeploymentKeyPair()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create private and public key pairs", err}
|
||||
|
|
|
@ -26,16 +26,12 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/deployment_keys",
|
||||
// bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.deploymentkeyCreate))).Methods(http.MethodPost)
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.deploymentkeyCreate))).Methods(http.MethodPost)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyCreate))).Methods(http.MethodPost)
|
||||
h.Handle("/deployment_keys",
|
||||
// bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.deploymentkeyList))).Methods(http.MethodGet)
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.deploymentkeyList))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyList))).Methods(http.MethodGet)
|
||||
h.Handle("/deployment_keys/{id}",
|
||||
// bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.deploymentkeyList))).Methods(http.MethodGet)
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.deploymentkeyInspect))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyInspect))).Methods(http.MethodGet)
|
||||
h.Handle("/deployment_keys/{id}",
|
||||
// bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.deploymentkeyDelete))).Methods(http.MethodDelete)
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.deploymentkeyDelete))).Methods(http.MethodDelete)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyDelete))).Methods(http.MethodDelete)
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -87,17 +87,16 @@ 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
|
||||
Env []portainer.Pair
|
||||
Name string
|
||||
RepositoryURL string
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthenticationType string
|
||||
RepositoryDeploymentKey string
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
ComposeFilePathInRepository string
|
||||
Env []portainer.Pair
|
||||
}
|
||||
|
||||
func (payload *composeStackFromGitRepositoryPayload) Validate(r *http.Request) error {
|
||||
|
@ -107,8 +106,11 @@ func (payload *composeStackFromGitRepositoryPayload) Validate(r *http.Request) e
|
|||
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.RepositoryAuthenticationType == "BasicAuth" && (govalidator.IsNull(payload.RepositoryUsername) || govalidator.IsNull(payload.RepositoryPassword)) {
|
||||
return portainer.Error("Invalid repository credentials or deploymenet key. Either username & password must be specified when authentication type is BasicAuth")
|
||||
}
|
||||
if payload.RepositoryAuthenticationType == "DeploymentKey" && govalidator.IsNull(payload.RepositoryDeploymentKey) {
|
||||
return portainer.Error("Invalid repository deploymenet key. A deployment key must be specified when authentication type is DeploymentKey")
|
||||
}
|
||||
if govalidator.IsNull(payload.ComposeFilePathInRepository) {
|
||||
payload.ComposeFilePathInRepository = filesystem.ComposeFileDefaultName
|
||||
|
@ -147,15 +149,22 @@ 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,
|
||||
url: payload.RepositoryURL,
|
||||
referenceName: payload.RepositoryReferenceName,
|
||||
path: projectPath,
|
||||
authenticationType: payload.RepositoryAuthenticationType,
|
||||
deploymentKey: nil,
|
||||
username: payload.RepositoryUsername,
|
||||
password: payload.RepositoryPassword,
|
||||
}
|
||||
|
||||
if payload.RepositoryDeploymentKey != "" {
|
||||
deploymentKey, err := handler.DeploymentKeyService.DeploymentKeyByName(payload.Name)
|
||||
if err != nil && err != portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "An error occurred retrieving deploymentkey from the database", err}
|
||||
}
|
||||
gitCloneParams.deploymentKey = deploymentKey.PrivateKey
|
||||
}
|
||||
|
||||
doCleanUp := true
|
||||
|
|
|
@ -93,16 +93,16 @@ func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r
|
|||
|
||||
// Update struc to cater for deployment key
|
||||
type swarmStackFromGitRepositoryPayload struct {
|
||||
Name string
|
||||
SwarmID string
|
||||
Env []portainer.Pair
|
||||
RepositoryURL string
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryDeploymentKey string
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
ComposeFilePathInRepository string
|
||||
Name string
|
||||
SwarmID string
|
||||
Env []portainer.Pair
|
||||
RepositoryURL string
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthenticationType string
|
||||
RepositoryDeploymentKey string
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
ComposeFilePathInRepository string
|
||||
}
|
||||
|
||||
func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) error {
|
||||
|
@ -112,18 +112,15 @@ func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) err
|
|||
if govalidator.IsNull(payload.SwarmID) {
|
||||
return portainer.Error("Invalid Swarm ID")
|
||||
}
|
||||
|
||||
if govalidator.IsNull(payload.RepositoryURL) || !govalidator.IsURL(payload.RepositoryURL) {
|
||||
return portainer.Error("Invalid repository URL. Must correspond to a valid URL format")
|
||||
}
|
||||
|
||||
// Need to improve validators for SSH based URL and deployment key type
|
||||
/*
|
||||
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 payload.RepositoryAuthenticationType == "BasicAuth" && (govalidator.IsNull(payload.RepositoryUsername) || govalidator.IsNull(payload.RepositoryPassword)) {
|
||||
return portainer.Error("Invalid repository credentials or deploymenet key. Either username & password must be specified when authentication type is BasicAuth")
|
||||
}
|
||||
if payload.RepositoryAuthenticationType == "DeploymentKey" && govalidator.IsNull(payload.RepositoryDeploymentKey) {
|
||||
return portainer.Error("Invalid repository deploymenet key. A deployment key must be specified when authentication type is DeploymentKey")
|
||||
}
|
||||
*/
|
||||
|
||||
if govalidator.IsNull(payload.ComposeFilePathInRepository) {
|
||||
payload.ComposeFilePathInRepository = filesystem.ComposeFileDefaultName
|
||||
}
|
||||
|
@ -163,13 +160,22 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter,
|
|||
stack.ProjectPath = projectPath
|
||||
|
||||
gitCloneParams := &cloneRepositoryParameters{
|
||||
url: payload.RepositoryURL,
|
||||
referenceName: payload.RepositoryReferenceName,
|
||||
path: projectPath,
|
||||
authentication: payload.RepositoryAuthentication,
|
||||
deploymentKey: payload.RepositoryDeploymentKey,
|
||||
username: payload.RepositoryUsername,
|
||||
password: payload.RepositoryPassword,
|
||||
url: payload.RepositoryURL,
|
||||
referenceName: payload.RepositoryReferenceName,
|
||||
path: projectPath,
|
||||
authenticationType: payload.RepositoryAuthenticationType,
|
||||
deploymentKey: nil,
|
||||
username: payload.RepositoryUsername,
|
||||
password: payload.RepositoryPassword,
|
||||
}
|
||||
|
||||
if payload.RepositoryDeploymentKey != "" {
|
||||
deploymentKey, err := handler.DeploymentKeyService.DeploymentKeyByName(payload.RepositoryDeploymentKey)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "An error occurred retrieving deploymentkey from the database", err}
|
||||
}
|
||||
|
||||
gitCloneParams.deploymentKey = deploymentKey.PrivateKey
|
||||
}
|
||||
|
||||
doCleanUp := true
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
package stacks
|
||||
|
||||
type cloneRepositoryParameters struct {
|
||||
url string
|
||||
referenceName string
|
||||
path string
|
||||
authentication bool
|
||||
deploymentKey string
|
||||
username string
|
||||
password string
|
||||
url string
|
||||
referenceName string
|
||||
path string
|
||||
authenticationType string
|
||||
deploymentKey []byte
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
func (handler *Handler) cloneGitRepository(parameters *cloneRepositoryParameters) error {
|
||||
if parameters.authentication && parameters.username != "" && parameters.password != "" {
|
||||
if parameters.authenticationType != "" && 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)
|
||||
if parameters.authenticationType != "" && parameters.deploymentKey != nil {
|
||||
return handler.GitService.ClonePrivateRepositoryWithDeploymentKey(parameters.url, parameters.referenceName, parameters.path, parameters.deploymentKey)
|
||||
}
|
||||
return handler.GitService.ClonePublicRepository(parameters.url, parameters.referenceName, parameters.path)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue