Coverage for /builds/BuildGrid/buildgrid/buildgrid/server/operations/filtering/interpreter.py: 96.55%

29 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-10-04 17:48 +0000

1# Copyright (C) 2020 Bloomberg LP 

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 

16import operator 

17from dataclasses import dataclass 

18from typing import Any, List 

19 

20from lark import Tree 

21from lark.visitors import Interpreter 

22 

23from buildgrid.server.exceptions import InvalidArgumentError 

24from buildgrid.server.operations.filtering import OperationFilter 

25from buildgrid.server.operations.filtering.sanitizer import ( 

26 DatetimeValueSanitizer, 

27 OperationStageValueSanitizer, 

28 RegexValueSanitizer, 

29 SortKeyValueSanitizer, 

30 ValueSanitizer, 

31) 

32 

33 

34@dataclass 

35class OperationFilterSpec: 

36 name: str 

37 description: str 

38 sanitizer: ValueSanitizer 

39 

40 

41# Valid operation filters mapped to regexes representing valid values. 

42VALID_OPERATION_FILTERS = { 

43 "stage": OperationFilterSpec( 

44 name="Stage", 

45 description="Operation stage, for example QUEUED or EXECUTING.", 

46 sanitizer=OperationStageValueSanitizer("stage"), 

47 ), 

48 # The operation name can technically be parsed as a UUID4, but this 

49 # parses it as an arbitrary string in case the naming scheme changes 

50 # in the future 

51 "name": OperationFilterSpec( 

52 name="Name", 

53 description="Operation name, without the BuildGrid instance name.", 

54 sanitizer=RegexValueSanitizer("name", r"\S+"), 

55 ), 

56 # The command is an arbitrary string 

57 "command": OperationFilterSpec( 

58 name="Command", 

59 description="Command to be executed by the remote worker.", 

60 sanitizer=RegexValueSanitizer("command", r".+"), 

61 ), 

62 # The platform properties are `key:value` 

63 "platform": OperationFilterSpec( 

64 name="Platform", 

65 description="Platform property specified by the Action, in the form `key:value`.", 

66 sanitizer=RegexValueSanitizer("platform", r"\S+:.+"), 

67 ), 

68 "action_digest": OperationFilterSpec( 

69 name="Action Digest", 

70 description="Digest of the Action message, in the form `hash/size_bytes`.", 

71 sanitizer=RegexValueSanitizer("action_digest", r"[a-f0-9]+/[0-9]+"), 

72 ), 

73 # The request metadata is all arbitrary strings 

74 "invocation_id": OperationFilterSpec( 

75 name="Invocation ID", 

76 description="The unique ID for a given invocation of the tool which started the Operation.", 

77 sanitizer=RegexValueSanitizer("invocation_id", r".+"), 

78 ), 

79 "correlated_invocations_id": OperationFilterSpec( 

80 name="Correlated Invocations ID", 

81 description="The unique ID for a set of related tool invocations.", 

82 sanitizer=RegexValueSanitizer("correlated_invocations_id", r".+"), 

83 ), 

84 "tool_name": OperationFilterSpec( 

85 name="Tool Name", 

86 description="The name of the tool which started the Operation.", 

87 sanitizer=RegexValueSanitizer("tool_name", r".+"), 

88 ), 

89 "tool_version": OperationFilterSpec( 

90 name="Tool Version", 

91 description="The version of the tool that started the Operation.", 

92 sanitizer=RegexValueSanitizer("tool_version", r".+"), 

93 ), 

94 "action_mnemonic": OperationFilterSpec( 

95 name="Action Mnemonic", 

96 description="A brief description of the kind of action. This is not " 

97 "standardized and contents are up to the client tooling used.", 

98 sanitizer=RegexValueSanitizer("action_mnemonic", r".+"), 

99 ), 

100 "target_id": OperationFilterSpec( 

101 name="Target ID", 

102 description="An identifier for the target which produced this action. " 

103 "A single target may relate to one or many actions.", 

104 sanitizer=RegexValueSanitizer("target_id", r".+"), 

105 ), 

106 "configuration_id": OperationFilterSpec( 

107 name="Configuration ID", 

108 description="An identifier for the configuration in which the target was built, " 

109 "e.g. for differentiating host tooling or different target platforms.", 

110 sanitizer=RegexValueSanitizer("configuration_id", r".+"), 

111 ), 

112 # Validate timestamps with a special sanitizer 

113 "queued_time": OperationFilterSpec( 

114 name="Queued Time", 

115 description="The time at which the Action was queued.", 

116 sanitizer=DatetimeValueSanitizer("queued_time"), 

117 ), 

118 "start_time": OperationFilterSpec( 

119 name="Start Time", 

120 description="The time at which a worker started executing the Action.", 

121 sanitizer=DatetimeValueSanitizer("start_time"), 

122 ), 

123 "completed_time": OperationFilterSpec( 

124 name="Completed Time", 

125 description="The time at which a worker finished executing the Action.", 

126 sanitizer=DatetimeValueSanitizer("completed_time"), 

127 ), 

128 # Client identity metadata is all arbitrary strings 

129 "client_workflow": OperationFilterSpec( 

130 name="Client Workflow", 

131 description="The client workflow information that was used to authorize the Action.", 

132 sanitizer=RegexValueSanitizer("action_mnemonic", r".+"), 

133 ), 

134 "client_actor": OperationFilterSpec( 

135 name="Client Actor", 

136 description="The client actor that was used to authorize the Action.", 

137 sanitizer=RegexValueSanitizer("target_id", r".+"), 

138 ), 

139 "client_subject": OperationFilterSpec( 

140 name="Client Subject", 

141 description="The client subject that was used to authorize the Action.", 

142 sanitizer=RegexValueSanitizer("configuration_id", r".+"), 

143 ), 

144 # Backends determine what sort orders are acceptable 

145 "sort_order": OperationFilterSpec( 

146 name="Sort Order", 

147 description="Define an ordering for the results.", 

148 sanitizer=SortKeyValueSanitizer("sort_order"), 

149 ), 

150} 

151 

152 

153OPERATOR_MAP = { 

154 "=": operator.eq, 

155 ">": operator.gt, 

156 ">=": operator.ge, 

157 "<": operator.lt, 

158 "<=": operator.le, 

159 "!=": operator.ne, 

160} 

161 

162 

163class FilterTreeInterpreter(Interpreter[Any]): 

164 """Interpreter for the parse tree. 

165 

166 Calling FilterTreeInterpreter().visit(tree) walks the parse tree and 

167 outputs a list of OperationFilters.""" 

168 

169 def filter_phrase(self, tree: Tree) -> List[OperationFilter]: 

170 return self.visit_children(tree) 

171 

172 def filter_elem(self, tree: Tree) -> OperationFilter: 

173 try: 

174 token_map = {token.type: str(token) for token in tree.children} # type: ignore 

175 

176 # Check that the parameter is valid 

177 parameter = token_map["PARAMETER"] 

178 if parameter not in VALID_OPERATION_FILTERS: 

179 raise InvalidArgumentError(f"Invalid filter parameter [{parameter}].") 

180 

181 # Sanitize the value 

182 sanitizer = VALID_OPERATION_FILTERS[parameter].sanitizer 

183 sanitized_value = sanitizer.sanitize(token_map["VALUE"]) 

184 

185 return OperationFilter( 

186 parameter=token_map["PARAMETER"], operator=OPERATOR_MAP[token_map["OPERATOR"]], value=sanitized_value 

187 ) 

188 except KeyError as e: 

189 raise InvalidArgumentError(f"Invalid filter element. Token map: {token_map}") from e