diff -Nru python-aptly-0.12.10/aptly/publisher/__init__.py python-aptly-0.12.12/aptly/publisher/__init__.py --- python-aptly-0.12.10/aptly/publisher/__init__.py 2018-01-16 12:54:47.000000000 +0000 +++ python-aptly-0.12.12/aptly/publisher/__init__.py 2018-10-22 09:36:17.000000000 +0000 @@ -80,7 +80,8 @@ raise Exception('Publish(es) required not found') for publish in save_list: - save_path = ''.join([dump_dir, '/', prefix, publish.name.replace('/', '-'), '.yml']) + storage = '' if not publish.storage else '-{}-'.format(publish.storage) + save_path = ''.join([dump_dir, '/', prefix, storage, publish.name.replace('/', '-'), '.yml']) publish.save_publish(save_path) def _publish_match(self, publish, names=False, name_only=False): @@ -165,39 +166,57 @@ self.client.do_delete('/repos/%s/packages' % repo_name, data={'PackageRefs': packages}) def cleanup_snapshots(self): - snapshots = self.client.do_get('/snapshots', {'sort': 'time'}) - exclude = [] + # requesting graph.dot always works, even if graphviz (dot) is not installed + dot_data = self.client.do_get('/graph.dot') - # Add currently published snapshots into exclude list - publishes = self.client.do_get('/publish') - for publish in publishes: - exclude.extend( - [x['Name'] for x in publish['Sources']] - ) - - # Add last snapshots into exclude list - # TODO: ignore snapshots that are source for merged snapshots - snapshot_latest = [] - for snapshot in snapshots: - base_name = snapshot['Name'].split('-')[0] - if base_name not in snapshot_latest: - snapshot_latest.append(base_name) - if snapshot['Name'] not in exclude: - lg.debug("Not deleting latest snapshot %s" % snapshot['Name']) - exclude.append(snapshot['Name']) - - exclude = self.list_uniq(exclude) - - for snapshot in snapshots: - if snapshot['Name'] not in exclude: - lg.info("Deleting snapshot %s" % snapshot['Name']) - try: - self.client.do_delete('/snapshots/%s' % snapshot['Name']) - except AptlyException as e: - if e.res.status_code == 409: - lg.warning("Snapshot %s is being used, can't delete" % snapshot['Name']) - else: - raise + # extract edges from dot-data + # edges = list of pairs with (node_id, node_id) + edges = re.findall('"([^"]+)"->"([^"]+)";', dot_data) + + # extract nodes from dot-data + # list of tuples with (node_id, node_type, node_name) + # node_type is one of 'Repo'|'Snapshot'|'Publish' + nodes_raw = re.findall('[ \t]+"([^"]+)".*label="{(Repo|Snapshot|Published) ([^|]+)[^\}"]+}"', dot_data) + + # convert nodes_raw into nodes + # nodes = dict of node_id to node + # node = (node_type, node_name) + nodes = {node_id: (node_type, node_name) for node_id, node_type, node_name in nodes_raw} + + # start with published and flood fill other nodes + published_node_ids = [key for key in nodes if nodes[key][0] == 'Published'] + + # do flood fill + while True: + # get a list of source node_ids of incoming edges + incoming_node_ids = [edge_from for edge_from, edge_to in edges if edge_to in published_node_ids and edge_from not in published_node_ids] + + # add incoming node_ids + published_node_ids.extend(incoming_node_ids) + + # no new nodes, we are finished + if len(incoming_node_ids) == 0: + break + + # we got published nodes, invert the set + non_published_node_ids = [node_id for node_id in nodes if node_id not in published_node_ids] + + # list of nodes with (node_type, node_name) + unreleased_nodes = [nodes[node_id] for node_id in non_published_node_ids] + + # use only nodes of type 'Snapshot' + unreleased_snapshots = [node[1] for node in unreleased_nodes if node[0] == 'Snapshot'] + + # actually delete snapshots that are not published + for snapshot in unreleased_snapshots: + lg.info("Deleting snapshot %s" % snapshot) + try: + self.client.do_delete('/snapshots/%s' % snapshot) + except AptlyException as e: + if e.res.status_code == 409: + lg.warning("Snapshot %s is being used, can't delete" % snapshot) + else: + raise class Publish(object): @@ -282,6 +301,38 @@ return (diff, equal) + def get_component_snapshot(self, component): + if component in self.components.keys(): + return self.components[component][0] + return "" + + def replace_snapshot(self, component, new_snapshot): + newlist = [] + if component in self.components.keys(): + for snapshot in self.publish_snapshots: + if snapshot["Name"] not in self.components[component] and snapshot not in newlist: + newlist.append(snapshot) + else: + newlist = self.publish_snapshots + newlist.append({ + 'Component': component, + 'Name': new_snapshot + }) + + self.components[component] = [new_snapshot] + self.publish_snapshots = newlist + + def create_snapshot_from_packages(self, packages, name, description): + self.client.do_post( + '/snapshots', + data={ + 'Name': name, + 'SourceSnapshots': [], + 'Description': description, + 'PackageRefs': packages, + } + ) + @staticmethod @CachedMethod def _get_packages(client, source_type, source_name): @@ -449,7 +500,8 @@ to_publish.append(component_name) - snapshot_name = '{}-{}'.format("restored", saved_component.get('snapshot')) + timestamp = time.strftime("%Y%m%d%H%M%S") + snapshot_name = '{}-{}-{}'.format("restored", timestamp, saved_component.get('snapshot')) lg.debug("Creating snapshot %s for component %s of packages: %s" % (snapshot_name, component_name, saved_packages)) diff -Nru python-aptly-0.12.10/debian/changelog python-aptly-0.12.12/debian/changelog --- python-aptly-0.12.10/debian/changelog 2022-06-03 02:18:46.000000000 +0000 +++ python-aptly-0.12.12/debian/changelog 2022-10-20 11:59:07.000000000 +0000 @@ -1,3 +1,13 @@ +python-aptly (0.12.12-1) unstable; urgency=medium + + * Team upload. + * New upstream version 0.12.12 + * Fix deprecated yaml.load() (Closes: #1022035) + * Bump Standards-Version to 4.6.1 + * Update d/watch to version 4 format + + -- Timo Röhling Thu, 20 Oct 2022 13:59:07 +0200 + python-aptly (0.12.10-3) unstable; urgency=medium [ Ondřej Nový ] diff -Nru python-aptly-0.12.10/debian/control python-aptly-0.12.12/debian/control --- python-aptly-0.12.10/debian/control 2022-06-03 02:18:46.000000000 +0000 +++ python-aptly-0.12.12/debian/control 2022-10-20 11:52:09.000000000 +0000 @@ -11,7 +11,7 @@ Build-Depends-Indep: python3-apt, python3-requests, python3-yaml -Standards-Version: 4.5.1 +Standards-Version: 4.6.1 Testsuite: autopkgtest-pkg-python Homepage: https://github.com/tcpcloud/python-aptly Vcs-Browser: https://salsa.debian.org/python-team/packages/python-aptly diff -Nru python-aptly-0.12.10/debian/patches/0001-PyYAML-6-compat.patch python-aptly-0.12.12/debian/patches/0001-PyYAML-6-compat.patch --- python-aptly-0.12.10/debian/patches/0001-PyYAML-6-compat.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-aptly-0.12.12/debian/patches/0001-PyYAML-6-compat.patch 2022-10-20 11:50:45.000000000 +0000 @@ -0,0 +1,35 @@ +From: =?utf-8?q?Timo_R=C3=B6hling?= +Date: Thu, 20 Oct 2022 13:50:38 +0200 +Subject: PyYAML 6 compat + +--- + aptly/publisher/__init__.py | 2 +- + aptly/publisher/__main__.py | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/aptly/publisher/__init__.py b/aptly/publisher/__init__.py +index ce7fb0b..5e22a2c 100644 +--- a/aptly/publisher/__init__.py ++++ b/aptly/publisher/__init__.py +@@ -13,7 +13,7 @@ lg = logging.getLogger(__name__) + + def load_publish(publish): + with open(publish, 'r') as publish_file: +- return yaml.load(publish_file) ++ return yaml.load(publish_file, Loader=yaml.SafeLoader) + + + class PublishManager(object): +diff --git a/aptly/publisher/__main__.py b/aptly/publisher/__main__.py +index 055732c..2e624df 100644 +--- a/aptly/publisher/__main__.py ++++ b/aptly/publisher/__main__.py +@@ -20,7 +20,7 @@ lg = logging.getLogger('aptly-publisher') + + def load_config(config): + with open(config, 'r') as fh: +- return yaml.load(fh) ++ return yaml.load(fh, Loader=yaml.SafeLoader) + + + def get_latest_snapshot(snapshots, name): diff -Nru python-aptly-0.12.10/debian/patches/series python-aptly-0.12.12/debian/patches/series --- python-aptly-0.12.10/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ python-aptly-0.12.12/debian/patches/series 2022-10-20 11:50:45.000000000 +0000 @@ -0,0 +1 @@ +0001-PyYAML-6-compat.patch diff -Nru python-aptly-0.12.10/debian/watch python-aptly-0.12.12/debian/watch --- python-aptly-0.12.10/debian/watch 2022-06-03 02:18:46.000000000 +0000 +++ python-aptly-0.12.12/debian/watch 2022-10-20 11:58:02.000000000 +0000 @@ -1,3 +1,5 @@ -version=3 -opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/python-aptly-$1\.tar\.gz/ \ - https://github.com/tcpcloud/python-aptly/tags .*/v?(\d\S*)\.tar\.gz +version=4 +opts=searchmode=plain,filenamemangle=s%(?:.*?/)?v?@ANY_VERSION@%@PACKAGE@-$1.tar.gz% \ + https://api.github.com/repos/tcpcloud/python-aptly/tags \ + tarball/(?:[^"]*?/)?v?@ANY_VERSION@ + diff -Nru python-aptly-0.12.10/setup.py python-aptly-0.12.12/setup.py --- python-aptly-0.12.10/setup.py 2018-01-16 12:54:47.000000000 +0000 +++ python-aptly-0.12.12/setup.py 2018-10-22 09:36:17.000000000 +0000 @@ -7,7 +7,7 @@ setup( name="python-aptly", - version="0.12.10", + version="0.12.12", description="Aptly REST API client and tooling", long_description=long_desc, author="Filip Pytloun",