diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 8b34fc070..2ac6d8262 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -11,6 +11,7 @@ import ( "github.com/portainer/portainer/api/cli" "github.com/portainer/portainer/api/cron" "github.com/portainer/portainer/api/crypto" + "github.com/portainer/portainer/api/database" "github.com/portainer/portainer/api/docker" "github.com/portainer/portainer/api/exec" "github.com/portainer/portainer/api/filesystem" @@ -100,6 +101,10 @@ func initLDAPService() portainer.LDAPService { return &ldap.Service{} } +func initDatabaseService() portainer.DatabaseService { + return &database.Service{} +} + func initGitService() portainer.GitService { return &git.Service{} } @@ -526,6 +531,8 @@ func main() { gitService := initGitService() + databaseService := initDatabaseService() + cryptoService := initCryptoService() digitalSignatureService := initDigitalSignatureService() @@ -684,6 +691,7 @@ func main() { ComposeStackManager: composeStackManager, ExtensionManager: extensionManager, CryptoService: cryptoService, + DatabaseService: databaseService, JWTService: jwtService, FileService: fileService, LDAPService: ldapService, diff --git a/api/database/database.go b/api/database/database.go new file mode 100644 index 000000000..4a7e8ee70 --- /dev/null +++ b/api/database/database.go @@ -0,0 +1,44 @@ +package database + +import ( + "github.com/boltdb/bolt" + //"strconv" + //"net/http" + //"io/ioutil" +) + +const ( + databaseFileName = "portainer.db" +) + +// Service represents a service for managing Database. +type Service struct{} + +// DatabaseExport makes the BoltDB into read only mode, takes a backup and then put it back to writable mode. +func (service *Service) DatabaseExport(storePath string) (int64, error) { + //var databaseExport int64 + //var w http.ResponseWriter + // var r io.Reader + dataStorePath := storePath + "/" + databaseFileName + + _, err := bolt.Open(dataStorePath, 0666, &bolt.Options{ReadOnly: true}) + + /* + db, err := bolt.Open(dataStorePath, 0666, &bolt.Options{ReadOnly: true}) + if err != nil { + return 0, err + } + + err := db.View(func(tx *bolt.Tx) error { + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", `attachment; filename="portainer.db"`) + w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size()))) + databaseExport, err := tx.WriteTo(w) + return nil + }) + if err != nil { + return 0, err + } + */ + return 0, err +} \ No newline at end of file diff --git a/api/filesystem/filesystem.go b/api/filesystem/filesystem.go index cf4e06977..6606cda99 100644 --- a/api/filesystem/filesystem.go +++ b/api/filesystem/filesystem.go @@ -84,6 +84,11 @@ func (service *Service) GetBinaryFolder() string { return path.Join(service.fileStorePath, BinaryStorePath) } +// GetRootFolder returns the full path to the root store on the filesystem +func (service *Service) GetRootFolder() string { + return service.fileStorePath +} + // ExtractExtensionArchive extracts the content of an extension archive // specified as raw data into the binary store on the filesystem func (service *Service) ExtractExtensionArchive(data []byte) error { diff --git a/api/http/handler/database/database_export.go b/api/http/handler/database/database_export.go new file mode 100644 index 000000000..2e9934c25 --- /dev/null +++ b/api/http/handler/database/database_export.go @@ -0,0 +1,29 @@ +package database + +import ( + "net/http" + + httperror "github.com/portainer/libhttp/error" + //"github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" + "github.com/portainer/portainer/api" +) + +func (handler *Handler) databaseExport(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + storePath := handler.FileService.GetRootFolder() + databaseExport, err := handler.DatabaseService.DatabaseExport(storePath) + + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Error exporting database", err} + } + + database := &portainer.Database{ + DatabaseExport: databaseExport, + } + + //w.Header().Set("Content-Type", "application/octet-stream") + //w.Header().Set("Content-Disposition", `attachment; filename="my.db"`) + //w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size()))) + + return response.JSON(w, database) +} diff --git a/api/http/handler/database/handler.go b/api/http/handler/database/handler.go new file mode 100644 index 000000000..d36640f95 --- /dev/null +++ b/api/http/handler/database/handler.go @@ -0,0 +1,28 @@ +package database + +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 webhook operations. +type Handler struct { + *mux.Router + DatabaseService portainer.DatabaseService + FileService portainer.FileService +} + +// NewHandler creates a handler to manage settings operations. +func NewHandler(bouncer *security.RequestBouncer) *Handler { + h := &Handler{ + Router: mux.NewRouter(), + } + h.Handle("/database", + //bouncer.RestrictedAccess(httperror.LoggerHandler(h.databaseExport))).Methods(http.MethodGet) + bouncer.PublicAccess(httperror.LoggerHandler(h.databaseExport))).Methods(http.MethodGet) + return h +} diff --git a/api/http/handler/handler.go b/api/http/handler/handler.go index 9dc541641..9f0253b1c 100644 --- a/api/http/handler/handler.go +++ b/api/http/handler/handler.go @@ -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/database" "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 + DatabaseHandler *database.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/database"): + http.StripPrefix("/api", h.DatabaseHandler).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"): diff --git a/api/http/server.go b/api/http/server.go index b232adcdc..7e1b4504c 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -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/database" "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 + DatabaseService portainer.DatabaseService DockerHubService portainer.DockerHubService EndpointService portainer.EndpointService EndpointGroupService portainer.EndpointGroupService @@ -122,6 +124,10 @@ func (server *Server) Start() error { var roleHandler = roles.NewHandler(requestBouncer) roleHandler.RoleService = server.RoleService + var databaseHandler = database.NewHandler(requestBouncer) + databaseHandler.DatabaseService = server.DatabaseService + databaseHandler.FileService = server.FileService + var dockerHubHandler = dockerhub.NewHandler(requestBouncer) dockerHubHandler.DockerHubService = server.DockerHubService @@ -225,6 +231,7 @@ func (server *Server) Start() error { server.Handler = &handler.Handler{ RoleHandler: roleHandler, AuthHandler: authHandler, + DatabaseHandler: databaseHandler, DockerHubHandler: dockerHubHandler, EndpointGroupHandler: endpointGroupHandler, EndpointHandler: endpointHandler, diff --git a/api/portainer.go b/api/portainer.go index 852552ba6..043de1da4 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -224,6 +224,11 @@ type ( Password string `json:"Password,omitempty"` } + // Database represents all the required information to connect and use the database features + Database struct { + DatabaseExport int64 `json:"DatabaseExport"` + } + // EndpointID represents an endpoint identifier EndpointID int @@ -575,6 +580,11 @@ type ( Valid bool `json:"Valid,omitempty"` } + // Server defines the interface to serve the API + DatabaseService interface { + DatabaseExport(storePath string) (int64, error) + } + // CLIService represents a service for managing CLI CLIService interface { ParseFlags(version string) (*CLIFlags, error) @@ -789,6 +799,7 @@ type ( GetScheduleFolder(identifier string) string ExtractExtensionArchive(data []byte) error GetBinaryFolder() string + GetRootFolder() string } // GitService represents a service for managing Git