Compare commits
9 Commits
revert-502
...
feat1752-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a36c45822a | ||
|
|
ef800d5a7d | ||
|
|
5d169ad827 | ||
|
|
f496776490 | ||
|
|
7dd9e9e365 | ||
|
|
adf8846c28 | ||
|
|
1732548e59 | ||
|
|
1a387228b0 | ||
|
|
ba9838296e |
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/deploymentkey"
|
||||
"github.com/portainer/portainer/api/bolt/dockerhub"
|
||||
"github.com/portainer/portainer/api/bolt/endpoint"
|
||||
"github.com/portainer/portainer/api/bolt/endpointgroup"
|
||||
@@ -39,6 +40,7 @@ type Store struct {
|
||||
checkForDataMigration bool
|
||||
fileService portainer.FileService
|
||||
RoleService *role.Service
|
||||
DeploymentKeyService *deploymentkey.Service
|
||||
DockerHubService *dockerhub.Service
|
||||
EndpointGroupService *endpointgroup.Service
|
||||
EndpointService *endpoint.Service
|
||||
@@ -148,6 +150,12 @@ func (store *Store) initServices() error {
|
||||
}
|
||||
store.RoleService = authorizationsetService
|
||||
|
||||
deploymentkeyService, err := deploymentkey.NewService(store.db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.DeploymentKeyService = deploymentkeyService
|
||||
|
||||
dockerhubService, err := dockerhub.NewService(store.db)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
120
api/bolt/deploymentkey/deploymentkey.go
Normal file
120
api/bolt/deploymentkey/deploymentkey.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package deploymentkey
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "deploymentkey"
|
||||
)
|
||||
|
||||
// Service represents a service for managing deploymentkey data.
|
||||
type Service struct {
|
||||
db *bolt.DB
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(db *bolt.DB) (*Service, error) {
|
||||
err := internal.CreateBucket(db, BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeploymentKeys return all the deployment keys that are created.
|
||||
func (service *Service) DeploymentKeys() ([]portainer.DeploymentKey, error) {
|
||||
var deploymentkeys = make([]portainer.DeploymentKey, 0)
|
||||
|
||||
err := service.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var deploymentkey portainer.DeploymentKey
|
||||
err := internal.UnmarshalObject(v, &deploymentkey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deploymentkeys = append(deploymentkeys, deploymentkey)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return deploymentkeys, err
|
||||
}
|
||||
|
||||
// DeploymentKey returns the deployment key by deployment key ID.
|
||||
func (service *Service) DeploymentKey(ID portainer.DeploymentKeyID) (*portainer.DeploymentKey, error) {
|
||||
var deploymentkey portainer.DeploymentKey
|
||||
identifier := internal.Itob(int(ID))
|
||||
|
||||
err := internal.GetObject(service.db, BucketName, identifier, &deploymentkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &deploymentkey, nil
|
||||
}
|
||||
|
||||
// DeploymentKeyByName returns a deploymentkey by name.
|
||||
func (service *Service) DeploymentKeyByName(name string) (*portainer.DeploymentKey, error) {
|
||||
var deploymentkey *portainer.DeploymentKey
|
||||
|
||||
err := service.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var t portainer.DeploymentKey
|
||||
err := internal.UnmarshalObject(v, &t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t.Name == name {
|
||||
deploymentkey = &t
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if deploymentkey == nil {
|
||||
return portainer.ErrObjectNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return deploymentkey, err
|
||||
}
|
||||
|
||||
// DeleteDeploymentKey deletes a deployment key.
|
||||
func (service *Service) DeleteDeploymentKey(ID portainer.DeploymentKeyID) error {
|
||||
identifier := internal.Itob(int(ID))
|
||||
return internal.DeleteObject(service.db, BucketName, identifier)
|
||||
}
|
||||
|
||||
// CreateDeploymentKey creates a deployment key.
|
||||
func (service *Service) CreateDeploymentKey(deploymentkey *portainer.DeploymentKey) error {
|
||||
return service.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketName))
|
||||
|
||||
id, _ := bucket.NextSequence()
|
||||
deploymentkey.ID = portainer.DeploymentKeyID(id)
|
||||
|
||||
data, err := internal.MarshalObject(deploymentkey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(internal.Itob(int(deploymentkey.ID)), data)
|
||||
})
|
||||
}
|
||||
@@ -674,6 +674,7 @@ func main() {
|
||||
ResourceControlService: store.ResourceControlService,
|
||||
SettingsService: store.SettingsService,
|
||||
RegistryService: store.RegistryService,
|
||||
DeploymentKeyService: store.DeploymentKeyService,
|
||||
DockerHubService: store.DockerHubService,
|
||||
StackService: store.StackService,
|
||||
ScheduleService: store.ScheduleService,
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
@@ -135,3 +137,28 @@ func (service *ECDSAService) CreateSignature(message string) (string, error) {
|
||||
|
||||
return base64.RawStdEncoding.EncodeToString(signature), nil
|
||||
}
|
||||
|
||||
// GenerateDeploymentKeyPair will create a new PEM encoded key pairs with ECDSA.
|
||||
func (service *ECDSAService) GenerateDeploymentKeyPair() ([]byte, string, error) {
|
||||
pubkeyCurve := elliptic.P256()
|
||||
|
||||
privateKey, err := ecdsa.GenerateKey(pubkeyCurve, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
x509EncodedBytes, _ := x509.MarshalECPrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
pemEncodedPrivate := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: x509EncodedBytes})
|
||||
|
||||
publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
publicKeyAutorizedKey := ssh.MarshalAuthorizedKey(publicKey)
|
||||
|
||||
return pemEncodedPrivate, string(publicKeyAutorizedKey), nil
|
||||
}
|
||||
|
||||
@@ -115,3 +115,8 @@ const (
|
||||
ErrWebhookAlreadyExists = Error("A webhook for this resource already exists")
|
||||
ErrUnsupportedWebhookType = Error("Webhooks for this resource are not currently supported")
|
||||
)
|
||||
|
||||
// Deploymentkey errors
|
||||
const (
|
||||
ErrDeploymentkeyAlreadyExists = Error("A deployment key for this resource already exists")
|
||||
)
|
||||
|
||||
@@ -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,33 @@ func cloneRepository(repositoryURL, referenceName string, destination string) er
|
||||
_, err := git.PlainClone(destination, false, options)
|
||||
return err
|
||||
}
|
||||
|
||||
// ClonePrivateRepositoryWithDeploymentKey clones a private git repository using the specified URL in the specified
|
||||
// destination folder. It will use the specified deployment key for SSH based authentication
|
||||
func (service *Service) ClonePrivateRepositoryWithDeploymentKey(repositoryURL, referenceName string, destination string, privateKeyPem []byte) error {
|
||||
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 = strings.Replace(repositoryURL, "github.com/", "github.com:", 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
|
||||
}
|
||||
|
||||
58
api/http/handler/deploymentkeys/deploymentkey_create.go
Normal file
58
api/http/handler/deploymentkeys/deploymentkey_create.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package deploymentkeys
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
type deploymentKeyCreatePayload struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (payload *deploymentKeyCreatePayload) Validate(r *http.Request) error {
|
||||
if govalidator.IsNull(payload.Name) {
|
||||
return portainer.Error("Invalid deploymentkey name")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *Handler) deploymentkeyCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload deploymentKeyCreatePayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
if deploymentkey != nil {
|
||||
return &httperror.HandlerError{http.StatusConflict, "A deploymentkey for this resource already exists", portainer.ErrDeploymentkeyAlreadyExists}
|
||||
}
|
||||
|
||||
private, public, err := handler.SignatureService.GenerateDeploymentKeyPair()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create private and public key pairs", err}
|
||||
}
|
||||
|
||||
deploymentkey = &portainer.DeploymentKey{
|
||||
Name: payload.Name,
|
||||
PublicKey: public,
|
||||
PrivateKey: private,
|
||||
}
|
||||
|
||||
err = handler.DeploymentKeyService.CreateDeploymentKey(deploymentkey)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the deployment key inside the database", err}
|
||||
}
|
||||
|
||||
hideFields(deploymentkey)
|
||||
|
||||
return response.JSON(w, deploymentkey)
|
||||
}
|
||||
25
api/http/handler/deploymentkeys/deploymentkey_delete.go
Normal file
25
api/http/handler/deploymentkeys/deploymentkey_delete.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package deploymentkeys
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// DELETE request on /api/webhook/:serviceID
|
||||
func (handler *Handler) deploymentkeyDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
id, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid deployment key id", err}
|
||||
}
|
||||
|
||||
err = handler.DeploymentKeyService.DeleteDeploymentKey(portainer.DeploymentKeyID(id))
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the deployment key from the database", err}
|
||||
}
|
||||
|
||||
return response.Empty(w)
|
||||
}
|
||||
29
api/http/handler/deploymentkeys/deploymentkey_inspect.go
Normal file
29
api/http/handler/deploymentkeys/deploymentkey_inspect.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package deploymentkeys
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// GET request on /api/deployment_keys/:id
|
||||
func (handler *Handler) deploymentkeyInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
deploymentkeyID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid deploymentkey identifier route variable", err}
|
||||
}
|
||||
|
||||
deploymentkey, err := handler.DeploymentKeyService.DeploymentKey(portainer.DeploymentKeyID(deploymentkeyID))
|
||||
if err == portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a deployment key with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a deployment key with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
hideFields(deploymentkey)
|
||||
|
||||
return response.JSON(w, deploymentkey)
|
||||
}
|
||||
22
api/http/handler/deploymentkeys/deploymentkey_list.go
Normal file
22
api/http/handler/deploymentkeys/deploymentkey_list.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package deploymentkeys
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
)
|
||||
|
||||
// GET request on /api/deployment_keys
|
||||
func (handler *Handler) deploymentkeyList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
deploymentkeys, err := handler.DeploymentKeyService.DeploymentKeys()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve deploymentkeys from the database", err}
|
||||
}
|
||||
|
||||
for idx := range deploymentkeys {
|
||||
hideFields(&deploymentkeys[idx])
|
||||
}
|
||||
|
||||
return response.JSON(w, deploymentkeys)
|
||||
}
|
||||
37
api/http/handler/deploymentkeys/handler.go
Normal file
37
api/http/handler/deploymentkeys/handler.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package deploymentkeys
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle deploymentkey operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
DeploymentKeyService portainer.DeploymentKeyService
|
||||
SignatureService portainer.DigitalSignatureService
|
||||
}
|
||||
|
||||
func hideFields(deploymentkey *portainer.DeploymentKey) {
|
||||
deploymentkey.PrivateKey = nil
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage settings operations.
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/deployment_keys",
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyCreate))).Methods(http.MethodPost)
|
||||
h.Handle("/deployment_keys",
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyList))).Methods(http.MethodGet)
|
||||
h.Handle("/deployment_keys/{id}",
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyInspect))).Methods(http.MethodGet)
|
||||
h.Handle("/deployment_keys/{id}",
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyDelete))).Methods(http.MethodDelete)
|
||||
return h
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/portainer/portainer/api/http/handler/roles"
|
||||
|
||||
"github.com/portainer/portainer/api/http/handler/auth"
|
||||
"github.com/portainer/portainer/api/http/handler/deploymentkeys"
|
||||
"github.com/portainer/portainer/api/http/handler/dockerhub"
|
||||
"github.com/portainer/portainer/api/http/handler/endpointgroups"
|
||||
"github.com/portainer/portainer/api/http/handler/endpointproxy"
|
||||
@@ -34,6 +35,7 @@ import (
|
||||
// Handler is a collection of all the service handlers.
|
||||
type Handler struct {
|
||||
AuthHandler *auth.Handler
|
||||
DeploymentKeyHandler *deploymentkeys.Handler
|
||||
DockerHubHandler *dockerhub.Handler
|
||||
EndpointGroupHandler *endpointgroups.Handler
|
||||
EndpointHandler *endpoints.Handler
|
||||
@@ -63,6 +65,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case strings.HasPrefix(r.URL.Path, "/api/auth"):
|
||||
http.StripPrefix("/api", h.AuthHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/deployment_keys"):
|
||||
http.StripPrefix("/api", h.DeploymentKeyHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/dockerhub"):
|
||||
http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/endpoint_groups"):
|
||||
|
||||
@@ -88,14 +88,15 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter,
|
||||
}
|
||||
|
||||
type composeStackFromGitRepositoryPayload struct {
|
||||
Name string
|
||||
RepositoryURL string
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
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 {
|
||||
@@ -105,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
|
||||
@@ -146,12 +150,21 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
|
||||
stack.ProjectPath = projectPath
|
||||
|
||||
gitCloneParams := &cloneRepositoryParameters{
|
||||
url: payload.RepositoryURL,
|
||||
referenceName: payload.RepositoryReferenceName,
|
||||
path: projectPath,
|
||||
authentication: payload.RepositoryAuthentication,
|
||||
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
|
||||
|
||||
@@ -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,16 +91,18 @@ 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
|
||||
Env []portainer.Pair
|
||||
RepositoryURL string
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
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 {
|
||||
@@ -114,8 +115,11 @@ func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) err
|
||||
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
|
||||
@@ -156,12 +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,
|
||||
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,17 +1,21 @@
|
||||
package stacks
|
||||
|
||||
type cloneRepositoryParameters struct {
|
||||
url string
|
||||
referenceName string
|
||||
path string
|
||||
authentication bool
|
||||
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 {
|
||||
if parameters.authenticationType != "" && parameters.username != "" && parameters.password != "" {
|
||||
return handler.GitService.ClonePrivateRepositoryWithBasicAuth(parameters.url, parameters.referenceName, parameters.path, parameters.username, parameters.password)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/go-winio"
|
||||
"net"
|
||||
"net/http"
|
||||
"github.com/Microsoft/go-winio"
|
||||
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
"github.com/portainer/portainer/api/http/handler"
|
||||
"github.com/portainer/portainer/api/http/handler/auth"
|
||||
"github.com/portainer/portainer/api/http/handler/deploymentkeys"
|
||||
"github.com/portainer/portainer/api/http/handler/dockerhub"
|
||||
"github.com/portainer/portainer/api/http/handler/endpointgroups"
|
||||
"github.com/portainer/portainer/api/http/handler/endpointproxy"
|
||||
@@ -51,6 +52,7 @@ type Server struct {
|
||||
JobScheduler portainer.JobScheduler
|
||||
Snapshotter portainer.Snapshotter
|
||||
RoleService portainer.RoleService
|
||||
DeploymentKeyService portainer.DeploymentKeyService
|
||||
DockerHubService portainer.DockerHubService
|
||||
EndpointService portainer.EndpointService
|
||||
EndpointGroupService portainer.EndpointGroupService
|
||||
@@ -180,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
|
||||
@@ -222,9 +225,14 @@ func (server *Server) Start() error {
|
||||
webhookHandler.EndpointService = server.EndpointService
|
||||
webhookHandler.DockerClientFactory = server.DockerClientFactory
|
||||
|
||||
var deploymentKeyHandler = deploymentkeys.NewHandler(requestBouncer)
|
||||
deploymentKeyHandler.DeploymentKeyService = server.DeploymentKeyService
|
||||
deploymentKeyHandler.SignatureService = server.SignatureService
|
||||
|
||||
server.Handler = &handler.Handler{
|
||||
RoleHandler: roleHandler,
|
||||
AuthHandler: authHandler,
|
||||
DeploymentKeyHandler: deploymentKeyHandler,
|
||||
DockerHubHandler: dockerHubHandler,
|
||||
EndpointGroupHandler: endpointGroupHandler,
|
||||
EndpointHandler: endpointHandler,
|
||||
|
||||
@@ -216,6 +216,18 @@ type (
|
||||
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
||||
}
|
||||
|
||||
// DeploymentKeyID represents
|
||||
DeploymentKeyID int
|
||||
|
||||
// DeploymentKey represents the SSH key details that will be used to
|
||||
// connect to GitHub for deployments based on private key clone
|
||||
DeploymentKey struct {
|
||||
ID DeploymentKeyID `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
PublicKey string `json:"PublicKey"`
|
||||
PrivateKey []byte `json:"PrivateKey"`
|
||||
}
|
||||
|
||||
// DockerHub represents all the required information to connect and use the
|
||||
// Docker Hub
|
||||
DockerHub struct {
|
||||
@@ -674,6 +686,15 @@ type (
|
||||
GetNextIdentifier() int
|
||||
}
|
||||
|
||||
// DeploymentKeyService represents a service for managing the DeploymentKey object
|
||||
DeploymentKeyService interface {
|
||||
DeploymentKey(ID DeploymentKeyID) (*DeploymentKey, error)
|
||||
DeploymentKeyByName(name string) (*DeploymentKey, error)
|
||||
DeploymentKeys() ([]DeploymentKey, error)
|
||||
DeleteDeploymentKey(ID DeploymentKeyID) error
|
||||
CreateDeploymentKey(deploymentkey *DeploymentKey) error
|
||||
}
|
||||
|
||||
// DockerHubService represents a service for managing the DockerHub object
|
||||
DockerHubService interface {
|
||||
DockerHub() (*DockerHub, error)
|
||||
@@ -757,6 +778,7 @@ type (
|
||||
DigitalSignatureService interface {
|
||||
ParseKeyPair(private, public []byte) error
|
||||
GenerateKeyPair() ([]byte, []byte, error)
|
||||
GenerateDeploymentKeyPair() ([]byte, string, error)
|
||||
EncodedPublicKey() string
|
||||
PEMHeaders() (string, string)
|
||||
CreateSignature(message string) (string, error)
|
||||
@@ -795,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
|
||||
@@ -876,7 +899,7 @@ const (
|
||||
PortainerAgentSignatureMessage = "Portainer-App"
|
||||
// SupportedDockerAPIVersion is the minimum Docker API version supported by Portainer
|
||||
SupportedDockerAPIVersion = "1.24"
|
||||
// ExtensionServer represents the server used by Portainer to communicate with extensions
|
||||
// ExtensionServer represents the server used by Portainer to communicate with extensions
|
||||
ExtensionServer = "localhost"
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user