Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package apko for openSUSE:Factory checked in at 2026-06-25 10:52:22 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/apko (Old) and /work/SRC/openSUSE:Factory/.apko.new.2088 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "apko" Thu Jun 25 10:52:22 2026 rev:120 rq:1361521 version:1.2.19 Changes: -------- --- /work/SRC/openSUSE:Factory/apko/apko.changes 2026-06-22 17:37:02.749742103 +0200 +++ /work/SRC/openSUSE:Factory/.apko.new.2088/apko.changes 2026-06-25 10:57:26.477862093 +0200 @@ -1,0 +2,19 @@ +Wed Jun 24 05:13:59 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 1.2.19: + * paths: honor `recursive` for `type: permissions`, make uid/gid + nullable (#2281) + * Lower SBOM duplicate-package log from info to debug (#2293) + * build(deps): bump golang.org/x/term from 0.43.0 to 0.44.0 + (#2277) + * build(deps): bump go.step.sm/crypto from 0.82.0 to 0.83.0 + (#2280) + * build(deps): bump google.golang.org/api from 0.283.0 to 0.285.0 + (#2287) + * build(deps): bump actions/checkout from 6.0.3 to 7.0.0 (#2291) + * build(deps): bump chainguard.dev/sdk from 0.1.57 to 0.1.74 + (#2290) + * build(deps): bump chainguard-dev/actions from 1.6.22 to 1.6.24 + (#2292) + +------------------------------------------------------------------- Old: ---- apko-1.2.18.obscpio New: ---- apko-1.2.19.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ apko.spec ++++++ --- /var/tmp/diff_new_pack.awecK1/_old 2026-06-25 10:57:28.549933613 +0200 +++ /var/tmp/diff_new_pack.awecK1/_new 2026-06-25 10:57:28.565934166 +0200 @@ -17,7 +17,7 @@ Name: apko -Version: 1.2.18 +Version: 1.2.19 Release: 0 Summary: Build OCI images from APK packages directly without Dockerfile License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.awecK1/_old 2026-06-25 10:57:28.809942588 +0200 +++ /var/tmp/diff_new_pack.awecK1/_new 2026-06-25 10:57:28.833943416 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/chainguard-dev/apko.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">refs/tags/v1.2.18</param> + <param name="revision">refs/tags/v1.2.19</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.awecK1/_old 2026-06-25 10:57:29.025950044 +0200 +++ /var/tmp/diff_new_pack.awecK1/_new 2026-06-25 10:57:29.065951424 +0200 @@ -3,6 +3,6 @@ <param name="url">https://github.com/chainguard-dev/apko</param> <param name="changesrevision">861f83f69e6fa9114405a2f7bb5cf6585ad00421</param></service><service name="tar_scm"> <param name="url">https://github.com/chainguard-dev/apko.git</param> - <param name="changesrevision">ebd9255d91996b81a9b88723efbc9d563cfd863c</param></service></servicedata> + <param name="changesrevision">5558f35b5b5fd27b50fe000f64395d8c6616216f</param></service></servicedata> (No newline at EOF) ++++++ apko-1.2.18.obscpio -> apko-1.2.19.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.18/docs/apko_file.md new/apko-1.2.19/docs/apko_file.md --- old/apko-1.2.18/docs/apko_file.md 2026-06-16 07:15:12.000000000 +0200 +++ new/apko-1.2.19/docs/apko_file.md 2026-06-23 16:10:56.000000000 +0200 @@ -210,10 +210,17 @@ - `symlink`: create a symbolic link (`ln -s`) at the path, linking to the value specified in `source` - `permissions`: sets file permissions on the file or directory at the path. - - `uid`: UID to associate with the file - - `gid`: GID to associate with the file + - `uid`: UID to associate with the file. If both `uid` and `gid` are omitted, ownership is left + untouched; if only one is set, the other defaults to `0` (root). + - `gid`: GID to associate with the file. See the note on `uid` above. - `permissions`: file permissions to set. Permissions should be specified in octal e.g. 0o755 (see `man chmod` for details). - `source`: used in `hardlink` and `symlink`, this represents the path to link to. + - `recursive`: when `true`, apply `permissions` (and `uid`/`gid` when set) to the path and, if it + is a directory, to every entry beneath it. Honored for the `directory` and `permissions` types + (ignored for `empty-file`, `hardlink`, and `symlink`). The same mode is applied to files and + directories alike, and only paths that already exist when the mutation runs are affected. + Combine with omitted `uid`/`gid` to fix permissions across an existing tree without changing + ownership. ### Includes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.18/go.mod new/apko-1.2.19/go.mod --- old/apko-1.2.18/go.mod 2026-06-16 07:15:12.000000000 +0200 +++ new/apko-1.2.19/go.mod 2026-06-23 16:10:56.000000000 +0200 @@ -3,7 +3,7 @@ go 1.26.0 require ( - chainguard.dev/sdk v0.1.57 + chainguard.dev/sdk v0.1.74 github.com/chainguard-dev/clog v1.8.0 github.com/charmbracelet/log v1.0.0 github.com/go-git/go-git/v5 v5.19.1 @@ -25,13 +25,13 @@ go.lsp.dev/uri v0.3.0 go.opentelemetry.io/otel v1.44.0 go.opentelemetry.io/otel/trace v1.44.0 - go.step.sm/crypto v0.82.0 + go.step.sm/crypto v0.83.0 golang.org/x/oauth2 v0.36.0 golang.org/x/sync v0.21.0 golang.org/x/sys v0.46.0 - golang.org/x/term v0.43.0 + golang.org/x/term v0.44.0 golang.org/x/time v0.15.0 - google.golang.org/api v0.283.0 + google.golang.org/api v0.285.0 gopkg.in/ini.v1 v1.67.3 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.36.2 @@ -127,12 +127,12 @@ go.opentelemetry.io/otel/metric v1.44.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect - golang.org/x/crypto v0.52.0 // indirect + golang.org/x/crypto v0.53.0 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/net v0.55.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/text v0.38.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad // indirect google.golang.org/grpc v1.81.1 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.18/go.sum new/apko-1.2.19/go.sum --- old/apko-1.2.18/go.sum 2026-06-16 07:15:12.000000000 +0200 +++ new/apko-1.2.19/go.sum 2026-06-23 16:10:56.000000000 +0200 @@ -1,7 +1,7 @@ chainguard.dev/go-grpc-kit v0.17.17 h1:Jwhc0zyUwQbC2hNcsi+YMeUX/JUnM+dXVCkTw6wtPzs= chainguard.dev/go-grpc-kit v0.17.17/go.mod h1:qn0meP6RtrbLicE1bgBZnnVU9dvX95eLs0x0T6kZ+b4= -chainguard.dev/sdk v0.1.57 h1:PDfKlCtKiElIpFLKh6C0GA2ueuTZeWmymrh0AxoG8i0= -chainguard.dev/sdk v0.1.57/go.mod h1:LWSnEw2+4Y6dbmXyFHqbiyEKMWhG5FtWd/q8A82MX48= +chainguard.dev/sdk v0.1.74 h1:igqNf9pavV8+yAM8CI5ICGgeIgNfzy1ps8ILw2Pecto= +chainguard.dev/sdk v0.1.74/go.mod h1:t8m9wn52ylTmEaOCQWUkfxvXOIQe5xD3UsvYn1CP7Bo= cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA= cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= @@ -272,8 +272,8 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.step.sm/crypto v0.82.0 h1:JOT8b/7Jh4My3mxE4U7UkuaN2sUGkZ8fnjznXaTGoRE= -go.step.sm/crypto v0.82.0/go.mod h1:qyLTv666WJ6ImFPUjljux+684Y/GGYUjAZcKCnc6yBs= +go.step.sm/crypto v0.83.0 h1:llCPEiL2f+kUPY+CUJrOCsuSBoJZ2qooFG9EqGast6w= +go.step.sm/crypto v0.83.0/go.mod h1:qyLTv666WJ6ImFPUjljux+684Y/GGYUjAZcKCnc6yBs= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= @@ -285,22 +285,22 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -329,8 +329,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -338,28 +338,28 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/api v0.283.0 h1:0lkp8u0MPwJVHqRL+nJlMAoZVVzbmiXmFHXMOTmSPik= -google.golang.org/api v0.283.0/go.mod h1:6Wssta4c5n9qHq5CBhmlai5h/PUa1djdDAIhYEHyvcM= +google.golang.org/api v0.285.0 h1:B7eHHoKGAX/LrPkQvhQqnGwjgWxofbdGwCTQvpm8FkM= +google.golang.org/api v0.285.0/go.mod h1:NlOlUIr8MPoIhT9Bb/oUnRuHbJOLwxb6JSYJM8Yz+jQ= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 h1:PvEgGJf9C/1u5CHkInMg7UFYYUoiaQmW2LbtH0pjB78= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad h1:45WmJvIV6C2+O/jjLkPUH+F3aOj/1miDoU2DD0+NWbg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.18/pkg/build/paths.go new/apko-1.2.19/pkg/build/paths.go --- old/apko-1.2.18/pkg/build/paths.go 2026-06-16 07:15:12.000000000 +0200 +++ new/apko-1.2.19/pkg/build/paths.go 2026-06-23 16:10:56.000000000 +0200 @@ -37,9 +37,28 @@ } func mutatePermissions(fsys apkfs.FullFS, o *options.Options, mut types.PathMutation) error { + if mut.Recursive { + return mutatePermissionsRecursive(fsys, mut.Path, mut.Permissions, mut.UID, mut.GID) + } return mutatePermissionsDirect(fsys, mut.Path, mut.Permissions, mut.UID, mut.GID) } +// mutatePermissionsRecursive applies perms (and uid/gid, when set) to path and, +// if path is a directory, to every entry beneath it. WalkDir does not follow +// symlinks, so a symlink entry is mutated in place rather than its target's +// tree being descended. +func mutatePermissionsRecursive(fsys apkfs.FullFS, path string, perms uint32, uid types.UID, gid types.GID) error { + return fs.WalkDir(fsys, path, func(p string, _ fs.DirEntry, err error) error { + if err != nil { + return err + } + if err := mutatePermissionsDirect(fsys, p, perms, uid, gid); err != nil { + return fmt.Errorf("mutating permissions for path %q: %w", p, err) + } + return nil + }) +} + // unixModeToFsMode converts raw unix permission bits (as written in apko // configs, e.g. 0o2775) to fs.FileMode, mapping setuid/setgid/sticky to // their Go fs.FileMode equivalents. @@ -57,37 +76,40 @@ return mode } -func mutatePermissionsDirect(fsys apkfs.FullFS, path string, perms, uid, gid uint32) error { +func mutatePermissionsDirect(fsys apkfs.FullFS, path string, perms uint32, uid types.UID, gid types.GID) error { target := path if err := fsys.Chmod(target, unixModeToFsMode(perms)); err != nil { return fmt.Errorf("chmod %q: %w", target, err) } - if err := fsys.Chown(target, int(uid), int(gid)); err != nil { + + // Only chown when at least one of uid/gid is set. Omitting both leaves + // ownership untouched (mode-only), which is what makes a recursive + // "permissions" mutation safe to run over a pre-existing tree. When only + // one of the two is set, the other defaults to 0 (root), preserving the + // historical single-path behavior. + if uid == nil && gid == nil { + return nil + } + u, g := uint32(0), uint32(0) + if uid != nil { + u = *uid + } + if gid != nil { + g = *gid + } + if err := fsys.Chown(target, int(u), int(g)); err != nil { return fmt.Errorf("chown %q: %w", target, err) } return nil } +// mutateDirectory creates the directory tree. Applying the requested +// permissions and ownership (recursively, when mut.Recursive is set) is left to +// the mutatePermissions follow-up that mutatePaths runs for every non-permissions +// mutation, so the recursive walk lives in exactly one place. func mutateDirectory(fsys apkfs.FullFS, o *options.Options, mut types.PathMutation) error { - perms := unixModeToFsMode(mut.Permissions) - - if err := fsys.MkdirAll(mut.Path, perms); err != nil { - return err - } - - if mut.Recursive { - return fs.WalkDir(fsys, mut.Path, func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if err := mutatePermissionsDirect(fsys, path, mut.Permissions, mut.UID, mut.GID); err != nil { - return fmt.Errorf("mutating permissions for path %q: %w", path, err) - } - return nil - }) - } - return nil + return fsys.MkdirAll(mut.Path, unixModeToFsMode(mut.Permissions)) } func ensureParentDirectory(fsys apkfs.FullFS, path string) error { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.18/pkg/build/paths_test.go new/apko-1.2.19/pkg/build/paths_test.go --- old/apko-1.2.18/pkg/build/paths_test.go 2026-06-16 07:15:12.000000000 +0200 +++ new/apko-1.2.19/pkg/build/paths_test.go 2026-06-23 16:10:56.000000000 +0200 @@ -95,13 +95,15 @@ fsys := apkfs.NewMemFS() require.NoError(t, fsys.MkdirAll("/var/log/kolla/glance", 0o755)) - mut := types.PathMutation{ + // mutateDirectory only creates the tree; mutatePaths runs the recursive + // permissions follow-up, so exercise the full pipeline. + ic := &types.ImageConfiguration{Paths: []types.PathMutation{{ Path: "/var/log/kolla", Type: "directory", Permissions: 0o2775, Recursive: true, - } - require.NoError(t, mutateDirectory(fsys, nil, mut)) + }}} + require.NoError(t, mutatePaths(fsys, nil, ic)) for _, path := range []string{"/var/log/kolla", "/var/log/kolla/glance"} { fi, err := fsys.Stat(path) @@ -113,7 +115,185 @@ func TestMutatePermissionsMissingPath(t *testing.T) { fsys := apkfs.NewMemFS() - require.Error(t, mutatePermissionsDirect(fsys, "/does/not/exist", 0o2775, 0, 0)) + require.Error(t, mutatePermissionsDirect(fsys, "/does/not/exist", 0o2775, nil, nil)) +} + +// TestMutatePermissionsRecursive applies a "permissions" mutation recursively +// over a pre-existing tree and asserts perms + ownership reach every entry. +func TestMutatePermissionsRecursive(t *testing.T) { + fsys := apkfs.NewMemFS() + require.NoError(t, fsys.MkdirAll("/srv/data/sub", 0o700)) + f, err := fsys.Create("/srv/data/sub/file") + require.NoError(t, err) + require.NoError(t, f.Close()) + + ic := &types.ImageConfiguration{Paths: []types.PathMutation{{ + Path: "/srv/data", + Type: "permissions", + Permissions: 0o2750, + UID: new(uint32(64045)), + GID: new(uint32(64045)), + Recursive: true, + }}} + require.NoError(t, mutatePaths(fsys, nil, ic)) + + for _, path := range []string{"/srv/data", "/srv/data/sub", "/srv/data/sub/file"} { + fi, err := fsys.Stat(path) + require.NoError(t, err) + require.Equal(t, fs.FileMode(0o750), fi.Mode().Perm(), "perms wrong on %s", path) + require.Equal(t, fs.ModeSetgid, fi.Mode()&fs.ModeSetgid, "setgid missing on %s", path) + require.Equal(t, 64045, fileUID(t, fsys, path), "uid wrong on %s", path) + require.Equal(t, 64045, fileGID(t, fsys, path), "gid wrong on %s", path) + } +} + +// fileUID/fileGID read ownership from the memfs FileInfo, whose Sys() returns a +// *tar.Header carrying Uid/Gid. +func fileUID(t *testing.T, fsys apkfs.FullFS, path string) int { + t.Helper() + fi, err := fsys.Stat(path) + require.NoError(t, err) + hdr, ok := fi.Sys().(*tar.Header) + require.True(t, ok, "unexpected FileInfo.Sys() type for %s", path) + return hdr.Uid +} + +func fileGID(t *testing.T, fsys apkfs.FullFS, path string) int { + t.Helper() + fi, err := fsys.Stat(path) + require.NoError(t, err) + hdr, ok := fi.Sys().(*tar.Header) + require.True(t, ok, "unexpected FileInfo.Sys() type for %s", path) + return hdr.Gid +} + +// TestMutatePermissionsRecursiveModeOnly verifies that omitting uid/gid leaves +// ownership untouched while still applying the mode recursively. +func TestMutatePermissionsRecursiveModeOnly(t *testing.T) { + fsys := apkfs.NewMemFS() + require.NoError(t, fsys.MkdirAll("/srv/data/sub", 0o700)) + require.NoError(t, fsys.Chown("/srv/data", 1000, 1000)) + require.NoError(t, fsys.Chown("/srv/data/sub", 1000, 1000)) + + ic := &types.ImageConfiguration{Paths: []types.PathMutation{{ + Path: "/srv/data", + Type: "permissions", + Permissions: 0o755, + Recursive: true, + }}} + require.NoError(t, mutatePaths(fsys, nil, ic)) + + for _, path := range []string{"/srv/data", "/srv/data/sub"} { + fi, err := fsys.Stat(path) + require.NoError(t, err) + require.Equal(t, fs.FileMode(0o755), fi.Mode().Perm(), "perms wrong on %s", path) + // Ownership must be left as-is (1000:1000), not reset to root. + require.Equal(t, 1000, fileUID(t, fsys, path), "ownership changed on %s", path) + } +} + +// TestMutatePermissionsRecursiveOnFile applies a recursive "permissions" +// mutation to a single regular file: it must affect just that file, no error. +func TestMutatePermissionsRecursiveOnFile(t *testing.T) { + fsys := apkfs.NewMemFS() + require.NoError(t, fsys.MkdirAll("/usr/bin", 0o755)) + f, err := fsys.Create("/usr/bin/tool") + require.NoError(t, err) + require.NoError(t, f.Close()) + + require.NoError(t, mutatePermissionsRecursive(fsys, "/usr/bin/tool", 0o4755, new(uint32(0)), new(uint32(0)))) + + fi, err := fsys.Stat("/usr/bin/tool") + require.NoError(t, err) + require.Equal(t, fs.ModeSetuid, fi.Mode()&fs.ModeSetuid) + require.Equal(t, fs.FileMode(0o755), fi.Mode().Perm()) +} + +func TestMutatePermissionsRecursiveMissingPath(t *testing.T) { + fsys := apkfs.NewMemFS() + require.Error(t, mutatePermissionsRecursive(fsys, "/does/not/exist", 0o755, nil, nil)) +} + +// TestMutatePermissionsNonRecursiveDoesNotRecurse guards the original bug: a +// non-recursive "permissions" mutation must touch only the named path, never +// its children. +func TestMutatePermissionsNonRecursiveDoesNotRecurse(t *testing.T) { + fsys := apkfs.NewMemFS() + require.NoError(t, fsys.MkdirAll("/srv/data/sub", 0o700)) + + ic := &types.ImageConfiguration{Paths: []types.PathMutation{{ + Path: "/srv/data", + Type: "permissions", + Permissions: 0o755, + // Recursive intentionally omitted (false). + }}} + require.NoError(t, mutatePaths(fsys, nil, ic)) + + top, err := fsys.Stat("/srv/data") + require.NoError(t, err) + require.Equal(t, fs.FileMode(0o755), top.Mode().Perm(), "top path should change") + + child, err := fsys.Stat("/srv/data/sub") + require.NoError(t, err) + require.Equal(t, fs.FileMode(0o700), child.Mode().Perm(), "child must be untouched without recursive") +} + +// TestMutatePermissionsExplicitZeroResetsOwnership verifies an explicit +// uid:0/gid:0 (non-nil) resets ownership, distinct from omitting them (which +// leaves it untouched, see TestMutatePermissionsRecursiveModeOnly). This is the +// core distinction the nullable uid/gid type enables. +func TestMutatePermissionsExplicitZeroResetsOwnership(t *testing.T) { + fsys := apkfs.NewMemFS() + require.NoError(t, fsys.MkdirAll("/srv/data", 0o755)) + require.NoError(t, fsys.Chown("/srv/data", 1000, 1000)) + + require.NoError(t, mutatePermissionsDirect(fsys, "/srv/data", 0o755, new(uint32(0)), new(uint32(0)))) + + require.Equal(t, 0, fileUID(t, fsys, "/srv/data"), "explicit uid:0 must reset owner to root") + require.Equal(t, 0, fileGID(t, fsys, "/srv/data"), "explicit gid:0 must reset group to root") +} + +// TestMutatePermissionsOneOwnerDefaultsOtherToZero verifies that setting only +// one of uid/gid defaults the other to 0 (root), preserving historical behavior. +func TestMutatePermissionsOneOwnerDefaultsOtherToZero(t *testing.T) { + fsys := apkfs.NewMemFS() + require.NoError(t, fsys.MkdirAll("/srv/data", 0o755)) + + // uid set, gid omitted -> gid defaults to 0. + require.NoError(t, fsys.Chown("/srv/data", 1000, 1000)) + require.NoError(t, mutatePermissionsDirect(fsys, "/srv/data", 0o755, new(uint32(42)), nil)) + require.Equal(t, 42, fileUID(t, fsys, "/srv/data")) + require.Equal(t, 0, fileGID(t, fsys, "/srv/data")) + + // gid set, uid omitted -> uid defaults to 0. + require.NoError(t, fsys.Chown("/srv/data", 1000, 1000)) + require.NoError(t, mutatePermissionsDirect(fsys, "/srv/data", 0o755, nil, new(uint32(7)))) + require.Equal(t, 0, fileUID(t, fsys, "/srv/data")) + require.Equal(t, 7, fileGID(t, fsys, "/srv/data")) +} + +// TestMutatePermissionsRecursiveWithSymlink ensures a symlink inside the tree +// does not abort the recursive walk (WalkDir does not follow symlinks) and that +// regular entries are still mutated. +func TestMutatePermissionsRecursiveWithSymlink(t *testing.T) { + fsys := apkfs.NewMemFS() + require.NoError(t, fsys.MkdirAll("/srv/data", 0o700)) + f, err := fsys.Create("/srv/data/real") + require.NoError(t, err) + require.NoError(t, f.Close()) + require.NoError(t, fsys.Symlink("/srv/data/real", "/srv/data/link")) + + ic := &types.ImageConfiguration{Paths: []types.PathMutation{{ + Path: "/srv/data", + Type: "permissions", + Permissions: 0o750, + Recursive: true, + }}} + require.NoError(t, mutatePaths(fsys, nil, ic)) + + fi, err := fsys.Stat("/srv/data/real") + require.NoError(t, err) + require.Equal(t, fs.FileMode(0o750), fi.Mode().Perm(), "regular file in tree must be mutated") } // TestPathMutationSpecialBitsReachTar covers the full chain: paths mutation → @@ -133,8 +313,8 @@ Path: "var/log/kolla", Type: "directory", Permissions: 0o2775, - UID: 0, - GID: 42400, + UID: new(uint32(0)), + GID: new(uint32(42400)), }}, } require.NoError(t, mutatePaths(fsys, nil, ic)) @@ -165,7 +345,7 @@ require.NoError(t, err) require.NoError(t, f.Close()) - require.NoError(t, mutatePermissionsDirect(fsys, "/usr/bin/suidtool", 0o4755, 0, 0)) + require.NoError(t, mutatePermissionsDirect(fsys, "/usr/bin/suidtool", 0o4755, new(uint32(0)), new(uint32(0)))) fi, err := fsys.Stat("/usr/bin/suidtool") require.NoError(t, err) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.18/pkg/build/types/schema.json new/apko-1.2.19/pkg/build/types/schema.json --- old/apko-1.2.18/pkg/build/types/schema.json 2026-06-16 07:15:12.000000000 +0200 +++ new/apko-1.2.19/pkg/build/types/schema.json 2026-06-23 16:10:56.000000000 +0200 @@ -270,11 +270,11 @@ }, "uid": { "type": "integer", - "description": "The mutation's desired user ID" + "description": "The mutation's desired user ID. If unset (nil), ownership is left\nuntouched unless gid is set (see Recursive)." }, "gid": { "type": "integer", - "description": "The mutation's desired group ID" + "description": "The mutation's desired group ID. If unset (nil), ownership is left\nuntouched unless uid is set (see Recursive)." }, "permissions": { "type": "integer", @@ -286,7 +286,7 @@ }, "recursive": { "type": "boolean", - "description": "Toggle whether to mutate recursively" + "description": "Toggle whether to mutate recursively. Honored for the \"directory\" and\n\"permissions\" types: the permissions, and uid/gid when set, are applied\nto every entry beneath the path." } }, "additionalProperties": false, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.18/pkg/build/types/types.go new/apko-1.2.19/pkg/build/types/types.go --- old/apko-1.2.18/pkg/build/types/types.go 2026-06-16 07:15:12.000000000 +0200 +++ new/apko-1.2.19/pkg/build/types/types.go 2026-06-23 16:10:56.000000000 +0200 @@ -68,6 +68,10 @@ type GID *uint32 +// UID is a nullable user ID. A nil UID means "unset" (distinct from an +// explicit 0), which lets path mutations leave ownership untouched. +type UID *uint32 + type Group struct { // Required: The name of the group GroupName string `json:"groupname,omitempty"` @@ -84,15 +88,19 @@ // // This can be one of: directory, empty-file, hardlink, symlink, permissions Type string `json:"type,omitempty"` - // The mutation's desired user ID - UID uint32 `json:"uid,omitempty"` - // The mutation's desired group ID - GID uint32 `json:"gid,omitempty"` + // The mutation's desired user ID. If unset (nil), ownership is left + // untouched unless gid is set (see Recursive). + UID UID `json:"uid,omitempty" yaml:"uid,omitempty"` + // The mutation's desired group ID. If unset (nil), ownership is left + // untouched unless uid is set (see Recursive). + GID GID `json:"gid,omitempty" yaml:"gid,omitempty"` // The permission bits for the path Permissions uint32 `json:"permissions,omitempty"` // The source path to mutate Source string `json:"source,omitempty"` - // Toggle whether to mutate recursively + // Toggle whether to mutate recursively. Honored for the "directory" and + // "permissions" types: the permissions, and uid/gid when set, are applied + // to every entry beneath the path. Recursive bool `json:"recursive,omitempty"` } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.18/pkg/build/types/types_test.go new/apko-1.2.19/pkg/build/types/types_test.go --- old/apko-1.2.18/pkg/build/types/types_test.go 2026-06-16 07:15:12.000000000 +0200 +++ new/apko-1.2.19/pkg/build/types/types_test.go 2026-06-23 16:10:56.000000000 +0200 @@ -257,6 +257,59 @@ } } +// Test_YAML_PathMutation_UID_GID locks the wire semantics the recursive +// permissions feature depends on: an omitted uid/gid must unmarshal to nil +// (leave ownership untouched), distinct from an explicit 0. +func Test_YAML_PathMutation_UID_GID(t *testing.T) { + const raw = ` +paths: + - path: /omitted + type: permissions + permissions: 0o755 + - path: /explicit-zero + type: permissions + permissions: 0o750 + uid: 0 + gid: 0 + - path: /values + type: permissions + permissions: 0o750 + uid: 1000 + gid: 1001 +` + var ic ImageConfiguration + require.NoError(t, yaml.Unmarshal([]byte(raw), &ic)) + require.Len(t, ic.Paths, 3) + + // omitted → nil on both + require.Nil(t, ic.Paths[0].UID, "omitted uid should be nil") + require.Nil(t, ic.Paths[0].GID, "omitted gid should be nil") + + // explicit 0 → non-nil pointer to 0 (distinct from omitted) + require.NotNil(t, ic.Paths[1].UID) + require.Equal(t, uint32(0), *ic.Paths[1].UID) + require.NotNil(t, ic.Paths[1].GID) + require.Equal(t, uint32(0), *ic.Paths[1].GID) + + // explicit values round-trip + require.NotNil(t, ic.Paths[2].UID) + require.Equal(t, uint32(1000), *ic.Paths[2].UID) + require.NotNil(t, ic.Paths[2].GID) + require.Equal(t, uint32(1001), *ic.Paths[2].GID) + + // Round-trip through marshal/unmarshal must preserve nil vs explicit-0. + out, err := yaml.Marshal(&ic) + require.NoError(t, err) + var rt ImageConfiguration + require.NoError(t, yaml.Unmarshal(out, &rt)) + require.Nil(t, rt.Paths[0].UID, "nil uid must survive round-trip") + require.Nil(t, rt.Paths[0].GID, "nil gid must survive round-trip") + require.NotNil(t, rt.Paths[1].UID, "explicit 0 uid must survive round-trip") + require.Equal(t, uint32(0), *rt.Paths[1].UID) + require.NotNil(t, rt.Paths[1].GID, "explicit 0 gid must survive round-trip") + require.Equal(t, uint32(0), *rt.Paths[1].GID) +} + // Ensure marshalling YAML from a User // does not result in unexpected GID=0 func Test_YAML_Marshalling_UID_GID_mapping(t *testing.T) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.18/pkg/sbom/generator/spdx/spdx.go new/apko-1.2.19/pkg/sbom/generator/spdx/spdx.go --- old/apko-1.2.18/pkg/sbom/generator/spdx/spdx.go 2026-06-16 07:15:12.000000000 +0200 +++ new/apko-1.2.19/pkg/sbom/generator/spdx/spdx.go 2026-06-23 16:10:56.000000000 +0200 @@ -167,7 +167,7 @@ seenIDs[doc.Packages[i].ID] = struct{}{} dedupedPackages = append(dedupedPackages, doc.Packages[i]) } else { - clog.FromContext(ctx).Info("duplicate package ID found in SBOM, deduplicating package...", "ID", doc.Packages[i].ID) + clog.FromContext(ctx).Debug("duplicate package ID found in SBOM, deduplicating package...", "ID", doc.Packages[i].ID) } } doc.Packages = dedupedPackages ++++++ apko.obsinfo ++++++ --- /var/tmp/diff_new_pack.awecK1/_old 2026-06-25 10:57:31.990052353 +0200 +++ /var/tmp/diff_new_pack.awecK1/_new 2026-06-25 10:57:31.998052629 +0200 @@ -1,5 +1,5 @@ name: apko -version: 1.2.18 -mtime: 1781586912 -commit: ebd9255d91996b81a9b88723efbc9d563cfd863c +version: 1.2.19 +mtime: 1782223856 +commit: 5558f35b5b5fd27b50fe000f64395d8c6616216f ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/apko/vendor.tar.gz /work/SRC/openSUSE:Factory/.apko.new.2088/vendor.tar.gz differ: char 13, line 1
