github.com/go-chef/chef@v0.30.1/search.go (about) 1 package chef 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "strings" 9 ) 10 11 type SearchService struct { 12 client *Client 13 } 14 15 // SearchQuery Is the struct for holding a query request 16 type SearchQuery struct { 17 // The index you want to search 18 Index string 19 20 // The query you want to execute. This is the 'chef' query ex: 'chef_environment:prod' 21 Query string 22 23 // Sort order you want the search results returned 24 SortBy string 25 26 // Starting position for search 27 Start int 28 29 // Number of rows to return 30 Rows int 31 } 32 33 // String implements the Stringer Interface for the SearchQuery 34 func (q SearchQuery) String() string { 35 return fmt.Sprintf("%s?q=%s&rows=%d&sort=%s&start=%d", q.Index, q.Query, q.Rows, q.SortBy, q.Start) 36 } 37 38 // SearchResult 39 type SearchResult struct { 40 Total int 41 Start int 42 Rows []interface{} 43 } 44 45 // JSearchResult will return a slice of json.RawMessage which can then 46 // be json.Unmarshaled to any of the chef-like objects (Role/Node/etc) 47 type JSearchResult struct { 48 Total int 49 Start int 50 Rows []SearchRow 51 } 52 53 type SearchRow struct { 54 Url string 55 Data json.RawMessage 56 } 57 58 var inc = 1000 59 60 func (e SearchService) PageSize(setting int) { 61 inc = setting 62 } 63 64 // Do will execute the search query on the client 65 func (q SearchQuery) Do(client *Client) (res SearchResult, err error) { 66 fullUrl := fmt.Sprintf("search/%s", q) 67 err = client.magicRequestDecoder("GET", fullUrl, nil, &res) 68 return 69 } 70 71 // DoJSON will execute the search query on the client and return 72 // rawJSON formatted results 73 func (q SearchQuery) DoJSON(client *Client) (res JSearchResult, err error) { 74 fullUrl := fmt.Sprintf("search/%s", q) 75 err = client.magicRequestDecoder("GET", fullUrl, nil, &res) 76 return 77 } 78 79 // DoPartial will execute the search query on the client with partial mapping 80 func (q SearchQuery) DoPartial(client *Client, params map[string]interface{}) (res SearchResult, err error) { 81 fullUrl := fmt.Sprintf("search/%s", q) 82 83 body, err := JSONReader(params) 84 if err != nil { 85 debug("Problem encoding params for body %v", err.Error()) 86 return 87 } 88 89 err = client.magicRequestDecoder("POST", fullUrl, body, &res) 90 return 91 } 92 93 // DoPartialJSON will execute the search query on the client with partial mapping and return raw JSON results 94 func (q SearchQuery) DoPartialJSON(client *Client, params map[string]interface{}) (res JSearchResult, err error) { 95 fullUrl := fmt.Sprintf("search/%s", q) 96 97 body, err := JSONReader(params) 98 if err != nil { 99 debug("Problem encoding params for body %v", err.Error()) 100 return 101 } 102 103 err = client.magicRequestDecoder("POST", fullUrl, body, &res) 104 return 105 } 106 107 // NewSearch is a constructor for a SearchQuery struct. This is used by other search service methods to perform search requests on the server 108 func (e SearchService) NewQuery(idx, statement string) (query SearchQuery, err error) { 109 // validate statement 110 if !strings.Contains(statement, ":") { 111 err = errors.New("statement is malformed") 112 return 113 } 114 115 query = SearchQuery{ 116 Index: idx, 117 Query: statement, 118 // These are the defaults in chef: https://github.com/opscode/chef/blob/master/lib/chef/search/query.rb#L102-L105 119 SortBy: "X_CHEF_id_CHEF_X asc", 120 Start: 0, 121 Rows: inc, 122 } 123 124 return 125 } 126 127 // Exec runs the query on the index passed in. This is a helper method. If you want more control over the query use NewQuery and its Do() method. 128 // BUG(spheromak): Should we use Exec or SearchQuery.Do() or have both ? 129 func (e SearchService) Exec(idx, statement string) (res SearchResult, err error) { 130 // Copy-paste here till We decide which way to go with Exec vs Do 131 if !strings.Contains(statement, ":") { 132 err = errors.New("statement is malformed") 133 return 134 } 135 136 query := SearchQuery{ 137 Index: idx, 138 Query: statement, 139 // These are the defaults in chef: https://github.com/opscode/chef/blob/master/lib/chef/search/query.rb#L102-L105 140 SortBy: "X_CHEF_id_CHEF_X asc", 141 Start: 0, 142 Rows: inc, 143 } 144 145 res, err = query.Do(e.client) 146 if err != nil { 147 return 148 } 149 start := res.Start 150 total := res.Total 151 152 for start+inc <= total { 153 query.Start = query.Start + inc 154 start = query.Start 155 ares, err := query.Do(e.client) 156 if err != nil { 157 return res, err 158 } 159 res.Rows = append(res.Rows, ares.Rows...) 160 } 161 return 162 } 163 164 // PartialExec Executes a partial search based on passed in params and the query. 165 func (e SearchService) PartialExec(idx, statement string, params map[string]interface{}) (res SearchResult, err error) { 166 query := SearchQuery{ 167 Index: idx, 168 Query: statement, 169 // These are the defaults in chef: https://github.com/opscode/chef/blob/master/lib/chef/search/query.rb#L102-L105 170 // SortBy: "X_CHEF_id_CHEF_X asc", 171 SortBy: "X_CHEF_id_CHEF_X asc", 172 Start: 0, 173 Rows: inc, 174 } 175 176 fullUrl := fmt.Sprintf("search/%s", query) 177 body, err := JSONSeeker(params) 178 if err != nil { 179 debug("Problem encoding params for body") 180 return 181 } 182 183 err = e.client.magicRequestDecoder("POST", fullUrl, body, &res) 184 if err != nil { 185 return 186 } 187 188 start := res.Start 189 // the total rows available for this query across all pages 190 total := res.Total 191 paged_res := SearchResult{} 192 193 for start+inc <= total { 194 query.Start = query.Start + inc 195 start = query.Start 196 body.Seek(0, io.SeekStart) 197 if err != nil { 198 fmt.Printf("Seek error %+v\n", err) 199 return 200 } 201 fullUrl := fmt.Sprintf("search/%s", query) 202 err = e.client.magicRequestDecoder("POST", fullUrl, body, &paged_res) 203 if err != nil { 204 fmt.Printf("Partial search error %+v\n", err) 205 return 206 } 207 // add this page of results to the primary SearchResult instance 208 res.Rows = append(res.Rows, paged_res.Rows...) 209 } 210 return 211 } 212 213 // ExecJSON runs the query on the index passed in. This is a helper method. If you want more control over the query use NewQuery and its Do() method. 214 func (e SearchService) ExecJSON(idx, statement string) (res JSearchResult, err error) { 215 // Copy-paste here till We decide which way to go with Exec vs Do 216 if !strings.Contains(statement, ":") { 217 err = errors.New("statement is malformed") 218 return 219 } 220 221 query := SearchQuery{ 222 Index: idx, 223 Query: statement, 224 // These are the defaults in chef: https://github.com/opscode/chef/blob/master/lib/chef/search/query.rb#L102-L105 225 SortBy: "X_CHEF_id_CHEF_X asc", 226 Start: 0, 227 Rows: inc, 228 } 229 230 res, err = query.DoJSON(e.client) 231 if err != nil { 232 return 233 } 234 start := res.Start 235 total := res.Total 236 237 for start+inc <= total { 238 query.Start = query.Start + inc 239 start = query.Start 240 ares, err := query.DoJSON(e.client) 241 if err != nil { 242 return res, err 243 } 244 res.Rows = append(res.Rows, ares.Rows...) 245 } 246 return 247 } 248 249 // PartialExecJSON Executes a partial search based on passed in params and the query. 250 func (e SearchService) PartialExecJSON(idx, statement string, params map[string]interface{}) (res JSearchResult, err error) { 251 query := SearchQuery{ 252 Index: idx, 253 Query: statement, 254 // These are the defaults in chef: https://github.com/opscode/chef/blob/master/lib/chef/search/query.rb#L102-L105 255 // SortBy: "X_CHEF_id_CHEF_X asc", 256 SortBy: "X_CHEF_id_CHEF_X asc", 257 Start: 0, 258 Rows: inc, 259 } 260 261 fullUrl := fmt.Sprintf("search/%s", query) 262 body, err := JSONSeeker(params) 263 if err != nil { 264 debug("Problem encoding params for body") 265 return 266 } 267 268 err = e.client.magicRequestDecoder("POST", fullUrl, body, &res) 269 if err != nil { 270 return 271 } 272 273 start := res.Start 274 // the total rows available for this query across all pages 275 total := res.Total 276 paged_res := JSearchResult{} 277 278 for start+inc <= total { 279 query.Start = query.Start + inc 280 start = query.Start 281 body.Seek(0, io.SeekStart) 282 if err != nil { 283 fmt.Printf("Seek error %+v\n", err) 284 return 285 } 286 fullUrl := fmt.Sprintf("search/%s", query) 287 err = e.client.magicRequestDecoder("POST", fullUrl, body, &paged_res) 288 if err != nil { 289 fmt.Printf("Partial search error %+v\n", err) 290 return 291 } 292 // add this page of results to the primary SearchResult instance 293 res.Rows = append(res.Rows, paged_res.Rows...) 294 } 295 return 296 } 297 298 // Chef API docs: https://docs.chef.io/api_chef_server/#get-46 299 func (e SearchService) Indexes() (data map[string]string, err error) { 300 err = e.client.magicRequestDecoder("GET", "search", nil, &data) 301 return 302 }