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  }