| # |
| # Copyright (C) 2016 Codethink Limited |
| # |
| # This program is free software; you can redistribute it and/or |
| # modify it under the terms of the GNU Lesser General Public |
| # License as published by the Free Software Foundation; either |
| # version 2 of the License, or (at your option) any later version. |
| # |
| # This library is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # Lesser General Public License for more details. |
| # |
| # You should have received a copy of the GNU Lesser General Public |
| # License along with this library. If not, see <http://www.gnu.org/licenses/>. |
| # |
| # Authors: |
| # Andrew Leeming <andrew.leeming@codethink.co.uk> |
| |
| """ |
| ostree - stage files from an OSTree repository |
| ============================================== |
| |
| **Usage:** |
| |
| .. code:: yaml |
| |
| # Specify the ostree source kind |
| kind: ostree |
| |
| # Optionally specify a relative staging directory |
| # directory: path/to/stage |
| |
| # Specify the repository url, using an alias defined |
| # in your project configuration is recommended. |
| url: upstream:runtime |
| |
| # Optionally specify a symbolic tracking branch or tag, this |
| # will be used to update the 'ref' when refreshing the pipeline. |
| track: runtime/x86_64/stable |
| |
| # Specify the commit checksum, this must be specified in order |
| # to checkout sources and build, but can be automatically |
| # updated if the 'track' attribute was specified. |
| ref: d63cbb6fdc0bbdadc4a1b92284826a6d63a7ebcd |
| |
| # For signed ostree repositories, specify a local project relative |
| # path to the public verifying GPG key for this remote. |
| gpg-key: keys/runtime.gpg |
| """ |
| |
| import os |
| import shutil |
| |
| from buildstream import Source, SourceError, Consistency |
| from buildstream import _ostree |
| from buildstream import utils |
| from buildstream._ostree import OSTreeError |
| |
| |
| class OSTreeSource(Source): |
| # pylint: disable=attribute-defined-outside-init |
| |
| def configure(self, node): |
| |
| self.node_validate(node, ['url', 'ref', 'track', 'gpg-key'] + Source.COMMON_CONFIG_KEYS) |
| |
| self.original_url = self.node_get_member(node, str, 'url') |
| self.url = self.translate_url(self.original_url) |
| self.ref = self.node_get_member(node, str, 'ref', None) |
| self.tracking = self.node_get_member(node, str, 'track', None) |
| self.mirror = os.path.join(self.get_mirror_directory(), |
| utils.url_directory_name(self.url)) |
| |
| # (optional) Not all repos are signed. But if they are, get the gpg key |
| self.gpg_key = self.node_get_member(node, str, 'gpg-key', None) |
| self.gpg_key_path = None |
| if self.gpg_key is not None: |
| self.gpg_key_path = os.path.join(self.get_project_directory(), self.gpg_key) |
| |
| # Our OSTree repo handle |
| self.repo = None |
| |
| def preflight(self): |
| pass |
| |
| def get_unique_key(self): |
| return [self.original_url, self.ref] |
| |
| def load_ref(self, node): |
| self.ref = self.node_get_member(node, str, 'ref', None) |
| |
| def get_ref(self): |
| return self.ref |
| |
| def set_ref(self, ref, node): |
| node['ref'] = self.ref = ref |
| |
| def track(self): |
| # If self.tracking is not specified its' not an error, just silently return |
| if not self.tracking: |
| return None |
| |
| self.ensure() |
| with self.timed_activity("Fetching tracking ref '{}' from origin: {}" |
| .format(self.tracking, self.url)): |
| try: |
| _ostree.fetch(self.repo, ref=self.tracking, progress=self.progress) |
| except OSTreeError as e: |
| raise SourceError("{}: Failed to fetch tracking ref '{}' from origin {}\n\n{}" |
| .format(self, self.tracking, self.url, e)) from e |
| |
| return _ostree.checksum(self.repo, self.tracking) |
| |
| def fetch(self): |
| self.ensure() |
| if not _ostree.exists(self.repo, self.ref): |
| with self.timed_activity("Fetching remote ref: {} from origin: {}" |
| .format(self.ref, self.url)): |
| try: |
| _ostree.fetch(self.repo, ref=self.ref, progress=self.progress) |
| except OSTreeError as e: |
| raise SourceError("{}: Failed to fetch ref '{}' from origin: {}\n\n{}" |
| .format(self, self.ref, self.url, e)) from e |
| |
| def stage(self, directory): |
| self.ensure() |
| |
| # Checkout self.ref into the specified directory |
| with self.tempdir() as tmpdir: |
| checkoutdir = os.path.join(tmpdir, 'checkout') |
| |
| with self.timed_activity("Staging ref: {} from origin: {}" |
| .format(self.ref, self.url)): |
| try: |
| _ostree.checkout(self.repo, checkoutdir, self.ref, user=True) |
| except OSTreeError as e: |
| raise SourceError("{}: Failed to checkout ref '{}' from origin: {}\n\n{}" |
| .format(self, self.ref, self.url, e)) from e |
| |
| # The target directory is guaranteed to exist, here we must move the |
| # content of out checkout into the existing target directory. |
| # |
| # We may not be able to create the target directory as it's parent |
| # may be readonly, and the directory itself is often a mount point. |
| # |
| try: |
| for entry in os.listdir(checkoutdir): |
| source_path = os.path.join(checkoutdir, entry) |
| shutil.move(source_path, directory) |
| except (shutil.Error, OSError) as e: |
| raise SourceError("{}: Failed to move ostree checkout {} from '{}' to '{}'\n\n{}" |
| .format(self, self.url, tmpdir, directory, e)) from e |
| |
| def get_consistency(self): |
| if self.ref is None: |
| return Consistency.INCONSISTENT |
| |
| self.ensure() |
| if _ostree.exists(self.repo, self.ref): |
| return Consistency.CACHED |
| return Consistency.RESOLVED |
| |
| # |
| # Local helpers |
| # |
| def ensure(self): |
| if not self.repo: |
| self.status("Creating local mirror for {}".format(self.url)) |
| |
| self.repo = _ostree.ensure(self.mirror, True) |
| gpg_key = None |
| if self.gpg_key_path: |
| gpg_key = 'file://' + self.gpg_key_path |
| |
| try: |
| _ostree.configure_remote(self.repo, "origin", self.url, key_url=gpg_key) |
| except OSTreeError as e: |
| raise SourceError("{}: Failed to configure origin {}\n\n{}".format(self, self.url, e)) from e |
| |
| def progress(self, percent, message): |
| self.status(message) |
| |
| |
| # Plugin entry point |
| def setup(): |
| return OSTreeSource |