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

Commit

Permalink
Better data structures.
Browse files Browse the repository at this point in the history
  • Loading branch information
jadudm committed Apr 20, 2024
1 parent 2e58e9f commit c135326
Show file tree
Hide file tree
Showing 16 changed files with 335 additions and 328 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,5 @@ You can change the local DB values in `config.yaml` to reflect your config.

In a remote environment, the variable `VCAP_SERVICES` is referenced to extract values.

## adding a new command

44 changes: 37 additions & 7 deletions cmd/bucket.go → cmd/bucket._go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ Copyright © 2024 NAME HERE <EMAIL ADDRESS>
package cmd

import (
"database/sql"
"os"

"golang.org/x/exp/slices"

"github.com/spf13/cobra"
"gov.gsa.fac.cgov-util/internal/logging"
"gov.gsa.fac.cgov-util/internal/pipes"
"gov.gsa.fac.cgov-util/internal/structs"
"gov.gsa.fac.cgov-util/internal/util"

vcap "gov.gsa.fac.cgov-util/internal/vcap"
Expand All @@ -33,14 +33,44 @@ var backup_tag string
// }
// }

func bucket_local_tables(source_creds *structs.CredentialsRDS, up structs.UserProvidedCredentials) {
table_to_schema := util.Get_table_and_schema_names(source_creds)
func get_table_and_schema_names(source_creds vcap.Credentials) map[string]string {
// Do this table-by-table for RAM reasons.
db, err := sql.Open("postgres", source_creds.Get("uri").String())
if err != nil {
logging.Logger.Println("BACKUPS could not connect to DB for table-by-table dump")
logging.Logger.Printf("BACKUPS %s\n", err)
os.Exit(-1)
}

tables, err := db.Query("SELECT schemaname, tablename FROM pg_tables WHERE schemaname = 'public'")
if err != nil {
logging.Logger.Println("BACKUPS could not get table names for table-by-table dump")
logging.Logger.Printf("BACKUPS %s\n", err)
os.Exit(-1)
}

table_names := make(map[string]string, 0)

for tables.Next() {
var table string
var schema string
if err := tables.Scan(&schema, &table); err != nil {
logging.Logger.Println("BACKUPS could not scan table names in SELECT")
os.Exit(-1)
}
table_names[table] = schema
}

return table_names
}
func bucket_local_tables(source_creds vcap.Credentials, up vcap.Credentials) {
table_to_schema := get_table_and_schema_names(source_creds)
for table, schema := range table_to_schema {
mc_pipe := pipes.Mc(
pipes.PG_Dump_Table(source_creds, schema, table),
up,
backup_tag,
source_creds.DB_Name,
source_creds.Get("db_name").String(),
schema, table,
)
mc_pipe.Wait()
Expand All @@ -51,8 +81,8 @@ func bucket_local_tables(source_creds *structs.CredentialsRDS, up structs.UserPr
}
}

func bucket_cgov_tables(source_creds *structs.CredentialsRDS, up map[string]string) {
table_to_schema := util.Get_table_and_schema_names(source_creds)
func bucket_cgov_tables(source_creds vcap.Credentials, up vcap.Credentials) {
table_to_schema := get_table_and_schema_names(source_creds)
for table, schema := range table_to_schema {
s3_pipe := pipes.S3(
pipes.PG_Dump_Table(source_creds, schema, table),
Expand Down Expand Up @@ -80,7 +110,7 @@ Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
source_creds, _ := vcap.GetRDSCreds(SourceDB, "")
source_creds, _ := vcap.GetRDSCredentials(SourceDB)
if slices.Contains([]string{"LOCAL", "TESTING"}, os.Getenv("ENV")) {
up, _ := vcap.GetUserProvidedCredentials("mc")
bucket_local_tables(source_creds, up)
Expand Down
25 changes: 17 additions & 8 deletions cmd/check.go → cmd/check._go
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ import (

"github.com/spf13/cobra"
"gov.gsa.fac.cgov-util/internal/logging"
"gov.gsa.fac.cgov-util/internal/structs"

vcap "gov.gsa.fac.cgov-util/internal/vcap"
)

func get_row_count(creds *structs.CredentialsRDS, table string) int {
func get_row_count(creds vcap.Credentials, 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)
creds.Get("username").String(),
creds.Get("password").String(),
creds.Get("host").String(),
creds.Get("db_name").String(),
)
db, _ := sql.Open("postgres", connStr)
defer db.Close()
row := db.QueryRow(fmt.Sprintf("SELECT count(*) FROM %s", table))
Expand All @@ -32,7 +32,7 @@ func get_row_count(creds *structs.CredentialsRDS, table string) int {
return count
}

func check_results(source *structs.CredentialsRDS, dest *structs.CredentialsRDS, tables []string) {
func check_results(source vcap.Credentials, dest vcap.Credentials, 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.
Expand Down Expand Up @@ -65,7 +65,16 @@ 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.GetRDSCreds(SourceDB, DestinationDB)
source_creds, err := vcap.GetRDSCredentials(SourceDB)
if err != nil {
logging.Logger.Printf("CGOVUTIL cannot get source creds")
os.Exit(-1)
}
dest_creds, err := vcap.GetRDSCredentials(DestinationDB)
if err != nil {
logging.Logger.Printf("CGOVUTIL cannot get dest creds")
os.Exit(-1)
}
check_results(source_creds, dest_creds, args)

},
Expand Down
8 changes: 3 additions & 5 deletions cmd/clone.go → cmd/clone._go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ import (
"github.com/spf13/cobra"
"gov.gsa.fac.cgov-util/internal/logging"
"gov.gsa.fac.cgov-util/internal/pipes"
"gov.gsa.fac.cgov-util/internal/structs"
"gov.gsa.fac.cgov-util/internal/util"
vcap "gov.gsa.fac.cgov-util/internal/vcap"

_ "github.com/lib/pq"
)

func clone(source *structs.CredentialsRDS, dest *structs.CredentialsRDS) {
func clone(source *vcap.CredentialsRDS, dest *vcap.CredentialsRDS) {
psql_pipe := pipes.Psql(pipes.PG_Dump(source), dest)
psql_pipe.Wait()
if err := psql_pipe.Error(); err != nil {
Expand All @@ -25,8 +23,8 @@ func clone(source *structs.CredentialsRDS, dest *structs.CredentialsRDS) {
}
}

func clone_tables(source *structs.CredentialsRDS, dest *structs.CredentialsRDS) {
table_to_schema := util.Get_table_and_schema_names(source)
func clone_tables(source *vcap.CredentialsRDS, dest *vcap.CredentialsRDS) {
table_to_schema := get_table_and_schema_names(source)
for table, schema := range table_to_schema {
psql_pipe := pipes.Psql(pipes.PG_Dump_Table(source, schema, table), dest)
psql_pipe.Wait()
Expand Down
139 changes: 139 additions & 0 deletions cmd/dumpDbToS3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
*/
package cmd

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

_ "github.com/lib/pq"

"github.com/spf13/cobra"
"gov.gsa.fac.cgov-util/internal/logging"
"gov.gsa.fac.cgov-util/internal/pipes"

"gov.gsa.fac.cgov-util/internal/vcap"
)

var (
db string
s3path string
)

func get_table_and_schema_names(source_creds vcap.Credentials) map[string]string {
// Do this table-by-table for RAM reasons.
db, err := sql.Open("postgres", source_creds.Get("uri").String())
if err != nil {
logging.Logger.Println("DUMPDBTOS3 could not connect to DB for table-by-table dump")
logging.Logger.Printf("DUMPDBTOS3 %s\n", err)
os.Exit(-1)
}

tables, err := db.Query("SELECT schemaname, tablename FROM pg_tables WHERE schemaname = 'public'")
if err != nil {
logging.Logger.Println("DUMPDBTOS3 could not get table names for table-by-table dump")
logging.Logger.Printf("DUMPDBTOS3 %s\n", err)
os.Exit(-1)
}

table_names := make(map[string]string, 0)

for tables.Next() {
var table string
var schema string
if err := tables.Scan(&schema, &table); err != nil {
logging.Logger.Println("DUMPDBTOS3 could not scan table names in SELECT")
os.Exit(-1)
}
table_names[table] = schema
}

return table_names
}
func bucket_local_tables(source_creds vcap.Credentials, up vcap.Credentials) {
logging.Logger.Printf("DUMPDBTOS3 backing up from %s to %s\n",
source_creds.Get("name").String(),
up.Get("name").String(),
)
table_to_schema := get_table_and_schema_names(source_creds)
for table, schema := range table_to_schema {
mc_pipe := pipes.Mc(
pipes.PG_Dump_Table(source_creds, schema, table),
up,
fmt.Sprintf("%s/%s-%s.dump", s3path, schema, table),
)
mc_pipe.Wait()
if err := mc_pipe.Error(); err != nil {
logging.Logger.Println("DUMPDBTOS3 `dump | mc` pipe failed")
os.Exit(-1)
}
}
}

func bucket_cgov_tables(source_creds vcap.Credentials, up vcap.Credentials) {
table_to_schema := get_table_and_schema_names(source_creds)
for table, schema := range table_to_schema {
s3_pipe := pipes.S3(
pipes.PG_Dump_Table(source_creds, schema, table),
up,
fmt.Sprintf("%s/%s-%s.dump", s3path, schema, table),
)
s3_pipe.Wait()
if err := s3_pipe.Error(); err != nil {
logging.Logger.Println("DUMPDBTOS3 `dump | s3` pipe failed")
os.Exit(-1)
}
}
}

// dumpDbToS3Cmd represents the dumpDbToS3 command
var dumpDbToS3Cmd = &cobra.Command{
Use: "dumpDbToS3",
Short: "Dumps a full database to a file in S3",
Long: `Dumps a full database to a file in S3`,
Run: func(cmd *cobra.Command, args []string) {

// Check that we can get credentials.
db_creds, err := vcap.VCS.GetCredentials("aws-rds", db)
if err != nil {
logging.Logger.Printf("DUMPDBTOS3 could not get DB credentials for %s", db)
os.Exit(-1)
}

switch os.Getenv("ENV") {
case "LOCAL":
fallthrough
case "TESTING":
up, err := vcap.VCS.GetCredentials("user-provided", "backups")
if err != nil {
logging.Logger.Printf("DUMPDBTOS3 could not get minio credentials")
os.Exit(-1)
}
bucket_local_tables(db_creds, up)
case "DEV":
fallthrough
case "STAGING":
fallthrough
case "PRODUCTION":
up, err := vcap.VCS.GetCredentials("aws-rds", s3path)
if err != nil {
logging.Logger.Printf("DUMPDBTOS3 could not get s3 credentials")
os.Exit(-1)
}
bucket_cgov_tables(db_creds, up)

}
},
}

func init() {
rootCmd.AddCommand(dumpDbToS3Cmd)
dumpDbToS3Cmd.Flags().StringVarP(&db, "db", "", "", "source database label")
dumpDbToS3Cmd.Flags().StringVarP(&s3path, "s3path", "", "", "destination path")

dumpDbToS3Cmd.MarkFlagRequired("db")
dumpDbToS3Cmd.MarkFlagRequired("s3path")

}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
30 changes: 13 additions & 17 deletions internal/pipes/mc.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,44 @@ import (
"github.com/bitfield/script"
"github.com/google/uuid"
"gov.gsa.fac.cgov-util/internal/logging"
"gov.gsa.fac.cgov-util/internal/structs"
"gov.gsa.fac.cgov-util/internal/util"
"gov.gsa.fac.cgov-util/internal/vcap"
)

// https://bitfieldconsulting.com/golang/scripting
func Mc(in_pipe *script.Pipe,
upc structs.UserProvidedCredentials,
prefix string,
source_db string,
schema string,
table string) *script.Pipe {
creds vcap.Credentials,
path string) *script.Pipe {
// // mc pipe myminio/gsa-fac-private-s3/backups/${PREFIX}-${FROM_DATABASE}.dump
// Always set the alias first.
os.Setenv("AWS_PRIVATE_ACCESS_KEY_ID", upc["access_key_id"])
os.Setenv("AWS_PRIVATE_SECRET_ACCESS_KEY", upc["secret_access_key"])
os.Setenv("AWS_PRIVATE_ACCESS_KEY_ID", creds.Get("access_key_id").String())
os.Setenv("AWS_PRIVATE_SECRET_ACCESS_KEY", creds.Get("secret_access_key").String())

minio_alias := fmt.Sprintf("minio_alias_%s", uuid.New())

set_alias := []string{
"mc", "alias", "set", minio_alias,
upc["endpoint"],
upc["admin_username"],
upc["admin_password"],
creds.Get("endpoint").String(),
creds.Get("admin_username").String(),
creds.Get("admin_password").String(),
}
sa_combined := strings.Join(set_alias[:], " ")
logging.Logger.Printf("BACKUPS Running `%s`\n", sa_combined)
logging.Logger.Printf("MC Running `%s`\n", sa_combined)
script.Exec(sa_combined).Stdout()

cmd := []string{
"mc",
"pipe",
fmt.Sprintf("%s/%s/backups/%s-%s_%s.dump",
fmt.Sprintf("%s/%s/%s",
minio_alias,
upc["bucket"],
prefix,
schema, table),
creds.Get("bucket").String(),
path),
}
// Combine the slice for printing and execution.
combined := strings.Join(cmd[:], " ")
if util.IsDebugLevel("DEBUG") {
fmt.Printf("command: %s\n", combined)
}
logging.Logger.Printf("BACKUPS mc targeting %s", prefix)
logging.Logger.Printf("MC mc targeting %s", path)
return in_pipe.Exec(combined)
}
Loading

0 comments on commit c135326

Please sign in to comment.