blob: 43702f31d89841ca0200c0fbb34dd4344a313700 [file] [log] [blame]
// Backup files to Google Cloud Storage.
package main
import (
"bytes"
"compress/gzip"
"context"
"flag"
"fmt"
"os"
"path"
"time"
"cloud.google.com/go/storage"
"go.skia.org/infra/go/common"
"go.skia.org/infra/go/exec"
"go.skia.org/infra/go/fileutil"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/metrics2"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"
)
var (
gceBucket = flag.String("gce_bucket", "skia-backups", "GCS Bucket backups should be stored in")
gceFolder = flag.String("gce_folder", "Swarming", "Folder in the bucket that should hold the backup files")
localFilePath = flag.String("local_file_path", "", "Where the file is stored locally on disk. Cannot use with remote_file_path")
period = flag.Duration("period", 24*time.Hour, "How often to do the file backup.")
promPort = flag.String("prom_port", ":20000", "Metrics service address (e.g., ':10110')")
remoteCopyCommand = flag.String("remote_copy_command", "scp", "rsync or scp. The router does not have rsync installed.")
remoteFilePath = flag.String("remote_file_path", "", "Remote location for a file, to be used by remote_copy_command. E.g. foo@127.0.0.1:/etc/bar.conf Cannot use with local_file_path")
addHostname = flag.Bool("add_hostname", false, "If the hostname should be included in the backup file name")
local = flag.Bool("local", false, "Running locally if true. As opposed to in production.")
backupMetric metrics2.Liveness
)
func step(ctx context.Context, storageClient *storage.Client) {
sklog.Infof("Running backup to %s", *gceFolder)
if *remoteFilePath != "" {
// If backing up a remote file, copy it here first, then pretend it is a local file.
dir, err := os.MkdirTemp("", "backups")
if err != nil {
sklog.Fatalf("Could not create temp directory %s: %s", dir, err)
}
defer func() {
if err := os.RemoveAll(dir); err != nil {
sklog.Errorf("Failed to clean up temp directory %q: %s", dir, err)
}
}()
*localFilePath = path.Join(dir, "placeholder")
stdOut := bytes.Buffer{}
stdErr := bytes.Buffer{}
// This only works if the remote file's host has the source's SSH public key in
// $HOME/.ssh/authorized_key
err = exec.Run(ctx, &exec.Command{
Name: *remoteCopyCommand,
Args: []string{*remoteFilePath, *localFilePath},
Stdout: &stdOut,
Stderr: &stdErr,
})
sklog.Infof("StdOut of %s command: %s", *remoteCopyCommand, stdOut.String())
sklog.Infof("StdErr of %s command: %s", *remoteCopyCommand, stdErr.String())
if err != nil {
sklog.Fatalf("Could not copy remote file %s: %s", *remoteFilePath, err)
}
}
contents, hash, err := fileutil.ReadAndSha1File(*localFilePath)
if err != nil {
sklog.Fatalf("Could not read file %s: %s", *localFilePath, err)
}
// We name the file using date and sha1 hash of the file
day := time.Now().Format("2006-01-02")
name := fmt.Sprintf("%s/%s-%s.gz", *gceFolder, day, hash)
if *addHostname {
if hostname, err := os.Hostname(); err != nil {
sklog.Warningf("Could not get hostname for file name: %s", err)
} else {
name = fmt.Sprintf("%s/%s-%s-%s.gz", *gceFolder, day, hostname, hash)
}
}
w := storageClient.Bucket(*gceBucket).Object(name).NewWriter(ctx)
defer util.Close(w)
w.ContentEncoding = "application/gzip"
gw := gzip.NewWriter(w)
sklog.Infof("Uploading %s to gs://%s/%s", *localFilePath, *gceBucket, name)
// This takes a few minutes for a ~1.3 GB image (which gets compressed to about 400MB)
if i, err := gw.Write([]byte(contents)); err != nil {
util.Close(gw)
sklog.Errorf("Problem writing to GCS. Only wrote %d/%d bytes: %s", i, len(contents), err)
return
}
if err = gw.Close(); err != nil {
sklog.Errorf("Problem writing to GCS. Error when closing: %s", err)
} else {
backupMetric.Reset()
sklog.Infof("Upload complete")
}
}
func main() {
common.InitWithMust(
"router_backup_ansible",
common.PrometheusOpt(promPort),
common.CloudLogging(local, "skia-public"),
)
ctx := context.Background()
if *localFilePath == "" && *remoteFilePath == "" {
sklog.Fatalf("You must specify a file location")
}
if *localFilePath != "" && *remoteFilePath != "" {
sklog.Fatalf("You must specify a local_file_path OR a remote_file_path, not both")
}
tokenSource, err := google.DefaultTokenSource(ctx, storage.ScopeReadWrite)
if err != nil {
sklog.Fatal(err)
}
client := httputils.DefaultClientConfig().WithTokenSource(tokenSource).With2xxOnly().Client()
storageClient, err := storage.NewClient(ctx, option.WithHTTPClient(client))
if err != nil {
sklog.Fatalf("Could not authenticate to GCS: %s", err)
}
backupMetric = metrics2.NewLiveness("skolo_last_backup", nil)
step(ctx, storageClient)
for range time.Tick(*period) {
step(ctx, storageClient)
}
}