1
0
Fork 0

Merge pull request #585 from rqlite/cli_backup

Cli backup
master
Philip O'Toole 5 years ago committed by GitHub
commit 361e865bc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,4 +1,5 @@
## 4.6.0 (unreleased) ## 4.6.0 (unreleased)
- [PR #585](https://github.com/rqlite/rqlite/pull/585): Add backup command to CLI: Thanks @eariassoto.
- [PR #584](https://github.com/rqlite/rqlite/pull/584): Support showing timings in the CLI. Thanks @joaodrp. - [PR #584](https://github.com/rqlite/rqlite/pull/584): Support showing timings in the CLI. Thanks @joaodrp.
- [PR #583](https://github.com/rqlite/rqlite/pull/583): Add BasicAuth support to the CLI. Thanks @joaodrp. - [PR #583](https://github.com/rqlite/rqlite/pull/583): Add BasicAuth support to the CLI. Thanks @joaodrp.
- [PR #564](https://github.com/rqlite/rqlite/pull/564): rqlite server supports specifying trusted root CA certificate. Thanks @zmedico. - [PR #564](https://github.com/rqlite/rqlite/pull/564): rqlite server supports specifying trusted root CA certificate. Thanks @zmedico.

@ -0,0 +1,35 @@
package main
import (
"fmt"
"io/ioutil"
"net/url"
"github.com/mkideal/cli"
)
type backupResponse struct {
BackupFile []byte
}
func backup(ctx *cli.Context, filename string, argv *argT) error {
queryStr := url.Values{}
u := url.URL{
Scheme: argv.Protocol,
Host: fmt.Sprintf("%s:%d", argv.Host, argv.Port),
Path: fmt.Sprintf("%sdb/backup", argv.Prefix),
RawQuery: queryStr.Encode(),
}
response, err := sendRequest(ctx, "GET", u.String(), "", argv)
if err != nil {
return err
}
err = ioutil.WriteFile(filename, *response, 0644)
if err != nil {
return err
}
ctx.String("backup written successfully to %s\n", filename)
return nil
}

@ -32,8 +32,14 @@ func execute(ctx *cli.Context, cmd, line string, timer bool, argv *argT) error {
Path: fmt.Sprintf("%sdb/execute", argv.Prefix), Path: fmt.Sprintf("%sdb/execute", argv.Prefix),
RawQuery: queryStr.Encode(), RawQuery: queryStr.Encode(),
} }
response, err := sendRequest(ctx, "POST", u.String(), line, argv)
if err != nil {
return err
}
ret := &executeResponse{} ret := &executeResponse{}
if err := sendRequest(ctx, u.String(), line, argv, ret); err != nil { if err := parseResponse(response, &ret); err != nil {
return err return err
} }
if ret.Error != "" { if ret.Error != "" {

@ -6,6 +6,7 @@ import (
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strings" "strings"
@ -34,6 +35,7 @@ const cliHelp = `.help Show this message
.expvar Show expvar (Go runtime) information for connected node .expvar Show expvar (Go runtime) information for connected node
.tables List names of tables .tables List names of tables
.timer on|off Turn SQL timer on or off .timer on|off Turn SQL timer on or off
.backup <file> Write database backup to file
` `
func main() { func main() {
@ -78,6 +80,12 @@ func main() {
err = status(ctx, cmd, line, argv) err = status(ctx, cmd, line, argv)
case ".EXPVAR": case ".EXPVAR":
err = expvar(ctx, cmd, line, argv) err = expvar(ctx, cmd, line, argv)
case ".BACKUP":
if index == -1 || index == len(line)-1 {
err = fmt.Errorf("Please specify an output file for the backup")
break
}
err = backup(ctx, line[index+1:], argv)
case ".HELP": case ".HELP":
err = help(ctx, cmd, line, argv) err = help(ctx, cmd, line, argv)
case ".QUIT", "QUIT", "EXIT": case ".QUIT", "QUIT", "EXIT":
@ -127,22 +135,28 @@ func expvar(ctx *cli.Context, cmd, line string, argv *argT) error {
return cliJSON(ctx, cmd, line, url, argv) return cliJSON(ctx, cmd, line, url, argv)
} }
func sendRequest(ctx *cli.Context, urlStr string, line string, argv *argT, ret interface{}) error { func sendRequest(ctx *cli.Context, method string, urlStr string, line string, argv *argT) (*[]byte, error) {
data := makeJSONBody(line) var requestData io.Reader
if line != "" {
requestData = strings.NewReader(makeJSONBody(line))
} else {
requestData = nil
}
url := urlStr url := urlStr
var rootCAs *x509.CertPool var rootCAs *x509.CertPool
if argv.CACert != "" { if argv.CACert != "" {
pemCerts, err := ioutil.ReadFile(argv.CACert) pemCerts, err := ioutil.ReadFile(argv.CACert)
if err != nil { if err != nil {
return err return nil, err
} }
rootCAs = x509.NewCertPool() rootCAs = x509.NewCertPool()
ok := rootCAs.AppendCertsFromPEM(pemCerts) ok := rootCAs.AppendCertsFromPEM(pemCerts)
if !ok { if !ok {
return fmt.Errorf("failed to parse root CA certificate(s)") return nil, fmt.Errorf("failed to parse root CA certificate(s)")
} }
} }
@ -157,48 +171,52 @@ func sendRequest(ctx *cli.Context, urlStr string, line string, argv *argT, ret i
nRedirect := 0 nRedirect := 0
for { for {
req, err := http.NewRequest("POST", url, strings.NewReader(data)) req, err := http.NewRequest(method, url, requestData)
if err != nil { if err != nil {
return err return nil, err
} }
if argv.Credentials != "" { if argv.Credentials != "" {
creds := strings.Split(argv.Credentials, ":") creds := strings.Split(argv.Credentials, ":")
if len(creds) != 2 { if len(creds) != 2 {
return fmt.Errorf("invalid Basic Auth credentials format") return nil, fmt.Errorf("invalid Basic Auth credentials format")
} }
req.SetBasicAuth(creds[0], creds[1]) req.SetBasicAuth(creds[0], creds[1])
} }
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode == http.StatusUnauthorized { if resp.StatusCode == http.StatusUnauthorized {
return fmt.Errorf("unauthorized") return nil, fmt.Errorf("unauthorized")
} }
// Check for redirect. // Check for redirect.
if resp.StatusCode == http.StatusMovedPermanently { if resp.StatusCode == http.StatusMovedPermanently {
nRedirect++ nRedirect++
if nRedirect > maxRedirect { if nRedirect > maxRedirect {
return fmt.Errorf("maximum leader redirect limit exceeded") return nil, fmt.Errorf("maximum leader redirect limit exceeded")
} }
url = resp.Header["Location"][0] url = resp.Header["Location"][0]
continue continue
} }
body, err := ioutil.ReadAll(resp.Body) response, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return err return nil, err
} }
return json.Unmarshal(body, ret) return &response, nil
} }
} }
func parseResponse(response *[]byte, ret interface{}) error {
return json.Unmarshal(*response, ret)
}
// cliJSON fetches JSON from a URL, and displays it at the CLI. // cliJSON fetches JSON from a URL, and displays it at the CLI.
func cliJSON(ctx *cli.Context, cmd, line, url string, argv *argT) error { func cliJSON(ctx *cli.Context, cmd, line, url string, argv *argT) error {
// Recursive JSON printer. // Recursive JSON printer.

@ -91,8 +91,14 @@ func query(ctx *cli.Context, cmd, line string, timer bool, argv *argT) error {
Path: fmt.Sprintf("%sdb/query", argv.Prefix), Path: fmt.Sprintf("%sdb/query", argv.Prefix),
RawQuery: queryStr.Encode(), RawQuery: queryStr.Encode(),
} }
response, err := sendRequest(ctx, "POST", u.String(), line, argv)
if err != nil {
return err
}
ret := &queryResponse{} ret := &queryResponse{}
if err := sendRequest(ctx, u.String(), line, argv, ret); err != nil { if err := parseResponse(response, &ret); err != nil {
return err return err
} }
if ret.Error != "" { if ret.Error != "" {

Loading…
Cancel
Save