Skip to content
This repository was archived by the owner on Oct 29, 2024. It is now read-only.

Commit

Permalink
Improved many things.
Browse files Browse the repository at this point in the history
Got parsing of VCAP right. And some testing.
  • Loading branch information
jadudm committed Feb 23, 2024
1 parent 96d006f commit ea85823
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 163 deletions.
78 changes: 78 additions & 0 deletions cmd/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
"database/sql"
"fmt"
"os"

"github.com/spf13/cobra"
"gov.gsa.fac.backups/internal/logging"
vcap "gov.gsa.fac.backups/internal/vcap"
)

func get_row_count(creds *vcap.RDSCreds, table string) int {
var count int
// FIXME: Not sure if `disable` is correct for RDS sslmode.
connStr := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable",
creds.Username,
creds.Password,
creds.Host,
creds.DB_Name)
db, _ := sql.Open("postgres", connStr)
defer db.Close()
row := db.QueryRow(fmt.Sprintf("SELECT count(*) FROM %s", table))
if err := row.Scan(&count); err != nil {
logging.Logger.Printf("BACKUPS Could not get count of %s", table)
}
return count
}

func check_results(source *vcap.RDSCreds, dest *vcap.RDSCreds, tables []string) {
// FIXME: These won't exist in the VCAP_SERVICES version
// of the config. We'll have to always... load both?
// There needs to be a way to configure this in the remote env.
for _, table := range tables {
source_row_count := get_row_count(source, table)
dest_row_count := get_row_count(dest, table)
logging.Logger.Printf("CHECK OK %s source %d dest %d",
table, source_row_count, dest_row_count)
if source_row_count < dest_row_count {
logging.Logger.Printf("CHECK too many rows in '%s' source (%d < %d)",
table, source_row_count, dest_row_count)
os.Exit(-1)
}
}
}

// checkCmd represents the check command
var checkCmd = &cobra.Command{
Use: "check",
Short: "Checks table counts between source and destination",
Long: `
When given a source and destination, this command returns 0 when the
number of rows in the source are equal to or higher than the number
of rows in the destination.
This is because the clone tool is used against live tables. It is likely
that the source will increase between the time of the clone and the check.
Expects a space-separated list of table names as arguments.
`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
source_creds, dest_creds := vcap.GetCreds(SourceDB, DestinationDB)
check_results(source_creds, dest_creds, args)

},
}

func init() {
rootCmd.AddCommand(checkCmd)
checkCmd.Flags().StringVarP(&SourceDB, "source-db", "", "", "source database (req)")
checkCmd.Flags().StringVarP(&DestinationDB, "destination-db", "", "", "destination database (req)")
checkCmd.MarkFlagRequired("source-db")
checkCmd.MarkFlagRequired("destination-db")
}
99 changes: 99 additions & 0 deletions cmd/clone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
"fmt"
"os"
"strings"

"github.com/bitfield/script"
"github.com/spf13/cobra"
"gov.gsa.fac.backups/internal/logging"
vcap "gov.gsa.fac.backups/internal/vcap"

_ "github.com/lib/pq"
)

// https://bitfieldconsulting.com/golang/scripting
func pg_dump(creds *vcap.RDSCreds) *script.Pipe {
// Compose the command as a slice
cmd := []string{
"pg_dump",
"--clean",
"--no-password",
"--if-exists",
"--no-privileges",
"--no-owner",
"--format plain",
"--dbname",
fmt.Sprintf("postgres://%s:%s@%s:%s/%s",
creds.Username,
creds.Password,
creds.Host,
creds.Port,
creds.DB_Name,
),
}
// Combine the slice for printing and execution.
combined := strings.Join(cmd[:], " ")
// This will log the password...
// logging.Logger.Printf("BACKUPS Running `%s`\n", combined)
logging.Logger.Printf("BACKUPS pg_dump targeting %s", creds.DB_Name)
return script.Exec(combined)
}

func psql(in_pipe *script.Pipe, creds *vcap.RDSCreds) *script.Pipe {
cmd := []string{
"psql",
"--no-password",
"--dbname",
fmt.Sprintf("postgres://%s:%s@%s:%s/%s",
creds.Username,
creds.Password,
creds.Host,
creds.Port,
creds.DB_Name,
),
}
combined := strings.Join(cmd[:], " ")
logging.Logger.Printf("BACKUPS psql targeting %s", creds.DB_Name)
return in_pipe.Exec(combined)
}

func clone(source *vcap.RDSCreds, dest *vcap.RDSCreds) {
psql_pipe := psql(pg_dump(source), dest)
psql_pipe.Wait()
if err := psql_pipe.Error(); err != nil {
logging.Logger.Println("BACKUPS Pipe failed")
os.Exit(-1)
}
}

// snapshotDbToDbCmd represents the snapshotDbToDb command
var cloneCmd = &cobra.Command{
Use: "clone",
Short: "pg_dump | psql, source to destination",
Long: `
An imperfect, point-in-time snapshot.
This command copies the source database to the destination
database by streaming STDOUT of 'pg_dump' piped into the STNDIN
of 'psql'. The former reads from the FAC production database, and
writes to a snapshot clone DB.
`,
Run: func(cmd *cobra.Command, args []string) {
source_creds, dest_creds := vcap.GetCreds(SourceDB, DestinationDB)
clone(source_creds, dest_creds)
},
}

func init() {
rootCmd.AddCommand(cloneCmd)
cloneCmd.Flags().StringVarP(&SourceDB, "source-db", "", "", "source database (req)")
cloneCmd.Flags().StringVarP(&DestinationDB, "destination-db", "", "", "destination database (req)")
cloneCmd.MarkFlagRequired("source-db")
cloneCmd.MarkFlagRequired("destination-db")

}
105 changes: 0 additions & 105 deletions cmd/cloneDBToDB.go

This file was deleted.

23 changes: 14 additions & 9 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@ import (
"github.com/spf13/cobra"
)

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "backups",
Short: "A tool for backing up, testing, and restoring",
Long: ``,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
var (
SourceDB string
DestinationDB string

// rootCmd represents the base command when called without any subcommands
rootCmd = &cobra.Command{
Use: "backups",
Short: "A tool for backing up, testing, and restoring",
Long: ``,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
)

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
Expand Down
4 changes: 4 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# The keys "fac-db" and "fac-snapshot-db"
# must match the names of the DBs in use.
# Locally, these must be the names of the
# containers.
fac-db:
db_name: postgres
host: 127.0.0.1
Expand Down
Loading

0 comments on commit ea85823

Please sign in to comment.