blob: ee43d397e966245333beb8427457f6f6e9d4cbf5 [file] [log] [blame]
Alex Kleinc05f3d12019-05-29 14:16:21 -06001# -*- coding: utf-8 -*-
2# Copyright 2019 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Field handler classes.
7
8The field handlers are meant to parse information from or do some other generic
9action for a specific field type for the build_api script.
10"""
11
12from __future__ import print_function
13
14import contextlib
15import os
16import shutil
17
Alex Klein38c7d9e2019-05-08 09:31:19 -060018from chromite.api.controller import controller_util
Alex Kleinc05f3d12019-05-29 14:16:21 -060019from chromite.api.gen.chromiumos import common_pb2
Alex Kleinc05f3d12019-05-29 14:16:21 -060020from chromite.lib import cros_logging as logging
21from chromite.lib import osutils
22
Alex Kleinaa705412019-06-04 15:00:30 -060023from google.protobuf import message as protobuf_message
24
Alex Kleinc05f3d12019-05-29 14:16:21 -060025
26class ChrootHandler(object):
27 """Translate a Chroot message to chroot enter arguments and env."""
28
29 def __init__(self, clear_field):
30 self.clear_field = clear_field
31
32 def handle(self, message):
33 """Parse a message for a chroot field."""
34 # Find the Chroot field. Search for the field by type to prevent it being
35 # tied to a naming convention.
36 for descriptor in message.DESCRIPTOR.fields:
37 field = getattr(message, descriptor.name)
38 if isinstance(field, common_pb2.Chroot):
39 chroot = field
40 if self.clear_field:
41 message.ClearField(descriptor.name)
42 return self.parse_chroot(chroot)
43
44 return None
45
46 def parse_chroot(self, chroot_message):
47 """Parse a Chroot message instance."""
Alex Klein38c7d9e2019-05-08 09:31:19 -060048 return controller_util.ParseChroot(chroot_message)
Alex Kleinc05f3d12019-05-29 14:16:21 -060049
50
51def handle_chroot(message, clear_field=True):
52 """Find and parse the chroot field, returning the Chroot instance.
53
54 Returns:
55 chroot_lib.Chroot
56 """
57 handler = ChrootHandler(clear_field)
58 chroot = handler.handle(message)
59 if chroot:
60 return chroot
61
62 logging.warning('No chroot message found, falling back to defaults.')
63 return handler.parse_chroot(common_pb2.Chroot())
64
65
66class PathHandler(object):
67 """Handles copying a file or directory into or out of the chroot."""
68
69 INSIDE = common_pb2.Path.INSIDE
70 OUTSIDE = common_pb2.Path.OUTSIDE
71 ALL = -1
72
73 def __init__(self, field, destination, delete, prefix=None):
74 """Path handler initialization.
75
76 Args:
77 field (common_pb2.Path): The Path message.
78 destination (str): The destination base path.
79 delete (bool): Whether the copied file(s) should be deleted on cleanup.
80 prefix (str|None): A path prefix to remove from the destination path
81 when building the new Path message to pass back. This is largely meant
82 to support removing the chroot directory for files moved into the chroot
83 for endpoints that execute inside.
84 """
85 assert isinstance(field, common_pb2.Path)
86 assert field.path
87 assert field.location
88
89 self.field = field
90 self.destination = destination
91 self.prefix = prefix or ''
92 self.delete = delete
93 self.tempdir = None
Alex Kleinaa705412019-06-04 15:00:30 -060094 # For resetting the state.
95 self._transferred = False
96 self._original_message = common_pb2.Path()
97 self._original_message.CopyFrom(self.field)
Alex Kleinc05f3d12019-05-29 14:16:21 -060098
99 def transfer(self, direction=None):
100 """Copy the file or directory to its destination.
101
102 Args:
103 direction (int): The direction files are being copied (into or out of
104 the chroot). Specifying the direction allows avoiding performing
105 unnecessary copies.
106 """
Alex Kleinaa705412019-06-04 15:00:30 -0600107 if self._transferred:
108 return
109
Alex Kleinc05f3d12019-05-29 14:16:21 -0600110 if direction is None:
111 direction = self.ALL
112 assert direction in [self.INSIDE, self.OUTSIDE, self.ALL]
113
114 if self.field.location == direction:
Alex Kleinaa705412019-06-04 15:00:30 -0600115 # Already in the correct location, nothing to do.
116 return
Alex Kleinc05f3d12019-05-29 14:16:21 -0600117
118 if self.delete:
119 self.tempdir = osutils.TempDir(base_dir=self.destination)
120 destination = self.tempdir.tempdir
121 else:
122 destination = self.destination
123
124 if os.path.isfile(self.field.path):
125 # Use the old file name, just copy it into dest.
126 dest_path = os.path.join(destination, os.path.basename(self.field.path))
127 copy_fn = shutil.copy
128 else:
129 dest_path = destination
130 copy_fn = osutils.CopyDirContents
131
132 logging.debug('Copying %s to %s', self.field.path, dest_path)
133 copy_fn(self.field.path, dest_path)
134
135 # Clean up the destination path for returning, if applicable.
136 return_path = dest_path
137 if return_path.startswith(self.prefix):
138 return_path = return_path[len(self.prefix):]
139
Alex Kleinaa705412019-06-04 15:00:30 -0600140 self.field.path = return_path
141 self.field.location = direction
142 self._transferred = True
Alex Kleinc05f3d12019-05-29 14:16:21 -0600143
144 def cleanup(self):
145 if self.tempdir:
146 self.tempdir.Cleanup()
147 self.tempdir = None
148
Alex Kleinaa705412019-06-04 15:00:30 -0600149 self.field.CopyFrom(self._original_message)
150
Alex Kleinc05f3d12019-05-29 14:16:21 -0600151
152@contextlib.contextmanager
153def handle_paths(message, destination, delete=True, direction=None,
154 prefix=None):
155 """Context manager function to transfer and cleanup all Path messages.
156
157 Args:
158 message (Message): A message whose Path messages should be transferred.
159 destination (str): A base destination path.
160 delete (bool): Whether the file(s) should be deleted.
161 direction (int): One of the PathHandler constants (INSIDE, OUTSIDE, ALL).
162 This allows avoiding unnecessarily copying files already in the right
163 place (e.g. copying a file into the chroot that's already in the chroot).
164 prefix (str|None): A prefix path to remove from the final destination path
165 in the Path message (i.e. remove the chroot path).
166
167 Returns:
168 list[PathHandler]: The path handlers.
169 """
170 assert destination
171 direction = direction or PathHandler.ALL
172
Alex Kleinaa705412019-06-04 15:00:30 -0600173 handlers = _extract_handlers(message, destination, delete, prefix)
174
175 for handler in handlers:
176 handler.transfer(direction)
177
178 try:
179 yield handlers
180 finally:
181 for handler in handlers:
182 handler.cleanup()
183
184
185def _extract_handlers(message, destination, delete, prefix):
186 """Recursive helper for handle_paths to extract Path messages."""
Alex Kleinc05f3d12019-05-29 14:16:21 -0600187 handlers = []
188 for descriptor in message.DESCRIPTOR.fields:
189 field = getattr(message, descriptor.name)
190 if isinstance(field, common_pb2.Path):
191 if not field.path or not field.location:
192 logging.debug('Skipping %s; incomplete.', descriptor.name)
193 continue
194
195 handler = PathHandler(field, destination, delete=delete, prefix=prefix)
Alex Kleinaa705412019-06-04 15:00:30 -0600196 handlers.append(handler)
197 elif isinstance(field, protobuf_message.Message):
198 handlers.extend(_extract_handlers(field, destination, delete, prefix))
Alex Kleinc05f3d12019-05-29 14:16:21 -0600199
Alex Kleinaa705412019-06-04 15:00:30 -0600200 return handlers