diff --git a/cli/export.go b/cli/export.go index 860e2821..76316ad5 100644 --- a/cli/export.go +++ b/cli/export.go @@ -15,15 +15,100 @@ package cli import ( + "fmt" + "io" + "net/http" + "os" + "github.com/spf13/cobra" + + "github.com/abcum/surreal/log" ) var exportCmd = &cobra.Command{ - Use: "export", + Use: "export [flags] ", Short: "Export data from an existing database", - Example: " surreal export", - Run: func(cmd *cobra.Command, args []string) { - // Do Stuff Here + Example: " surreal export --auth root:root backup.db", + RunE: func(cmd *cobra.Command, args []string) (err error) { + + var fle *os.File + var req *http.Request + var res *http.Response + + // Ensure that the command has a filepath + // as the output file argument. If no filepath + // has been provided then return an error. + + if len(args) != 1 { + log.Fatalln("No filepath provided.") + return + } + + // Attempt to open or create the specified file + // in write-only mode, and if there is a problem + // creating the file, then return an error. + + if fle, err = os.OpenFile(args[0], os.O_CREATE|os.O_WRONLY, 0644); err != nil { + log.Fatalln("Export failed - please check the filepath and try again.") + return + } + + // Ensure that we properly close the file handle + // when we have finished with the file so that + // the file descriptor is released. + + defer fle.Close() + + // Configure the export connection endpoint url + // and specify the authentication header using + // basic auth for root login. + + url := fmt.Sprintf("http://%s@%s:%s/export", opts.Auth.Auth, opts.DB.Host, opts.DB.Port) + + // Create a new http request object that we + // can use to connect to the export endpoint + // using a GET http request type. + + if req, err = http.NewRequest("GET", url, nil); err != nil { + log.Fatalln("Connection failed - check the connection details and try again.") + return + } + + // Specify that the request is an octet stream + // so that we can stream the file contents to + // the server without reading the whole file. + + req.Header.Set("Content-Type", "application/octet-stream") + + // Attempt to dial the export endpoint and + // if there is an error then stop execution + // and return the connection error. + + if res, err = http.DefaultClient.Do(req); err != nil { + log.Fatalln("Connection failed - check the connection details and try again.") + return + } + + // Ensure that we received a http 200 status + // code back from the server, otherwise there + // was a problem with our authentication. + + if res.StatusCode != 200 { + log.Fatalln("Connection failed - check the connection details and try again.") + return + } + + // Copy the http response body to stdOut so + // that we can pipe the response to other + // commands or processes. + + if _, err = io.Copy(fle, res.Body); err != nil { + log.Fatalln("Export failed - there was an error saving the database content.") + return + } + + return + }, } diff --git a/cli/import.go b/cli/import.go index 214c11fd..cd47f6b4 100644 --- a/cli/import.go +++ b/cli/import.go @@ -15,15 +15,90 @@ package cli import ( + "fmt" + "net/http" + "os" + "github.com/spf13/cobra" + + "github.com/abcum/surreal/log" ) var importCmd = &cobra.Command{ - Use: "import", + Use: "import [flags] ", Short: "Import data into an existing database", - Example: " surreal import", - Run: func(cmd *cobra.Command, args []string) { - // Do Stuff Here + Example: " surreal import --auth root:root backup.db", + RunE: func(cmd *cobra.Command, args []string) (err error) { + + var fle *os.File + var req *http.Request + var res *http.Response + + // Ensure that the command has a filepath + // as the output file argument. If no filepath + // has been provided then return an error. + + if len(args) != 1 { + log.Fatalln("No filepath provided.") + return + } + + // Attempt to open or create the specified file + // in write-only mode, and if there is a problem + // creating the file, then return an error. + + if fle, err = os.OpenFile(args[0], os.O_RDONLY, 0644); err != nil { + log.Fatalln("Import failed - please check the filepath and try again.") + return + } + + // Ensure that we properly close the file handle + // when we have finished with the file so that + // the file descriptor is released. + + defer fle.Close() + + // Configure the export connection endpoint url + // and specify the authentication header using + // basic auth for root login. + + url := fmt.Sprintf("http://%s@%s:%s/import", opts.Auth.Auth, opts.DB.Host, opts.DB.Port) + + // Create a new http request object that we + // can use to connect to the import endpoint + // using a POST http request type. + + if req, err = http.NewRequest("POST", url, fle); err != nil { + log.Fatalln("Connection failed - check the connection details and try again.") + return + } + + // Specify that the request is an octet stream + // so that we can stream the file contents to + // the server without reading the whole file. + + req.Header.Set("Content-Type", "application/octet-stream") + + // Attempt to dial the import endpoint and + // if there is an error then stop execution + // and return the connection error. + + if res, err = http.DefaultClient.Do(req); err != nil { + log.Fatalln("Connection failed - check the connection details and try again.") + return + } + + // Ensure that we received a http 200 status + // code back from the server, otherwise there + // was a problem with our authentication. + + if res.StatusCode != 200 { + log.Fatalln("Connection failed - check the connection details and try again.") + return + } + + return + }, } diff --git a/cli/start.go b/cli/start.go index 56f822a2..60f0792a 100644 --- a/cli/start.go +++ b/cli/start.go @@ -27,7 +27,7 @@ import ( ) var startCmd = &cobra.Command{ - Use: "start", + Use: "start [flags]", Short: "Start the database and http server", PreRun: func(cmd *cobra.Command, args []string) { diff --git a/web/export.go b/web/export.go new file mode 100644 index 00000000..217882f8 --- /dev/null +++ b/web/export.go @@ -0,0 +1,34 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package web + +import ( + "github.com/abcum/fibre" + "github.com/abcum/surreal/cnf" + "github.com/abcum/surreal/db" + "github.com/abcum/surreal/sql" +) + +func exporter(c *fibre.Context) (err error) { + + if c.Get("auth").(*cnf.Auth).Kind != sql.AuthKV { + return fibre.NewHTTPError(401) + } + + c.Response().Header().Set("Content-Type", "application/octet-stream") + + return db.Export(c.Response()) + +} diff --git a/web/import.go b/web/import.go new file mode 100644 index 00000000..268fbdb8 --- /dev/null +++ b/web/import.go @@ -0,0 +1,34 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package web + +import ( + "github.com/abcum/fibre" + "github.com/abcum/surreal/cnf" + "github.com/abcum/surreal/db" + "github.com/abcum/surreal/sql" +) + +func importer(c *fibre.Context) (err error) { + + if c.Get("auth").(*cnf.Auth).Kind != sql.AuthKV { + return fibre.NewHTTPError(401) + } + + c.Response().Header().Set("Content-Type", "application/octet-stream") + + return db.Import(c.Request().Body) + +} diff --git a/web/routes.go b/web/routes.go index ec26b99e..3a0b650c 100644 --- a/web/routes.go +++ b/web/routes.go @@ -89,6 +89,18 @@ func routes(s *fibre.Fibre) { return signin(c) }) + // -------------------------------------------------- + // Endpoints for import and exporting data + // -------------------------------------------------- + + s.Get("/export", func(c *fibre.Context) error { + return exporter(c) + }) + + s.Post("/import", func(c *fibre.Context) error { + return importer(c) + }) + // -------------------------------------------------- // Endpoints for submitting sql queries // -------------------------------------------------- diff --git a/web/web.go b/web/web.go index c2ba81a4..dc4be614 100644 --- a/web/web.go +++ b/web/web.go @@ -59,6 +59,7 @@ func Setup(opts *cnf.Options) (err error) { "application/json": true, "application/cbor": true, "application/msgpack": true, + "application/octet-stream": true, "application/x-www-form-urlencoded": true, }, }))