Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package melange for openSUSE:Factory checked in at 2025-04-14 12:59:18 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/melange (Old) and /work/SRC/openSUSE:Factory/.melange.new.1907 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "melange" Mon Apr 14 12:59:18 2025 rev:76 rq:1269154 version:0.23.6 Changes: -------- --- /work/SRC/openSUSE:Factory/melange/melange.changes 2025-04-07 17:38:00.576318638 +0200 +++ /work/SRC/openSUSE:Factory/.melange.new.1907/melange.changes 2025-04-14 12:59:24.582837852 +0200 @@ -1,0 +2,23 @@ +Mon Apr 14 07:57:49 UTC 2025 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- Update to version 0.23.6: + * pipelines: cmake/build: Use verbose mode by default (#1910) + * Include /usr/local paths in generateShbangDeps; drop /bin + (#1909) + * fix(pipelines/git-checkout): preserve ownership of existing + files (#1893) + * sbin-merge: reorder default pipeline path (#1907) + * Move apko's tarball package into melange (#1906) + * Upgrade golangci-lint to 1.64.8 (#1904) + * build(deps): bump the gomod group across 1 directory with 6 + updates (#1879) + * build(deps): bump the actions group across 1 directory with 5 + updates (#1895) + * deprecate goreleaser build pipeline (#1892) + * test.go: Invoke apkofs.DirFS with option to create dir if + missing (#1897) + * github: fixup presubmit workflow (#1900) + * fix random issue with package.full-version (#1898) + * Improve failure message for usrmerge linter (#1894) + +------------------------------------------------------------------- Old: ---- melange-0.23.5.obscpio New: ---- melange-0.23.6.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ melange.spec ++++++ --- /var/tmp/diff_new_pack.k57TwO/_old 2025-04-14 12:59:25.250865905 +0200 +++ /var/tmp/diff_new_pack.k57TwO/_new 2025-04-14 12:59:25.250865905 +0200 @@ -17,7 +17,7 @@ Name: melange -Version: 0.23.5 +Version: 0.23.6 Release: 0 Summary: Build APKs from source code License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.k57TwO/_old 2025-04-14 12:59:25.278867081 +0200 +++ /var/tmp/diff_new_pack.k57TwO/_new 2025-04-14 12:59:25.282867249 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/chainguard-dev/melange</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v0.23.5</param> + <param name="revision">v0.23.6</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.k57TwO/_old 2025-04-14 12:59:25.298867921 +0200 +++ /var/tmp/diff_new_pack.k57TwO/_new 2025-04-14 12:59:25.302868089 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/chainguard-dev/melange</param> - <param name="changesrevision">a5d28473b9091cf7a45a0864bb149ae9fddfff99</param></service></servicedata> + <param name="changesrevision">1c075e12cf123600b57cfb8bb66b9aa107a37e08</param></service></servicedata> (No newline at EOF) ++++++ melange-0.23.5.obscpio -> melange-0.23.6.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/Makefile new/melange-0.23.6/Makefile --- old/melange-0.23.5/Makefile 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/Makefile 2025-04-11 21:28:28.000000000 +0200 @@ -117,7 +117,7 @@ setup-golangci-lint: rm -f $(GOLANGCI_LINT_BIN) || : set -e ; - GOBIN=$(GOLANGCI_LINT_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4; + GOBIN=$(GOLANGCI_LINT_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.8; .PHONY: fmt fmt: ## Format all go files diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/e2e-tests/git-checkout-build.yaml new/melange-0.23.6/e2e-tests/git-checkout-build.yaml --- old/melange-0.23.5/e2e-tests/git-checkout-build.yaml 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/e2e-tests/git-checkout-build.yaml 2025-04-11 21:28:28.000000000 +0200 @@ -206,3 +206,88 @@ [ "$hash" != "$expected_hash" ] cd .. rm -R cherry-pick-test + + # When ownership of files created inside and outside of the runner container do not + # match, if mode is restrictive on files like 0700 on the Melange workspace, if the git-checkout + # pipeline changes ownership on them, being that it runs inside the runner container, subsequent + # filesystem operations run outside of the runner fail because of missing permissions. + # This happens specifically with tar --extract without preserving ownership of existing files, + # like '.', which in case of the default input and standard usage of the pipeline, is the + # workspace directory. + # This results in subsequent operations like xattrs restore to fail as they are run outside of + # the runner container. + # + # Bubblewrap runner: + # Since the user namespace is unshared and that the runner uid is mapped to the + # uid of the user who runs Melange, ownership always match. + # - Melange run by root -> runner user: root. + # Owner of files created outside of the runner: + # - host: root + # - runner: root + # Owner of files creted inside the runner: + # - host: root + # - runner: root + # - Melange run by non-root -> runner user: build (1000 mapped to non-root). + # Owner of files created outside of the runner: + # - host: non-root + # - runner: build (1000 -> host's non-root) + # Owner of files creted inside the runner: + # - host: non-root + # - runner: build (1000 -> host's non-root) + # + # Docker runner: + # Since the user namespace is shared, ownership between files created inside and + # outside the runner, match only when the user who runs Melange is root. + # - Melange run by root -> runner user: root. + # Owner of files created outside of the runner: + # - host: root + # - runner: root + # Owner of files creted inside the runner: + # - host: root + # - runner: root + # - Melange run by non-root -> runner user: root. + # Owner of files created outside of the runner: + # - host: non-root + # - runner: non-root + # Owner of files creted inside the runner: + # - host: root + # - runner: root + # + # QEMU runner: + # File ownership inside and outside of the runner VM always match. + # + - name: "check ownership of existing files" + runs: | + set -eu + runner_user=$(id -u) + melange_user=$(stat -c "%u" .) + expected_runner="" + expected_melange="" + if [[ $runner_user == $melange_user ]]; then + # Ownership of existing files match the ownership of files created within the runner + # (i.e. by the git-checkout pipeline tar --extract command). + expected_runner=48 + expected_melange=2 + else + # Ownership of existing files is not changed. + expected_runner=42 + expected_melange=6 + fi + exclude_args="! -regex ^\.\/\.ssh.*$ ! -regex ^./.gitconfig$" + found_runner=$(find . -user $runner_user $exclude_args | wc -l) + mismatch="" + if [[ $found_runner != $expected_runner ]]; then + echo "Expected $expected_runner files owned by $runner_user, found $found_runner" + find . -user $runner_user $exclude_args + mismatch=true + fi + if [[ $melange_user != $runner_user ]]; then + found_melange=$(find . -user $melange_user $exclude_args | wc -l) + if [[ $found_runner != $expected_runner || $found_melange != $expected_melange ]]; then + echo "Expected $expected_melange files owned by $melange_user, found $found_melange" + find . -user $melange_user $exclude_args + mismatch=true + fi + fi + [[ $mismatch != "" ]] && exit 1 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/e2e-tests/run-tests new/melange-0.23.6/e2e-tests/run-tests --- old/melange-0.23.5/e2e-tests/run-tests 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/e2e-tests/run-tests 2025-04-11 21:28:28.000000000 +0200 @@ -14,6 +14,7 @@ if [ "$MELANGE" = "melange" ]; then MELANGE=$(which melange) fi +ARGS=${ARGS:-""} echo "Testing with melange from $MELANGE" $MELANGE version --json @@ -32,7 +33,7 @@ fails="" for yaml in "$@"; do - args="" + args="${ARGS}" ops="" base=${yaml%.yaml} case "$base" in diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/go.mod new/melange-0.23.6/go.mod --- old/melange-0.23.5/go.mod 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/go.mod 2025-04-11 21:28:28.000000000 +0200 @@ -3,15 +3,15 @@ go 1.23.4 require ( - chainguard.dev/apko v0.25.4 + chainguard.dev/apko v0.25.6 cloud.google.com/go/storage v1.51.0 dagger.io/dagger v0.16.3 github.com/chainguard-dev/clog v1.7.0 github.com/chainguard-dev/go-pkgconfig v0.0.0-20240404163941-6351b37b2a10 - github.com/chainguard-dev/yam v0.2.10 + github.com/chainguard-dev/yam v0.2.12 github.com/charmbracelet/log v0.4.1 - github.com/docker/cli v28.0.1+incompatible - github.com/docker/docker v28.0.1+incompatible + github.com/docker/cli v28.0.4+incompatible + github.com/docker/docker v28.0.4+incompatible github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 github.com/github/go-spdx/v2 v2.3.2 github.com/go-git/go-git/v5 v5.14.0 @@ -25,7 +25,7 @@ github.com/klauspost/compress v1.18.0 github.com/klauspost/pgzip v1.2.6 github.com/kubescape/go-git-url v0.0.30 - github.com/moby/moby v28.0.1+incompatible + github.com/moby/moby v28.0.4+incompatible github.com/opencontainers/image-spec v1.1.1 github.com/package-url/packageurl-go v0.1.3 github.com/pkg/errors v0.9.1 @@ -50,7 +50,7 @@ gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 mvdan.cc/sh/v3 v3.11.0 - sigs.k8s.io/release-utils v0.11.0 + sigs.k8s.io/release-utils v0.11.1 sigs.k8s.io/yaml v1.4.0 ) @@ -222,5 +222,5 @@ google.golang.org/protobuf v1.36.5 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/apimachinery v0.32.3 // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/go.sum new/melange-0.23.6/go.sum --- old/melange-0.23.5/go.sum 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/go.sum 2025-04-11 21:28:28.000000000 +0200 @@ -1,7 +1,7 @@ cel.dev/expr v0.19.2 h1:V354PbqIXr9IQdwy4SYA4xa0HXaWq1BUPAGzugBY5V4= cel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= -chainguard.dev/apko v0.25.4 h1:QFFCD7QR3q9AT5H+/ZCXQReCNN0CgRn1mkOq1T+FZfQ= -chainguard.dev/apko v0.25.4/go.mod h1:1xdjg538oPqb7VCiDMc4IlNtS5gRfpJqv2VIveR6wIo= +chainguard.dev/apko v0.25.6 h1:ie7+FO02C/UVbfIz1TwDUg0zsdY3SFALf24mlcLGecY= +chainguard.dev/apko v0.25.6/go.mod h1:1xdjg538oPqb7VCiDMc4IlNtS5gRfpJqv2VIveR6wIo= chainguard.dev/go-grpc-kit v0.17.7 h1:TqHua7er5k8m6WM96y0Tm7IoLLkuZ5vh3+5SR1gruKg= chainguard.dev/go-grpc-kit v0.17.7/go.mod h1:JroMzTY9mdhKe/bvtyChgfECaNh80+bMZH3HS+TGXHw= chainguard.dev/sdk v0.1.31 h1:Blvpa0Ji/tC1VVV8/l8UyQe022LoRxZLfgasyFE1EhQ= @@ -89,8 +89,8 @@ github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o= github.com/chainguard-dev/go-pkgconfig v0.0.0-20240404163941-6351b37b2a10 h1:XR2vgQC024I9/boh9r1ihVv8Z14+pbvWqXeYMCnZJpc= github.com/chainguard-dev/go-pkgconfig v0.0.0-20240404163941-6351b37b2a10/go.mod h1:1p6+MesLcjKeON5BRWa7I87mvAY0QmKjgginIM3w6BI= -github.com/chainguard-dev/yam v0.2.10 h1:2iklMLB+o+H6r30wWV5JYLcykNAXTE0Q8snjXsTBbx0= -github.com/chainguard-dev/yam v0.2.10/go.mod h1:Dqfj6H4GyIBTyBRQi5S+cji6wH4UlZDaN/sFirjOjjA= +github.com/chainguard-dev/yam v0.2.12 h1:8d7a7Ri5EJjEnleEo0vSN0rlw1FYa0aWAabsvjSiRN0= +github.com/chainguard-dev/yam v0.2.12/go.mod h1:Dqfj6H4GyIBTyBRQi5S+cji6wH4UlZDaN/sFirjOjjA= github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= github.com/charmbracelet/log v0.4.1 h1:6AYnoHKADkghm/vt4neaNEXkxcXLSV2g1rdyFDOpTyk= @@ -128,12 +128,12 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v28.0.1+incompatible h1:g0h5NQNda3/CxIsaZfH4Tyf6vpxFth7PYl3hgCPOKzs= -github.com/docker/cli v28.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v28.0.4+incompatible h1:pBJSJeNd9QeIWPjRcV91RVJihd/TXB77q1ef64XEu4A= +github.com/docker/cli v28.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0= -github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok= +github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -345,8 +345,8 @@ github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/moby v28.0.1+incompatible h1:10ejBTwFhM3/9p6pSaKrLyXnx7QzzCmCYHAedOp67cQ= -github.com/moby/moby v28.0.1+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= +github.com/moby/moby v28.0.4+incompatible h1:16Yx7MFk2ucIqMdWJ06YdPn6sCmso02RKkD1OROxrho= +github.com/moby/moby v28.0.4+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -723,11 +723,11 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw= mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg= -sigs.k8s.io/release-utils v0.11.0 h1:FUVSw2dO67M7mfcQx9AITEGnTHoBOdJNbbQ3FT3o8mA= -sigs.k8s.io/release-utils v0.11.0/go.mod h1:wAlXz8xruzvqZUsorI64dZ3lbkiDnYSlI4IYC6l2yEA= +sigs.k8s.io/release-utils v0.11.1 h1:hzvXGpHgHJfLOJB6TRuu14bzWc3XEglHmXHJqwClSZE= +sigs.k8s.io/release-utils v0.11.1/go.mod h1:ybR2V/uQAOGxYfzYtBenSYeXWkBGNP2qnEiX77ACtpc= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/build/package.go new/melange-0.23.6/pkg/build/package.go --- old/melange-0.23.5/pkg/build/package.go 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/pkg/build/package.go 2025-04-11 21:28:28.000000000 +0200 @@ -38,8 +38,8 @@ "chainguard.dev/melange/pkg/config" "chainguard.dev/melange/pkg/sca" + "chainguard.dev/melange/pkg/tarball" - "chainguard.dev/apko/pkg/apk/tarball" "github.com/chainguard-dev/clog" "github.com/psanford/memfs" "go.opentelemetry.io/otel" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/build/pipeline.go new/melange-0.23.6/pkg/build/pipeline.go --- old/melange-0.23.5/pkg/build/pipeline.go 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/pkg/build/pipeline.go 2025-04-11 21:28:28.000000000 +0200 @@ -82,7 +82,7 @@ config.SubstitutionPackageName: pkg.Name, config.SubstitutionPackageVersion: pkg.Version, config.SubstitutionPackageEpoch: strconv.FormatUint(pkg.Epoch, 10), - config.SubstitutionPackageFullVersion: fmt.Sprintf("%s-r%s", config.SubstitutionPackageVersion, config.SubstitutionPackageEpoch), + config.SubstitutionPackageFullVersion: fmt.Sprintf("%s-r%d", pkg.Version, pkg.Epoch), config.SubstitutionPackageSrcdir: "/home/build", config.SubstitutionTargetsOutdir: "/home/build/melange-out", config.SubstitutionTargetsDestdir: fmt.Sprintf("/home/build/melange-out/%s", pkg.Name), @@ -207,7 +207,7 @@ // Pipelines can have their own environment variables, which override the global ones. envOverride := map[string]string{ - "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "PATH": "/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin", } for k, v := range pipeline.Environment { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/build/pipelines/cmake/build.yaml new/melange-0.23.6/pkg/build/pipelines/cmake/build.yaml --- old/melange-0.23.5/pkg/build/pipelines/cmake/build.yaml 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/pkg/build/pipelines/cmake/build.yaml 2025-04-11 21:28:28.000000000 +0200 @@ -13,4 +13,4 @@ pipeline: - runs: | - cmake --build ${{inputs.output-dir}} + VERBOSE=1 cmake --build ${{inputs.output-dir}} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/build/pipelines/git-checkout.yaml new/melange-0.23.6/pkg/build/pipelines/git-checkout.yaml --- old/melange-0.23.5/pkg/build/pipelines/git-checkout.yaml 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/pkg/build/pipelines/git-checkout.yaml 2025-04-11 21:28:28.000000000 +0200 @@ -187,7 +187,7 @@ vr cd "$workdir" msg "tar -c . | tar -C \"$dest_fullpath\" -x" - ( tar -c . ; echo $? > "$rcfile") | tar -C "$dest_fullpath" -x + ( tar -c . ; echo $? > "$rcfile") | tar -C "$dest_fullpath" -x --no-same-owner read rc < "$rcfile" || fail "failed to read rc file" [ $rc -eq 0 ] || fail "tar creation in $workdir failed" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/build/pipelines/goreleaser/README.md new/melange-0.23.6/pkg/build/pipelines/goreleaser/README.md --- old/melange-0.23.5/pkg/build/pipelines/goreleaser/README.md 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/pkg/build/pipelines/goreleaser/README.md 1970-01-01 01:00:00.000000000 +0100 @@ -1,23 +0,0 @@ -<!-- start:pipeline-reference-gen --> -# Pipeline Reference - - -- [goreleaser/build](#goreleaserbuild) - -## goreleaser/build - -Run a build using the GoReleaser - -### Inputs - -| Name | Required | Description | Default | -| ---- | -------- | ----------- | ------- | -| args | false | List of space-separated args to pass to the GoReleaser `release` command. | | -| config-file | false | Path to the GoReleaser config file. If not specified, the default config file will be used. | | -| output | true | Filename to use when writing the binary. The final install location inside the apk will be in /usr/bin by default. | ${{targets.contextdir}}/usr/bin/${{package.name}} | -| skip | true | List of comma-separated skip values to pass to the GoReleaser `release` command. | docker,ko,publish | -| snapshot | false | If true, the GoReleaser `release` command will be run with the `--snapshot` flag. | false | -| working-dir | false | Top directory of the go module, this is where go.mod lives. Before buiding the go pipeline wil cd into this directory. | . | - - -<!-- end:pipeline-reference-gen --> \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/build/pipelines/goreleaser/build.yaml new/melange-0.23.6/pkg/build/pipelines/goreleaser/build.yaml --- old/melange-0.23.5/pkg/build/pipelines/goreleaser/build.yaml 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/pkg/build/pipelines/goreleaser/build.yaml 1970-01-01 01:00:00.000000000 +0100 @@ -1,77 +0,0 @@ -name: Run a build using the GoReleaser - -needs: - packages: - - busybox - - ca-certificates-bundle - - goreleaser - -inputs: - args: - description: | - List of space-separated args to pass to the GoReleaser `release` command. - required: false - skip: - description: | - List of comma-separated skip values to pass to the GoReleaser `release` command. - required: true - default: "docker,ko,publish" - - output: - description: | - Filename to use when writing the binary. The final install location inside - the apk will be in /usr/bin by default. - required: true - default: "${{targets.contextdir}}/usr/bin/${{package.name}}" - - snapshot: - description: | - If true, the GoReleaser `release` command will be run with the `--snapshot` - flag. - default: "false" - - config-file: - description: | - Path to the GoReleaser config file. If not specified, the default config - file will be used. - required: false - - working-dir: - default: "." - required: false - description: | - Top directory of the go module, this is where go.mod lives. Before buiding - the go pipeline wil cd into this directory. - -pipeline: - - runs: | - #!/bin/sh - set -eux -o pipefail - goreleaser_flags="--clean --skip=${{inputs.skip}} ${{inputs.args}}" - - DIR="$(dirname '${{inputs.output}}')" - mkdir -p $DIR - BASENAME="$(basename '${{inputs.output}}')" - - # if working-dir is rather than "." cd into that directory - if [ "${{inputs.working-dir}}" != "." ]; then - cd ${{inputs.working-dir}} - fi - - if [ "${{inputs.snapshot}}" = "true" ]; then - goreleaser_flags="$goreleaser_flags --snapshot" - fi - - if [ -n "${{inputs.config-file}}" ]; then - goreleaser_flags="$goreleaser_flags --config ${{inputs.config-file}}" - fi - - goreleaser release $goreleaser_flags - echo "Copying binary to ${{inputs.output}}" - build_arch="${{build.arch}}" - if [ "$build_arch" = "aarch64" ]; then - build_arch="arm64" - elif [ "$build_arch" = "x86_64" ]; then - build_arch="amd64" - fi - install -Dm755 ./dist/${BASENAME}_linux_$build_arch*/* "${{inputs.output}}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/build/test.go new/melange-0.23.6/pkg/build/test.go --- old/melange-0.23.5/pkg/build/test.go 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/pkg/build/test.go 2025-04-11 21:28:28.000000000 +0200 @@ -311,7 +311,11 @@ log.Infof("populating workspace %s from %s", t.WorkspaceDir, t.SourceDir) - fsys := apkofs.DirFS(t.SourceDir) + fsys := apkofs.DirFS(t.SourceDir, apkofs.WithCreateDir()) + + if fsys == nil { + return fmt.Errorf("unable to create/use directory %s", t.SourceDir) + } return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/linter/linter.go new/melange-0.23.6/pkg/linter/linter.go --- old/melange-0.23.5/pkg/linter/linter.go 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/pkg/linter/linter.go 2025-04-11 21:28:28.000000000 +0200 @@ -794,7 +794,7 @@ // without special casing it with the package name. if path == "sbin" || path == "bin" || path == "usr/sbin" { if d.IsDir() || d.Type().IsRegular() { - return fmt.Errorf("package contains non-symlink file at /sbin or /bin in violation of usrmerge") + return fmt.Errorf("package contains non-symlink file at /sbin, /bin or /usr/sbin in violation of usrmerge") } else { return nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/sca/sca.go new/melange-0.23.6/pkg/sca/sca.go --- old/melange-0.23.5/pkg/sca/sca.go 2025-04-04 16:33:33.000000000 +0200 +++ new/melange-0.23.6/pkg/sca/sca.go 2025-04-11 21:28:28.000000000 +0200 @@ -822,7 +822,9 @@ return nil } - if !strings.HasPrefix(path, "usr/bin/") && !strings.HasPrefix(path, "bin/") { + if !strings.HasPrefix(path, "usr/bin/") && + !strings.HasPrefix(path, "usr/local/bin/") && + !strings.HasPrefix(path, "usr/local/sbin/") { return nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/sign/index.go new/melange-0.23.6/pkg/sign/index.go --- old/melange-0.23.5/pkg/sign/index.go 1970-01-01 01:00:00.000000000 +0100 +++ new/melange-0.23.6/pkg/sign/index.go 2025-04-11 21:28:28.000000000 +0200 @@ -0,0 +1,146 @@ +// Copyright 2023 Chainguard, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sign + +import ( + "archive/tar" + "bytes" + "context" + "crypto" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/chainguard-dev/clog" + "github.com/klauspost/compress/gzip" + "github.com/psanford/memfs" + + "chainguard.dev/apko/pkg/apk/signature" + "chainguard.dev/melange/pkg/tarball" +) + +func SignIndex(ctx context.Context, signingKey string, indexFile string) error { + log := clog.FromContext(ctx) + is, err := indexIsAlreadySigned(indexFile) + if err != nil { + return err + } + if is { + log.Infof("index %s is already signed, doing nothing", indexFile) + return nil + } + + log.Infof("signing index %s with key %s", indexFile, signingKey) + + indexData, err := os.ReadFile(indexFile) + if err != nil { + return fmt.Errorf("unable to read index for signing: %w", err) + } + + sigFS := memfs.New() + indexDigest, err := HashData(indexData, crypto.SHA256) + if err != nil { + return err + } + + sigData, err := signature.RSASignDigest(indexDigest, crypto.SHA256, signingKey, "") + if err != nil { + return fmt.Errorf("unable to sign index: %w", err) + } + + log.Infof("appending signature RSA256 to index %s", indexFile) + + if err := sigFS.WriteFile(fmt.Sprintf(".SIGN.RSA256.%s.pub", filepath.Base(signingKey)), sigData, 0644); err != nil { + return fmt.Errorf("unable to append signature: %w", err) + } + + // prepare control.tar.gz + multitarctx, err := tarball.NewContext( + tarball.WithOverrideUIDGID(0, 0), + tarball.WithOverrideUname("root"), + tarball.WithOverrideGname("root"), + tarball.WithSkipClose(true), + ) + if err != nil { + return fmt.Errorf("unable to build tarball context: %w", err) + } + + log.Infof("writing signed index to %s", indexFile) + + var sigBuffer bytes.Buffer + if err := multitarctx.WriteTargz(ctx, &sigBuffer, sigFS, sigFS); err != nil { + return fmt.Errorf("unable to write signature tarball: %w", err) + } + + idx, err := os.Create(indexFile) + if err != nil { + return fmt.Errorf("unable to open index for writing: %w", err) + } + defer idx.Close() + + if _, err := io.Copy(idx, &sigBuffer); err != nil { + return fmt.Errorf("unable to write index signature: %w", err) + } + + if _, err := idx.Write(indexData); err != nil { + return fmt.Errorf("unable to write index data: %w", err) + } + + log.Infof("signed index %s with key %s", indexFile, signingKey) + + return nil +} + +func indexIsAlreadySigned(indexFile string) (bool, error) { + index, err := os.Open(indexFile) + if err != nil { + return false, fmt.Errorf("cannot open index file %s: %w", indexFile, err) + } + defer index.Close() + + gzi, err := gzip.NewReader(index) + if err != nil { + return false, fmt.Errorf("cannot open index file %s as gzip: %w", indexFile, err) + } + defer gzi.Close() + + tari := tar.NewReader(gzi) + for { + hdr, err := tari.Next() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return false, fmt.Errorf("cannot read tar index %s: %w", indexFile, err) + } + + if strings.HasPrefix(hdr.Name, ".SIGN.RSA") { + return true, nil + } + } + + return false, nil +} + +func HashData(data []byte, digestType crypto.Hash) ([]byte, error) { + digest := digestType.New() + if n, err := digest.Write(data); err != nil || n != len(data) { + return nil, fmt.Errorf("unable to hash data: %w", err) + } + return digest.Sum(nil), nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/tarball/tarball.go new/melange-0.23.6/pkg/tarball/tarball.go --- old/melange-0.23.5/pkg/tarball/tarball.go 1970-01-01 01:00:00.000000000 +0100 +++ new/melange-0.23.6/pkg/tarball/tarball.go 2025-04-11 21:28:28.000000000 +0200 @@ -0,0 +1,132 @@ +// Copyright 2022, 2023 Chainguard, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tarball + +import ( + "archive/tar" + "time" +) + +type Context struct { + SourceDateEpoch time.Time + + OverrideUIDGID bool + UID int + GID int + OverrideUname string + OverrideGname string + SkipClose bool + UseChecksums bool + remapUIDs map[int]int + remapGIDs map[int]int + overridePerms map[string]tar.Header +} + +type Option func(*Context) error + +// Generates a new context from a set of options. +func NewContext(opts ...Option) (*Context, error) { + ctx := Context{} + + for _, opt := range opts { + if err := opt(&ctx); err != nil { + return nil, err + } + } + + return &ctx, nil +} + +// Sets SourceDateEpoch for Context. +func WithSourceDateEpoch(t time.Time) Option { + return func(ctx *Context) error { + ctx.SourceDateEpoch = t + + return nil + } +} + +// WithOverrideUIDGID sets the UID/GID to override with for all files for Context. +func WithOverrideUIDGID(uid, gid int) Option { + return func(ctx *Context) error { + ctx.OverrideUIDGID = true + ctx.UID = uid + ctx.GID = gid + return nil + } +} + +// WithOverridePerms sets the UID/GID and file permissions to override with for specific files Context. +func WithOverridePerms(files []tar.Header) Option { + return func(ctx *Context) error { + overrides := map[string]tar.Header{} + for _, f := range files { + overrides[f.Name] = f + } + ctx.overridePerms = overrides + return nil + } +} + +// WithOverrideUname sets the Uname to use with Context. +func WithOverrideUname(uname string) Option { + return func(ctx *Context) error { + ctx.OverrideUname = uname + return nil + } +} + +// WithRemapUIDs sets a UID remapping in the Context. +func WithRemapUIDs(uids map[int]int) Option { + return func(ctx *Context) error { + ctx.remapUIDs = uids + return nil + } +} + +// WithRemapGIDs sets a GID remapping in the Context. +func WithRemapGIDs(gids map[int]int) Option { + return func(ctx *Context) error { + ctx.remapGIDs = gids + return nil + } +} + +// WithOverrideGname sets the Gname to use with Context. +func WithOverrideGname(gname string) Option { + return func(ctx *Context) error { + ctx.OverrideGname = gname + return nil + } +} + +// WithSkipClose is used to determine whether the tar stream +// should be closed. For concatenated tar streams such as APKv2 +// containers, only the final tar stream should be closed. +func WithSkipClose(skipClose bool) Option { + return func(ctx *Context) error { + ctx.SkipClose = skipClose + return nil + } +} + +// WithUseChecksums is used to determine whether the tar stream +// should have the APK-TOOLS.checksum.SHA1 extension. +func WithUseChecksums(useChecksums bool) Option { + return func(ctx *Context) error { + ctx.UseChecksums = useChecksums + return nil + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/tarball/testdata/bar/bar.txt new/melange-0.23.6/pkg/tarball/testdata/bar/bar.txt --- old/melange-0.23.5/pkg/tarball/testdata/bar/bar.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/melange-0.23.6/pkg/tarball/testdata/bar/bar.txt 2025-04-11 21:28:28.000000000 +0200 @@ -0,0 +1 @@ +bar diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/tarball/testdata/foo/foo.txt new/melange-0.23.6/pkg/tarball/testdata/foo/foo.txt --- old/melange-0.23.5/pkg/tarball/testdata/foo/foo.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/melange-0.23.6/pkg/tarball/testdata/foo/foo.txt 2025-04-11 21:28:28.000000000 +0200 @@ -0,0 +1 @@ +foo diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/tarball/write.go new/melange-0.23.6/pkg/tarball/write.go --- old/melange-0.23.5/pkg/tarball/write.go 1970-01-01 01:00:00.000000000 +0100 +++ new/melange-0.23.6/pkg/tarball/write.go 2025-04-11 21:28:28.000000000 +0200 @@ -0,0 +1,339 @@ +// Copyright 2022, 2023 Chainguard, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tarball + +import ( + "archive/tar" + "compress/gzip" + "context" + "crypto/sha1" //nolint:gosec + "encoding/hex" + "fmt" + "io" + "io/fs" + "os" + "syscall" + + "go.opentelemetry.io/otel" + "golang.org/x/sys/unix" + + apkfs "chainguard.dev/apko/pkg/apk/fs" + "chainguard.dev/apko/pkg/apk/passwd" +) + +const xattrTarPAXRecordsPrefix = "SCHILY.xattr." + +func hasHardlinks(fi fs.FileInfo) bool { + if stat := fi.Sys(); stat != nil { + si, ok := stat.(*syscall.Stat_t) + if !ok { + return false + } + + // if we don't have inodes, we just assume the filesystem + // does not support hardlinks + if si == nil { + return false + } + + return si.Nlink > 1 + } + + return false +} + +func getInodeFromFileInfo(fi fs.FileInfo) (uint64, error) { + if stat := fi.Sys(); stat != nil { + si, ok := stat.(*syscall.Stat_t) + if !ok { + return 0, fmt.Errorf("unable to stat underlying file") + } + + // if we don't have inodes, we just assume the filesystem + // does not support hardlinks + if si == nil { + return 0, fmt.Errorf("unable to stat underlying file") + } + + return si.Ino, nil + } + + return 0, fmt.Errorf("unable to stat underlying file") +} + +func (c *Context) writeTar(ctx context.Context, tw *tar.Writer, fsys fs.FS, users, groups map[int]string) error { //nolint:gocyclo + if users == nil { + users = map[int]string{} + } + if groups == nil { + groups = map[int]string{} + } + seenFiles := map[uint64]string{} + // set this once, to make it easy to look up later + if c.overridePerms == nil { + c.overridePerms = map[string]tar.Header{} + } + + buf := make([]byte, 1<<20) + + if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { + if err := ctx.Err(); err != nil { + return err + } + // skip the root path, superfluous + if path == "." { + return nil + } + + if err != nil { + return err + } + + info, err := d.Info() + if err != nil { + return err + } + + var ( + link string + major, minor uint32 + isCharDevice bool + ) + if info.Mode()&os.ModeSymlink == os.ModeSymlink { + rlfs, ok := fsys.(apkfs.ReadLinkFS) + if !ok { + return fmt.Errorf("readlink not supported by this fs: path (%s)", path) + } + + if link, err = rlfs.Readlink(path); err != nil { + return err + } + } + + if info.Mode()&os.ModeCharDevice == os.ModeCharDevice { + rlfs, ok := fsys.(apkfs.ReadnodFS) + if !ok { + return fmt.Errorf("read character device not supported by this fs: path (%s) %#v %#v", path, info, fsys) + } + isCharDevice = true + dev, err := rlfs.Readnod(path) + if err != nil { + return err + } + major = unix.Major(uint64(dev)) + minor = unix.Minor(uint64(dev)) + } + + header, err := tar.FileInfoHeader(info, link) + if err != nil { + return err + } + // devices + if isCharDevice { + header.Devmajor = int64(major) + header.Devminor = int64(minor) + } + // work around some weirdness, without this we wind up with just the basename + header.Name = path + + // if SourceDateEpoch is set explicitly, overwrite the timestamps, otherwise propagate modtime + // (this option is unused in apko but melange uses this, so we support it for now) + header.AccessTime = c.SourceDateEpoch + header.ModTime = c.SourceDateEpoch + header.ChangeTime = c.SourceDateEpoch + + if uid, ok := c.remapUIDs[header.Uid]; ok { + header.Uid = uid + } + + if gid, ok := c.remapGIDs[header.Gid]; ok { + header.Gid = gid + } + + if name, ok := users[header.Uid]; ok { + header.Uname = name + } + if name, ok := groups[header.Gid]; ok { + header.Gname = name + } + + if c.OverrideUIDGID { + header.Uid = c.UID + header.Gid = c.GID + } + + if c.OverrideUname != "" { + header.Uname = c.OverrideUname + } + + if c.OverrideGname != "" { + header.Gname = c.OverrideGname + } + + // look for the override perms with or without the leading / + if h, ok := c.overridePerms[header.Name]; ok { + header.Mode = h.Mode + header.Uid = h.Uid + header.Gid = h.Gid + header.Uname = h.Uname + header.Gname = h.Gname + } + if h, ok := c.overridePerms["/"+header.Name]; ok { + header.Mode = h.Mode + header.Uid = h.Uid + header.Gid = h.Gid + header.Uname = h.Uname + header.Gname = h.Gname + } + + if link != "" { + header.Typeflag = tar.TypeSymlink + } + if !info.IsDir() && hasHardlinks(info) { + inode, err := getInodeFromFileInfo(info) + if err != nil { + return err + } + + if oldpath, ok := seenFiles[inode]; ok { + header.Typeflag = tar.TypeLink + header.Linkname = oldpath + header.Size = 0 + } else { + seenFiles[inode] = header.Name + } + } + + if header.PAXRecords == nil { + header.PAXRecords = map[string]string{} + } + if c.UseChecksums { + if link != "" { + linkDigest := sha1.Sum([]byte(link)) //nolint:gosec + linkChecksum := hex.EncodeToString(linkDigest[:]) + header.PAXRecords["APK-TOOLS.checksum.SHA1"] = linkChecksum + } else if info.Mode().IsRegular() { + data, err := fsys.Open(path) + if err != nil { + return err + } + defer data.Close() + + fileDigest := sha1.New() //nolint:gosec + if _, err := io.CopyBuffer(fileDigest, data, buf); err != nil { + return err + } + + fileChecksum := hex.EncodeToString(fileDigest.Sum(nil)) + header.PAXRecords["APK-TOOLS.checksum.SHA1"] = fileChecksum + } + } + + // only capture xattrs for real objects in the FS + if header.Typeflag == tar.TypeReg || header.Typeflag == tar.TypeDir { + xfs, ok := fsys.(apkfs.XattrFS) + if ok { + xattrs, err := xfs.ListXattrs(path) + // we can ignore errors + if err == nil && xattrs != nil { + for name, value := range xattrs { + header.PAXRecords[xattrTarPAXRecordsPrefix+name] = string(value) + } + } + } + } + + if err := tw.WriteHeader(header); err != nil { + return err + } + + if info.Mode().IsRegular() && header.Size > 0 { + data, err := fsys.Open(path) + if err != nil { + return err + } + + defer data.Close() + + if _, err := io.CopyBuffer(tw, data, buf); err != nil { + return err + } + } + + return nil + }); err != nil { + return err + } + + return nil +} + +// WriteArchive writes a tarball to the provided io.Writer from the provided fs.FS. +// To override permissions, set the OverridePerms when creating the Context. +// If you need to get multiple filesystems, merge them prior to calling WriteArchive. +// +// Deprecated: Use WriteTargz or WriteTar instead. +func (c *Context) WriteArchive(dst io.Writer, src fs.FS) error { + return c.WriteTargz(context.Background(), dst, src, src) +} + +// WriteTargz writes a gzipped tarball to the provided io.Writer from the provided fs.FS. +// To override permissions, set the OverridePerms when creating the Context. +// If you need to get multiple filesystems, merge them prior to calling WriteArchive. +// userinfosrc should be a fs which can provide an optionally provide an etc/passwd and etc/group file. +// The etc/passwd and etc/group file provide username and group name mappings for the tar. +func (c *Context) WriteTargz(ctx context.Context, dst io.Writer, src fs.FS, userinfofs fs.FS) error { + ctx, span := otel.Tracer("go-apk").Start(ctx, "WriteTargz") + defer span.End() + + gzw := gzip.NewWriter(dst) + defer gzw.Close() + + return c.WriteTar(ctx, gzw, src, userinfofs) +} + +// WriteTar writes a tarball to the provided io.Writer from the provided fs.FS. +// To override permissions, set the OverridePerms when creating the Context. +// If you need to get multiple filesystems, merge them prior to calling WriteArchive. +// userinfosrc should be a fs which can provide an optionally provide an etc/passwd and etc/group file. +// The etc/passwd and etc/group file provide username and group name mappings for the tar. +func (c *Context) WriteTar(ctx context.Context, dst io.Writer, src fs.FS, userinfosrc fs.FS) error { + ctx, span := otel.Tracer("go-apk").Start(ctx, "WriteTar") + defer span.End() + + tw := tar.NewWriter(dst) + if !c.SkipClose { + defer tw.Close() + } else { + defer tw.Flush() + } + + // get the uname and gname maps + usersFile, _ := passwd.ReadUserFile(userinfosrc, "etc/passwd") + groupsFile, _ := passwd.ReadGroupFile(userinfosrc, "etc/group") + users := map[int]string{} + groups := map[int]string{} + for _, u := range usersFile.Entries { + users[int(u.UID)] = u.UserName + } + for _, g := range groupsFile.Entries { + groups[int(g.GID)] = g.GroupName + } + if err := c.writeTar(ctx, tw, src, users, groups); err != nil { + return fmt.Errorf("writing TAR archive failed: %w", err) + } + + return nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.23.5/pkg/tarball/write_test.go new/melange-0.23.6/pkg/tarball/write_test.go --- old/melange-0.23.5/pkg/tarball/write_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/melange-0.23.6/pkg/tarball/write_test.go 2025-04-11 21:28:28.000000000 +0200 @@ -0,0 +1,49 @@ +package tarball + +import ( + "archive/tar" + "bytes" + "context" + "testing" + + "github.com/stretchr/testify/require" + + "chainguard.dev/apko/pkg/apk/fs" +) + +func TestWriteTar(t *testing.T) { + var buf bytes.Buffer + var ( + m = fs.NewMemFS() + dir = "a" + file = "a/b" + ) + err := m.MkdirAll(dir, 0o755) + require.NoError(t, err, "error creating dir %s", dir) + err = m.WriteFile(file, []byte("hello world"), 0o644) + require.NoError(t, err, "error creating file %s", file) + + // set xattrs, then see if the tar gets it + err = m.SetXattr(dir, "user.dir", []byte("foo")) + require.NoError(t, err, "error setting xattr on %s", dir) + err = m.SetXattr(file, "user.file", []byte("bar")) + require.NoError(t, err, "error setting xattr on %s", file) + ctx := Context{} + tw := tar.NewWriter(&buf) + err = ctx.writeTar(context.TODO(), tw, m, nil, nil) + require.NoError(t, err, "error writing tar") + err = tw.Close() + require.NoError(t, err, "error closing tar writer") + + // now should be able to read the tar and check the xattrs + tr := tar.NewReader(&buf) + hdr, err := tr.Next() + require.NoError(t, err, "error reading dir tar header") + require.Equal(t, dir, hdr.Name, "tar dir header name mismatch") + require.Equal(t, "foo", hdr.PAXRecords[xattrTarPAXRecordsPrefix+"user.dir"], "tar header for dir xattr mismatch") + + hdr, err = tr.Next() + require.NoError(t, err, "error reading file tar header") + require.Equal(t, file, hdr.Name, "tar file header name mismatch") + require.Equal(t, "bar", hdr.PAXRecords[xattrTarPAXRecordsPrefix+"user.file"], "tar header for file xattr mismatch") +} ++++++ melange.obsinfo ++++++ --- /var/tmp/diff_new_pack.k57TwO/_old 2025-04-14 12:59:25.514876992 +0200 +++ /var/tmp/diff_new_pack.k57TwO/_new 2025-04-14 12:59:25.518877160 +0200 @@ -1,5 +1,5 @@ name: melange -version: 0.23.5 -mtime: 1743777213 -commit: a5d28473b9091cf7a45a0864bb149ae9fddfff99 +version: 0.23.6 +mtime: 1744399708 +commit: 1c075e12cf123600b57cfb8bb66b9aa107a37e08 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/melange/vendor.tar.gz /work/SRC/openSUSE:Factory/.melange.new.1907/vendor.tar.gz differ: char 5, line 1