Skip to content

Commit e7a1e42

Browse files
committed
snapshot: add pg_dump command builder with 'snapshot databases'
1 parent 4d2957c commit e7a1e42

File tree

2 files changed

+215
-0
lines changed

2 files changed

+215
-0
lines changed

cmd/src/snapshot_databases.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
"strings"
8+
9+
"github.com/sourcegraph/sourcegraph/lib/errors"
10+
"github.com/sourcegraph/sourcegraph/lib/output"
11+
"gopkg.in/yaml.v3"
12+
13+
"github.com/sourcegraph/src-cli/internal/pgdump"
14+
)
15+
16+
func init() {
17+
usage := `'src snapshot databases' generates commands to export Sourcegraph database dumps.
18+
Note that these commands are intended for use as reference - you may need to adjust the commands for your deployment.
19+
20+
USAGE
21+
src [-v] snapshot databases <pg_dump|docker|kubectl> [--targets=<docker|k8s|"targets.yaml">]
22+
23+
TARGETS FILES
24+
Predefined targets are available based on default Sourcegraph configurations ('docker', 'k8s').
25+
Custom targets configuration can be provided in YAML format with '--targets=target.yaml', e.g.
26+
27+
primary:
28+
entity: ...
29+
dbname: ...
30+
username: ...
31+
password: ...
32+
codeintel:
33+
# same as above
34+
codeinsights:
35+
# same as above
36+
`
37+
flagSet := flag.NewFlagSet("databases", flag.ExitOnError)
38+
targetsKeyFlag := flagSet.String("targets", "auto", "predefined targets ('docker' or 'k8s'), or a custom targets.yaml file")
39+
40+
snapshotCommands = append(snapshotCommands, &command{
41+
flagSet: flagSet,
42+
handler: func(args []string) error {
43+
if err := flagSet.Parse(args); err != nil {
44+
return err
45+
}
46+
out := output.NewOutput(flagSet.Output(), output.OutputOpts{Verbose: *verbose})
47+
48+
var builder string
49+
if len(args) > 0 {
50+
builder = args[0]
51+
}
52+
53+
targetKey := "docker"
54+
var commandBuilder pgdump.CommandBuilder
55+
switch builder {
56+
case "pg_dump", "":
57+
commandBuilder = func(t pgdump.Target) (string, error) {
58+
return pgdump.Command(t), nil
59+
}
60+
case "docker":
61+
commandBuilder = func(t pgdump.Target) (string, error) {
62+
// docker exec -it pgsql sh -c 'pg_dump --no-owner --format=p --no-acl --username=sg --dbname=sg'
63+
return fmt.Sprintf("docker exec -it %s sh -c '%s'", t.Target, pgdump.Command(t)), nil
64+
}
65+
case "kubectl":
66+
targetKey = "k8s"
67+
commandBuilder = func(t pgdump.Target) (string, error) {
68+
// docker exec -it pgsql sh -c 'pg_dump --no-owner --format=p --no-acl --username=sg --dbname=sg'
69+
return fmt.Sprintf("kubectl exec -it %s -- bash -c '%s'", t.Target, pgdump.Command(t)), nil
70+
}
71+
default:
72+
return errors.Newf("unknown or invalid template type %q", builder)
73+
}
74+
if *targetsKeyFlag != "auto" {
75+
targetKey = *targetsKeyFlag
76+
}
77+
78+
targets, ok := predefinedDatabaseDumpTargets[targetKey]
79+
if !ok {
80+
out.WriteLine(output.Emojif(output.EmojiInfo, "Using targets defined in targets file %q", targetKey))
81+
f, err := os.Open(targetKey)
82+
if err != nil {
83+
return errors.Wrapf(err, "invalid targets file %q", targetKey)
84+
}
85+
if err := yaml.NewDecoder(f).Decode(&targets); err != nil {
86+
return errors.Wrapf(err, "invalid targets file %q", targetKey)
87+
}
88+
} else {
89+
out.WriteLine(output.Emojif(output.EmojiInfo, "Using predefined targets for %s environments", targetKey))
90+
}
91+
92+
commands, err := pgdump.BuildCommands(commandBuilder, targets)
93+
if err != nil {
94+
return errors.Wrap(err, "failed to build commands")
95+
}
96+
97+
b := out.Block(output.Emoji(output.EmojiSuccess, "Commands generated - run them all to generate required database dumps:"))
98+
b.Write("\n" + strings.Join(commands, "\n"))
99+
b.Close()
100+
101+
out.WriteLine(output.Styledf(output.StyleSuggestion, "Note that you may need to do some additional setup, such as authentication, beforehand."))
102+
103+
return nil
104+
},
105+
usageFunc: func() { fmt.Fprint(flag.CommandLine.Output(), usage) },
106+
})
107+
}
108+
109+
// predefinedDatabaseDumpTargets is based on default Sourcegraph configurations.
110+
var predefinedDatabaseDumpTargets = map[string]pgdump.Targets{
111+
"docker": { // based on deploy-sourcegraph-managed
112+
Primary: pgdump.Target{
113+
Target: "pgsql",
114+
DBName: "sg",
115+
Username: "sg",
116+
Password: "sg",
117+
},
118+
CodeIntel: pgdump.Target{
119+
Target: "codeintel-db",
120+
DBName: "sg",
121+
Username: "sg",
122+
Password: "sg",
123+
},
124+
CodeInsights: pgdump.Target{
125+
Target: "codeinsights-db",
126+
DBName: "postgres",
127+
Username: "postgres",
128+
Password: "password",
129+
},
130+
},
131+
"k8s": { // based on deploy-sourcegraph-helm
132+
Primary: pgdump.Target{
133+
Target: "statefulset/pgsql",
134+
DBName: "sg",
135+
Username: "sg",
136+
Password: "sg",
137+
},
138+
CodeIntel: pgdump.Target{
139+
Target: "statefulset/codeintel-db",
140+
DBName: "sg",
141+
Username: "sg",
142+
Password: "sg",
143+
},
144+
CodeInsights: pgdump.Target{
145+
Target: "statefulset/codeinsights-db",
146+
DBName: "postgres",
147+
Username: "postgres",
148+
Password: "password",
149+
},
150+
},
151+
}

internal/pgdump/pgdump.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package pgdump
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/sourcegraph/sourcegraph/lib/errors"
7+
)
8+
9+
// Targets represents configuration for each of Sourcegraph's databases.
10+
type Targets struct {
11+
Primary Target `yaml:"primary"`
12+
CodeIntel Target `yaml:"codeintel"`
13+
CodeInsights Target `yaml:"codeinsights"`
14+
}
15+
16+
// Target represents a database for pg_dump to export.
17+
type Target struct {
18+
// e.g. container, deployment, statefulset, etc.
19+
Target string `yaml:"entity"`
20+
21+
DBName string `yaml:"dbname"`
22+
Username string `yaml:"username"`
23+
24+
// Only include password if non-sensitive
25+
Password string `yaml:"password"`
26+
}
27+
28+
// Command generates a pg_dump command that can be used for on-prem-to-Cloud migrations.
29+
func Command(t Target) string {
30+
dump := fmt.Sprintf("pg_dump --no-owner --format=p --no-acl --username=%s --dbname=%s",
31+
t.Username, t.DBName)
32+
if t.Password == "" {
33+
return dump
34+
}
35+
return fmt.Sprintf("PGPASSWORD=%s %s", t.Password, dump)
36+
}
37+
38+
type CommandBuilder func(Target) (string, error)
39+
40+
// BuildCommands generates commands that output Postgres dumps and sends them to predefined
41+
// files for each target database.
42+
func BuildCommands(commandBuilder CommandBuilder, targets Targets) ([]string, error) {
43+
var commands []string
44+
for _, t := range []struct {
45+
Output string
46+
Target Target
47+
}{{
48+
Output: "primary.sql",
49+
Target: targets.Primary,
50+
}, {
51+
Output: "codeintel.sql",
52+
Target: targets.CodeIntel,
53+
}, {
54+
Output: "codeinsights.sql",
55+
Target: targets.CodeInsights,
56+
}} {
57+
c, err := commandBuilder(t.Target)
58+
if err != nil {
59+
return nil, errors.Wrapf(err, "generating command for %q", t.Output)
60+
}
61+
commands = append(commands, fmt.Sprintf("%s > %s", c, t.Output))
62+
}
63+
return commands, nil
64+
}

0 commit comments

Comments
 (0)