-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
snapshot: add 'snapshot summary', 'snapshot test' (#888)
- Loading branch information
Showing
6 changed files
with
674 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
) | ||
|
||
var snapshotCommands commander | ||
|
||
func init() { | ||
usage := `'src snapshot' manages snapshots of Sourcegraph instance data. All subcommands are currently EXPERIMENTAL. | ||
USAGE | ||
src [-v] snapshot <command> | ||
COMMANDS | ||
summary export summary data about an instance for acceptance testing of a restored Sourcegraph instance | ||
test use exported summary data and instance health indicators to validate a restored and upgraded instance | ||
` | ||
flagSet := flag.NewFlagSet("snapshot", flag.ExitOnError) | ||
|
||
commands = append(commands, &command{ | ||
flagSet: flagSet, | ||
handler: func(args []string) error { | ||
snapshotCommands.run(flagSet, "src snapshot", usage, args) | ||
return nil | ||
}, | ||
usageFunc: func() { fmt.Fprint(flag.CommandLine.Output(), usage) }, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"flag" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/sourcegraph/sourcegraph/lib/errors" | ||
"github.com/sourcegraph/sourcegraph/lib/output" | ||
|
||
"github.com/sourcegraph/src-cli/internal/api" | ||
) | ||
|
||
func init() { | ||
usage := `'src snapshot summary' generates summary data for acceptance testing of a restored Sourcegraph instance with 'src snapshot test'. | ||
USAGE | ||
src login # site-admin authentication required | ||
src [-v] snapshot summary [-summary-path="./src-snapshot-summary.json"] | ||
` | ||
flagSet := flag.NewFlagSet("summary", flag.ExitOnError) | ||
snapshotPath := flagSet.String("summary-path", "./src-snapshot-summary.json", "path to write snapshot summary to") | ||
apiFlags := api.NewFlags(flagSet) | ||
|
||
snapshotCommands = append(snapshotCommands, &command{ | ||
flagSet: flagSet, | ||
handler: func(args []string) error { | ||
if err := flagSet.Parse(args); err != nil { | ||
return err | ||
} | ||
out := output.NewOutput(flagSet.Output(), output.OutputOpts{Verbose: *verbose}) | ||
|
||
client := cfg.apiClient(apiFlags, flagSet.Output()) | ||
|
||
snapshotResult, err := fetchSnapshotSummary(context.Background(), client) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
f, err := os.OpenFile(*snapshotPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) | ||
if err != nil { | ||
return errors.Wrap(err, "open snapshot file") | ||
} | ||
enc := json.NewEncoder(f) | ||
enc.SetIndent("", "\t") | ||
if err := enc.Encode(snapshotResult); err != nil { | ||
return errors.Wrap(err, "write snapshot file") | ||
} | ||
|
||
out.WriteLine(output.Emoji(output.EmojiSuccess, "Summary snapshot data generated!")) | ||
return nil | ||
}, | ||
usageFunc: func() { fmt.Fprint(flag.CommandLine.Output(), usage) }, | ||
}) | ||
} | ||
|
||
type snapshotSummary struct { | ||
ExternalServices struct { | ||
TotalCount int | ||
Nodes []struct { | ||
Kind string | ||
ID string | ||
} | ||
} | ||
Site struct { | ||
AuthProviders struct { | ||
TotalCount int | ||
Nodes []struct { | ||
ServiceType string | ||
ServiceID string | ||
} | ||
} | ||
} | ||
} | ||
|
||
func fetchSnapshotSummary(ctx context.Context, client api.Client) (*snapshotSummary, error) { | ||
var snapshotResult snapshotSummary | ||
ok, err := client.NewQuery(` | ||
query GenerateSnapshotAcceptanceData { | ||
externalServices { | ||
totalCount | ||
nodes { | ||
kind | ||
id | ||
} | ||
} | ||
site { | ||
authProviders { | ||
totalCount | ||
nodes { | ||
serviceType | ||
serviceID | ||
} | ||
} | ||
} | ||
} | ||
`).Do(ctx, &snapshotResult) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "generate snapshot") | ||
} else if !ok { | ||
return nil, errors.New("received no data") | ||
} | ||
return &snapshotResult, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"flag" | ||
"fmt" | ||
"os" | ||
"time" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/sourcegraph/sourcegraph/lib/errors" | ||
"github.com/sourcegraph/sourcegraph/lib/output" | ||
|
||
"github.com/sourcegraph/src-cli/internal/api" | ||
"github.com/sourcegraph/src-cli/internal/instancehealth" | ||
) | ||
|
||
// note that this file is called '_testcmd.go' because '_test.go' cannot be used | ||
|
||
func init() { | ||
usage := `'src snapshot test' uses exported summary data to validate a restored and upgraded instance. | ||
USAGE | ||
src login # site-admin authentication required | ||
src [-v] snapshot test [-summary-path="./src-snapshot-summary.json"] | ||
` | ||
flagSet := flag.NewFlagSet("test", flag.ExitOnError) | ||
snapshotSummaryPath := flagSet.String("summary-path", "./src-snapshot-summary.json", "path to read snapshot summary from") | ||
since := flagSet.Duration("since", 1*time.Hour, "duration ago to look for healthcheck data") | ||
apiFlags := api.NewFlags(flagSet) | ||
|
||
snapshotCommands = append(snapshotCommands, &command{ | ||
flagSet: flagSet, | ||
handler: func(args []string) error { | ||
if err := flagSet.Parse(args); err != nil { | ||
return err | ||
} | ||
out := output.NewOutput(flagSet.Output(), output.OutputOpts{Verbose: *verbose}) | ||
client := cfg.apiClient(apiFlags, flagSet.Output()) | ||
|
||
// Fetch health data | ||
instanceHealth, err := instancehealth.GetIndicators(context.Background(), client) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Optionally validate snapshots | ||
if *snapshotSummaryPath != "" { | ||
f, err := os.OpenFile(*snapshotSummaryPath, os.O_RDONLY, os.ModePerm) | ||
if err != nil { | ||
return errors.Wrap(err, "open snapshot file") | ||
} | ||
var recordedSummary snapshotSummary | ||
if err := json.NewDecoder(f).Decode(&recordedSummary); err != nil { | ||
return errors.Wrap(err, "read snapshot file") | ||
} | ||
// Fetch new snapshot | ||
newSummary, err := fetchSnapshotSummary(context.Background(), client) | ||
if err != nil { | ||
return errors.Wrap(err, "get snapshot") | ||
} | ||
if err := compareSnapshotSummaries(out, recordedSummary, *newSummary); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
// generate checks set | ||
checks := instancehealth.NewChecks(*since, *instanceHealth) | ||
|
||
// Run checks | ||
var validationErrors error | ||
for _, check := range checks { | ||
validationErrors = errors.Append(validationErrors, check(out)) | ||
} | ||
if validationErrors != nil { | ||
out.WriteLine(output.Linef(output.EmojiFailure, output.StyleFailure, | ||
"Critical issues found: %s", err.Error())) | ||
return errors.New("validation failed") | ||
} | ||
out.WriteLine(output.Line(output.EmojiSuccess, output.StyleSuccess, | ||
"No critical issues found!")) | ||
return nil | ||
}, | ||
usageFunc: func() { fmt.Fprint(flag.CommandLine.Output(), usage) }, | ||
}) | ||
} | ||
|
||
func compareSnapshotSummaries(out *output.Output, recordedSummary, newSummary snapshotSummary) error { | ||
b := out.Block(output.Styled(output.StyleBold, "Snapshot contents")) | ||
defer b.Close() | ||
|
||
// Compare | ||
diff := cmp.Diff(recordedSummary, newSummary) | ||
if diff != "" { | ||
b.WriteLine(output.Line(output.EmojiFailure, output.StyleFailure, "Snapshot diff detected:")) | ||
b.WriteCode("diff", diff) | ||
return errors.New("snapshot mismatch") | ||
} | ||
b.WriteLine(output.Emoji(output.EmojiSuccess, "Snapshots match!")) | ||
return nil | ||
} |
Oops, something went wrong.