~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/gopkg.in/juju/charmstore.v5-unstable/internal/v5/search.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2014 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package v5 // import "gopkg.in/juju/charmstore.v5-unstable/internal/v5"
 
5
 
 
6
import (
 
7
        "net/http"
 
8
        "strconv"
 
9
        "sync/atomic"
 
10
 
 
11
        "github.com/juju/utils/parallel"
 
12
        "gopkg.in/errgo.v1"
 
13
        "gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
 
14
 
 
15
        "gopkg.in/juju/charmstore.v5-unstable/internal/charmstore"
 
16
        "gopkg.in/juju/charmstore.v5-unstable/internal/mongodoc"
 
17
        "gopkg.in/juju/charmstore.v5-unstable/internal/router"
 
18
)
 
19
 
 
20
const maxConcurrency = 20
 
21
 
 
22
// GET search[?text=text][&autocomplete=1][&filter=value…][&limit=limit][&include=meta][&skip=count][&sort=field[+dir]]
 
23
// https://github.com/juju/charmstore/blob/v4/docs/API.md#get-search
 
24
func (h *ReqHandler) serveSearch(_ http.Header, req *http.Request) (interface{}, error) {
 
25
        sp, err := ParseSearchParams(req)
 
26
        if err != nil {
 
27
                return "", err
 
28
        }
 
29
        auth, err := h.Authenticate(req)
 
30
        if err != nil {
 
31
                logger.Infof("authorization failed on search request, granting no privileges: %v", err)
 
32
        }
 
33
        sp.Admin = auth.Admin
 
34
        if auth.Username != "" {
 
35
                sp.Groups = append(sp.Groups, auth.Username)
 
36
                groups, err := h.Handler.GroupsForUser(auth.Username)
 
37
                if err != nil {
 
38
                        logger.Infof("cannot get groups for user %q, assuming no groups: %v", auth.Username, err)
 
39
                }
 
40
                sp.Groups = append(sp.Groups, groups...)
 
41
        }
 
42
        return h.Search(sp, req)
 
43
}
 
44
 
 
45
// Search performs the search specified by SearchParams. If sp
 
46
// specifies that additional metadata needs to be added to the results,
 
47
// then it is added.
 
48
func (h *ReqHandler) Search(sp charmstore.SearchParams, req *http.Request) (interface{}, error) {
 
49
        // perform query
 
50
        results, err := h.Store.Search(sp)
 
51
        if err != nil {
 
52
                return nil, errgo.Notef(err, "error performing search")
 
53
        }
 
54
        return params.SearchResponse{
 
55
                SearchTime: results.SearchTime,
 
56
                Total:      results.Total,
 
57
                Results:    h.addMetaData(results.Results, sp.Include, req),
 
58
        }, nil
 
59
}
 
60
 
 
61
// addMetaData adds the requested meta data with the include list.
 
62
func (h *ReqHandler) addMetaData(results []*mongodoc.Entity, include []string, req *http.Request) []params.EntityResult {
 
63
        entities := make([]params.EntityResult, len(results))
 
64
        run := parallel.NewRun(maxConcurrency)
 
65
        var missing int32
 
66
        for i, ent := range results {
 
67
                i, ent := i, ent
 
68
                run.Do(func() error {
 
69
                        meta, err := h.Router.GetMetadata(charmstore.EntityResolvedURL(ent), include, req)
 
70
                        if err != nil {
 
71
                                // Unfortunately it is possible to get errors here due to
 
72
                                // internal inconsistency, so rather than throwing away
 
73
                                // all the search results, we just log the error and move on.
 
74
                                logger.Errorf("cannot retrieve metadata for %v: %v", ent.PreferredURL(true), err)
 
75
                                atomic.AddInt32(&missing, 1)
 
76
                                return nil
 
77
                        }
 
78
                        entities[i] = params.EntityResult{
 
79
                                Id:   ent.PreferredURL(true),
 
80
                                Meta: meta,
 
81
                        }
 
82
                        return nil
 
83
                })
 
84
        }
 
85
        // We never return an error from the Do function above, so no need to
 
86
        // check the error here.
 
87
        run.Wait()
 
88
        if missing == 0 {
 
89
                return entities
 
90
        }
 
91
        // We're missing some results - shuffle all the results down to
 
92
        // fill the gaps.
 
93
        j := 0
 
94
        for _, result := range entities {
 
95
                if result.Id != nil {
 
96
                        entities[j] = result
 
97
                        j++
 
98
                }
 
99
        }
 
100
 
 
101
        return entities[0:j]
 
102
}
 
103
 
 
104
// GET search/interesting[?limit=limit][&include=meta]
 
105
// https://github.com/juju/charmstore/blob/v4/docs/API.md#get-searchinteresting
 
106
func (h *ReqHandler) serveSearchInteresting(w http.ResponseWriter, req *http.Request) {
 
107
        router.WriteError(w, errNotImplemented)
 
108
}
 
109
 
 
110
// ParseSearchParms extracts the search paramaters from the request
 
111
func ParseSearchParams(req *http.Request) (charmstore.SearchParams, error) {
 
112
        sp := charmstore.SearchParams{}
 
113
        var err error
 
114
        for k, v := range req.Form {
 
115
                switch k {
 
116
                case "text":
 
117
                        sp.Text = v[0]
 
118
                case "autocomplete":
 
119
                        sp.AutoComplete, err = router.ParseBool(v[0])
 
120
                        if err != nil {
 
121
                                return charmstore.SearchParams{}, badRequestf(err, "invalid autocomplete parameter")
 
122
                        }
 
123
                case "limit":
 
124
                        sp.Limit, err = strconv.Atoi(v[0])
 
125
                        if err != nil {
 
126
                                return charmstore.SearchParams{}, badRequestf(err, "invalid limit parameter: could not parse integer")
 
127
                        }
 
128
                        if sp.Limit < 1 {
 
129
                                return charmstore.SearchParams{}, badRequestf(nil, "invalid limit parameter: expected integer greater than zero")
 
130
                        }
 
131
                case "include":
 
132
                        for _, s := range v {
 
133
                                if s != "" {
 
134
                                        sp.Include = append(sp.Include, s)
 
135
                                }
 
136
                        }
 
137
                case "description", "name", "owner", "provides", "requires", "series", "summary", "tags", "type":
 
138
                        if sp.Filters == nil {
 
139
                                sp.Filters = make(map[string][]string)
 
140
                        }
 
141
                        sp.Filters[k] = v
 
142
                case "promulgated":
 
143
                        promulgated, err := router.ParseBool(v[0])
 
144
                        if err != nil {
 
145
                                return charmstore.SearchParams{}, badRequestf(err, "invalid promulgated filter parameter")
 
146
                        }
 
147
                        if sp.Filters == nil {
 
148
                                sp.Filters = make(map[string][]string)
 
149
                        }
 
150
                        if promulgated {
 
151
                                sp.Filters[k] = []string{"1"}
 
152
                        } else {
 
153
                                sp.Filters[k] = []string{"0"}
 
154
                        }
 
155
                case "skip":
 
156
                        sp.Skip, err = strconv.Atoi(v[0])
 
157
                        if err != nil {
 
158
                                return charmstore.SearchParams{}, badRequestf(err, "invalid skip parameter: could not parse integer")
 
159
                        }
 
160
                        if sp.Skip < 0 {
 
161
                                return charmstore.SearchParams{}, badRequestf(nil, "invalid skip parameter: expected non-negative integer")
 
162
                        }
 
163
                case "sort":
 
164
                        err = sp.ParseSortFields(v...)
 
165
                        if err != nil {
 
166
                                return charmstore.SearchParams{}, badRequestf(err, "invalid sort field")
 
167
                        }
 
168
                default:
 
169
                        return charmstore.SearchParams{}, badRequestf(nil, "invalid parameter: %s", k)
 
170
                }
 
171
        }
 
172
        return sp, nil
 
173
}