Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package openSUSE-release-tools for
openSUSE:Factory checked in at 2025-10-22 12:16:13
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/openSUSE-release-tools (Old)
and /work/SRC/openSUSE:Factory/.openSUSE-release-tools.new.18484 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "openSUSE-release-tools"
Wed Oct 22 12:16:13 2025 rev:541 rq:1312862 version:20251014.2d75aced
Changes:
--------
---
/work/SRC/openSUSE:Factory/openSUSE-release-tools/openSUSE-release-tools.changes
2025-10-08 18:20:21.105087755 +0200
+++
/work/SRC/openSUSE:Factory/.openSUSE-release-tools.new.18484/openSUSE-release-tools.changes
2025-10-22 12:20:50.811002964 +0200
@@ -1,0 +2,19 @@
+Tue Oct 14 15:34:04 UTC 2025 - [email protected]
+
+- Update to version 20251014.2d75aced:
+ * gocd: sles.target: 16.1: release transactional images
+
+-------------------------------------------------------------------
+Thu Oct 09 08:54:21 UTC 2025 - [email protected]
+
+- Update to version 20251009.414a66f7:
+ * Update .editorconfig-checker.json
+ * .editorconfig: Allow trailing whitespace in .md files
+ * ttm: Simplify version_from_project
+ * ttm: Refactor core logic with per-product attributes
+ * ttm: Drop workaround for NonOSS trees not containing the product version
+ * ttm: Unify methods to get build version of a product
+ * ttm: Drop need_same_build_number, not used anywhere anymore
+ * ttm: Complain about unknown config options
+
+-------------------------------------------------------------------
Old:
----
openSUSE-release-tools-20251001.0ed4bc8b.obscpio
New:
----
openSUSE-release-tools-20251014.2d75aced.obscpio
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ openSUSE-release-tools.spec ++++++
--- /var/tmp/diff_new_pack.E5bS0S/_old 2025-10-22 12:20:51.999053023 +0200
+++ /var/tmp/diff_new_pack.E5bS0S/_new 2025-10-22 12:20:52.003053191 +0200
@@ -21,7 +21,7 @@
%define announcer_filename factory-package-news
%define services osrt-slsa.target [email protected]
[email protected] [email protected] [email protected]
Name: openSUSE-release-tools
-Version: 20251001.0ed4bc8b
+Version: 20251014.2d75aced
Release: 0
Summary: Tools to aid in staging and release work for openSUSE/SUSE
License: GPL-2.0-or-later AND MIT
@@ -61,9 +61,11 @@
Requires: python3-python-dateutil
Requires: python3-pyxdg
Requires: python3-requests
-# typing extensions are needed on SLE & Leap
%if 0%{?suse_version} <= 1500
+# typing extensions are needed on SLE & Leap
Requires: python3-typing_extensions
+# Backport for py 3.6
+Requires: python3-dataclasses
%endif
# Spec related requirements.
++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.E5bS0S/_old 2025-10-22 12:20:52.055055383 +0200
+++ /var/tmp/diff_new_pack.E5bS0S/_new 2025-10-22 12:20:52.059055551 +0200
@@ -1,7 +1,7 @@
<servicedata>
<service name="tar_scm">
<param
name="url">https://github.com/openSUSE/openSUSE-release-tools.git</param>
- <param
name="changesrevision">0ed4bc8bcafea4303ea5d5fef6d960d3e3f95155</param>
+ <param
name="changesrevision">2d75aced96a43eafd9d1a411e7d2f06501a2575c</param>
</service>
</servicedata>
++++++ openSUSE-release-tools-20251001.0ed4bc8b.obscpio ->
openSUSE-release-tools-20251014.2d75aced.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/openSUSE-release-tools-20251001.0ed4bc8b/.ecrc
new/openSUSE-release-tools-20251014.2d75aced/.ecrc
--- old/openSUSE-release-tools-20251001.0ed4bc8b/.ecrc 2025-10-01
10:58:08.000000000 +0200
+++ new/openSUSE-release-tools-20251014.2d75aced/.ecrc 1970-01-01
01:00:00.000000000 +0100
@@ -1,14 +0,0 @@
-{
- "Verbose": false,
- "IgnoreDefaults": false,
- "Exclude": ["tests/fixtures", "LICENSE", "\\.py$", "bs_copy"],
- "SpacesAfterTabs": true,
- "Disable": {
- "EndOfLine": false,
- "Indentation": false,
- "IndentSize": false,
- "InsertFinalNewline": false,
- "TrimTrailingWhitespace": false,
- "MaxLineLength": false
- }
-}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20251001.0ed4bc8b/.editorconfig
new/openSUSE-release-tools-20251014.2d75aced/.editorconfig
--- old/openSUSE-release-tools-20251001.0ed4bc8b/.editorconfig 2025-10-01
10:58:08.000000000 +0200
+++ new/openSUSE-release-tools-20251014.2d75aced/.editorconfig 2025-10-14
17:33:14.000000000 +0200
@@ -42,3 +42,6 @@
[data/repos.json]
indent_size = 3
+
+[**.md]
+trim_trailing_whitespace = false
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20251001.0ed4bc8b/.editorconfig-checker.json
new/openSUSE-release-tools-20251014.2d75aced/.editorconfig-checker.json
--- old/openSUSE-release-tools-20251001.0ed4bc8b/.editorconfig-checker.json
1970-01-01 01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20251014.2d75aced/.editorconfig-checker.json
2025-10-14 17:33:14.000000000 +0200
@@ -0,0 +1,14 @@
+{
+ "Verbose": false,
+ "IgnoreDefaults": false,
+ "Exclude": ["tests/fixtures", "LICENSE", "\\.py$"],
+ "SpacesAfterTabs": true,
+ "Disable": {
+ "EndOfLine": false,
+ "Indentation": false,
+ "IndentSize": false,
+ "InsertFinalNewline": false,
+ "TrimTrailingWhitespace": false,
+ "MaxLineLength": false
+ }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20251001.0ed4bc8b/.github/workflows/ci-test.yml
new/openSUSE-release-tools-20251014.2d75aced/.github/workflows/ci-test.yml
--- old/openSUSE-release-tools-20251001.0ed4bc8b/.github/workflows/ci-test.yml
2025-10-01 10:58:08.000000000 +0200
+++ new/openSUSE-release-tools-20251014.2d75aced/.github/workflows/ci-test.yml
2025-10-14 17:33:14.000000000 +0200
@@ -87,6 +87,9 @@
if test -e metrics.py; then
zypper -n in python3-influxdb-client python3-GitPython
fi
+ if test -e /usr/bin/python3.6; then
+ zypper -n in python3-dataclasses
+ fi
- name: run a simple smoke test whether --help actually works
run: |
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20251001.0ed4bc8b/dist/package/openSUSE-release-tools.spec
new/openSUSE-release-tools-20251014.2d75aced/dist/package/openSUSE-release-tools.spec
---
old/openSUSE-release-tools-20251001.0ed4bc8b/dist/package/openSUSE-release-tools.spec
2025-10-01 10:58:08.000000000 +0200
+++
new/openSUSE-release-tools-20251014.2d75aced/dist/package/openSUSE-release-tools.spec
2025-10-14 17:33:14.000000000 +0200
@@ -61,9 +61,11 @@
Requires: python3-python-dateutil
Requires: python3-pyxdg
Requires: python3-requests
-# typing extensions are needed on SLE & Leap
%if 0%{?suse_version} <= 1500
+# typing extensions are needed on SLE & Leap
Requires: python3-typing_extensions
+# Backport for py 3.6
+Requires: python3-dataclasses
%endif
# Spec related requirements.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20251001.0ed4bc8b/docs/totest.asciidoc
new/openSUSE-release-tools-20251014.2d75aced/docs/totest.asciidoc
--- old/openSUSE-release-tools-20251001.0ed4bc8b/docs/totest.asciidoc
2025-10-01 10:58:08.000000000 +0200
+++ new/openSUSE-release-tools-20251014.2d75aced/docs/totest.asciidoc
2025-10-14 17:33:14.000000000 +0200
@@ -45,10 +45,10 @@
-------------------------------------------------------------------------------
$ osc meta prjconf MyProject:ToTest
%if "%_repository" == "images"
-%Type: kiwi
-%Repotype: staticlinks
-%Patterntype: none
-%Prefer: openSUSE-release
+Type: kiwi
+Repotype: staticlinks
+Patterntype: none
+Prefer: openSUSE-release
%endif
-------------------------------------------------------------------------------
@@ -81,7 +81,6 @@
- MyProduct-Live:
- x86_64
take_source_from_product: true
-need_same_build_number: true
set_snapshot_number: true
test_subproject: ToTest
EOF
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20251001.0ed4bc8b/gocd/sles.target.gocd.yaml
new/openSUSE-release-tools-20251014.2d75aced/gocd/sles.target.gocd.yaml
--- old/openSUSE-release-tools-20251001.0ed4bc8b/gocd/sles.target.gocd.yaml
2025-10-01 10:58:08.000000000 +0200
+++ new/openSUSE-release-tools-20251014.2d75aced/gocd/sles.target.gocd.yaml
2025-10-14 17:33:14.000000000 +0200
@@ -38,7 +38,7 @@
tasks:
- script: |-
set -e
- for image in agama-installer-SLES kiwi-templates-Minimal; do
+ for image in agama-installer-SLES kiwi-templates-Minimal
SLES_transactional; do
osc -A https://api.suse.de release
--target-project=SUSE:SLFO:Products:SLES:16.1:TEST --target-repository=images
-r images SUSE:SLFO:Products:SLES:16.1 $image
done
osc -A https://api.suse.de release
--target-project=SUSE:SLFO:Products:SLES:16.1:TEST --target-repository=product
-r product SUSE:SLFO:Products:SLES:16.1 000productcompose
@@ -83,7 +83,7 @@
tasks:
- script: |-
set -e
- for image in agama-installer-SLES kiwi-templates-Minimal; do
+ for image in agama-installer-SLES kiwi-templates-Minimal
SLES_transactional; do
osc -A https://api.suse.de release
--target-project=SUSE:SLFO:Products:SLES:16.1:PUBLISH
--target-repository=images -r images SUSE:SLFO:Products:SLES:16.1:TEST $image
done
osc -A https://api.suse.de release
--target-project=SUSE:SLFO:Products:SLES:16.1:PUBLISH
--target-repository=product -r product SUSE:SLFO:Products:SLES:16.1:TEST
000productcompose
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20251001.0ed4bc8b/ttm/README.md
new/openSUSE-release-tools-20251014.2d75aced/ttm/README.md
--- old/openSUSE-release-tools-20251001.0ed4bc8b/ttm/README.md 2025-10-01
10:58:08.000000000 +0200
+++ new/openSUSE-release-tools-20251014.2d75aced/ttm/README.md 2025-10-14
17:33:14.000000000 +0200
@@ -8,68 +8,133 @@
Both stages can run independently, but they communicate using the
`ToTestManagerStatus` attribute to avoid races, like releasing a product while
it's being published or overwriting a product while it's being tested. The
releaser publish disables the test subproject before releasing a product.
-Products
+Project Configuration
--------
-
-There are various kinds of product types:
-
-* main: Built in the main project in the `product_repo` for the given
architectures and released into the `product_repo` in the test subproject.
Optionally, it uses the OBS `set_release` option to set the build number of the
products on release. On publish, the `product_repo` in the test subproject is
publish enabled.
-* livecd: Like main, but uses the `:Live` subproject's `livecd_repo` as source.
-* ftp: Like main, but does not use `set_release`.
-* image: Like main, but released into the `totest_images_repo` in the test
subproject. On publishing, the `totest_images_repo` is publish enabled.
-* container: Like main, but released into the `totest_container_repo` instead,
which is always publish enabled so that they can be fetched from the OBS
registry during testing. For publishing, those products are released into the
releasetarget of `totest_container_repo`. This is best combined with a
`kind="maintenance_release"` as the target project, to keep older builds
instead of overwriting them. For long-living projects, `container-cleaner.py`
is run which deletes older images.
-* containerfile: Like container, but taken from a repo called `containerfile`
instead, where container images are built using `Dockerfile` recipes.
-
-Every product can have multiple architectures defined, those are only used to
check for build success before doing a release. If any of the listed
architectures failed to build, a release is blocked.
-
-OBS does not allow to release a multibuild container without all of its
flavors, so mentioning a multibuild container itself can be used instead of
listing all flavors explicitly. In that case, there is no check for build
success for the individual flavors, unless they are listed in addition.
-
-There is a check to ensure that every successful build in the `product_repo`
maps to a product. If this is not the case, an error is printed and the release
is blocked.
-
-set_release
------------
-
-The `set_release` mechanism can be enabled for main, livecd and image
products, where their build number is overwritten by `snapshot_number_prefix` +
a number. If `take_source_from_product` is set, that number is taken from the
first main product (`product_repo` + `product_arch`) or if that doesn't exist,
the first image product (first arch). Otherwise, the
`000release-packages:(base)-release` package from the main project's `standard`
repo and `arch` is looked at.
-
-Configuration
--------------
-
The configuration is stored in the `ToTestManagerConfig` attribute in YAML
format.
+Available options and their defaults are:
+
```
base: openSUSE # Defaults to the toplevel project name
test_subproject: ToTest
+test_project: <project>:ToTest # Defaults to <project>:<test_subproject>
do_not_release: False # If set, publishing is a noop (and the releaser doesn't
publish disable!)
-need_same_build_number: False # See set_release above
-set_snapshot_number: False # See set_release above
-snapshot_number_prefix: Snapshot # See set_release above
-take_source_from_product: False # See set_release above
-arch: x86_64 # See set_release above
+set_snapshot_number: False
+snapshot_number_prefix: Snapshot
+take_source_from_product: False
+arch: x86_64
+
+# openQA settings
+openqa_server: None # URL to the openQA server, e.g.
"https://openqa.opensuse.org"
+openqa_group: None # Name of the job group in openQA, e.g. "openSUSE
Tumbleweed"
jobs_num: 42 # Minimum number of openQA jobs before publishing is possible
+# Global defaults for products
product_repo: images
-product_arch: local # See set_release above
+product_arch: local
livecd_repo: images
totest_container_repo: containers
-totest_images_repo: images # Repo for image_products. If not set, uses
product_repo.
+totest_images_repo: images # Default: Same as product_repo.
+
+products:
+ (see below)
+```
+
+Product Configuration
+--------
+
+Every ttm managed project has a list of products which are defined by
following attributes:
+* `package`: The package name, optionally with multibuild flavor, e.g.
"opensuse-tumbleweed-image:docker"
+* `archs`: List of architectures the product is built for. Default:
`[product_arch]`
+* `build_prj`: The project the package is built in. Default: main project
+* `build_repo`: The repository the package is built in. Default: `product_repo`
+* `needs_to_contain_product_version`: If true, the *.report binary needs to
contain the product version in its name. Default: false.
+* `max_size`: If set, maximum allowed size of the *.iso binary in bytes.
Default: None.
+* `release_prj`: The project the product is released into. Default:
`test_project` resp. defined by `test_subproject`.
+* `release_repo`: The repository the product is released into. Default:
`product_repo`
+* `release_set_version`: If true, the "setrelease" mechanism (see below) is
used.
+* `publish_using_release`: Defines the publishing method:
+ True: The package is released from release_prj/release_repo according
+ to the releasetarget in the prj meta.
+ False: release_prj/release_repo is publish disabled on release and publish
enabled on publish.
+
+To allow a simpler configuration for most common product types and backwards
compatibility, there are various kinds of pre-defined product types:
+
+* main: Built in the main project in the `product_repo` for the given
architectures and released into the `product_repo` in the test subproject.
Optionally, it uses the OBS `set_release` option to set the build number of the
products on release. On publish, the `product_repo` in the test subproject is
publish enabled.
+* livecd: Like main, but uses the `:Live` subproject's `livecd_repo` as source.
+* ftp: Like main, but does not use `set_release`.
+* image: Like main, but released into the `totest_images_repo` in the test
subproject. On publishing, the `totest_images_repo` is publish enabled.
+* container: Like main, but released into the `totest_container_repo` instead,
which is always publish enabled so that they can be fetched from the OBS
registry during testing. For publishing, those products are released into the
releasetarget of `totest_container_repo`. This is best combined with a
`kind="maintenance_release"` as the target project, to keep older builds
instead of overwriting them. For long-living projects, `container-cleaner.py`
is run which deletes older images.
+* containerfile: Like container, but taken from a repo called `containerfile`
instead, where container images are built using `Dockerfile` recipes.
+
+The following product definitions are equivalent:
+
+```
+totest_images_repo: appliances
products:
main:
- foo:dvd
- livecd:
- - livecd-foo:
- - x86_64
ftp:
- foo:ftp
+ livecds:
+ - livecd-foo:
+ - x86_64
image:
- foo:kvm:
- x86_64
- - foo-container-image:lxc:
- - x86_64
container:
- foo-container-image:docker:
- x86_64
containerfile:
- some-dockerfile:
- x86_64
+---
+products:
+ custom:
+ # Implicit defaults for each custom product:
+ # archs: [local]
+ # build_prj: openSUSE:Factory
+ # build_repo: images
+ # needs_to_contain_product_version: false
+ # max_size: None
+ # release_prj: openSUSE:Factory:ToTest
+ # release_repo: images
+ # release_set_version: false
+ # publish_using_release: false
+ foo:dvd:
+ max_size: 4700372992
+ release_set_version: true
+ foo:ftp:
+ needs_to_contain_product_version: true
+ livecd-foo:
+ archs: [x86_64]
+ release_set_version: true
+ build_prj: openSUSE:Factory:Live
+ foo:kvm:
+ archs: [x86_64]
+ release_set_version: true
+ release_repo: appliances
+ foo-container-image:docker:
+ archs: [x86_64]
+ release_repo: containers
+ publish_using_release: true
+ some-dockerfile:
+ archs: [x86_64]
+ build_repo: containerfile
+ release_repo: containers
+ publish_using_release: true
```
+
+Every product can have multiple architectures defined, those are only used to
check for build success before doing a release. If any of the listed
architectures failed to build, a release is blocked.
+
+OBS does not allow to release a multibuild container without all of its
flavors, so mentioning a multibuild container itself can be used instead of
listing all flavors explicitly. In that case, there is no check for build
success for the individual flavors, unless they are listed in addition.
+
+There is a check to ensure that every successful build in the `product_repo`
maps to a product. If this is not the case, an error is printed and the release
is blocked.
+
+set_release
+-----------
+
+The `set_release` mechanism can be enabled products, in which case their build
number is overwritten by `snapshot_number_prefix` + a number.
+If `take_source_from_product` is enabled, that number is taken from the first
"main" product, or if there is none, the first "custom" product.
+If `take_source_from_product` is disabled, the
`000release-packages:(base)-release` package from the main project's `standard`
repo and `arch` is looked at.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20251001.0ed4bc8b/ttm/manager.py
new/openSUSE-release-tools-20251014.2d75aced/ttm/manager.py
--- old/openSUSE-release-tools-20251001.0ed4bc8b/ttm/manager.py 2025-10-01
10:58:08.000000000 +0200
+++ new/openSUSE-release-tools-20251014.2d75aced/ttm/manager.py 2025-10-14
17:33:14.000000000 +0200
@@ -64,35 +64,20 @@
def current_qa_version(self):
return self.api.pseudometa_file_load(self.version_file('totest'))
- def iso_build_version(self, project, tree, repo=None, arch=None):
- for binary in self.binaries_of_product(project, tree, repo=repo,
arch=arch):
+ def build_version(self, project, tree, repo, arch):
+ for binary in self.binaries_of_product(project, tree, repo, arch):
result = re.match(
-
r'.*-(?:Build|Snapshot)([0-9.]+)(?:-Media.*\.iso|\.docker\.tar\.xz|\.tar\.xz|\.raw\.xz|\.appx)',
binary)
+
r'.*-(?:Build|Snapshot)([0-9.]+)(?:-Media.*\.iso|\.docker\.tar\.xz|\.tar\.xz|\.raw\.xz|\.appx|\.report)',
binary)
if result:
return result.group(1)
- raise NotFoundException(f"can't find {project} iso version")
-
- def productcompose_build_version(self, project, tree, repo=None,
arch=None):
- for binary in self.binaries_of_product(project, tree, repo=repo,
arch=arch):
- result = re.match(
- r'.*-(?:Build|Snapshot)([0-9.]+)(.report)', binary)
- if result:
- return result.group(1)
- raise NotFoundException(f"can't find {project} productcompose version")
+ raise NotFoundException(f"can't find version in
{project}/{tree}/{repo}/{arch}")
def version_from_totest_project(self):
- if len(self.project.main_products):
- return self.iso_build_version(self.project.test_project,
self.project.main_products[0])
-
- return self.iso_build_version(self.project.test_project,
self.project.image_products[0].package,
-
arch=self.project.image_products[0].archs[0])
-
- def binaries_of_product(self, project, product, repo=None, arch=None):
- if repo is None:
- repo = self.project.product_repo
- if arch is None:
- arch = self.project.product_arch
+ first_product = self.project.products[0]
+ return self.build_version(first_product.release_prj,
first_product.package,
+ first_product.release_repo,
first_product.archs[0])
+ def binaries_of_product(self, project, product, repo, arch):
url = self.api.makeurl(['build', project, repo, arch, product])
try:
f = self.api.retried_GET(url)
@@ -106,13 +91,6 @@
return ret
- def ftp_build_version(self, project, tree):
- for binary in self.binaries_of_product(project, tree):
- result = re.match(r'.*-Build(.*)-Media1.report', binary)
- if result:
- return result.group(1)
- raise NotFoundException(f"can't find {project} ftp version")
-
# make sure to update the attribute as atomic as possible - as such
# only update the snapshot and don't erase anything else. The snapshots
# have very different update times within the pipeline, so there is
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20251001.0ed4bc8b/ttm/publisher.py
new/openSUSE-release-tools-20251014.2d75aced/ttm/publisher.py
--- old/openSUSE-release-tools-20251001.0ed4bc8b/ttm/publisher.py
2025-10-01 10:58:08.000000000 +0200
+++ new/openSUSE-release-tools-20251014.2d75aced/ttm/publisher.py
2025-10-14 17:33:14.000000000 +0200
@@ -220,14 +220,15 @@
if not force:
wait_time = 20
- while not self.all_repos_done(self.project.test_project):
- if self.dryrun:
- self.logger.info('{} is still not published, do not wait
as dryrun.'.format(
- self.project.test_project))
- return
- self.logger.info('{} is still not published, waiting {}
seconds'.format(
- self.project.test_project, wait_time))
- time.sleep(wait_time)
+ for prj in set([p.release_prj for p in self.project.products if
not p.publish_using_release]):
+ while not self.all_repos_done(prj):
+ if self.dryrun:
+ self.logger.info('{} is still not published, do not
wait as dryrun.'.format(
+ prj))
+ return
+ self.logger.info('{} is still not published, waiting {}
seconds'.format(
+ prj, wait_time))
+ time.sleep(wait_time)
current_snapshot = self.get_status('publishing')
if self.dryrun:
@@ -296,21 +297,15 @@
self.logger.info('Publish test project content')
if self.dryrun or self.project.do_not_release:
return
- if self.project.container_products or
self.project.containerfile_products:
- self.logger.info('Releasing container products from ToTest')
- for container in self.project.container_products +
self.project.containerfile_products:
- self.release_package(self.project.test_project,
container.package,
-
repository=self.project.totest_container_repo)
-
- self.api.switch_flag_in_prj(
- self.project.test_project, flag='publish', state='enable',
- repository=self.project.product_repo)
- if self.project.publish_multiple_product_repo and
len(self.project.product_repo_overrides):
- for key, value in self.project.product_repo_overrides.items():
- self.api.switch_flag_in_prj(self.project.test_project,
flag='publish',
- state='enable', repository=value)
-
- if self.project.totest_images_repo != self.project.product_repo:
- self.logger.info('Publish test project content (image_products)')
- self.api.switch_flag_in_prj(self.project.test_project,
flag='publish', state='enable',
-
repository=self.project.totest_images_repo)
+
+ prj_repo_to_publish_enable = set()
+
+ self.logger.info('Releasing container products from ToTest')
+ for p in self.project.products:
+ if p.publish_using_release:
+ self.release_package(p.release_prj, p.package,
repository=p.release_repo)
+ else:
+ prj_repo_to_publish_enable |= set([(p.release_prj,
p.release_repo)])
+
+ for prj, repo in prj_repo_to_publish_enable:
+ self.api.switch_flag_in_prj(prj, flag='publish', state='enable',
repository=repo)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20251001.0ed4bc8b/ttm/releaser.py
new/openSUSE-release-tools-20251014.2d75aced/ttm/releaser.py
--- old/openSUSE-release-tools-20251001.0ed4bc8b/ttm/releaser.py
2025-10-01 10:58:08.000000000 +0200
+++ new/openSUSE-release-tools-20251014.2d75aced/ttm/releaser.py
2025-10-14 17:33:14.000000000 +0200
@@ -9,6 +9,7 @@
# Distribute under GPLv2 or GPLv3
import re
+from collections import defaultdict
from lxml import etree as ET
from ttm.manager import ToTestManager, NotFoundException, QAResult
@@ -58,81 +59,51 @@
self.write_version_to_dashboard('totest', new_snapshot)
return QAResult.passed
- def release_version(self):
- url = self.api.makeurl(['build', self.project.name, 'standard',
self.project.arch,
-
f'000release-packages:{self.project.base}-release'])
- f = self.api.retried_GET(url)
- root = ET.parse(f).getroot()
- for binary in root.findall('binary'):
- binary = binary.get('filename', '')
+ def version_from_project(self):
+ if self.project.take_source_from_product:
+ first_product = self.project.products[0]
+ return self.build_version(first_product.build_prj,
first_product.package,
+ first_product.build_repo,
first_product.archs[0])
+
+ for binary in self.binaries_of_product(self.project.name,
+
f'000release-packages:{self.project.base}-release',
+ repo='standard',
arch=self.project.arch):
result = re.match(r'.*-([^-]*)-[^-]*.src.rpm', binary)
if result:
return result.group(1)
raise NotFoundException(f"can't find {self.project.name} version")
- def version_from_project(self):
- if not self.project.take_source_from_product:
- return self.release_version()
-
- if len(self.project.main_products):
- # 000productcompose has ftp built only and the build number
- # offline installer carry over build number from ftp product
- # as well as agama-installer
- if 'productcompose' in self.project.main_products[0] and\
- 'productcompose' in self.project.ftp_products[0]:
- return self.productcompose_build_version(self.project.name,
self.project.ftp_products[0],
-
repo=self.project.product_repo_overrides.get(self.project.ftp_products[0],
-
self.project.product_repo))
- return self.iso_build_version(self.project.name,
self.project.main_products[0])
-
- return self.iso_build_version(self.project.name,
self.project.image_products[0].package,
-
arch=self.project.image_products[0].archs[0])
-
- def maxsize_for_package(self, package, arch):
- if re.match(r'.*[-_]mini[-_].*', package):
- return 737280000 # a CD needs to match
-
- if re.match(r'.*[-_]dvd5[-_].*', package):
- return 4700372992 # a DVD needs to match
-
- if re.match(r'.*[-_](dvd9[-_]dvd|cd[-_]DVD)[-_].*', package):
- return 8539996159
+ def package_ok(self, prjresult, product, arch):
+ """Checks one product/arch in a project and returns True if it's
succeeded"""
- # Other types don't have a fixed size limit
- return None
-
- def package_ok(self, prjresult, project, package, repository, arch):
- """Checks one package in a project and returns True if it's
succeeded"""
-
- status =
prjresult.xpath(f'result[@repository="{repository}"][@arch="{arch}"]/'
- f'status[@package="{package}"]')
+ status =
prjresult.xpath(f'result[@repository="{product.build_repo}"][@arch="{arch}"]/'
+ f'status[@package="{product.package}"]')
failed = [s for s in status if s.get('code') != 'succeeded']
if len(failed):
self.logger.info(
- f"{project} {package} {repository} {arch} ->
{failed[0].get('code')}")
+ f"{product.build_prj} {product.package} {product.build_repo}
{arch} -> {failed[0].get('code')}")
return False
succeeded = [s for s in status if s.get('code') == 'succeeded']
if not len(succeeded):
- self.logger.info(f'No "succeeded" for {project} {package}
{repository} {arch}')
+ self.logger.info(f'No "succeeded" for {product.build_prj}
{product.package} {product.build_repo} {arch}')
return False
- maxsize = self.maxsize_for_package(package, arch)
- if not maxsize:
+ if product.max_size is None:
return True
- url = self.api.makeurl(['build', project, repository, arch, package])
+ url = self.api.makeurl(['build', product.build_prj,
product.build_repo, arch, product.package])
f = self.api.retried_GET(url)
root = ET.parse(f).getroot()
for binary in root.findall('binary'):
if not binary.get('filename', '').endswith('.iso'):
continue
isosize = int(binary.get('size', 0))
- if isosize > maxsize:
+ if isosize > product.max_size:
self.logger.error('%s %s %s %s: %s' % (
- project, package, repository, arch, 'too large by %s
bytes' % (isosize - maxsize)))
+ product.build_prj, product.package, product.build_repo,
arch, 'too large by %s bytes' % (isosize - product.max_size)))
return False
return True
@@ -140,29 +111,19 @@
def all_built_products_in_config(self):
"""Verify that all succeeded products are mentioned in the ttm
config"""
- # First for all products in product_repo
- products = {}
- for simple_product in self.project.ftp_products +
self.project.main_products:
- products[simple_product] = [self.project.product_arch]
- for image_product in self.project.image_products +
self.project.container_products:
- products[image_product.package] = image_product.archs
-
- all_found =
self.verify_package_list_complete(self.project.product_repo, products)
- if len(self.project.product_repo_overrides):
- for key, value in self.project.product_repo_overrides.items():
- all_found = self.verify_package_list_complete(value, products)
and all_found
-
- # Then for containerfile_products
- if self.project.containerfile_products:
- products = {}
- for image_product in self.project.containerfile_products:
- products[image_product.package] = image_product.archs
+ # Dict of products per prj/repo, e.g.
+ # {('openSUSE:Leap:15.6:Images', 'images'): {'livecd-leap-gnome':
['aarch64', 'x86_64'], ...}}
+ products_for_prj_repo = defaultdict(dict)
+ for p in self.project.products:
+ products_for_prj_repo[(p.build_prj, p.build_repo)][p.package] =
p.archs
- all_found = self.verify_package_list_complete('containerfile',
products) and all_found
+ all_found = True
+ for (prj, repo), products in products_for_prj_repo.items():
+ all_found = self.verify_package_list_complete(prj, repo, products)
and all_found
return all_found
- def verify_package_list_complete(self, repository, product_archs):
+ def verify_package_list_complete(self, project, repository, product_archs):
"""Loop through all successfully built products and check whether they
are part of product_archs (e.g. {'foo:ftp': ['local'], some-image':
['x86_64'], ...})"""
@@ -170,7 +131,7 @@
all_found = True
# Get all results for the product repo from OBS
- url = self.api.makeurl(['build', self.project.name, "_result"],
+ url = self.api.makeurl(['build', project, "_result"],
{'repository': repository,
'multibuild': 1})
f = self.api.retried_GET(url)
@@ -204,49 +165,26 @@
return all_found
def is_snapshotable(self):
- """Check various conditions required for factory to be snapshotable
-
- """
-
- if not self.all_repos_done(self.project.name):
- return False
+ """Check various conditions required for factory to be snapshotable"""
all_ok = True
- resultxml = self.api.retried_GET(self.api.makeurl(['build',
self.project.name, '_result']))
- prjresult = ET.parse(resultxml).getroot()
-
- for product in self.project.ftp_products + self.project.main_products:
- if not self.package_ok(prjresult, self.project.name, product,
-
self.project.product_repo_overrides.get(product, self.project.product_repo),
- self.project.product_arch):
+ # Collect a list of projects to check
+ projects = set([p.build_prj for p in self.project.products])
+ for prj in projects:
+ if not self.all_repos_done(prj):
all_ok = False
+ continue
- # agama-installer in Leap uses images repo as source repo as well as
target repo
- source_repo = self.project.product_repo
- if self.project.same_target_images_repo_for_source_repo:
- source_repo = self.project.totest_images_repo
- for product in self.project.image_products +
self.project.container_products:
- for arch in product.archs:
- if not self.package_ok(prjresult, self.project.name,
product.package, source_repo, arch):
- all_ok = False
-
- for product in self.project.containerfile_products:
- for arch in product.archs:
- if not self.package_ok(prjresult, self.project.name,
product.package, 'containerfile', arch):
- all_ok = False
-
- if len(self.project.livecd_products):
- liveprjname = f'{self.project.name}:Live'
- if not self.all_repos_done(liveprjname):
- return False
+ resultxml = self.api.retried_GET(self.api.makeurl(['build', prj,
'_result']))
+ prjresult = ET.parse(resultxml).getroot()
+
+ for product in self.project.products:
+ if product.build_prj != prj:
+ continue
- liveresultxml = self.api.retried_GET(self.api.makeurl(['build',
liveprjname, '_result']))
- liveprjresult = ET.parse(liveresultxml).getroot()
- for product in self.project.livecd_products:
for arch in product.archs:
- if not self.package_ok(liveprjresult, liveprjname,
product.package,
- self.project.product_repo, arch):
+ if not self.package_ok(prjresult, product, arch):
all_ok = False
if not all_ok:
@@ -256,74 +194,15 @@
# the product version already.
product_version = self.get_product_version()
if product_version is not None:
- for product in self.project.ftp_products:
- for binary in self.binaries_of_product(self.project.name,
product,
-
repo=self.project.product_repo_overrides.get(
- product,
self.project.product_repo)):
- # The NonOSS tree doesn't include the version...
- if binary.endswith('.report') and 'NonOss' not in binary
and product_version not in binary:
+ for product in [p for p in self.project.products if
p.needs_to_contain_product_version]:
+ for binary in self.binaries_of_product(product.build_prj,
product.package,
+
repo=product.build_repo, arch=product.archs[0]):
+ if binary.endswith('.report') and product_version not in
binary:
self.logger.debug(f'{binary} in {product} does not
include {product_version}')
return False
- if self.project.need_same_build_number:
- # make sure all medias have the same build number
- builds = set()
- for p in self.project.ftp_products:
- if 'Addon-NonOss' in p:
- # XXX: don't care about nonoss atm.
- continue
- builds.add(self.ftp_build_version(self.project.name, p))
- for p in self.project.main_products:
- builds.add(self.iso_build_version(self.project.name, p))
- for p in self.project.livecd_products +
self.project.image_products:
- for arch in p.archs:
- builds.add(self.iso_build_version(self.project.name,
p.package,
- arch=arch))
- if len(builds) != 1:
- self.logger.debug('not all medias have the same build number')
- return False
-
return True
- def _release(self, set_release=None):
- for container in self.project.container_products:
- # Containers are built in the same repo as other image products,
- # but released into a different repo in :ToTest
- self.release_package(self.project.name, container.package,
repository=self.project.product_repo,
- target_project=self.project.test_project,
-
target_repository=self.project.totest_container_repo)
-
- for container in self.project.containerfile_products:
- # Dockerfile builds are done in a separate repo, but released into
the same location
- # as container_products
- self.release_package(self.project.name, container.package,
repository='containerfile',
- target_project=self.project.test_project,
-
target_repository=self.project.totest_container_repo)
-
- if len(self.project.main_products):
- for product in self.project.ftp_products:
- self.release_package(self.project.name, product,
-
repository=self.project.product_repo_overrides.get(
- product, self.project.product_repo))
-
- for cd in self.project.main_products:
- self.release_package(self.project.name, cd,
set_release=set_release,
- repository=self.project.product_repo)
-
- for cd in self.project.livecd_products:
- self.release_package('%s:Live' %
- self.project.name, cd.package,
set_release=set_release,
- repository=self.project.livecd_repo)
-
- for image in self.project.image_products:
- source_repo = self.project.product_repo
- if self.project.same_target_images_repo_for_source_repo:
- source_repo = self.project.totest_images_repo
- self.release_package(self.project.name, image.package,
set_release=set_release,
- repository=source_repo,
- target_project=self.project.test_project,
-
target_repository=self.project.totest_images_repo)
-
def update_totest(self, snapshot=None):
# omit snapshot, we don't want to rename on release
if not self.project.set_snapshot_number:
@@ -334,11 +213,14 @@
else:
release = None
if not (self.dryrun or self.project.do_not_release):
- self.api.switch_flag_in_prj(self.project.test_project,
flag='publish', state='disable',
- repository=self.project.product_repo)
-
- if self.project.totest_images_repo != self.project.product_repo:
- self.api.switch_flag_in_prj(self.project.test_project,
flag='publish', state='disable',
-
repository=self.project.totest_images_repo)
-
- self._release(set_release=release)
+ prj_repo_to_publish_disable = \
+ set([(p.release_prj, p.release_repo) for p in
self.project.products if not p.publish_using_release])
+ for prj, repo in prj_repo_to_publish_disable:
+ self.api.switch_flag_in_prj(prj, flag='publish',
state='disable', repository=repo)
+
+ for p in self.project.products:
+ self.release_package(p.build_prj, p.package,
+ set_release=release if p.release_set_version
else None,
+ repository=p.build_repo,
+ target_project=p.release_prj,
+ target_repository=p.release_repo)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20251001.0ed4bc8b/ttm/totest.py
new/openSUSE-release-tools-20251014.2d75aced/ttm/totest.py
--- old/openSUSE-release-tools-20251001.0ed4bc8b/ttm/totest.py 2025-10-01
10:58:08.000000000 +0200
+++ new/openSUSE-release-tools-20251014.2d75aced/ttm/totest.py 2025-10-14
17:33:14.000000000 +0200
@@ -8,15 +8,120 @@
# (C) 2018 [email protected], openSUSE.org
# Distribute under GPLv2 or GPLv3
+import dataclasses
import yaml
import re
from osclib.core import attribute_value_load
+from typing import List, Optional
-class ImageProduct(object):
- def __init__(self, package, archs):
- self.package = package
- self.archs = archs
[email protected]
+class Product(object):
+ """Attributes documented in README.md"""
+ package: str
+ archs: List[str] # [totest.product_arch]
+ build_prj: str # Main prj, e.g. openSUSE:Factory
+ build_repo: str # totest.product_repo
+ needs_to_contain_product_version: bool # False
+ max_size: Optional[int] # In B, e.g. 737280000 for CDs
+ release_prj: str # totest.test_project
+ release_repo: str # totest.product_repo
+ release_set_version: bool # False
+ publish_using_release: bool # False
+
+ @staticmethod
+ def custom_product(totest, name, **options):
+ # Start with defaults
+ p = Product(package=name, archs=[totest.product_arch],
+ build_prj=totest.name,
+ build_repo=totest.product_repo,
+ needs_to_contain_product_version=False,
+ max_size=None,
+ release_prj=totest.test_project,
+ release_repo=totest.product_repo,
+ release_set_version=False,
+ publish_using_release=False)
+
+ # Override options
+ return Product(**{**dataclasses.asdict(p), **options})
+
+ @staticmethod
+ def max_size_default(package):
+ """Used by ftp_product and main_product. livecd size is checked
+ during build."""
+ if re.match(r'.*[-_]mini[-_].*', package):
+ return 737280000 # a CD needs to match
+
+ if re.match(r'.*[-_]dvd5[-_].*', package):
+ return 4700372992 # a DVD needs to match
+
+ if re.match(r'.*[-_](dvd9[-_]dvd|cd[-_]DVD)[-_].*', package):
+ return 8539996159
+
+ # Other types don't have a fixed size limit
+ return None
+
+ @staticmethod
+ def ftp_product(totest, package):
+ """ FTP Repo """
+ build_repo = totest.product_repo
+ extract_product = re.search(r"(.+)/product_repo:(.+)", package)
+ if extract_product:
+ package = extract_product.group(1)
+ build_repo = extract_product.group(2)
+
+ return Product.custom_product(totest, package,
+ build_repo=build_repo,
+ needs_to_contain_product_version=True)
+
+ @staticmethod
+ def main_product(totest, package):
+ """ Installation DVD """
+ return Product.custom_product(totest, package,
+
max_size=Product.max_size_default(package),
+ release_set_version=True)
+
+ @staticmethod
+ def livecd_product(totest, package, archs):
+ """ LiveCD products are like main products, but built in a different
+ project. """
+ p = Product.main_product(totest, package)
+ p.archs = archs
+ p.build_prj = f'{totest.name}:Live'
+ return p
+
+ @staticmethod
+ def image_product(totest, package, archs):
+ if totest.same_target_images_repo_for_source_repo:
+ build_repo = totest.totest_images_repo
+ else:
+ build_repo = totest.product_repo
+
+ release_repo = totest.totest_images_repo
+ if release_repo is None:
+ release_repo = totest.product_repo
+
+ return Product.custom_product(totest, package,
+ archs=archs,
+ build_repo=build_repo,
+ release_repo=release_repo,
+ release_set_version=True)
+
+ @staticmethod
+ def container_product(totest, package, archs):
+ """ Containers are built in the same repo as other image products,
+ but released into a different repo in :ToTest """
+ return Product.custom_product(totest, package,
+ archs=archs,
+
release_repo=totest.totest_container_repo,
+ publish_using_release=True)
+
+ @staticmethod
+ def containerfile_product(totest, package, archs):
+ """ Containerfile builds are like containers but built in a separate
repo"""
+ p = Product.container_product(totest, package, archs)
+ p.build_repo = 'containerfile'
+ return p
class ToTest(object):
@@ -28,76 +133,72 @@
# set the defaults
self.do_not_release = False
- self.need_same_build_number = False
self.set_snapshot_number = False
self.snapshot_number_prefix = "Snapshot"
self.take_source_from_product = False
- self.same_target_images_repo_for_source_repo = False
self.arch = 'x86_64'
+ self.openqa_group = None
self.openqa_server = None
+ self.jobs_num = 42
+ self.test_subproject = 'ToTest'
+ self.base = project.split(':')[0]
+ self.products = []
+ # Defaults for products
self.product_repo = 'images'
self.product_arch = 'local'
+ self.test_project = None
+
+ # Defaults for fixed product types
self.livecd_repo = 'images'
self.totest_container_repo = 'containers'
- # Repo for image_products. If not set, uses product_repo.
+ self.same_target_images_repo_for_source_repo = False
self.totest_images_repo = None
- self.main_products = []
- self.ftp_products = []
- self.container_products = []
- self.containerfile_products = []
- self.livecd_products = []
- self.image_products = []
- self.product_repo_overrides = {}
- # publish the default product_repo, ignore product_repo_overrides
- self.publish_multiple_product_repo = False
+ self.load_config(apiurl)
- self.test_subproject = 'ToTest'
- self.base = project.split(':')[0]
+ def parse_products(self, products, factory):
+ parsed = []
+ for package in products:
+ for key, value in package.items():
+ parsed.append(factory(self, key, value))
- self.jobs_num = 42
- self.load_config(apiurl)
- if not hasattr(self, 'test_project'):
- self.test_project = f'{project}:{self.test_subproject}'
+ return parsed
def load_config(self, apiurl):
- config = yaml.safe_load(attribute_value_load(apiurl, self.name,
'ToTestManagerConfig'))
+ config_yaml = attribute_value_load(apiurl, self.name,
'ToTestManagerConfig')
+ if not config_yaml:
+ raise Exception('Failed to read ToTestManagerConfig')
+
+ config = yaml.safe_load(config_yaml)
+ raw_products = {}
for key, value in config.items():
if key == 'products':
- self.set_products(value)
- else:
+ raw_products = value
+ elif hasattr(self, key):
setattr(self, key, value)
+ else:
+ raise Exception(f'Unknown config option {key}={value}')
- # Set default for totest_images_repo
+ # Some config migrations
if self.totest_images_repo is None:
self.totest_images_repo = self.product_repo
- # do allow to override repository for ftp product
- ftp_products_copy = self.ftp_products.copy()
- for product in ftp_products_copy:
- extract_product = re.search(r"(.+)/product_repo:(.+)", product)
- if extract_product:
- self.ftp_products.remove(product)
- self.ftp_products.append(extract_product.group(1))
- self.product_repo_overrides[extract_product.group(1)] =
extract_product.group(2)
-
- def parse_images(self, products):
- parsed = []
- for package in products:
- # there is only one
- for key, value in package.items():
- parsed.append(ImageProduct(key, value))
-
- return parsed
+ if self.test_project is None:
+ self.test_project = f'{self.name}:{self.test_subproject}'
- def set_products(self, products):
- # plain arrays
- setattr(self, 'main_products', products.get('main', []))
- setattr(self, 'ftp_products', products.get('ftp', []))
-
- # image products
- setattr(self, 'livecd_products',
self.parse_images(products.get('livecds', [])))
- setattr(self, 'image_products',
self.parse_images(products.get('images', [])))
- setattr(self, 'container_products',
self.parse_images(products.get('container', [])))
- setattr(self, 'containerfile_products',
self.parse_images(products.get('containerfile', [])))
+ # Fill self.products. Order is important: The first product is used for
+ # getting the setrelease number for take_source_from_product == True.
+ for mp in raw_products.get('main', []):
+ self.products += [Product.main_product(self, mp)]
+
+ for key, value in raw_products.get('custom', {}).items():
+ self.products += [Product.custom_product(self, key, **value)]
+
+ for mp in raw_products.get('ftp', []):
+ self.products += [Product.ftp_product(self, mp)]
+
+ self.products += self.parse_products(raw_products.get('livecds', []),
Product.livecd_product)
+ self.products += self.parse_products(raw_products.get('images', []),
Product.image_product)
+ self.products += self.parse_products(raw_products.get('container',
[]), Product.container_product)
+ self.products += self.parse_products(raw_products.get('containerfile',
[]), Product.containerfile_product)
++++++ openSUSE-release-tools.obsinfo ++++++
--- /var/tmp/diff_new_pack.E5bS0S/_old 2025-10-22 12:20:53.443113869 +0200
+++ /var/tmp/diff_new_pack.E5bS0S/_new 2025-10-22 12:20:53.455114374 +0200
@@ -1,5 +1,5 @@
name: openSUSE-release-tools
-version: 20251001.0ed4bc8b
-mtime: 1759309088
-commit: 0ed4bc8bcafea4303ea5d5fef6d960d3e3f95155
+version: 20251014.2d75aced
+mtime: 1760455994
+commit: 2d75aced96a43eafd9d1a411e7d2f06501a2575c