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

Add new store endpoints & snap find support for sections handling #2288

Merged
merged 21 commits into from Nov 24, 2016
Merged

Add new store endpoints & snap find support for sections handling #2288

merged 21 commits into from Nov 24, 2016

Conversation

AlexandreAbreu
Copy link
Contributor

@AlexandreAbreu AlexandreAbreu commented Nov 17, 2016

Add new store endpoints & snap find support for sections handling.

The sections endpoint is currently in the staging store.

@@ -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)
Copy link
Contributor

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.

@AlexandreAbreu AlexandreAbreu changed the title [WIP] Add new store endpoints & snap find support for sections handling Add new store endpoints & snap find support for sections handling Nov 18, 2016
Copy link
Contributor

@chipaca chipaca left a 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
Copy link
Contributor

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"),
Copy link
Contributor

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())
Copy link
Contributor

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))
Copy link
Contributor

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)
Copy link
Contributor

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")
Copy link
Contributor

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)
Copy link
Contributor

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(&sectionData); err != nil {
return nil, fmt.Errorf("cannot decode reply (got %v) when trying to get sections via %q", err, resp.Request.URL)
Copy link
Contributor

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 :-)

Copy link
Contributor

@mvo5 mvo5 left a 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, &sections)
Copy link
Contributor

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).

Copy link
Contributor Author

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 {
Copy link
Contributor

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 {

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for catching that.

Copy link
Contributor

@niemeyer niemeyer left a 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) {
Copy link
Contributor

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).

Copy link
Contributor

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)
Copy link
Contributor

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"},
Copy link
Contributor

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()
Copy link
Contributor

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
Copy link
Contributor

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")
Copy link
Contributor

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")
Copy link
Contributor

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": [
Copy link
Contributor

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"}],

Copy link
Contributor

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)
Copy link
Contributor

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"
Copy link
Contributor

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?

@mvo5
Copy link
Contributor

mvo5 commented Nov 23, 2016

@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 $ snap find --section=invalid-or-misspelled gives a rather ugly http 400 error message right now. It looks like we do not have much logic to decode store errors properly yet (store.go:respToError()). The store sends a 400 error with the body:

{
  "error_list": [
    {
      "code": "invalid-field", 
      "message": "Invalid section given: not-exists"
    }
  ]
}

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 :)

@mvo5 mvo5 added this to the 2.18 milestone Nov 24, 2016
@mvo5
Copy link
Contributor

mvo5 commented Nov 24, 2016

@AlexandreAbreu Yeah, the hardcoding of that string is not ideal, I think we want to tweak that in the future.

Copy link
Contributor

@niemeyer niemeyer left a 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"
Copy link
Contributor

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": [
Copy link
Contributor

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.

@mvo5 mvo5 merged commit 4963489 into snapcore:master Nov 24, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants