Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Follow-up to gzip encoding of request body #343

Merged
merged 6 commits into from
Oct 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ All notable changes to `src-cli` are documented in this file.
- `src campaign [preview|apply]` now check whether `git` and `docker` are available before trying to execute a campaign spec's steps. [#326](https://github.com/sourcegraph/src-cli/pull/326)
- The progress bar displayed by `src campaign [preview|apply]` has been extended by status bars that show which steps are currently being executed for each repository. [#338](https://github.com/sourcegraph/src-cli/pull/338)
- `src campaign [preview|apply]` now shows a warning when no changeset specs have been created.
- Requests sent to Sourcegraph by the `src campaign` commands now use gzip compression for the body when talking to Sourcegraph 3.21.0 and later. [#336](https://github.com/sourcegraph/src-cli/pull/336) and [#343](https://github.com/sourcegraph/src-cli/pull/343)

### Fixed

Expand Down
4 changes: 4 additions & 0 deletions cmd/src/campaigns_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ Examples:
Client: cfg.apiClient(flags.api, flagSet.Output()),
})

if err := svc.DetermineFeatureFlags(ctx); err != nil {
return err
}

if err := doApply(ctx, out, svc, flags); err != nil {
printExecutionError(out, err)
return &exitCodeError{nil, 1}
Expand Down
4 changes: 4 additions & 0 deletions cmd/src/campaigns_preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ Examples:
Client: cfg.apiClient(flags.api, flagSet.Output()),
})

if err := svc.DetermineFeatureFlags(ctx); err != nil {
return err
}

_, url, err := campaignsExecute(ctx, out, svc, flags)
if err != nil {
printExecutionError(out, err)
Expand Down
5 changes: 5 additions & 0 deletions cmd/src/campaigns_repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ Examples:
client := cfg.apiClient(apiFlags, flagSet.Output())

svc := campaigns.NewService(&campaigns.ServiceOpts{Client: client})

if err := svc.DetermineFeatureFlags(ctx); err != nil {
return err
}

spec, _, err := svc.ParseCampaignSpec(specFile)
if err != nil {
return errors.Wrap(err, "parsing campaign spec")
Expand Down
1 change: 1 addition & 0 deletions cmd/src/campaigns_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Examples:
defer specFile.Close()

svc := campaigns.NewService(&campaigns.ServiceOpts{})

spec, _, err := svc.ParseCampaignSpec(specFile)
if err != nil {
return errors.Wrap(err, "parsing campaign spec")
Expand Down
4 changes: 3 additions & 1 deletion cmd/src/config_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ Examples:
ViewerSettings *SettingsCascade
SettingsSubject *SettingsSubject
}
if ok, err := client.NewRequest(query, queryVars).Do(context.Background(), &result); err != nil || !ok {

ok, err := client.NewRequest(query, queryVars).Do(context.Background(), &result)
if err != nil || !ok {
return err
}

Expand Down
4 changes: 3 additions & 1 deletion cmd/src/config_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ Examples:
ViewerSettings *SettingsCascade
SettingsSubject *SettingsSubject
}
if ok, err := client.NewRequest(query, queryVars).Do(context.Background(), &result); err != nil || !ok {

ok, err := client.NewRequest(query, queryVars).Do(context.Background(), &result)
if err != nil || !ok {
return err
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/sourcegraph/src-cli
go 1.13

require (
github.com/Masterminds/semver v1.5.0
github.com/dustin/go-humanize v1.0.0
github.com/efritz/pentimento v0.0.0-20190429011147-ade47d831101
github.com/google/go-cmp v0.5.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
Expand Down
136 changes: 31 additions & 105 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ import (
"io/ioutil"
"net/http"
"os"
"regexp"
"strings"

"github.com/Masterminds/semver"
"github.com/hashicorp/go-multierror"
"github.com/jig/teereadcloser"
ioaux "github.com/jig/teereadcloser"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like goimports is what keeps adding the ioaux alias.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I think it's correct, since the package defined in teereadcloser is called ioaux: https://github.com/jig/teereadcloser/blob/953720c48e058a8869b07daa7db756dc7823b2e1/teereadcloser.go#L5

"github.com/kballard/go-shellquote"
"github.com/mattn/go-isatty"
"github.com/pkg/errors"
Expand All @@ -31,6 +29,13 @@ type Client interface {
// NewRequest creates a GraphQL request.
NewRequest(query string, vars map[string]interface{}) Request

// NewGzippedRequest creates a GraphQL request with gzip compression turned on.
NewGzippedRequest(query string, vars map[string]interface{}) Request

// NewGzippedQuery is a convenience wrapper around NewQuery with gzip
// compression turned on.
NewGzippedQuery(query string) Request

// NewHTTPRequest creates an http.Request for the Sourcegraph API.
//
// path is joined against the API route. For example on Sourcegraph.com this
Expand All @@ -56,15 +61,15 @@ type Request interface {

// client is the internal concrete type implementing Client.
type client struct {
opts ClientOpts
supportsGzip *bool
opts ClientOpts
}

// request is the internal concrete type implementing Request.
type request struct {
client *client
query string
vars map[string]interface{}
gzip bool
}

// ClientOpts encapsulates the options given to NewClient.
Expand Down Expand Up @@ -116,36 +121,25 @@ func (c *client) NewRequest(query string, vars map[string]interface{}) Request {
}
}

func (c *client) NewHTTPRequest(ctx context.Context, method, p string, body io.Reader) (*http.Request, error) {
if c.supportsGzip == nil {
// set to false, unless we have a new enough version
supportsGzip := false
c.supportsGzip = &supportsGzip

version, err := c.getSourcegraphVersion(ctx)

// ignore errors; we only care if the version is sufficently new
if err == nil {
supportsGzip, err = sourcegraphVersionCheck(version, ">= 3.21.0", "2020-10-12")
if err == nil {
c.supportsGzip = &supportsGzip
}
}
func (c *client) NewGzippedRequest(query string, vars map[string]interface{}) Request {
return &request{
client: c,
query: query,
vars: vars,
gzip: true,
}
}

if *c.supportsGzip && body != nil {
body = codeintelutils.Gzip(body)
}
func (c *client) NewGzippedQuery(query string) Request {
return c.NewGzippedRequest(query, nil)
}

func (c *client) NewHTTPRequest(ctx context.Context, method, p string, body io.Reader) (*http.Request, error) {
req, err := c.createHTTPRequest(ctx, method, p, body)
if err != nil {
return nil, err
}

if *c.supportsGzip {
req.Header.Set("Content-Encoding", "gzip")
}

return req, nil
}

Expand Down Expand Up @@ -201,12 +195,21 @@ func (r *request) do(ctx context.Context, result interface{}) (bool, error) {
return false, err
}

var bufBody io.Reader = bytes.NewBuffer(reqBody)
if r.gzip {
bufBody = codeintelutils.Gzip(bufBody)
}

// Create the HTTP request.
req, err := r.client.NewHTTPRequest(ctx, "POST", ".api/graphql", bytes.NewBuffer(reqBody))
req, err := r.client.NewHTTPRequest(ctx, "POST", ".api/graphql", bufBody)
if err != nil {
return false, err
}

if r.gzip {
req.Header.Set("Content-Encoding", "gzip")
}

// Perform the request.
resp, err := http.DefaultClient.Do(req)
if err != nil {
Expand Down Expand Up @@ -303,80 +306,3 @@ func (r *request) curlCmd() (string, error) {
s += fmt.Sprintf(" %s", shellquote.Join(r.client.opts.Endpoint+"/.api/graphql"))
return s, nil
}

const sourcegraphVersionQuery = `query SourcegraphVersion {
site {
productVersion
}
}
`

func (c *client) getSourcegraphVersion(ctx context.Context) (string, error) {
var sourcegraphVersion struct {
Data struct {
Site struct {
ProductVersion string
}
}
}

// Create the JSON object.
reqBody, err := json.Marshal(map[string]interface{}{
"query": sourcegraphVersionQuery,
})
if err != nil {
return "", err
}

// Create the HTTP request.
req, err := c.createHTTPRequest(ctx, "POST", ".api/graphql", bytes.NewBuffer(reqBody))
if err != nil {
return "", err
}

// Perform the request.
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()

respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}

if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("checking sourcegraph backend version; got status code %d", resp.StatusCode)
}

err = json.Unmarshal(respBytes, &sourcegraphVersion)
if err != nil {
return "", err
}

return sourcegraphVersion.Data.Site.ProductVersion, err
}

func sourcegraphVersionCheck(version, constraint, minDate string) (bool, error) {
if version == "dev" || version == "0.0.0+dev" {
return true, nil
}

buildDate := regexp.MustCompile(`^\d+_(\d{4}-\d{2}-\d{2})_[a-z0-9]{7}$`)
matches := buildDate.FindStringSubmatch(version)
if len(matches) > 1 {
return matches[1] >= minDate, nil
}

c, err := semver.NewConstraint(constraint)
if err != nil {
return false, nil
}

v, err := semver.NewVersion(version)
if err != nil {
return false, err
}
return c.Check(v), nil
}
30 changes: 30 additions & 0 deletions internal/api/version_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package api

import (
"regexp"

"github.com/Masterminds/semver"
)

func CheckSourcegraphVersion(version, constraint, minDate string) (bool, error) {
if version == "dev" || version == "0.0.0+dev" {
return true, nil
}

buildDate := regexp.MustCompile(`^\d+_(\d{4}-\d{2}-\d{2})_[a-z0-9]{7}$`)
matches := buildDate.FindStringSubmatch(version)
if len(matches) > 1 {
return matches[1] >= minDate, nil
}

c, err := semver.NewConstraint(constraint)
if err != nil {
return false, nil
}

v, err := semver.NewVersion(version)
if err != nil {
return false, err
}
return c.Check(v), nil
}
74 changes: 74 additions & 0 deletions internal/api/version_check_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package api

import "testing"

func TestCheckSourcegraphVersion(t *testing.T) {
for _, tc := range []struct {
currentVersion, constraint, minDate string
expected bool
}{
{
currentVersion: "3.12.6",
constraint: ">= 3.12.6",
minDate: "2020-01-19",
expected: true,
},
{
currentVersion: "3.12.6-rc.1",
constraint: ">= 3.12.6-0",
minDate: "2020-01-19",
expected: true,
},
{
currentVersion: "3.12.6",
constraint: ">= 3.13",
minDate: "2020-01-19",
expected: false,
},
{
currentVersion: "3.13.0",
constraint: ">= 3.13",
minDate: "2020-01-19",
expected: true,
},
{
currentVersion: "dev",
constraint: ">= 3.13",
minDate: "2020-01-19",
expected: true,
},
{
currentVersion: "0.0.0+dev",
constraint: ">= 3.13",
minDate: "2020-01-19",
expected: true,
},
{
currentVersion: "54959_2020-01-29_9258595",
minDate: "2020-01-19",
constraint: ">= 999.13",
expected: true,
},
{
currentVersion: "54959_2020-01-29_9258595",
minDate: "2020-01-30",
constraint: ">= 999.13",
expected: false,
},
{
currentVersion: "54959_2020-01-29_9258595",
minDate: "2020-01-29",
constraint: ">= 0.0",
expected: true,
},
} {
actual, err := CheckSourcegraphVersion(tc.currentVersion, tc.constraint, tc.minDate)
if err != nil {
t.Errorf("err: %s", err)
}

if actual != tc.expected {
t.Errorf("wrong result. want=%t, got=%t (version=%q, constraint=%q)", tc.expected, actual, tc.currentVersion, tc.constraint)
}
}
}
Loading