package main import ( "bytes" "encoding/json" "flag" "fmt" "io" "log" "net/http" "net/url" "os" "github.com/influxdb/influxdb" ) // BackupSuffix is a suffix added to the backup while it's in-process. const BackupSuffix = ".pending" // BackupCommand represents the program execution for "influxd backup". type BackupCommand struct { // The logger passed to the ticker during execution. Logger *log.Logger // Standard input/output, overridden for testing. Stderr io.Writer } // NewBackupCommand returns a new instance of BackupCommand with default settings. func NewBackupCommand() *BackupCommand { return &BackupCommand{ Stderr: os.Stderr, } } // Run excutes the program. func (cmd *BackupCommand) Run(args ...string) error { // Set up logger. cmd.Logger = log.New(cmd.Stderr, "", log.LstdFlags) cmd.Logger.Printf("influxdb backup, version %s, commit %s", version, commit) // Parse command line arguments. u, path, err := cmd.parseFlags(args) if err != nil { return err } // Retrieve snapshot from local file. ss, err := influxdb.ReadFileSnapshot(path) if err != nil && !os.IsNotExist(err) { return fmt.Errorf("read file snapshot: %s", err) } // Determine temporary path to download to. tmppath := path + BackupSuffix // Calculate path of next backup file. // This uses the path if it doesn't exist. // Otherwise it appends an autoincrementing number. path, err = cmd.nextPath(path) if err != nil { return fmt.Errorf("next path: %s", err) } // Retrieve snapshot. if err := cmd.download(u, ss, tmppath); err != nil { return fmt.Errorf("download: %s", err) } // Rename temporary file to final path. if err := os.Rename(tmppath, path); err != nil { return fmt.Errorf("rename: %s", err) } // TODO: Check file integrity. // Notify user of completion. cmd.Logger.Println("backup complete") return nil } // parseFlags parses and validates the command line arguments. func (cmd *BackupCommand) parseFlags(args []string) (url.URL, string, error) { fs := flag.NewFlagSet("", flag.ContinueOnError) host := fs.String("host", DefaultSnapshotURL.String(), "") fs.SetOutput(cmd.Stderr) fs.Usage = cmd.printUsage if err := fs.Parse(args); err != nil { return url.URL{}, "", err } // Parse host. u, err := url.Parse(*host) if err != nil { return url.URL{}, "", fmt.Errorf("parse host url: %s", err) } // Require output path. path := fs.Arg(0) if path == "" { return url.URL{}, "", fmt.Errorf("snapshot path required") } return *u, path, nil } // nextPath returns the next file to write to. func (cmd *BackupCommand) nextPath(path string) (string, error) { // Use base path if it doesn't exist. if _, err := os.Stat(path); os.IsNotExist(err) { return path, nil } else if err != nil { return "", err } // Otherwise iterate through incremental files until one is available. for i := 0; ; i++ { s := fmt.Sprintf(path+".%d", i) if _, err := os.Stat(s); os.IsNotExist(err) { return s, nil } else if err != nil { return "", err } } } // download downloads a snapshot from a host to a given path. func (cmd *BackupCommand) download(u url.URL, ss *influxdb.Snapshot, path string) error { // Create local file to write to. f, err := os.Create(path) if err != nil { return fmt.Errorf("open temp file: %s", err) } defer f.Close() // Encode snapshot. var buf bytes.Buffer if ss != nil { if err := json.NewEncoder(&buf).Encode(ss); err != nil { return fmt.Errorf("encode snapshot: %s", err) } } // Create request with existing snapshot as the body. u.Path = "/data/snapshot" req, err := http.NewRequest("GET", u.String(), &buf) if err != nil { return fmt.Errorf("new request: %s", err) } // Fetch the archive from the server. resp, err := http.DefaultClient.Do(req) if err != nil { return fmt.Errorf("get: %s", err) } defer func() { _ = resp.Body.Close() }() // Check the status code. if resp.StatusCode != http.StatusOK { return fmt.Errorf("snapshot error: status=%d", resp.StatusCode) } // Write the archive to disk. if _, err := io.Copy(f, resp.Body); err != nil { return fmt.Errorf("write snapshot: %s", err) } return nil } // printUsage prints the usage message to STDERR. func (cmd *BackupCommand) printUsage() { fmt.Fprintf(cmd.Stderr, `usage: influxd backup [flags] PATH backup downloads a snapshot of a data node and saves it to disk. -host The host to connect to snapshot. Defaults to http://127.0.0.1:8087. `) }