blob: 80a1da534d6d7b78e2895f0252524252ddb8f629 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2019 The ChromiumOS Authors
Alex Kleinc05f3d12019-05-29 14:16:21 -06002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Field handler classes.
6
7The field handlers are meant to parse information from or do some other generic
8action for a specific field type for the build_api script.
9"""
10
Alex Kleinc05f3d12019-05-29 14:16:21 -060011import contextlib
Alex Kleinbd6edf82019-07-18 10:30:49 -060012import functools
Chris McDonald1672ddb2021-07-21 11:48:23 -060013import logging
Alex Kleinc05f3d12019-05-29 14:16:21 -060014import os
15import shutil
Brian Norris5c637c42023-05-04 18:07:48 -070016from typing import Iterator, List, Optional
Alex Kleinc05f3d12019-05-29 14:16:21 -060017
Mike Frysinger2c024062021-05-22 15:43:22 -040018from chromite.third_party.google.protobuf import message as protobuf_message
Mike Frysinger849d6402019-10-17 00:14:16 -040019
Alex Klein38c7d9e2019-05-08 09:31:19 -060020from chromite.api.controller import controller_util
Alex Kleinc05f3d12019-05-29 14:16:21 -060021from chromite.api.gen.chromiumos import common_pb2
Brian Norris5c637c42023-05-04 18:07:48 -070022from chromite.lib import chroot_lib
Alex Kleinc05f3d12019-05-29 14:16:21 -060023from chromite.lib import osutils
24
25
Alex Kleinbd6edf82019-07-18 10:30:49 -060026class Error(Exception):
Alex Klein1699fab2022-09-08 08:46:06 -060027 """Base error class for the module."""
Alex Kleinbd6edf82019-07-18 10:30:49 -060028
29
30class InvalidResultPathError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060031 """Result path is invalid."""
Alex Kleinbd6edf82019-07-18 10:30:49 -060032
33
Alex Klein074f94f2023-06-22 10:32:06 -060034class ChrootHandler:
Alex Klein1699fab2022-09-08 08:46:06 -060035 """Translate a Chroot message to chroot enter arguments and env."""
Alex Kleinc05f3d12019-05-29 14:16:21 -060036
Alex Klein1699fab2022-09-08 08:46:06 -060037 def __init__(self, clear_field):
38 self.clear_field = clear_field
Alex Kleinc05f3d12019-05-29 14:16:21 -060039
Alex Klein1699fab2022-09-08 08:46:06 -060040 def handle(self, message, recurse=True) -> Optional["chroot_lib.Chroot"]:
41 """Parse a message for a chroot field."""
Alex Klein54c891a2023-01-24 10:45:41 -070042 # Find the Chroot field. Search for the field by type to prevent it
43 # being tied to a naming convention.
Alex Klein1699fab2022-09-08 08:46:06 -060044 for descriptor in message.DESCRIPTOR.fields:
45 field = getattr(message, descriptor.name)
46 if isinstance(field, common_pb2.Chroot):
47 chroot = field
48 if self.clear_field:
49 message.ClearField(descriptor.name)
50 return self.parse_chroot(chroot)
Alex Kleinc05f3d12019-05-29 14:16:21 -060051
Alex Klein54c891a2023-01-24 10:45:41 -070052 # Recurse down one level. This is handy for meta-endpoints that use
53 # another endpoint's request to produce data for or about the second
54 # endpoint. e.g. PackageService/NeedsChromeSource.
Alex Klein1699fab2022-09-08 08:46:06 -060055 if recurse:
56 for descriptor in message.DESCRIPTOR.fields:
57 field = getattr(message, descriptor.name)
58 if isinstance(field, protobuf_message.Message):
59 chroot = self.handle(field, recurse=False)
60 if chroot:
61 return chroot
Alex Klein6becabc2020-09-11 14:03:05 -060062
Alex Klein1699fab2022-09-08 08:46:06 -060063 return None
Alex Kleinc05f3d12019-05-29 14:16:21 -060064
Alex Klein1699fab2022-09-08 08:46:06 -060065 def parse_chroot(
66 self, chroot_message: common_pb2.Chroot
67 ) -> "chroot_lib.Chroot":
68 """Parse a Chroot message instance."""
69 return controller_util.ParseChroot(chroot_message)
Alex Kleinc05f3d12019-05-29 14:16:21 -060070
71
Alex Klein1699fab2022-09-08 08:46:06 -060072def handle_chroot(
Brian Norris6d8d2d42023-05-08 17:22:38 -070073 message: protobuf_message.Message, clear_field: bool = True
Alex Klein1699fab2022-09-08 08:46:06 -060074) -> "chroot_lib.Chroot":
75 """Find and parse the chroot field, returning the Chroot instance."""
76 handler = ChrootHandler(clear_field)
77 chroot = handler.handle(message)
78 if chroot:
79 return chroot
Alex Kleinc05f3d12019-05-29 14:16:21 -060080
Alex Klein1699fab2022-09-08 08:46:06 -060081 logging.warning("No chroot message found, falling back to defaults.")
82 return handler.parse_chroot(common_pb2.Chroot())
Alex Kleinc05f3d12019-05-29 14:16:21 -060083
84
Brian Norrisd0dfeae2023-03-09 13:06:47 -080085def handle_goma(message, chroot_path, out_path):
Alex Klein1699fab2022-09-08 08:46:06 -060086 """Find and parse the GomaConfig field, returning the Goma instance."""
87 for descriptor in message.DESCRIPTOR.fields:
88 field = getattr(message, descriptor.name)
89 if isinstance(field, common_pb2.GomaConfig):
90 goma_config = field
Brian Norrisd0dfeae2023-03-09 13:06:47 -080091 return controller_util.ParseGomaConfig(
92 goma_config, chroot_path, out_path
93 )
Alex Klein9b7331e2019-12-30 14:37:21 -070094
Alex Klein1699fab2022-09-08 08:46:06 -060095 return None
Alex Klein9b7331e2019-12-30 14:37:21 -070096
97
Joanna Wang92cad812021-11-03 14:52:08 -070098def handle_remoteexec(message: protobuf_message.Message):
Alex Klein1699fab2022-09-08 08:46:06 -060099 """Find the RemoteexecConfig field, returning the Remoteexec instance."""
100 for descriptor in message.DESCRIPTOR.fields:
101 field = getattr(message, descriptor.name)
102 if isinstance(field, common_pb2.RemoteexecConfig):
103 remoteexec_config = field
104 return controller_util.ParseRemoteexecConfig(remoteexec_config)
Joanna Wang92cad812021-11-03 14:52:08 -0700105
Alex Klein1699fab2022-09-08 08:46:06 -0600106 return None
Joanna Wang92cad812021-11-03 14:52:08 -0700107
108
Alex Klein074f94f2023-06-22 10:32:06 -0600109class PathHandler:
Alex Klein1699fab2022-09-08 08:46:06 -0600110 """Handles copying a file or directory into or out of the chroot."""
Alex Kleinc05f3d12019-05-29 14:16:21 -0600111
Alex Klein1699fab2022-09-08 08:46:06 -0600112 INSIDE = common_pb2.Path.INSIDE
113 OUTSIDE = common_pb2.Path.OUTSIDE
Alex Kleinc05f3d12019-05-29 14:16:21 -0600114
Alex Klein1699fab2022-09-08 08:46:06 -0600115 def __init__(
116 self,
117 field: common_pb2.Path,
118 destination: str,
119 delete: bool,
Brian Norris5c637c42023-05-04 18:07:48 -0700120 chroot: Optional[chroot_lib.Chroot] = None,
Alex Klein1699fab2022-09-08 08:46:06 -0600121 reset: Optional[bool] = True,
122 ) -> None:
123 """Path handler initialization.
Alex Kleinc05f3d12019-05-29 14:16:21 -0600124
Alex Klein1699fab2022-09-08 08:46:06 -0600125 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600126 field: The Path message.
127 destination: The destination base path.
128 delete: Whether the copied file(s) should be deleted on cleanup.
Brian Norris5c637c42023-05-04 18:07:48 -0700129 chroot: Chroot object to use for translating the paths in/out of
130 the chroot as necessary -- modifying the destination path when
131 moving files into the chroot, or modifying the source path when
132 moving files outside.
Alex Kleina0442682022-10-10 13:47:38 -0600133 reset: Whether to reset the state on cleanup.
Alex Klein1699fab2022-09-08 08:46:06 -0600134 """
135 assert isinstance(field, common_pb2.Path)
136 assert field.path
137 assert field.location
Alex Kleinc05f3d12019-05-29 14:16:21 -0600138
Alex Klein1699fab2022-09-08 08:46:06 -0600139 self.field = field
140 self.destination = destination
Brian Norris5c637c42023-05-04 18:07:48 -0700141 self.chroot = chroot
Alex Klein1699fab2022-09-08 08:46:06 -0600142 self.delete = delete
143 self.tempdir = None
144 self.reset = reset
Alex Kleinbd6edf82019-07-18 10:30:49 -0600145
Alex Klein1699fab2022-09-08 08:46:06 -0600146 # For resetting the state.
147 self._transferred = False
148 self._original_message = common_pb2.Path()
149 self._original_message.CopyFrom(self.field)
Alex Kleinc05f3d12019-05-29 14:16:21 -0600150
Alex Klein1699fab2022-09-08 08:46:06 -0600151 def transfer(self, direction: int) -> None:
152 """Copy the file or directory to its destination.
Alex Kleinc05f3d12019-05-29 14:16:21 -0600153
Alex Klein1699fab2022-09-08 08:46:06 -0600154 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600155 direction: The direction files are being copied (into or out of the
156 chroot). Specifying the direction allows avoiding performing
157 unnecessary copies.
Alex Klein1699fab2022-09-08 08:46:06 -0600158 """
159 if self._transferred:
160 return
Alex Kleinaa705412019-06-04 15:00:30 -0600161
Alex Klein1699fab2022-09-08 08:46:06 -0600162 assert direction in [self.INSIDE, self.OUTSIDE]
Alex Kleinc05f3d12019-05-29 14:16:21 -0600163
Alex Klein1699fab2022-09-08 08:46:06 -0600164 if self.field.location == direction:
165 # Already in the correct location, nothing to do.
166 return
Alex Kleinc05f3d12019-05-29 14:16:21 -0600167
Alex Klein54c891a2023-01-24 10:45:41 -0700168 # Create a tempdir for the copied file if we're cleaning it up
169 # afterwords.
Alex Klein1699fab2022-09-08 08:46:06 -0600170 if self.delete:
171 self.tempdir = osutils.TempDir(base_dir=self.destination)
172 destination = self.tempdir.tempdir
173 else:
174 destination = self.destination
Alex Kleinc05f3d12019-05-29 14:16:21 -0600175
Alex Klein1699fab2022-09-08 08:46:06 -0600176 source = self.field.path
Brian Norris5c637c42023-05-04 18:07:48 -0700177 if direction == self.OUTSIDE and self.chroot:
178 source = self.chroot.full_path(source)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600179
Alex Klein1699fab2022-09-08 08:46:06 -0600180 if os.path.isfile(source):
181 # File - use the old file name, just copy it into the destination.
182 dest_path = os.path.join(destination, os.path.basename(source))
183 copy_fn = shutil.copy
184 else:
185 # Directory - just copy everything into the new location.
186 dest_path = destination
187 copy_fn = functools.partial(
188 osutils.CopyDirContents, allow_nonempty=True
189 )
Alex Kleinc05f3d12019-05-29 14:16:21 -0600190
Alex Klein1699fab2022-09-08 08:46:06 -0600191 logging.debug("Copying %s to %s", source, dest_path)
192 copy_fn(source, dest_path)
Alex Kleinc05f3d12019-05-29 14:16:21 -0600193
Alex Klein1699fab2022-09-08 08:46:06 -0600194 # Clean up the destination path for returning, if applicable.
195 return_path = dest_path
Brian Norris5c637c42023-05-04 18:07:48 -0700196 if direction == self.INSIDE and self.chroot:
197 return_path = self.chroot.chroot_path(return_path)
Alex Kleinc05f3d12019-05-29 14:16:21 -0600198
Alex Klein1699fab2022-09-08 08:46:06 -0600199 self.field.path = return_path
200 self.field.location = direction
201 self._transferred = True
Alex Kleinc05f3d12019-05-29 14:16:21 -0600202
Alex Klein1699fab2022-09-08 08:46:06 -0600203 def cleanup(self):
Alex Kleinb6d52022022-10-18 08:55:06 -0600204 """Post-execution cleanup."""
Alex Klein1699fab2022-09-08 08:46:06 -0600205 if self.tempdir:
206 self.tempdir.Cleanup()
207 self.tempdir = None
Alex Kleinc05f3d12019-05-29 14:16:21 -0600208
Alex Klein1699fab2022-09-08 08:46:06 -0600209 if self.reset:
210 self.field.CopyFrom(self._original_message)
Alex Kleinaa705412019-06-04 15:00:30 -0600211
Alex Kleinc05f3d12019-05-29 14:16:21 -0600212
Alex Klein074f94f2023-06-22 10:32:06 -0600213class SyncedDirHandler:
Alex Klein1699fab2022-09-08 08:46:06 -0600214 """Handler for syncing directories across the chroot boundary."""
Alex Kleinf0717a62019-12-06 09:45:00 -0700215
Brian Norris5c637c42023-05-04 18:07:48 -0700216 def __init__(
217 self,
218 field: common_pb2.SyncedDir,
219 destination: str,
220 chroot: chroot_lib.Chroot,
221 ):
Alex Klein1699fab2022-09-08 08:46:06 -0600222 self.field = field
Brian Norris5c637c42023-05-04 18:07:48 -0700223 self.chroot = chroot
Alex Kleinf0717a62019-12-06 09:45:00 -0700224
Alex Klein1699fab2022-09-08 08:46:06 -0600225 self.source = self.field.dir
226 if not self.source.endswith(os.sep):
227 self.source += os.sep
Alex Kleinf0717a62019-12-06 09:45:00 -0700228
Alex Klein1699fab2022-09-08 08:46:06 -0600229 self.destination = destination
230 if not self.destination.endswith(os.sep):
231 self.destination += os.sep
Alex Kleinf0717a62019-12-06 09:45:00 -0700232
Alex Klein1699fab2022-09-08 08:46:06 -0600233 # For resetting the message later.
234 self._original_message = common_pb2.SyncedDir()
235 self._original_message.CopyFrom(self.field)
Alex Kleinf0717a62019-12-06 09:45:00 -0700236
Alex Klein1699fab2022-09-08 08:46:06 -0600237 def _sync(self, src, dest):
238 logging.info("Syncing %s to %s", src, dest)
239 # TODO: This would probably be more efficient with rsync.
240 osutils.EmptyDir(dest)
241 osutils.CopyDirContents(src, dest)
Alex Kleinf0717a62019-12-06 09:45:00 -0700242
Alex Klein1699fab2022-09-08 08:46:06 -0600243 def sync_in(self):
244 """Sync files from the source directory to the destination directory."""
245 self._sync(self.source, self.destination)
Brian Norris5c637c42023-05-04 18:07:48 -0700246 self.field.dir = self.chroot.chroot_path(self.destination)
Alex Kleinf0717a62019-12-06 09:45:00 -0700247
Alex Klein1699fab2022-09-08 08:46:06 -0600248 def sync_out(self):
249 """Sync files from the destination directory to the source directory."""
250 self._sync(self.destination, self.source)
251 self.field.CopyFrom(self._original_message)
Alex Kleinf0717a62019-12-06 09:45:00 -0700252
253
Alex Kleinc05f3d12019-05-29 14:16:21 -0600254@contextlib.contextmanager
Alex Klein1699fab2022-09-08 08:46:06 -0600255def copy_paths_in(
256 message: protobuf_message.Message,
257 destination: str,
258 delete: Optional[bool] = True,
Brian Norris5c637c42023-05-04 18:07:48 -0700259 chroot: Optional[chroot_lib.Chroot] = None,
Alex Klein1699fab2022-09-08 08:46:06 -0600260) -> Iterator[List[PathHandler]]:
261 """Context manager function to transfer and cleanup all Path messages.
Alex Kleinc05f3d12019-05-29 14:16:21 -0600262
Alex Klein1699fab2022-09-08 08:46:06 -0600263 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600264 message: A message whose Path messages should be transferred.
265 destination: The base destination path.
266 delete: Whether the file(s) should be deleted.
Brian Norris5c637c42023-05-04 18:07:48 -0700267 chroot: Chroot object to use for translating the final destination path
268 into the chroot.
Alex Kleinc05f3d12019-05-29 14:16:21 -0600269
Alex Klein1699fab2022-09-08 08:46:06 -0600270 Yields:
Alex Kleina0442682022-10-10 13:47:38 -0600271 list[PathHandler]: The path handlers.
Alex Klein1699fab2022-09-08 08:46:06 -0600272 """
273 assert destination
Alex Kleinc05f3d12019-05-29 14:16:21 -0600274
Alex Klein1699fab2022-09-08 08:46:06 -0600275 handlers = _extract_handlers(
Brian Norris5c637c42023-05-04 18:07:48 -0700276 message, destination, chroot, delete=delete, reset=True
Alex Klein1699fab2022-09-08 08:46:06 -0600277 )
Alex Kleinaa705412019-06-04 15:00:30 -0600278
Alex Kleinaa705412019-06-04 15:00:30 -0600279 for handler in handlers:
Alex Klein1699fab2022-09-08 08:46:06 -0600280 handler.transfer(PathHandler.INSIDE)
281
282 try:
283 yield handlers
284 finally:
285 for handler in handlers:
286 handler.cleanup()
Alex Kleinaa705412019-06-04 15:00:30 -0600287
288
Alex Kleinf0717a62019-12-06 09:45:00 -0700289@contextlib.contextmanager
Alex Klein1699fab2022-09-08 08:46:06 -0600290def sync_dirs(
Brian Norris5c637c42023-05-04 18:07:48 -0700291 message: protobuf_message.Message,
292 destination: str,
293 chroot: chroot_lib.Chroot,
Alex Klein1699fab2022-09-08 08:46:06 -0600294) -> Iterator[SyncedDirHandler]:
295 """Context manager function to handle SyncedDir messages.
Alex Kleinf0717a62019-12-06 09:45:00 -0700296
Alex Klein1699fab2022-09-08 08:46:06 -0600297 The sync semantics are effectively:
Alex Kleina0442682022-10-10 13:47:38 -0600298 rsync -r --del source/ destination/
299 * The endpoint runs. *
300 rsync -r --del destination/ source/
Alex Kleinf0717a62019-12-06 09:45:00 -0700301
Alex Klein1699fab2022-09-08 08:46:06 -0600302 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600303 message: A message whose SyncedPath messages should be synced.
304 destination: The destination path.
Brian Norris5c637c42023-05-04 18:07:48 -0700305 chroot: Chroot object to use for translating the final destination path
306 into the chroot.
Alex Kleinf0717a62019-12-06 09:45:00 -0700307
Alex Klein1699fab2022-09-08 08:46:06 -0600308 Yields:
Alex Kleina0442682022-10-10 13:47:38 -0600309 The handlers.
Alex Klein1699fab2022-09-08 08:46:06 -0600310 """
311 assert destination
Alex Kleinf0717a62019-12-06 09:45:00 -0700312
Alex Klein1699fab2022-09-08 08:46:06 -0600313 handlers = _extract_handlers(
314 message,
315 destination,
Brian Norris5c637c42023-05-04 18:07:48 -0700316 chroot=chroot,
Alex Klein1699fab2022-09-08 08:46:06 -0600317 delete=False,
318 reset=True,
319 message_type=common_pb2.SyncedDir,
320 )
Alex Kleinf0717a62019-12-06 09:45:00 -0700321
Alex Kleinf0717a62019-12-06 09:45:00 -0700322 for handler in handlers:
Alex Klein1699fab2022-09-08 08:46:06 -0600323 handler.sync_in()
324
325 try:
326 yield handlers
327 finally:
328 for handler in handlers:
329 handler.sync_out()
Alex Kleinf0717a62019-12-06 09:45:00 -0700330
331
Alex Klein1699fab2022-09-08 08:46:06 -0600332def extract_results(
333 request_message: protobuf_message.Message,
334 response_message: protobuf_message.Message,
335 chroot: "chroot_lib.Chroot",
336) -> None:
337 """Transfer all response Path messages to the request's ResultPath.
Alex Kleinbd6edf82019-07-18 10:30:49 -0600338
Alex Klein1699fab2022-09-08 08:46:06 -0600339 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600340 request_message: The request message containing a ResultPath message.
341 response_message: The response message whose Path message(s) are to be
342 transferred.
343 chroot: The chroot the files are being copied out of.
Alex Klein1699fab2022-09-08 08:46:06 -0600344 """
345 # Find the ResultPath.
346 for descriptor in request_message.DESCRIPTOR.fields:
347 field = getattr(request_message, descriptor.name)
348 if isinstance(field, common_pb2.ResultPath):
349 result_path_message = field
350 break
Alex Kleinbd6edf82019-07-18 10:30:49 -0600351 else:
Alex Klein1699fab2022-09-08 08:46:06 -0600352 # No ResultPath to handle.
353 return
Alex Kleinbd6edf82019-07-18 10:30:49 -0600354
Alex Klein1699fab2022-09-08 08:46:06 -0600355 destination = result_path_message.path.path
356 handlers = _extract_handlers(
Brian Norris5c637c42023-05-04 18:07:48 -0700357 response_message, destination, chroot=chroot, delete=False, reset=False
Alex Klein1699fab2022-09-08 08:46:06 -0600358 )
Alex Kleinc05f3d12019-05-29 14:16:21 -0600359
Alex Klein1699fab2022-09-08 08:46:06 -0600360 for handler in handlers:
361 handler.transfer(PathHandler.OUTSIDE)
362 handler.cleanup()
Alex Kleinc05f3d12019-05-29 14:16:21 -0600363
Alex Klein1699fab2022-09-08 08:46:06 -0600364
365def _extract_handlers(
366 message,
367 destination,
Brian Norris5c637c42023-05-04 18:07:48 -0700368 chroot,
Alex Klein1699fab2022-09-08 08:46:06 -0600369 delete=False,
370 reset=False,
371 field_name=None,
372 message_type=None,
373):
374 """Recursive helper for handle_paths to extract Path messages."""
375 message_type = message_type or common_pb2.Path
376 is_path_target = message_type is common_pb2.Path
377 is_synced_target = message_type is common_pb2.SyncedDir
378
379 is_message = isinstance(message, protobuf_message.Message)
380 is_result_path = isinstance(message, common_pb2.ResultPath)
381 if not is_message or is_result_path:
382 # Base case: Nothing to handle.
383 # There's nothing we can do with scalar values.
384 # Skip ResultPath instances to avoid unnecessary file copying.
385 return []
386 elif is_path_target and isinstance(message, common_pb2.Path):
387 # Base case: Create handler for this message.
388 if not message.path or not message.location:
389 logging.debug("Skipping %s; incomplete.", field_name or "message")
390 return []
391
392 handler = PathHandler(
Brian Norris5c637c42023-05-04 18:07:48 -0700393 message, destination, delete=delete, chroot=chroot, reset=reset
Alex Klein1699fab2022-09-08 08:46:06 -0600394 )
395 return [handler]
396 elif is_synced_target and isinstance(message, common_pb2.SyncedDir):
397 if not message.dir:
398 logging.debug(
399 "Skipping %s; no directory given.", field_name or "message"
400 )
401 return []
402
Brian Norris5c637c42023-05-04 18:07:48 -0700403 handler = SyncedDirHandler(message, destination, chroot)
Alex Klein1699fab2022-09-08 08:46:06 -0600404 return [handler]
405
406 # Iterate through each field and recurse.
407 handlers = []
408 for descriptor in message.DESCRIPTOR.fields:
409 field = getattr(message, descriptor.name)
410 if field_name:
411 new_field_name = "%s.%s" % (field_name, descriptor.name)
412 else:
413 new_field_name = descriptor.name
414
415 if isinstance(field, protobuf_message.Message):
416 # Recurse for nested Paths.
417 handlers.extend(
418 _extract_handlers(
419 field,
420 destination,
Brian Norris5c637c42023-05-04 18:07:48 -0700421 chroot,
Alex Klein1699fab2022-09-08 08:46:06 -0600422 delete,
423 reset,
424 field_name=new_field_name,
425 message_type=message_type,
426 )
427 )
428 else:
429 # If it's iterable it may be a repeated field, try each element.
430 try:
431 iterator = iter(field)
432 except TypeError:
433 # Definitely not a repeated field, just move on.
434 continue
435
436 for element in iterator:
437 handlers.extend(
438 _extract_handlers(
439 element,
440 destination,
Brian Norris5c637c42023-05-04 18:07:48 -0700441 chroot,
Alex Klein1699fab2022-09-08 08:46:06 -0600442 delete,
443 reset,
444 field_name=new_field_name,
445 message_type=message_type,
446 )
447 )
448
449 return handlers