Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package obs-service-go_modules for
openSUSE:Factory checked in at 2022-06-12 17:41:37
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/obs-service-go_modules (Old)
and /work/SRC/openSUSE:Factory/.obs-service-go_modules.new.1548 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "obs-service-go_modules"
Sun Jun 12 17:41:37 2022 rev:4 rq:982163 version:0.5.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/obs-service-go_modules/obs-service-go_modules.changes
2022-05-03 21:19:04.849006889 +0200
+++
/work/SRC/openSUSE:Factory/.obs-service-go_modules.new.1548/obs-service-go_modules.changes
2022-06-12 17:43:21.526503593 +0200
@@ -1,0 +2,13 @@
+Sat Jun 11 01:56:21 UTC 2022 - [email protected]
+
+- Update to version 0.5.0:
+ * README update
+ * Check go mod subcommand return code, log and exit on error
+ * Log go.mod file not found as error not info
+ * Execute go mod subcommands using subprocess.run()
+ * Rework the service to better work with obs_scm
+- Add Require: python3-libarchive-c
+- Drop Require: tar
+- Drop Require: gzip
+
+-------------------------------------------------------------------
Old:
----
obs-service-go_modules-0.4.1.tar.gz
New:
----
obs-service-go_modules-0.5.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ obs-service-go_modules.spec ++++++
--- /var/tmp/diff_new_pack.VELZlk/_old 2022-06-12 17:43:21.906504138 +0200
+++ /var/tmp/diff_new_pack.VELZlk/_new 2022-06-12 17:43:21.910504144 +0200
@@ -37,7 +37,7 @@
%define use_test test
%endif
Name: obs-service-%{service}
-Version: 0.4.1
+Version: 0.5.0
Release: 0
Summary: An OBS source service: Download, verify and vendor Go module
dependencies
License: GPL-2.0-or-later
@@ -46,8 +46,7 @@
Source: %{name}-%{version}.tar.gz
BuildRequires: go-md2man
Requires: go >= 1.11
-Requires: gzip
-Requires: tar
+Requires: python3-libarchive-c
BuildArch: noarch
%if %{with needs_external_argparse}
BuildRequires: %{use_python}-argparse
++++++ _service ++++++
--- /var/tmp/diff_new_pack.VELZlk/_old 2022-06-12 17:43:21.942504190 +0200
+++ /var/tmp/diff_new_pack.VELZlk/_new 2022-06-12 17:43:21.946504196 +0200
@@ -3,7 +3,7 @@
<param
name="url">https://github.com/openSUSE/obs-service-go_modules</param>
<param name="scm">git</param>
<param name="exclude">.git</param>
- <param name="revision">v0.4.1</param>
+ <param name="revision">v0.5.0</param>
<param name="versionformat">@PARENT_TAG@</param>
<param name="changesgenerate">enable</param>
<param name="versionrewrite-pattern">v(.*)</param>
++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.VELZlk/_old 2022-06-12 17:43:21.962504219 +0200
+++ /var/tmp/diff_new_pack.VELZlk/_new 2022-06-12 17:43:21.966504224 +0200
@@ -1,6 +1,6 @@
<servicedata>
<service name="tar_scm">
<param
name="url">https://github.com/openSUSE/obs-service-go_modules</param>
- <param
name="changesrevision">2c5088e247519c74c025cfb6ed16a56dd7b4a5c2</param></service></servicedata>
+ <param
name="changesrevision">3ee3fd053c57ab951c7347243e6f4d017c36c7ee</param></service></servicedata>
(No newline at EOF)
++++++ obs-service-go_modules-0.4.1.tar.gz ->
obs-service-go_modules-0.5.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/obs-service-go_modules-0.4.1/README.md
new/obs-service-go_modules-0.5.0/README.md
--- old/obs-service-go_modules-0.4.1/README.md 2022-05-02 10:34:44.000000000
+0200
+++ new/obs-service-go_modules-0.5.0/README.md 2022-06-11 02:15:31.000000000
+0200
@@ -1,5 +1,24 @@
# OBS Source Service `obs-service-go_modules`
+## Contents
+
+- [Overview](#overview)
+- [Usage for packagers](#usage-for-packagers)
+- [Compression format support](#compression-format-support)
+- [OBS Source Service Build Mode
support](#obs-source-service-build-mode-support)
+- [Building Go applications with vendored dependency
modules](#building-go-applications-with-vendored-dependency-modules)
+- [Example](#example)
+- [Example `_service` configuration](#example-_service-configuration)
+- [Transition note](#transition-note)
+- [openSUSE RPM packages built using
`obs-service-go_modules`](#opensuse-rpm-packages-built-using-obs-service-go_modules)
+- [Dependencies](#dependencies)
+- [FAQ](#faq)
+- [Support](#support)
+- [Contributing](#contributing)
+- [License](#license)
+
+## Overview
+
This is the git repository for
[`devel:languages:go/obs-service-go_modules`](https://build.opensuse.org/package/show/devel:languages:go/obs-service-go_modules),
an [Open Build Service (OBS)](https://build.opensuse.org)
@@ -18,21 +37,21 @@
go mod vendor
```
-`obs-service-go_modules` will create a `vendor.tar[.<tar compression>]` archive
-containing the `vendor/` directory populated by `go mod vendor`. The archive
-is generated in the rpm package directory, and can be committed to
+`obs-service-go_modules` will create a `vendor.tar.gz` archive or other
supported compression type
+containing the `vendor/` directory populated by `go mod vendor`.
+The archive is generated in the rpm package directory, and can be committed to
[OBS](https://build.opensuse.org) to facilitate offline Go application package
builds
for [openSUSE](https://www.opensuse.org),
[SUSE](https://www.suse.com), and numerous other distributions.
## Usage for packagers
-Presently it is assumed the Go application is distributed as a tarball named
-`app-0.1.0.tar[.<tar compression>]`, unpacking to `app-0.1.0/`.
-The `<tar compression>` extension can be specified using the `compression`
parameter,
-and defaults to `gz`.
-`obs-service-go_modules` will autodetect tarball archives of the form
`app-0.1.0.tar[.<tar compression>]`,
-where the RPM packaging uses spec file `app.spec`.
+Presently it is assumed the Go application source is distributed as a
compressed tarball named
+`app-0.1.0.tar.gz` or other supported compression type, unpacking to
`app-0.1.0/`.
+The compression type can be specified using the `compression` parameter,
+and defaults to `gz` (gzip).
+`obs-service-go_modules` will autodetect tarball archives of the form
`app-0.1.0.tar.gz`,
+where the RPM packaging uses spec file `app.spec` sharing the base name `app`.
Create a `_service` file containing:
@@ -53,6 +72,65 @@
See [Example](#example) below for typical output with a complete `_service`
file.
+## Compression format support
+
+`obs-service-go_modules` reads and writes compressed tar archives
+using [`libarchive`](https://libarchive.org/) via the Python3 `ctypes` wrapper
+[`python3-libarchive-c`](https://github.com/Changaco/python-libarchive-c).
+While `libarchive` supports numerous compression formats,
+`obs-service-go_modules` usage recommends limiting selections to
+`gz` (default), `xz` and `zstd`.
+Tables of representative compression method relative sizes and timings
+with Go vendored dependency sources (`vendor/`) are shown below.
+
+### Compression sizes with Go dependency sources
+
+| project | version | uncompressed | gz | xz | zstd |
+| ---------- | -------- | ------------ | --- | ---- | ---- |
+| hugo | v0.100.2 | 75M | 11M | 7.5M | 8.3M |
+| kubernetes | v1.24.1 | 129M | 17M | 12M | 14M |
+
+### Compression timing with Go dependency sources
+
+| project | version | gz | xz | zstd |
+| ---------- | -------- | ---- | ----- | ---- |
+| hugo | v0.100.2 | 1.8s | 6.3s | 0.3s |
+| kubernetes | v1.24.1 | 2.9s | 10.9s | 0.4s |
+
+The above are an average of five runs on Intel i7-6820HQ CPU.
+The `zstd` format has a clear advantage in speed with reasonable compression
ratio,
+and is likely to become the default compression method in a future release.
+Decompression timings are closely matched among `gz`, `xz`, and `zstd`
compression methods.
+
+## OBS Source Service Build Mode support
+
+OBS Source Services can run in one of several modes as shown in
+[OBS Documentation: Using Source Services: Modes of
Services](https://openbuildservice.org/help/manuals/obs-user-guide/cha.obs.source_service.html#sec.obs.sserv.mode).
+
+Currently the recommended mode is `disabled` (aliased as `manual`) which
implies:
+
+- The service is run locally via explicit CLI call `osc service disabledrun`
+
+- `obs-service-go_modules` and its dependencies
+ `python3`, `libarchive` and `python3-libarchive-c`
+ are installed on the local machine.
+ In particular, `python3-libarchive-c` packages
+ are not available in all SUSE repositories at this time.
+
+- The resulting `vendor.tar.gz` or other supported compression type
+ should be committed and referenced as an RPM `Source:`.
+
+- `Source:` file names are as given in the RPM `.spec`, no `_service:` prefix
is applied.
+
+If and when `obs-service-go_modules` is available on
+[OBS](https://build.opensuse.org),
+additional modes including `Default` will be supported.
+`Default` runs server side after each commit
+and locally before every local build.
+This will enable tighter integration with the
+[tar_scm / obs_scm](https://github.com/openSUSE/obs-service-tar_scm) source
service,
+including uncommitted server-managed `.obscpio` source and vendor archives.
+
## Building Go applications with vendored dependency modules
Go commands support building with vendored dependencies,
@@ -156,7 +234,7 @@
## Transition note
Until such time as `obs-service-go_modules` is available on
-[OBS](https://build.opensuse.org), `vendor.tar[.<tar compression>]` should
+[OBS](https://build.opensuse.org), `vendor.tar.gz` should
be committed along with the Go application release tarball.
## openSUSE RPM packages built using `obs-service-go_modules`
@@ -167,13 +245,27 @@
- [mod](https://build.opensuse.org/package/show/devel:languages:go/mod)
- [mgit](https://build.opensuse.org/package/show/devel:languages:go/mgit)
+## Dependencies
+
+`obs-service-go_modules` requires:
+
+- `python3`
+- `python-libarchive-c` ctypes wrapper for the `libarchive` C library (added
in `v0.5.0`)
+
+The Python standard library supports only gzipped tar archives.
+The `libarchive` dependency was chosen to support additional compression types
+including `xz` and the `cpio_newc` used by `.obscpio` archives.
+Supported compression types are intentionally limited to
+`gz`, `xz`, `zstd` and `cpio_newc` to preserve future flexibility
+in the event eliminating the `python-libarchive-c` dependency is desirable.
+
## FAQ
-### Q: Does `vendor.tar[.<tar compression>]` need to be committed to OBS
package?
+### Q: Does `vendor.tar.gz` need to be committed to OBS package?
A: Currently yes.
As long as `obs-service-go_modules` is run locally via `osc service
disabledrun`,
-then `vendor.tar[.<tar compression>]` should be committed and referenced as an
additional `Source:`.
+then `vendor.tar.gz` should be committed and referenced as an additional
`Source:`.
If and when `obs-service-go_modules` is available on
[OBS](https://build.opensuse.org),
additional strategies should be possible such as a `vendor.cpio`
@@ -207,6 +299,24 @@
as well as provide protections against third-party service outages and
upstream Go modules being removed by the author.
+## Support
+
+`obs-service-go_modules` intends to be compatible with most upstream Go
projects that use best practice source layouts and module conventions.
+If you are packaging a Go application with an uncommon project layout or
nonstandard `go.mod` usage pattern that presents compatibility problems,
+please file an
[issue](https://github.com/openSUSE/obs-service-go_modules/issues) with
description and reference the specific upstream tag or commit.
+While it may not be possible to support every unique upstream layout,
+a maintainer will evaluate feasibility of adding support for that special case
or improve output messages to clearly indicate the error.
+
+## Contributing
+
+In keeping with [support](#support) objectives,
+feature ideas are welcome,
+particularly those that improve idiomatic OBS and RPM usage,
+packaging automation and commonality among Go application package sources.
+It is also a goal to keep manual configuration parameters in `_service` to a
minimum for maintainability.
+Please file proposals as an
[issue](https://github.com/openSUSE/obs-service-go_modules/issues)
+to discuss feasibility and feature design leading to a subsequent
implementation via pull request.
+
## License
GNU General Public License v2.0 or later
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/obs-service-go_modules-0.4.1/go_modules
new/obs-service-go_modules-0.5.0/go_modules
--- old/obs-service-go_modules-0.4.1/go_modules 2022-05-02 10:34:44.000000000
+0200
+++ new/obs-service-go_modules-0.5.0/go_modules 2022-06-11 02:15:31.000000000
+0200
@@ -30,56 +30,73 @@
import logging
import argparse
import re
-import tarfile
+import libarchive
import os
import shutil
from pathlib import Path
-from subprocess import check_output
-from subprocess import CalledProcessError
+from subprocess import run
app_name = "obs-service-go_modules"
description = __doc__
-logging.basicConfig(level=logging.DEBUG)
-log = logging.getLogger(app_name)
-
DEFAULT_COMPRESSION = "gz"
-parser = argparse.ArgumentParser(
- description=description,
formatter_class=argparse.RawDescriptionHelpFormatter
-)
-parser.add_argument("--strategy", default="vendor")
-parser.add_argument("--archive")
-parser.add_argument("--outdir")
-parser.add_argument("--compression", default=DEFAULT_COMPRESSION)
-args = parser.parse_args()
-
-outdir = args.outdir
+def get_archive_parameters(args):
+ archive_format = None
+ archive_compression = None
+ archive_extension = None
+
+ if args.compression == "obscpio" and "cpio" in libarchive.ffi.READ_FORMATS:
+ archive_format = "cpio_newc"
+ archive_compression = None
+ archive_extension = args.compression
+
+ elif args.compression == "tar" and "tar" in libarchive.ffi.READ_FORMATS:
+ archive_format = "gnutar"
+ archive_compression = None
+ archive_extension = args.compression
-def get_archive_extension():
- if args.compression not in tarfile.TarFile.OPEN_METH:
- log.error(f"The specified compression mode is not supported:
\"{args.compression}\"")
- exit(1)
-
- if args.compression == "tar":
- return "tar"
+ else:
+ compression_format = args.compression
+
+ if args.compression == "gz":
+ compression_format = "gzip"
+
+ elif args.compression == "zst":
+ compression_format = "zstd"
- return "tar." + (args.compression)
+ if compression_format not in libarchive.ffi.READ_FILTERS:
+ log.error(
+ f'The specified compression mode is not supported:
"{args.compression}"'
+ )
+ exit(1)
+ archive_format = "gnutar"
+ archive_compression = compression_format
+ archive_extension = "tar." + (args.compression)
-archive_ext = get_archive_extension()
-vendor_tarname = f"vendor.{archive_ext}"
+ return archive_format, archive_compression, archive_extension
+
+
+def basename_from_archive_name(archive_name):
+ return re.sub(
+
"^(?P<service_prefix>_service:[^:]+:)?(?P<basename>.*)\.(?P<extension>obscpio|tar\.[^\.]+)$",
+ r"\g<basename>",
+ archive_name,
+ )
def archive_autodetect():
- """ Find the most likely candidate file that contains go.mod and go.sum.
- For most Go applications this will be app-x.y.z.tar[.<tar
compression>].
- Use the name of the .spec file as the stem for the archive to detect.
- Archive formats supported:
- - .tar.gz
+ """Find the most likely candidate file that contains go.mod and go.sum.
+ For most Go applications this will be app-x.y.z.tar.gz or other supported
compression.
+ Use the name of the .spec file as the stem for the archive to detect.
+ Archive formats supported:
+ - .tar.gz
+ - .tar.xz
+ - .tar.zstd
"""
log.info(f"Autodetecting archive since no archive param provided in
_service")
cwd = Path.cwd()
@@ -89,49 +106,64 @@
log.error(f"Archive autodetection found no spec file under {cwd}")
exit(1)
else:
+ archive = None
spec_dir = spec.parent # typically the same as cwd
spec_stem = spec.stem # stem is app in app.spec
# highest sorted archive under spec_dir
- pattern = f"{spec.stem}*.{archive_ext}"
- archive = next(reversed(sorted(Path(spec_dir).glob(pattern))), None)
+ patterns = [
+ f"{spec.stem}*.tar.*",
+ f"{spec.stem}*.obscpio",
+ f"_service:*:{spec.stem}*tar.*",
+ f"_service:*:{spec.stem}*obscpio",
+ ]
+ for pattern in patterns:
+ log.debug(f"Trying to find archive name with pattern {pattern}")
+ archive = next(reversed(sorted(Path(spec_dir).glob(pattern))),
None)
+
+ if archive:
+ break
+
if not archive:
- log.error(f"Archive autodetection found no matching archive under
{cwd}")
+ log.error(f"Archive autodetection found no matching archive")
exit(1)
- else:
- log.info(f"Archive autodetected at {archive}")
- # Check that app.spec Version: directive value
- # is a substring of detected archive filename
- # Warn if there is disagreement between the versions.
- pattern = re.compile(r"^Version:\s+([\S]+)$", re.IGNORECASE)
- with spec.open(encoding="utf-8") as f:
- for line in f:
- versionmatch = pattern.match(line)
- if versionmatch:
- version = versionmatch.groups(0)[0]
- if not version:
- log.warning(f"Version not found in {spec.name}")
- else:
- if not (version in archive.name):
- log.warning(
- f"Version {version} in {spec.name} does not match
{archive.name}"
- )
- return str(archive.name) # return string not PosixPath
+ log.info(f"Archive autodetected at {archive}")
+ # Check that app.spec Version: directive value
+ # is a substring of detected archive filename
+ # Warn if there is disagreement between the versions.
+ pattern = re.compile(r"^Version:\s+([\S]+)$", re.IGNORECASE)
+ with spec.open(encoding="utf-8") as f:
+ for line in f:
+ versionmatch = pattern.match(line)
+ if versionmatch:
+ version = versionmatch.groups(0)[0]
+ if not version:
+ log.warning(f"Version not found in {spec.name}")
+ else:
+ if not (version in archive.name):
+ log.warning(
+ f"Version {version} in {spec.name} does not match
{archive.name}"
+ )
+ return str(archive.name) # return string not PosixPath
+
+
+def extract(filename, outdir):
+ log.info(f"Extracting {filename} to {outdir}")
-archive = args.archive or archive_autodetect()
-log.info(f"Using archive {archive}")
+ cwd = os.getcwd()
-basename = archive.replace(f".{archive_ext}", "")
+ # make path absolute so we can switch away from the current working
directory
+ filename = os.path.join(cwd, filename)
+ log.info(f"Switching to {outdir}")
+ os.chdir(outdir)
-def extract(filename, dir):
- if filename.endswith(f".{archive_ext}"):
- tar = tarfile.open(filename)
- tar.extractall(path=dir)
- tar.close()
- else:
- log.info(f"Unsupported archive file format for {filename}")
- exit(1)
+ try:
+ libarchive.extract_file(filename)
+ except libarchive.exception.ArchiveError as archive_error:
+ log.error(archive_error)
+
+ os.chdir(cwd)
def find_file(path, filename):
@@ -141,22 +173,39 @@
def cmd_go_mod(cmd, dir):
- try:
- log.info(f"go mod {cmd}")
- output = check_output(["go", "mod", cmd],
cwd=dir).decode("utf-8").strip()
- if output:
- log.info(output)
- except CalledProcessError as e:
- error = e.output.decode("utf-8").strip()
- if error:
- log.info(error)
- raise
+ """Execute go mod subcommand using subprocess.run().
+ Capture both stderr and stdout as text.
+ Log as info or error in this function body.
+ Return CompletedProcess object to caller for control flow.
+ """
+ log.info(f"go mod {cmd}")
+ # subprocess.run() returns CompletedProcess cp
+ cp = run(["go", "mod", cmd], cwd=dir, capture_output=True, text=True)
+ if cp.returncode:
+ log.error(cp.stderr.strip())
+ return cp
def main():
log.info(f"Running OBS Source Service: {app_name}")
- log.info(f"Extracting {archive} to {outdir}")
+ parser = argparse.ArgumentParser(
+ description=description,
formatter_class=argparse.RawDescriptionHelpFormatter
+ )
+ parser.add_argument("--strategy", default="vendor")
+ parser.add_argument("--archive")
+ parser.add_argument("--outdir")
+ parser.add_argument("--compression", default=DEFAULT_COMPRESSION)
+ args = parser.parse_args()
+
+ outdir = args.outdir
+
+ archive_format, archive_compression, archive_ext =
get_archive_parameters(args)
+ vendor_tarname = f"vendor.{archive_ext}"
+ archive = args.archive or archive_autodetect()
+ log.info(f"Using archive {archive}")
+
+ basename = basename_from_archive_name(archive)
extract(archive, outdir)
go_mod_path = find_file(outdir, "go.mod")
@@ -164,28 +213,64 @@
go_mod_dir = os.path.dirname(go_mod_path)
log.info(f"Using go.mod found at {go_mod_path}")
else:
- log.info(f"File go.mod not found under {outdir}")
+ log.error(f"File go.mod not found under {outdir}")
exit(1)
if args.strategy == "vendor":
+ # go subcommand sequence:
+ # - go mod download
+ # (is sensitive to invalid module versions, try and log warn if
fails)
+ # - go mod vendor
+ # (also downloads but use separate steps for visibility in OBS
environment)
+ # - go mod verify
+ # (validates checksums)
+
+ # return value cp is type subprocess.CompletedProcess
+ cp = cmd_go_mod("download", go_mod_dir)
+ if cp.returncode:
+ if "invalid version" in cp.stderr:
+ log.warning(
+ f"go mod download is more sensitive to invalid module
versions than go mod vendor"
+ )
+ log.warning(
+ f"if go mod vendor and go mod verify complete, vendoring
is successful"
+ )
+ else:
+ log.error("go mod download failed")
+ exit(1)
- cmd_go_mod("download", go_mod_dir)
- cmd_go_mod("verify", go_mod_dir)
- cmd_go_mod("vendor", go_mod_dir)
+ cp = cmd_go_mod("vendor", go_mod_dir)
+ if cp.returncode:
+ log.error("go mod vendor failed")
+ exit(1)
+
+ cp = cmd_go_mod("verify", go_mod_dir)
+ if cp.returncode:
+ log.error("go mod verify failed")
+ exit(1)
log.info(f"Vendor go.mod dependencies to {vendor_tarname}")
vendor_tarfile = os.path.join(outdir, vendor_tarname)
- vendor_dir = os.path.join(go_mod_dir, "vendor")
- with tarfile.open(vendor_tarfile, "w:" + args.compression) as tar:
- tar.add(vendor_dir, arcname=("vendor"))
+ cwd = os.getcwd()
+ os.chdir(go_mod_dir)
+ vendor_dir = "vendor"
+
+ with libarchive.file_writer(
+ vendor_tarfile, archive_format, archive_compression
+ ) as new_archive:
+ new_archive.add_files(vendor_dir)
+ os.chdir(cwd)
# remove extracted Go application source
try:
to_remove = os.path.join(outdir, basename)
+ log.info(f"Cleaning up working dir {to_remove}")
shutil.rmtree(to_remove)
- except FileNotFoundError as e:
+ except FileNotFoundError:
log.error(f"Could not remove directory not found {to_remove}")
if __name__ == "__main__":
+ logging.basicConfig(level=logging.DEBUG)
+ log = logging.getLogger(app_name)
main()