From f1e51e7fd02dfbf9e9fe9f88a68cbc3c080a8ad3 Mon Sep 17 00:00:00 2001 From: Jose Luis Rivero Date: Wed, 31 Mar 2021 00:00:42 +0200 Subject: [PATCH] aptly automation for reprepro updater (#105) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Steven! Ragnarök Co-authored-by: Jose Luis Rivero Co-authored-by: Steven! Ragnarök --- .github/workflows/ci.yml | 60 +++ scripts/aptly/aptly_importer.py | 344 ++++++++++++++++++ scripts/aptly/aptly_importer_TEST.py | 186 ++++++++++ scripts/aptly/test/example.yaml | 7 + .../aptly/test/example_no_source_package.yaml | 8 + .../test/missing_architectures_field.yaml | 5 + 6 files changed, 610 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 scripts/aptly/aptly_importer.py create mode 100644 scripts/aptly/aptly_importer_TEST.py create mode 100644 scripts/aptly/test/example.yaml create mode 100644 scripts/aptly/test/example_no_source_package.yaml create mode 100644 scripts/aptly/test/missing_architectures_field.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..87850e6a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +--- +name: Aptly Importer + +on: [push, pull_request] + +jobs: + bionic-ci: + runs-on: ubuntu-18.04 + name: Bionic Aptly CI - + debug ${{ matrix.show_debug_cmds }} + strategy: + matrix: + show_debug_cmds: + - true + - false + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install aptly repo + run: | + sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ED75B5A4483DA07C + sudo bash -c "echo 'deb http://repo.aptly.info/ squeeze main' > /etc/apt/sources.list.d/aptly.list" + - name: Install software + run: | + sudo apt-get update + sudo apt-get install -y gpg python3 aptly ubuntu-keyring + - name: Fix weird problem with trust keys + run: | + gpg --check-trustdb 2>&1| grep 'not found' | awk '{print $8}' >bad-keys.txt + gpg --export-ownertrust > ownertrust-gpg.txt + mv ~/.gnupg/trustdb.gpg ~/.gnupg/trustdb.gpg-broken + for KEY in `cat bad-keys.txt` ; do sed -i "/$KEY/d" ownertrust-gpg.txt ; done + gpg --import-ownertrust ownertrust-gpg.txt + rm bad-keys.txt ownertrust-gpg.txt + - name: Setup enviroment + run: | + gpg --no-default-keyring --keyring /usr/share/keyrings/ubuntu-archive-keyring.gpg --export | gpg --no-default-keyring --keyring trustedkeys.gpg --import + gpg --no-default-keyring --keyring trustedkeys.gpg --keyserver keys.gnupg.net --recv-keys 16E90B3FDF65EDE3AA7F323C04EE7237B7D453EC + gpg --no-default-keyring --keyring trustedkeys.gpg --keyserver keys.gnupg.net --recv-keys 0146DC6D4A0B2914BDED34DB648ACFD622F3D138 + gpg --no-default-keyring --keyring trustedkeys.gpg --keyserver keys.gnupg.net --recv-keys 67170598AF249743 + + - name: Generate key + run: | + cat <<-EOF > genkey + Key-Type: 1 + Key-Length: 4096 + Subkey-Type: 1 + Subkey-Length: 4096 + Name-Real: Testing name + Name-Email: test@test.org + Expire-Date: 0 + %no-protection + EOF + gpg --gen-key --batch genkey + - name: Run tests + run: | + python3 scripts/aptly/aptly_importer_TEST.py + env: + _DEBUG_MSGS_REPREPRO_UPDATER_TEST_SUITE_: ${{ matrix.show_debug_cmds }} + _ALLOW_DESTRUCTIVE_TESTS_REPREPRO_UPDATER_TEST_SUITE_: true diff --git a/scripts/aptly/aptly_importer.py b/scripts/aptly/aptly_importer.py new file mode 100644 index 00000000..255bab2d --- /dev/null +++ b/scripts/aptly/aptly_importer.py @@ -0,0 +1,344 @@ +import argparse +from collections import defaultdict +from enum import Enum +from pathlib import Path +from os import path +from subprocess import check_output, PIPE, run, CalledProcessError +from sys import stderr, exit +import re +import time +import yaml + + +class Reprepro2AptlyFilter(): + def convert(self, filter_str): + # remove begin/end whitespaces + r_str = filter_str.strip() + # Package is not a valid Aptly filter. Could be Name or empty. + r_str = r_str.replace('Package', 'Name') + # Aptly filters use (= value) for exact matches rather than (% =value) + r_str = r_str.replace('% =', '= ') + return r_str + + +class Aptly(): + class ArtifactType(Enum): + MIRROR = 'mirror' + REPOSITORY = 'repo' + SNAPSHOT = 'snapshot' + PUBLISH = 'publish' + + def __init__(self, debug=False, config_file=None): + self.debug = debug + self.config_file = config_file + + def __error(self, cmd, msg, exit_on_errors=False): + print(f"Aptly error running: {cmd}", file=stderr) + print(f" --> {msg} \n", file=stderr) + if exit_on_errors: + exit(1) + + def check_valid_filter(self, filter_str): + fake_mirror_name = '_test_aptly_filter' + create_mirror_cmd = ['mirror', 'create', + f"-filter={filter_str}", + fake_mirror_name, + 'http://deb.debian.org/debian', 'sid', 'main'] + result = self.run(create_mirror_cmd, fail_on_errors=False) + if not result: + return result + delete_mirror_cmd = ['mirror', 'drop', fake_mirror_name] + self.run(delete_mirror_cmd) + return True + + def exists(self, aptly_type: ArtifactType, name): + assert(aptly_type != Aptly.ArtifactType.PUBLISH), 'PUBLISH uses exists_publication' + return self.run([aptly_type.value, 'show', name], + fail_on_errors=False, show_errors=False) + + def exists_all_source_packages(self, aptly_type: ArtifactType, name): + if not self.exists(aptly_type, name): + self.__error('exists_all_source_packages method', + f"{aptly_type.value} does not exist") + return False + + packages_by_source = self.get_packages_by_source_package(aptly_type, name) + source_packages = self.get_source_packages(aptly_type, name) + + result = True + for source in packages_by_source: + if source not in source_packages: + print(f"Source package '{source}' is missing for packages: " + f"{', '.join(packages_by_source[source])}") + result = False + return result + + def exists_publication(self, distribution, end_point): + return self.run(['publish', 'show', distribution, end_point], + fail_on_errors=False, show_errors=False) + + def get_number_of_packages(self, aptly_type: ArtifactType, name): + output = check_output(f"aptly {aptly_type.value} show {name}", shell=True) + + for row in output.splitlines(): + if 'Number of packages' in row.decode(): + return int(row.decode().split(':')[1]) + assert(False), "get_number_of_packages did not found a valid 'Number of packages' line" + + # returns a dictionary with source package as index and deb packages corresponding to + # that source package as values + def get_packages_by_source_package(self, aptly_type: ArtifactType, name): + packages_by_source = defaultdict(set) + cmd = [aptly_type.value, 'search', + '-format={{.Package}}::{{.Source}}', + name, + '$PackageType (= deb)'] + result = self.run(cmd, return_all_info=True) + if result.returncode != 0: + self.__error(cmd, result.stderr.decode('utf-8'), exit_on_errors=True) + + for line in result.stdout.splitlines(): + # ignore empty entries with 'no value' + if 'no value' in line.decode('utf-8'): + continue + package, source = line.decode('utf-8').split('::') + # Source field may include a parenthesized version which we'll ignore for now. + # e.g. `pcl (1.11.1+dfsg-1)` + if len(source.split(' ')) > 1: + source = source.split(' ')[0] + packages_by_source[source].add(package) + return packages_by_source + + def get_source_packages(self, aptly_type, name): + result = self.run([aptly_type.value, 'search', + '-format={{.Package}}', + name, + '$PackageType (= source)'], + return_all_info=True) + if result.returncode == 0: + return result.stdout.decode('utf-8').splitlines() + # handle no source packages scenario + if 'no results' in result.stderr.decode('utf-8'): + return [] + else: + self.__error('get_source_packages method', result.stderr.decode('utf-8')) + + def get_snapshots_from_mirror(self, mirror_name): + result = [] + output = check_output('aptly snapshot list', shell=True) + for row in output.splitlines(): + if f"from mirror [{mirror_name}" in row.decode(): + m = re.findall(r"\[(.*)\]: Snapshot", row.decode()) + result.append(m[0]) + return result + + def run(self, cmd=[], fail_on_errors=True, show_errors=True, return_all_info=False): + run_cmd = ['aptly'] + if self.config_file: + run_cmd += [f"-config={self.config_file}"] + run_cmd += cmd + if self.debug: + print(f"RUN {' '.join(run_cmd)}") + try: + r = run(run_cmd, stdout=PIPE, stderr=PIPE) + except CalledProcessError as e: + if return_all_info: + return r + return False + # if return_all_info is enabled return result object + if return_all_info: + return r + + if r.returncode == 0: + return True + else: + if show_errors: + self.__error(run_cmd, f"{r.stderr.decode('utf-8')}", fail_on_errors) + return False + + +class UpdaterConfiguration(): + def __init__(self, input_file): + try: + self.config = self.__load_config_file(input_file) + self.reprepro2aptly = Reprepro2AptlyFilter() + + self.architectures = self.config['architectures'] + # source was accepted as a valid architecture to indicate that + # source packages need to be download. It is not a valid arch in aptly + if 'source' in self.config['architectures']: + self.architectures = self.config['architectures'].remove('source') + self.architectures = self.config['architectures'] + self.component = self.config['component'] + self.filter_formula = self.reprepro2aptly.convert( + self.config['filter_formula']) + self.method = self.config['method'] + self.name = self.config['name'] + self.suites = self.config['suites'] + except KeyError as e: + self.__error(f"{e} key was not found in file {input_file}") + + def __error(self, msg): + print(f"Configuration file error: {msg} \n", file=stderr) + exit(2) + + def __load_config_file(self, config_file_path): + fn = Path(__file__).parent / config_file_path + try: + with open(str(fn), 'r') as stream: + config = yaml.safe_load(stream) + return config + except yaml.YAMLError as exc: + self.__error(f"yaml parsing error {exc}") + except FileNotFoundError as e: + self.__error(f"not found {config_file_path}") + + +class UpdaterManager(): + def __init__(self, input_file, debug=False, aptly_config_file=None, simulate_repo_import=False, snapshot_and_publish=False): + self.aptly = Aptly(debug, + config_file=aptly_config_file) + self.config = UpdaterConfiguration(input_file) + self.debug = debug + self.simulate_repo_import = simulate_repo_import + self.snapshot_and_publish = snapshot_and_publish + self.snapshot_timestamp = None + + def __create_aptly_mirror(self, distribution): + assert(self.config) + self.__log(f"Creating aptly mirror for {distribution}") + mirror_name = self.__get_mirror_name(distribution) + if self.aptly.exists(Aptly.ArtifactType.MIRROR, mirror_name): + self.__log_ok('Removing existing mirror') + self.aptly.run(['mirror', 'drop', mirror_name]) + self.aptly.run(['mirror', 'create', '-with-sources', + f"-architectures={','.join(self.config.architectures)}", + f"-filter={self.config.filter_formula}", + mirror_name, + self.config.method, + distribution, + self.config.component]) + self.aptly.run(['mirror', 'update', mirror_name]) + self.__log_ok(f"mirror {mirror_name} created") + + def __create_aptly_snapshot(self, distribution): + self.__log('Creating an aptly snapshot from local aptly repository') + self.aptly.run(['snapshot', 'create', self.__get_snapshot_name(distribution), + 'from', 'repo', self.__get_repo_name(distribution)]) + self.__log_ok(f"snapshot {self.__get_snapshot_name(distribution)} created from repo {self.__get_repo_name(distribution)}") + + def __error(self, msg): + print(f"Update Manager error: {msg} \n", file=stderr) + exit(1) + + def __get_endpoint_name(self, distribution): + return f"filesystem:live:ros_bootstrap" + + def __get_mirror_name(self, distribution): + return f"{self.config.name}-{distribution}" + + def __get_repo_name(self, distribution): + return f"ros_bootstrap-{distribution}" + + def __get_snapshot_name(self, distribution): + if not self.snapshot_timestamp: + self.__generate_snapshot_timestamp(distribution) + return f"{self.__get_repo_name(distribution)}-{self.snapshot_timestamp}" + + def __generate_snapshot_timestamp(self, distribution): + self.snapshot_timestamp = f"{time.time()}" + + def __import__aptly_mirror_to_repo(self, distribution): + self.__log('Import aptly mirror into local aptly repo') + repo_name = self.__get_repo_name(distribution) + # create repository if it does not exist. New distribution probably + if not self.aptly.exists(Aptly.ArtifactType.REPOSITORY, repo_name): + self.aptly.run(['repo', 'create', repo_name]) + self.__log_ok(f"aptly repository {repo_name} was created") + import_cmd = ['repo', 'import', + self.__get_mirror_name(distribution), + repo_name, + self.config.filter_formula] + if self.simulate_repo_import: + import_cmd.insert(2, '-dry-run') + + self.aptly.run(import_cmd) + self.__log_ok(f"aptly mirror {self.__get_mirror_name(distribution)} imported to the repo {repo_name}") + + def __log(self, msg): + print(f" {msg} ") + + def __log_ok(self, msg): + self.__log(f" [ok] {msg}") + + def __publish_new_snapshot(self, dist): + self.__log('Publish the new snapshot') + if (self.aptly.exists_publication(dist, self.__get_endpoint_name(dist))): + self.aptly.run(['publish', 'switch', + dist, + self.__get_endpoint_name(dist), + self.__get_snapshot_name(dist)]) + self.__log_ok(f"publish switch in {self.__get_endpoint_name(dist)} to use {self.__get_snapshot_name(dist)}") + + else: + self.aptly.run(['publish', 'snapshot', + f"-distribution={dist}", + self.__get_snapshot_name(dist), + self.__get_endpoint_name(dist)]) + self.__log_ok(f"new publication in {self.__get_endpoint_name(dist)} for {self.__get_snapshot_name(dist)}") + + def __remove_all_generated_mirrors(self): + for dist in self.config.suites: + mirror_name = self.__get_mirror_name(dist) + if self.aptly.exists(Aptly.ArtifactType.MIRROR, mirror_name): + self.aptly.run(['mirror', 'drop', mirror_name]) + + def run(self): + self.__log(f"\n == [ PROCESSING {self.config.name} ] ==\n") + for dist in self.config.suites: + # 1. Create aptly mirrors from yaml configuration file + self.__create_aptly_mirror(dist) + # 2. Be sure mirror has all source packages + self.__log(f'Check all source packages exist') + if not self.aptly.exists_all_source_packages(Aptly.ArtifactType.MIRROR, + self.__get_mirror_name(dist)): + self.__remove_all_generated_mirrors() + self.__error(f'{self.__get_mirror_name(dist)} does not have a source package. Removing generated mirrors') + self.__log_ok('There is a source pakage in the mirror') + # 2. Import from mirrors to local repositories + self.__import__aptly_mirror_to_repo(dist) + if self.simulate_repo_import: + self.__log_ok(f"Simulation of the import actions from mirrors to repos finished") + exit(0) + if self.snapshot_and_publish: + # 3. Create snapshots from repositories + self.__create_aptly_snapshot(dist) + # 4. Publish new snapshots + self.__publish_new_snapshot(dist) + self.__log(f"\n == [ END OF PROCESSING {self.config.name} ] ==\n") + return True + + +def main(): + """ + Usage: python3 aptly_importer.py [--simulate-repo-import] + """ + usage = "usage: %prog [--simulate-repo-import] config_file" + parser = argparse.ArgumentParser(usage) + parser.add_argument('config_file', type=str, default=None) + parser.add_argument('--simulate-repo-import', + action='store_true', + help='Perform a dry-run until the point of simulation mirrors import to repositories') + parser.add_argument('--snapshot-and-publish', action='store_true', help='Create and publish a snapshot of the updated distributions') + + args = parser.parse_args() + + if not path.exists(args.config_file): + parser.error("Missing input file from %s" % args.config_file) + + manager = UpdaterManager(args.config_file, simulate_repo_import=args.simulate_repo_import, snapshot_and_publish=args.snapshot_and_publish) + manager.run() + + +if __name__ == '__main__': + main() diff --git a/scripts/aptly/aptly_importer_TEST.py b/scripts/aptly/aptly_importer_TEST.py new file mode 100644 index 00000000..1aa654bb --- /dev/null +++ b/scripts/aptly/aptly_importer_TEST.py @@ -0,0 +1,186 @@ +import aptly_importer +import os +import sys +import tempfile +import unittest + +class TestYamlConfiguration(unittest.TestCase): + def test_non_existsing_file(self): + with self.assertRaises(SystemExit): + aptly_importer.UpdaterConfiguration('test/i_do_not_exists.yaml') + + def test_missing_field(self): + with self.assertRaises(SystemExit): + aptly_importer.UpdaterConfiguration('test/missing_architectures_field.yaml') + + def test_ok(self): + self.assertTrue(aptly_importer.UpdaterConfiguration('test/example.yaml')) + + +class TestAptly(unittest.TestCase): + def setUp(self): + self.aptly = aptly_importer.Aptly() + + def test_aptly_installed(self): + self.assertTrue(self.aptly.run(['version'])) + + def test_valid_filter(self): + self.assertTrue(self.aptly.check_valid_filter('mysql-client (>= 3.6)')) + + def test_invalid_filter(self): + self.assertFalse(self.aptly.check_valid_filter('mysql-client (>= 3.6')) + + def tets_invalid_mirror(self): + self.assertFalse(self.aptly.check_mirror_exists('i_do_not_exists')) + + +class TestUpdaterManager(unittest.TestCase): + def setUp(self): + with tempfile.NamedTemporaryFile(dir='/tmp', delete=False) as tmpfile: + tmpfile.write("""\ + { + "downloadSourcePackages": true, + "gpgDisableSign": false, + "FileSystemPublishEndpoints": { + "live": { + "rootDir": "/tmp/reprepro_updater/" + } + } + }""".encode()) + self.aptly_config_file = tmpfile.name + + self.aptly = aptly_importer.Aptly(config_file=self.aptly_config_file) + self.debug_msgs =\ + os.environ['_DEBUG_MSGS_REPREPRO_UPDATER_TEST_SUITE_'] if '_DEBUG_MSGS_REPREPRO_UPDATER_TEST_SUITE_' in os.environ else False + + def tearDown(self): + self.__clean_up_aptly_test_artifacts() + if os.path.exists(self.aptly_config_file): + os.remove(self.aptly_config_file) + else: + assert(False), f"{self.aptly_config_file} file does not exist while trying to remove it" + + def __add_mirror(self, mirror_name): + self.assertTrue(self.aptly.run(['mirror', 'create', mirror_name, 'http://packages.osrfoundation.org/gazebo/ubuntu-stable', 'focal'])) + + def __add_repo(self, repo_name): + self.assertTrue(self.aptly.run(['repo', 'create', repo_name])) + + def __assert_no_mirrors(self): + for name in self.expected_mirrors_test_name: + self.assertFalse(self.aptly.exists(aptly_importer.Aptly.ArtifactType.MIRROR, + name)) + + def __assert_expected_repos_mirrors(self): + for name in self.expected_mirrors_test_name: + self.assertTrue(self.aptly.exists(aptly_importer.Aptly.ArtifactType.MIRROR, + name)) + self.assertGreater( + self.aptly.get_number_of_packages(aptly_importer.Aptly.ArtifactType.MIRROR, + name), + 0) + for name in self.expected_repos_test_name: + self.assertTrue(self.aptly.exists(aptly_importer.Aptly.ArtifactType.REPOSITORY, + name)) + + def __clean_up_aptly_test_artifacts(self): + [self.__remove_publish(dist) for dist in self.expected_distros] + [self.__remove_repo(name) for name in self.expected_repos_test_name] + [self.__remove_mirror(name) for name in self.expected_mirrors_test_name] + [self.__remove_snapshots_from_mirror(name) for name in self.expected_mirrors_test_name] + self.aptly.run(['db', 'cleanup']) + + def __remove_mirror(self, mirror_name): + self.aptly.run(['mirror', 'drop', '-force', mirror_name], + show_errors=False, fail_on_errors=False) + + def __remove_publish(self, distro): + self.aptly.run(['publish', 'drop', + f"-config={self.aptly_config_file}", + distro, + self.expected_endpoint_name], + show_errors=False, fail_on_errors=False) + + def __remove_repo(self, repo_name): + # be sure to unpublish the repo otherwise can not be removed + self.aptly.run(['repo', 'drop', '-force', repo_name], + show_errors=False, fail_on_errors=False) + + def __remove_snapshots_from_mirror(self, mirror_name): + for snap in self.aptly.get_snapshots_from_mirror(mirror_name): + self.aptly.run(['snapshot', 'drop', snap]) + + def __setup__(self, distros_expected, config_file): + self.expected_distros = distros_expected + self.expected_endpoint_name = 'filesystem:live:ros_bootstrap' + self.expected_mirrors_test_name = [f"_reprepro_updater_test_suite_-{distro}" + for distro in self.expected_distros] + self.expected_repos_test_name = [f"ros_bootstrap-{distro}" + for distro in self.expected_distros] + self.expected_repos_by_distro_test_name =\ + {f"{distro}": f"ros_bootstrap-{distro}" for distro in self.expected_distros} + self.manager = aptly_importer.UpdaterManager(config_file, + debug=self.debug_msgs, + aptly_config_file=self.aptly_config_file, + snapshot_and_publish=True) + # clean up testing artifacts if they previously exists + self.__clean_up_aptly_test_artifacts() + + def test_basic_example_creation_from_scratch(self): + self.__setup__(['focal', 'groovy'], 'test/example.yaml') + self.assertTrue(self.manager.run()) + self.__assert_expected_repos_mirrors() + + def test_basic_example_creation_existing_mirror(self): + self.__setup__(['focal', 'groovy'], 'test/example.yaml') + [self.__add_mirror(name) for name in self.expected_mirrors_test_name] + self.assertTrue(self.manager.run()) + self.__assert_expected_repos_mirrors() + + def test_basic_example_creation_existing_repo(self): + self.__setup__(['focal', 'groovy'], 'test/example.yaml') + [self.__add_repo(name) for name in self.expected_repos_test_name] + self.assertTrue(self.manager.run()) + self.__assert_expected_repos_mirrors() + + def test_example_no_sources(self): + self.__setup__(['xenial'], 'test/example_no_source_package.yaml') + with self.assertRaises(SystemExit): + self.manager.run() + self.__assert_no_mirrors() + + +class TestReprepro2AptlyFilter(unittest.TestCase): + def setUp(self): + self.aptly = aptly_importer.Aptly() + self.filter = aptly_importer.Reprepro2AptlyFilter() + + def _assert_valid_filter(self, filter_str): + self.assertTrue(self.aptly.check_valid_filter( + self.filter.convert(filter_str))) + + def test_package(self): + self._assert_valid_filter('Package (% sdformat)') + + def test_package_version(self): + self._assert_valid_filter('Package (% sdformat*), $Version (>= 6.2.0+dfsg-2build1)') + + def test_multi_package_version(self): + filter_formula = """ \ + ((Package (% =lark-parser) |\ + Package (% =python3-lark-parser) |\ + Package (% =python3-lark-parser-doc)), + $Version (% 0.7.2-3osrf~* ))\ + """ + self._assert_valid_filter(filter_formula) + + +if __name__ == '__main__': + aptly = aptly_importer.Aptly() + # test suite is potentially dangerous for production machines + if not os.getenv('_ALLOW_DESTRUCTIVE_TESTS_REPREPRO_UPDATER_TEST_SUITE_') or \ + not os.environ['_ALLOW_DESTRUCTIVE_TESTS_REPREPRO_UPDATER_TEST_SUITE_']: + print("_ALLOW_DESTRUCTIVE_TESTS_REPREPRO_UPDATER_TEST_SUITE_ variable is" + "not set to true. Refuse to run test suite") + sys.exit(2) + unittest.main() diff --git a/scripts/aptly/test/example.yaml b/scripts/aptly/test/example.yaml new file mode 100644 index 00000000..6ba41568 --- /dev/null +++ b/scripts/aptly/test/example.yaml @@ -0,0 +1,7 @@ +--- +name: _reprepro_updater_test_suite_ +method: http://archive.ubuntu.com/ubuntu/ +suites: ['focal', 'groovy'] +component: main +architectures: [i386, amd64, source] +filter_formula: Package (% cmake* ) diff --git a/scripts/aptly/test/example_no_source_package.yaml b/scripts/aptly/test/example_no_source_package.yaml new file mode 100644 index 00000000..e5b005f5 --- /dev/null +++ b/scripts/aptly/test/example_no_source_package.yaml @@ -0,0 +1,8 @@ +--- +name: _reprepro_updater_test_suite_ +method: http://packages.osrfoundation.org/gazebo/ubuntu-stable +suites: ['xenial'] +component: main +architectures: [amd64, source] +# sdformat4 has no source package for xenial and it is not updated anymore +filter_formula: Package (% *sdformat4* ) diff --git a/scripts/aptly/test/missing_architectures_field.yaml b/scripts/aptly/test/missing_architectures_field.yaml new file mode 100644 index 00000000..ddfe447e --- /dev/null +++ b/scripts/aptly/test/missing_architectures_field.yaml @@ -0,0 +1,5 @@ +name: gazebo +method: http://packages.osrfoundation.org/gazebo/ubuntu +suites: [precise, quantal, raring, saucy] +component: main +filter_formula: Package (% gazebo-dbg ) | Package (% gazebo ) | Package (% sdformat)