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
Add new store endpoints & snap find support for sections handling #2288
Conversation
@@ -885,6 +903,8 @@ func (s *Store) Find(search *Search, user *auth.UserState) ([]*snap.Info, error) | |||
} else { | |||
q.Set("q", searchTerm) | |||
} | |||
fmt.Println(search.Section) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably something we want to remove before landing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks very good! I especially liked that you've implemented a Completer for it.
I'm marking it as "request changes" because of the retry dance; all the rest are trivials.
Good job!
@@ -84,8 +84,22 @@ func getPriceString(prices map[string]float64, suggestedCurrency, status string) | |||
return formatPrice(price, currency) | |||
} | |||
|
|||
type SectionName string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yesss! <3
@@ -96,20 +110,17 @@ func init() { | |||
return &cmdFind{} | |||
}, map[string]string{ | |||
"private": i18n.G("Search private snaps"), | |||
"section": i18n.G("Restrict the search to a given section name"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest dropping the name
from the string
case store.ErrEmptyQuery, store.ErrBadQuery: | ||
return BadRequest("%v", err) | ||
case store.ErrUnauthenticated: | ||
return Unauthorized(err.Error()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why ("%v", err)
in the others and err.Error()
here? AFAIK they're both the same unless err==nil
, which you already split out explicitly
return InternalError("%v", err) | ||
} | ||
|
||
results := make([]*json.RawMessage, 0, len(sections)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm missing something here. sections
is a []string
, so why not just return it? As in return SyncResponse(sections, meta)
?
URL: &u, | ||
Accept: halJsonContentType, | ||
} | ||
resp, err := s.doRequest(s.client, reqOptions, user) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as this is new code, could you write it already doing the retry dance? Search for retry.Start
in this same file for examples. We're slowly rewriting all things that call doRequest
to work this way, but we shouldn't add to the backlog.
} | ||
defer resp.Body.Close() | ||
if resp.StatusCode != 200 { | ||
return nil, respToError(resp, "search") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not really "search"
, is it?
} | ||
|
||
if ct := resp.Header.Get("Content-Type"); ct != halJsonContentType { | ||
return nil, fmt.Errorf("received an unexpected content type (%q) when trying to search via %q", ct, resp.Request.URL) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or is it search? I think it isn't search.
|
||
dec := json.NewDecoder(resp.Body) | ||
if err := dec.Decode(§ionData); err != nil { | ||
return nil, fmt.Errorf("cannot decode reply (got %v) when trying to get sections via %q", err, resp.Request.URL) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OTOH this one seems right; make the other two like this one :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, thank you. One question - it looks like now we do allow a snap find
without further arguments again. AIUI this will mean that we get the first 100 snaps again as "featured". But right now AFAICT the store does not support this so we get a random list of 100 snaps (which may include useless ones like test-snapd-python-webserver). If that is indeed the case, should we keep the restriction for snap find for now (until the store has support for featured)?
// GetSections returns the list of existing snap sections in the store | ||
func (client *Client) GetSections() ([]string, error) { | ||
var sections []string | ||
_, err := client.doSync("GET", "/v2/sections", nil, nil, nil, §ions) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nitpick) This and the following line could be combined into a single: if _err := client.doSync(..); err != nil {
line. But its not really important (just an idiom we use a lot in the code).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well maybe but I did it this way to stay consistent with the rest of the code that does not seem to merge assignment/tests,
@@ -84,8 +84,22 @@ func getPriceString(prices map[string]float64, suggestedCurrency, status string) | |||
return formatPrice(price, currency) | |||
} | |||
|
|||
type SectionName string | |||
|
|||
func (s *SectionName) Complete(match string) []flags.Completion { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
@@ -153,7 +153,6 @@ The snap tool interacts with the snapd daemon to control the snappy software pla | |||
|
|||
cmd, err := parser.AddCommand(c.name, c.shortHelp, strings.TrimSpace(c.longHelp), obj) | |||
if err != nil { | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good, thanks for contributing it.
A few comments:
@@ -129,6 +130,16 @@ func (client *Client) List(names []string) ([]*Snap, error) { | |||
return result, nil | |||
} | |||
|
|||
// GetSections returns the list of existing snap sections in the store | |||
func (client *Client) GetSections() ([]string, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Get//, please. (client.Snap, client.Changes, etc).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(also maybe interessting in this context in "effective go" which is a interesting read: https://golang.org/doc/effective_go.html#Getters)
@@ -150,6 +161,7 @@ func (client *Client) Find(opts *FindOptions) ([]*Snap, *ResultInfo, error) { | |||
case opts.Private: | |||
q.Set("select", "private") | |||
} | |||
q.Set("section", opts.Section) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if opts.Section != "" { ... }
, so we continue to not send it unless requested? Feels slightly cleaner than sending an empty string which is ignored.
@@ -43,7 +43,7 @@ func (cs *clientSuite) TestClientFindRefreshSetsQuery(c *check.C) { | |||
c.Check(cs.req.Method, check.Equals, "GET") | |||
c.Check(cs.req.URL.Path, check.Equals, "/v2/find") | |||
c.Check(cs.req.URL.Query(), check.DeepEquals, url.Values{ | |||
"q": []string{""}, "select": []string{"refresh"}, | |||
"q": []string{""}, "section": []string{""}, "select": []string{"refresh"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a test where the string is not empty.
func (s *SectionName) Complete(match string) []flags.Completion { | ||
// TODO cache the result | ||
cli := Client() | ||
sections, _ := cli.GetSections() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if err != nil { return nil }
, more explicit and clear than just ignoring the error silently, which tends to raise eyebrows. Also saves the allocation bellow, but that's an irrelevant saving in this context.
type SectionName string | ||
|
||
func (s *SectionName) Complete(match string) []flags.Completion { | ||
// TODO cache the result |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be cached on the snapd daemon end rather than here, which means we can cache in memory instead of on disk. So it's okay to drop the TODO here.
|
||
q := u.Query() | ||
|
||
q.Set("confinement", "strict") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this necessary? We are asking for store sections.. sections don't have confinement settings? Doesn't feel right.
|
||
return sectionNames, nil | ||
} | ||
panic("unreachable") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is bogus. Please catch up with @stolowski.
*/ | ||
const MockSectionsJSON = `{ | ||
"_embedded": { | ||
"clickindex:sections": [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's probably not much gain in having 20 lines here instead of:
"clickindex:sections": [{"name": "featured"}, {"name": "database"}],
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The line above is still better than the 8 lines below.
repo := New(&cfg, nil) | ||
c.Assert(repo, NotNil) | ||
|
||
_, err := repo.Sections(t.user) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be checking the result?
expected="(?s)Name +Version +Developer +Notes +Summary *\n\ | ||
(.*?\n)?\ | ||
.*" | ||
snap find | grep -Pzq "$expected" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we test something we know will tend to remain featured here, so we have a hint that the feature in fact works?
@niemeyer @AlexandreAbreu I would love to get this into 2.18, this is why I picked it up in #2333 and did some minor tweaks (most importantly to use the new store.retryRequest() helper and minor tweaks to respond to Gustavos feedback). Feel free to cherry-pick in here if its not reviewed/merged. If it misses 2.18 thats fine, I think we just continue in here in this case. One thing (maybe not for this branch) I noticed is that
If we translated that to a go error we could convert it to an error-kind in our REST api and the client could show a nicer error message. But probably a branch on its own :) |
@AlexandreAbreu Yeah, the hardcoding of that string is not ideal, I think we want to tweak that in the future. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, but there's a test missing.
return errors.New(i18n.G("you need to specify a query. Try \"snap find hello-world\".")) | ||
// magic! `snap find` returns the featured snaps | ||
if x.Positional.Query == "" && x.Section == "" { | ||
x.Section = "featured" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks untested.
*/ | ||
const MockSectionsJSON = `{ | ||
"_embedded": { | ||
"clickindex:sections": [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The line above is still better than the 8 lines below.
Add new store endpoints & snap find support for sections handling.
The sections endpoint is currently in the staging store.