Skip to content

Commit 8ee8f1e

Browse files
committed
snapshot: add snapshot contents uploader
1 parent 5d82eb0 commit 8ee8f1e

7 files changed

Lines changed: 275 additions & 62 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ release
66
.idea
77
.env
88
.envrc
9+
src-snapshot/

cmd/src/snapshot_databases.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ TARGETS FILES
9494
out.WriteLine(output.Emojif(output.EmojiInfo, "Using predefined targets for %s environments", targetKey))
9595
}
9696

97-
commands, err := pgdump.BuildCommands(commandBuilder, targets)
97+
commands, err := pgdump.BuildCommands(srcSnapshotDir, commandBuilder, targets)
9898
if err != nil {
9999
return errors.Wrap(err, "failed to build commands")
100100
}

cmd/src/snapshot_summary.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@ func init() {
1818
1919
USAGE
2020
src login # site-admin authentication required
21-
src [-v] snapshot summary [-summary-path="./src-snapshot-summary.json"]
21+
src [-v] snapshot summary
2222
`
2323
flagSet := flag.NewFlagSet("summary", flag.ExitOnError)
24-
snapshotPath := flagSet.String("summary-path", "./src-snapshot-summary.json", "path to write snapshot summary to")
2524
apiFlags := api.NewFlags(flagSet)
2625

2726
snapshotCommands = append(snapshotCommands, &command{
@@ -39,7 +38,7 @@ USAGE
3938
return err
4039
}
4140

42-
f, err := os.OpenFile(*snapshotPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
41+
f, err := os.OpenFile(snapshotSummaryPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
4342
if err != nil {
4443
return errors.Wrap(err, "open snapshot file")
4544
}

cmd/src/snapshot_upload.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"flag"
6+
"fmt"
7+
"io"
8+
"io/fs"
9+
"os"
10+
"path"
11+
12+
"cloud.google.com/go/storage"
13+
"github.com/sourcegraph/sourcegraph/lib/errors"
14+
"github.com/sourcegraph/sourcegraph/lib/group"
15+
"github.com/sourcegraph/sourcegraph/lib/output"
16+
"google.golang.org/api/option"
17+
18+
"github.com/sourcegraph/src-cli/internal/pgdump"
19+
)
20+
21+
const srcSnapshotDir = "./src-snapshot"
22+
23+
var snapshotSummaryPath = path.Join(srcSnapshotDir, "summary.json")
24+
25+
// https://pkg.go.dev/cloud.google.com/go/storage#section-readme
26+
27+
func init() {
28+
usage := `'src snapshot upload' uploads snapshot contents.
29+
30+
USAGE
31+
src
32+
`
33+
flagSet := flag.NewFlagSet("upload", flag.ExitOnError)
34+
bucketName := flagSet.String("bucket", "", "destination Cloud Storage bucket name")
35+
credentialsPath := flagSet.String("credentials", "", "JSON credentials file for Google Cloud")
36+
37+
snapshotCommands = append(snapshotCommands, &command{
38+
flagSet: flagSet,
39+
handler: func(args []string) error {
40+
if err := flagSet.Parse(args); err != nil {
41+
return err
42+
}
43+
44+
if *bucketName == "" {
45+
return errors.New("-bucket required")
46+
}
47+
if *credentialsPath == "" {
48+
return errors.New("-credentials required")
49+
}
50+
51+
out := output.NewOutput(flagSet.Output(), output.OutputOpts{Verbose: *verbose})
52+
ctx := context.Background()
53+
c, err := storage.NewClient(ctx, option.WithCredentialsFile(*credentialsPath))
54+
if err != nil {
55+
return errors.Wrap(err, "create Cloud Storage client")
56+
}
57+
58+
type upload struct {
59+
file *os.File
60+
stat os.FileInfo
61+
}
62+
var (
63+
uploads []upload // index aligned with progressBars
64+
progressBars []output.ProgressBar // index aligned with uploads
65+
)
66+
67+
// Open snapshot summary
68+
if f, err := os.Open(snapshotSummaryPath); err != nil {
69+
return errors.Wrap(err, "failed to open snapshot summary - generate one with 'src snapshot summary'")
70+
} else {
71+
stat, err := f.Stat()
72+
if err != nil {
73+
return errors.Wrap(err, "get file size")
74+
}
75+
uploads = append(uploads, upload{
76+
file: f,
77+
stat: stat,
78+
})
79+
progressBars = append(progressBars, output.ProgressBar{
80+
Label: stat.Name(),
81+
Max: float64(stat.Size()),
82+
})
83+
}
84+
85+
// Open database dumps
86+
for _, o := range pgdump.Outputs(srcSnapshotDir, pgdump.Targets{}) {
87+
if f, err := os.Open(o.Output); err != nil {
88+
return errors.Wrap(err, "failed to database dump - generate one with 'src snapshot databases'")
89+
} else {
90+
stat, err := f.Stat()
91+
if err != nil {
92+
return errors.Wrap(err, "get file size")
93+
}
94+
uploads = append(uploads, upload{
95+
file: f,
96+
stat: stat,
97+
})
98+
progressBars = append(progressBars, output.ProgressBar{
99+
Label: stat.Name(),
100+
Max: float64(stat.Size()),
101+
})
102+
}
103+
}
104+
105+
// Start uploads
106+
progress := out.Progress(progressBars, nil)
107+
progress.WriteLine(output.Emoji(output.EmojiHourglass, "Starting uploads..."))
108+
bucket := c.Bucket(*bucketName)
109+
g := group.New().WithErrors().WithContext(ctx)
110+
for i, u := range uploads {
111+
i := i
112+
u := u
113+
g.Go(func(ctx context.Context) error {
114+
progressFn := func(p int64) { progress.SetValue(i, float64(p)) }
115+
116+
if err := copyToBucket(ctx, u.file, u.stat, bucket, progressFn); err != nil {
117+
return errors.Wrap(err, u.stat.Name())
118+
}
119+
120+
return nil
121+
})
122+
}
123+
124+
// Finalize
125+
errs := g.Wait()
126+
progress.Complete()
127+
if errs != nil {
128+
out.WriteLine(output.Line(output.EmojiFailure, output.StyleFailure, "Some snapshot contents failed to upload."))
129+
return errs
130+
}
131+
132+
out.WriteLine(output.Emoji(output.EmojiSuccess, "Summary contents uploaded!"))
133+
return nil
134+
},
135+
usageFunc: func() { fmt.Fprint(flag.CommandLine.Output(), usage) },
136+
})
137+
}
138+
139+
func copyToBucket(ctx context.Context, src io.Reader, stat fs.FileInfo, dst *storage.BucketHandle, progressFn func(int64)) error {
140+
writer := dst.Object(stat.Name()).NewWriter(ctx)
141+
writer.ProgressFunc = progressFn
142+
defer writer.Close()
143+
144+
// io.Copy is the best way to copy from a reader to writer in Go, and storage.Writer
145+
// has its own chunking mechanisms internally.
146+
written, err := io.Copy(writer, src)
147+
if err != nil {
148+
return err
149+
}
150+
151+
// Validate we have sent all data
152+
if size := stat.Size(); written != size {
153+
return errors.Newf("expected to write %d bytes, but actually wrote %d bytes",
154+
size, written)
155+
}
156+
157+
return nil
158+
}

go.mod

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ module github.com/sourcegraph/src-cli
33
go 1.18
44

55
require (
6+
cloud.google.com/go/storage v1.28.0
67
github.com/Masterminds/semver v1.5.0
78
github.com/creack/goselect v0.1.2
89
github.com/derision-test/glock v0.0.0-20210316032053-f5b74334bb29
910
github.com/dineshappavoo/basex v0.0.0-20170425072625-481a6f6dc663
1011
github.com/dustin/go-humanize v1.0.0
1112
github.com/gobwas/glob v0.2.3
12-
github.com/google/go-cmp v0.5.8
13+
github.com/google/go-cmp v0.5.9
1314
github.com/grafana/regexp v0.0.0-20220304100321-149c8afcd6cb
1415
github.com/hexops/autogold v1.3.0
1516
github.com/jedib0t/go-pretty/v6 v6.3.7
@@ -22,19 +23,27 @@ require (
2223
github.com/sourcegraph/go-diff v0.6.1
2324
github.com/sourcegraph/jsonx v0.0.0-20200629203448-1a936bd500cf
2425
github.com/sourcegraph/scip v0.2.3
25-
github.com/sourcegraph/sourcegraph/lib v0.0.0-20221004162410-237d5855fa13
26+
github.com/sourcegraph/sourcegraph/lib v0.0.0-20221128221602-4b48d5d69f5d
2627
github.com/stretchr/testify v1.8.0
27-
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
28-
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
29-
google.golang.org/protobuf v1.28.0
28+
golang.org/x/net v0.2.0
29+
golang.org/x/sync v0.1.0
30+
google.golang.org/api v0.102.0
31+
google.golang.org/protobuf v1.28.1
3032
gopkg.in/yaml.v3 v3.0.1
3133
jaytaylor.com/html2text v0.0.0-20200412013138-3577fbdbcff7
3234
)
3335

3436
require (
37+
cloud.google.com/go v0.104.0 // indirect
38+
cloud.google.com/go/compute v1.12.1 // indirect
39+
cloud.google.com/go/compute/metadata v0.2.1 // indirect
40+
cloud.google.com/go/iam v0.5.0 // indirect
3541
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
3642
github.com/Masterminds/sprig v2.15.0+incompatible // indirect
3743
github.com/alecthomas/chroma v0.10.0 // indirect
44+
github.com/alecthomas/kingpin v2.2.6+incompatible // indirect
45+
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
46+
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
3847
github.com/aokoli/goutils v1.0.1 // indirect
3948
github.com/aymerick/douceur v0.2.0 // indirect
4049
github.com/bufbuild/buf v1.4.0 // indirect
@@ -43,7 +52,9 @@ require (
4352
github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f // indirect
4453
github.com/cockroachdb/redact v1.1.3 // indirect
4554
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
55+
github.com/dave/jennifer v1.6.0 // indirect
4656
github.com/davecgh/go-spew v1.1.1 // indirect
57+
github.com/derision-test/go-mockgen v1.3.7 // indirect
4758
github.com/dlclark/regexp2 v1.4.0 // indirect
4859
github.com/envoyproxy/protoc-gen-validate v0.3.0-java // indirect
4960
github.com/fatih/color v1.13.0 // indirect
@@ -55,6 +66,8 @@ require (
5566
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
5667
github.com/golang/protobuf v1.5.2 // indirect
5768
github.com/google/uuid v1.3.0 // indirect
69+
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
70+
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
5871
github.com/gorilla/css v1.0.0 // indirect
5972
github.com/hexops/gotextdiff v1.0.3 // indirect
6073
github.com/hexops/valast v1.4.1 // indirect
@@ -72,6 +85,7 @@ require (
7285
github.com/mattn/go-colorable v0.1.13 // indirect
7386
github.com/mattn/go-runewidth v0.0.13 // indirect
7487
github.com/microcosm-cc/bluemonday v1.0.17 // indirect
88+
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
7589
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
7690
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
7791
github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -104,13 +118,16 @@ require (
104118
go.uber.org/multierr v1.8.0 // indirect
105119
go.uber.org/zap v1.23.0 // indirect
106120
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
107-
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
108-
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
109-
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect
110-
golang.org/x/text v0.3.7 // indirect
111-
golang.org/x/tools v0.1.12 // indirect
112-
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4 // indirect
113-
google.golang.org/grpc v1.45.0 // indirect
121+
golang.org/x/mod v0.7.0 // indirect
122+
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
123+
golang.org/x/sys v0.2.0 // indirect
124+
golang.org/x/term v0.2.0 // indirect
125+
golang.org/x/text v0.4.0 // indirect
126+
golang.org/x/tools v0.3.0 // indirect
127+
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
128+
google.golang.org/appengine v1.6.7 // indirect
129+
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect
130+
google.golang.org/grpc v1.50.1 // indirect
114131
gopkg.in/yaml.v2 v2.4.0 // indirect
115132
mvdan.cc/gofumpt v0.2.1 // indirect
116133
)

0 commit comments

Comments
 (0)