go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/client/coordinator/query.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package coordinator
    16  
    17  import (
    18  	"context"
    19  
    20  	logdog "go.chromium.org/luci/logdog/api/endpoints/coordinator/logs/v1"
    21  	"go.chromium.org/luci/logdog/api/logpb"
    22  	"go.chromium.org/luci/logdog/common/types"
    23  )
    24  
    25  // QueryTrinary is a 3-value query option type.
    26  type QueryTrinary int
    27  
    28  const (
    29  	// Both means that the value should not have an effect.
    30  	Both QueryTrinary = iota
    31  	// Yes is a positive effect.
    32  	Yes
    33  	// No is a negative effect.
    34  	No
    35  )
    36  
    37  func (t QueryTrinary) queryValue() logdog.QueryRequest_Trinary {
    38  	switch t {
    39  	case Yes:
    40  		return logdog.QueryRequest_YES
    41  	case No:
    42  		return logdog.QueryRequest_NO
    43  	default:
    44  		return logdog.QueryRequest_BOTH
    45  	}
    46  }
    47  
    48  // QueryStreamType is a 3-value query option type.
    49  type QueryStreamType int
    50  
    51  const (
    52  	// Any means that the value should not have an effect.
    53  	Any QueryStreamType = iota
    54  	// Text selects only text streams.
    55  	Text
    56  	// Binary selects only binary streams.
    57  	Binary
    58  	// Datagram selects only datagram streams.
    59  	Datagram
    60  )
    61  
    62  // queryValue returns the StreamType for a specified QueryStreamType parameter.
    63  // If no StreamType is specified (Any), it will return -1 to indicate this.
    64  func (t QueryStreamType) queryValue() logpb.StreamType {
    65  	switch t {
    66  	case Text:
    67  		return logpb.StreamType_TEXT
    68  	case Binary:
    69  		return logpb.StreamType_BINARY
    70  	case Datagram:
    71  		return logpb.StreamType_DATAGRAM
    72  	default:
    73  		return -1
    74  	}
    75  }
    76  
    77  // QueryOptions is the set of query options that can accompany a query.
    78  type QueryOptions struct {
    79  	// Tags is the list of tags to require. The value may be empty if key presence
    80  	// is all that is being asserted.
    81  	Tags map[string]string
    82  	// ContentType, if not empty, restricts results to streams with the supplied
    83  	// content type.
    84  	ContentType string
    85  
    86  	// StreamType, if not STAny, is the stream type to query for.
    87  	StreamType QueryStreamType
    88  
    89  	// Purged, if not QBoth, selects logs streams that are/aren't purged.
    90  	Purged QueryTrinary
    91  
    92  	// State, if true, requests that the query results include the log streams'
    93  	// state.
    94  	State bool
    95  }
    96  
    97  // QueryCallback is a callback method type that is used in query requests.
    98  //
    99  // If it returns false, additional callbacks and queries will be aborted.
   100  type QueryCallback func(r *LogStream) bool
   101  
   102  // Query executes a query, invoking the supplied callback once for each query
   103  // result.
   104  //
   105  // The path is the query parameter.
   106  //
   107  // The path expression may substitute a glob ("*") for a specific path
   108  // component. That is, any stream that matches the remaining structure qualifies
   109  // regardless of its value in that specific positional field.
   110  //
   111  // An unbounded wildcard may appear as a component at the end of both the
   112  // prefix and name query components. "**" matches all remaining components.
   113  //
   114  // If the supplied path query does not contain a path separator ("+"), it will
   115  // be treated as if the prefix is "**".
   116  //
   117  // Examples:
   118  //   - Empty ("") will return all streams.
   119  //   - **/+/** will return all streams.
   120  //   - foo/bar/** will return all streams with the "foo/bar" prefix.
   121  //   - foo/bar/**/+/baz will return all streams beginning with the "foo/bar"
   122  //     prefix and named "baz" (e.g., "foo/bar/qux/lol/+/baz")
   123  //   - foo/bar/+/** will return all streams with a "foo/bar" prefix.
   124  //   - foo/*/+/baz will return all streams with a two-component prefix whose
   125  //     first value is "foo" and whose name is "baz".
   126  //   - foo/bar will return all streams whose name is "foo/bar".
   127  //   - */* will return all streams with two-component names.
   128  func (c *Client) Query(ctx context.Context, project string, path string, o QueryOptions, cb QueryCallback) error {
   129  	req := logdog.QueryRequest{
   130  		Project:     project,
   131  		Path:        path,
   132  		ContentType: o.ContentType,
   133  		Purged:      o.Purged.queryValue(),
   134  		State:       o.State,
   135  	}
   136  	if st := o.StreamType.queryValue(); st >= 0 {
   137  		req.StreamType = &logdog.QueryRequest_StreamTypeFilter{Value: st}
   138  	}
   139  
   140  	// Clone tags.
   141  	if len(o.Tags) > 0 {
   142  		req.Tags = make(map[string]string, len(o.Tags))
   143  		for k, v := range o.Tags {
   144  			req.Tags[k] = v
   145  		}
   146  	}
   147  
   148  	// Iteratively query until either our query is done (Next is empty) or we are
   149  	// asked to stop via callback.
   150  	for {
   151  		resp, err := c.C.Query(ctx, &req)
   152  		if err != nil {
   153  			return normalizeError(err)
   154  		}
   155  
   156  		for _, s := range resp.Streams {
   157  			st := loadLogStream(resp.Project, types.StreamPath(s.Path), s.State, s.Desc)
   158  			if !cb(st) {
   159  				return nil
   160  			}
   161  		}
   162  
   163  		// Advance our query cursor.
   164  		if resp.Next == "" {
   165  			return nil
   166  		}
   167  		req.Next = resp.Next
   168  	}
   169  }