Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package melange for openSUSE:Factory checked in at 2026-10-12 22:06:04 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/melange (Old) and /work/SRC/openSUSE:Factory/.melange.new.3698736 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "melange" Mon Oct 12 22:06:04 2026 rev:184 rq:1384146 version:0.62.1 Changes: -------- --- /work/SRC/openSUSE:Factory/melange/melange.changes 2026-10-08 12:00:00.433518526 +0200 +++ /work/SRC/openSUSE:Factory/.melange.new.3698736/melange.changes 2026-10-12 22:06:22.114184468 +0200 @@ -1,0 +2,14 @@ +Mon Oct 12 04:46:13 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 0.62.1: + * sca: use libcrypto.so.4 for go-msft 1.26.9+ via + microsoft_toolset_version (#2694) + * sca: drop obsolete experiments (#2693) + * linter: recognise -docs and versioned -doc-<version> as doc + packages (#2686) + * fix(renovate): read the yam config from the config file's + repository (#2690) + * build(deps): bump golang.org/x/sync (#2691) + * build(deps): bump github.com/go-git/go-git/v5 (#2687) + +------------------------------------------------------------------- Old: ---- melange-0.62.0.obscpio New: ---- melange-0.62.1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ melange.spec ++++++ --- /var/tmp/diff_new_pack.ju0HfL/_old 2026-10-12 22:06:24.547286129 +0200 +++ /var/tmp/diff_new_pack.ju0HfL/_new 2026-10-12 22:06:24.549286212 +0200 @@ -17,7 +17,7 @@ Name: melange -Version: 0.62.0 +Version: 0.62.1 Release: 0 Summary: Build APKs from source code License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.ju0HfL/_old 2026-10-12 22:06:24.595288134 +0200 +++ /var/tmp/diff_new_pack.ju0HfL/_new 2026-10-12 22:06:24.599288301 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/chainguard-dev/melange.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">refs/tags/v0.62.0</param> + <param name="revision">refs/tags/v0.62.1</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.ju0HfL/_old 2026-10-12 22:06:24.621289220 +0200 +++ /var/tmp/diff_new_pack.ju0HfL/_new 2026-10-12 22:06:24.626289429 +0200 @@ -3,6 +3,6 @@ <param name="url">https://github.com/chainguard-dev/melange</param> <param name="changesrevision">3f6115b820985d70ca3c93cdf8519c1b3b4cfe81</param></service><service name="tar_scm"> <param name="url">https://github.com/chainguard-dev/melange.git</param> - <param name="changesrevision">0ae0a5c52b587563b72755df1c7520b082712517</param></service></servicedata> + <param name="changesrevision">88faac23efa1fae08a915518e6782a455d5dfd3f</param></service></servicedata> (No newline at EOF) ++++++ melange-0.62.0.obscpio -> melange-0.62.1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.62.0/go.mod new/melange-0.62.1/go.mod --- old/melange-0.62.0/go.mod 2026-10-07 22:14:27.000000000 +0200 +++ new/melange-0.62.1/go.mod 2026-10-11 13:26:29.000000000 +0200 @@ -13,7 +13,7 @@ github.com/dprotaso/go-yit v0.0.0-20250513224043-18a80f8f6df4 github.com/dustin/go-humanize v1.1.0 github.com/github/go-spdx/v2 v2.7.0 - github.com/go-git/go-git/v5 v5.19.2 + github.com/go-git/go-git/v5 v5.19.3 github.com/google/go-cmp v0.7.0 github.com/google/go-containerregistry v0.22.1 github.com/google/go-licenses/v2 v2.0.1 @@ -43,7 +43,7 @@ go.yaml.in/yaml/v2 v2.4.4 golang.org/x/crypto v0.57.0 golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba - golang.org/x/sync v0.23.0 + golang.org/x/sync v0.24.0 golang.org/x/sys v0.48.0 golang.org/x/term v0.46.0 golang.org/x/text v0.42.0 @@ -96,7 +96,7 @@ github.com/erofs/go-erofs v0.3.2-0.20260804074615-52cc42c5291c // indirect github.com/felixge/httpsnoop v1.1.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.9.1 // indirect + github.com/go-git/go-billy/v5 v5.9.2 // indirect github.com/go-jose/go-jose/v3 v3.0.5 // indirect github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.4 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.62.0/go.sum new/melange-0.62.1/go.sum --- old/melange-0.62.0/go.sum 2026-10-07 22:14:27.000000000 +0200 +++ new/melange-0.62.1/go.sum 2026-10-11 13:26:29.000000000 +0200 @@ -123,12 +123,12 @@ github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.9.1 h1:8U73XiOTfINdItHVa6z4Gv7ToObcZ6grkqQbLryLCdA= -github.com/go-git/go-billy/v5 v5.9.1/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0= +github.com/go-git/go-billy/v5 v5.9.2 h1:OXFSRyz4g20upsGDJgQG9Bak1l/ZEv8GHVYB52O71sE= +github.com/go-git/go-billy/v5 v5.9.2/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY= -github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk= +github.com/go-git/go-git/v5 v5.19.3 h1:qHttbZ+Am7wEjdTpR1XMQ4rl42FK9SIXzZ4o9x7s6M4= +github.com/go-git/go-git/v5 v5.19.3/go.mod h1:Ye4C8dVigkqrv9UsB4NMdSLV4yAIkLiIhW61gcOyhl4= github.com/go-jose/go-jose/v3 v3.0.5 h1:BLLJWbC4nMZOfuPVxoZIxeYsn6Nl2r1fITaJ78UQlVQ= github.com/go-jose/go-jose/v3 v3.0.5/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= @@ -429,8 +429,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= -golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= +golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= +golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.62.0/pkg/linter/linter_test.go new/melange-0.62.1/pkg/linter/linter_test.go --- old/melange-0.62.0/pkg/linter/linter_test.go 2026-10-07 22:14:27.000000000 +0200 +++ new/melange-0.62.1/pkg/linter/linter_test.go 2026-10-11 13:26:29.000000000 +0200 @@ -309,6 +309,64 @@ pkgname: "regular-doc", pass: true, }, { + // Plural. jujutsu-docs and squashfs-tools-docs both ship this way. + dirFunc: mkfile(t, "usr/share/man/man1/foo.1"), + linter: "maninfo", + pkgname: "regular-docs", + pass: true, + }, { + // Version-streamed doc subpackage, the shape split/manpages + // produces for a versioned package: podman-doc-6.1, + // pdns-auth-doc-5.0. + dirFunc: mkfile(t, "usr/share/man/man1/foo.1"), + linter: "maninfo", + pkgname: "podman-doc-6.1", + pass: true, + }, { + // A date, not a dotted version: dwarf-tools-doc-20210528. + dirFunc: mkfile(t, "usr/share/man/man1/foo.1"), + linter: "maninfo", + pkgname: "dwarf-tools-doc-20210528", + pass: true, + }, { + dirFunc: mkfile(t, "usr/share/man/man1/foo.1"), + linter: "maninfo", + pkgname: "regular-docs-5.0", + pass: true, + }, { + // A version before -doc is the common case and already worked: + // rust-1.95-doc. + dirFunc: mkfile(t, "usr/share/info/test.info"), + linter: "maninfo", + pkgname: "rust-1.95-doc", + pass: true, + }, { + // Must still fail. "-doc" has to be a whole trailing component, + // not a prefix of the last word. + dirFunc: mkfile(t, "usr/share/man/man1/foo.1"), + linter: "maninfo", + pkgname: "foo-document", + pass: false, + }, { + // Must still fail: no separator before "doc". + dirFunc: mkfile(t, "usr/share/man/man1/foo.1"), + linter: "maninfo", + pkgname: "docker", + pass: false, + }, { + // Must still fail: -man is deliberately not treated as a doc + // package. caddy-man is the only instance in the tree. + dirFunc: mkfile(t, "usr/share/man/man1/foo.1"), + linter: "maninfo", + pkgname: "caddy-man", + pass: false, + }, { + // Must still fail: a trailing version alone is not a doc package. + dirFunc: mkfile(t, "usr/share/man/man1/foo.1"), + linter: "maninfo", + pkgname: "openstack-barbican-2026.1", + pass: false, + }, { dirFunc: mkfile(t, "usr/share/man/man8/bar.8.gz"), linter: "maninfo", pkgname: "regular-package", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.62.0/pkg/linter/linters/maninfo.go new/melange-0.62.1/pkg/linter/linters/maninfo.go --- old/melange-0.62.0/pkg/linter/linters/maninfo.go 2026-10-07 22:14:27.000000000 +0200 +++ new/melange-0.62.1/pkg/linter/linters/maninfo.go 2026-10-11 13:26:29.000000000 +0200 @@ -18,13 +18,37 @@ "context" "fmt" "io/fs" - "strings" + "regexp" "chainguard.dev/melange/pkg/config" ) +// docPackageRegex matches the names of packages whose job is to hold +// documentation, and which this linter therefore skips. +// +// A plain HasSuffix(pkgname, "-doc") test is too narrow for two naming +// shapes already in wide use, and it fails them while they are doing +// exactly the right thing: +// +// - the plural, "-docs": jujutsu-docs, squashfs-tools-docs +// - a version-streamed doc subpackage, "<name>-doc-<version>", which is +// what a versioned package produces when it splits its manual pages: +// podman-doc-6.1, pdns-auth-doc-5.0, dwarf-tools-doc-20210528 +// +// Measured over the 433 packages in one repository that ship manual pages: +// the suffix test flags 50 of them, and 10 of those 50 -- 962 of the 1,036 +// pages -- are correctly packaged documentation caught by one of the two +// shapes above. So 93% of what the narrow test reports is noise, which is +// the main reason this linter has not been promoted out of the warn set. +// +// Deliberately not matched: "-man". A package holding only manual pages is +// arguably a documentation package whatever it is called, but "-doc" is the +// convention split/manpages produces, and widening the rule to a second +// word trades a little noise for a weaker check. +var docPackageRegex = regexp.MustCompile(`-docs?(?:-[0-9][a-zA-Z0-9.]*)?$`) + func ManInfoLinter(ctx context.Context, _ *config.Configuration, pkgname string, fsys fs.FS) error { - if strings.HasSuffix(pkgname, "-doc") { + if docPackageRegex.MatchString(pkgname) { return nil } return AllPaths(ctx, pkgname, fsys, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.62.0/pkg/renovate/renovate.go new/melange-0.62.1/pkg/renovate/renovate.go --- old/melange-0.62.0/pkg/renovate/renovate.go 2026-10-07 22:14:27.000000000 +0200 +++ new/melange-0.62.1/pkg/renovate/renovate.go 2026-10-11 13:26:29.000000000 +0200 @@ -16,10 +16,16 @@ import ( "context" + "errors" + "fmt" + "io" + "io/fs" "os" + "path/filepath" "runtime" "strconv" + "github.com/chainguard-dev/yam/pkg/util" "github.com/chainguard-dev/yam/pkg/yam/formatted" apko_types "chainguard.dev/apko/pkg/build/types" @@ -127,7 +133,10 @@ } defer configFile.Close() - enc := formatted.NewEncoder(configFile).AutomaticConfig() + enc, err := rc.encoder(configFile) + if err != nil { + return err + } if err := enc.Encode(rc.Configuration.Root().Content[0]); err != nil { return err @@ -135,3 +144,45 @@ return nil } + +// encoder returns a yam encoder configured from the nearest .yam.yaml at or +// above the config file's directory, so the output follows the formatting +// rules of the repository the file lives in rather than whatever happens to +// be in the process working directory. Callers such as bots run with a +// working directory outside the checkout; without this they wrote with no +// gap expressions, which drops the blank line before a comment block and +// makes yaml.v3 re-attach the block to the next item on the next parse. +// When no .yam.yaml is found, fall back to yam's own lookup in the working +// directory to preserve the existing CLI behaviour. +func (rc *RenovationContext) encoder(w io.Writer) (formatted.Encoder, error) { + dir, err := filepath.Abs(filepath.Dir(rc.Context.ConfigFile)) + if err != nil { + return formatted.Encoder{}, err + } + + for { + path := filepath.Join(dir, util.ConfigFileName) + f, err := os.Open(path) + if err == nil { + defer f.Close() + opts, err := formatted.ReadConfigFrom(f) + if err != nil { + return formatted.Encoder{}, fmt.Errorf("reading %s: %w", path, err) + } + if opts.Indent == 0 { + // Match the yam CLI default when the config omits indent. + opts.Indent = 2 + } + return formatted.NewEncoder(w).UseOptions(*opts) + } + if !errors.Is(err, fs.ErrNotExist) { + return formatted.Encoder{}, err + } + + parent := filepath.Dir(dir) + if parent == dir { + return formatted.NewEncoder(w).AutomaticConfig(), nil + } + dir = parent + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.62.0/pkg/renovate/renovate_test.go new/melange-0.62.1/pkg/renovate/renovate_test.go --- old/melange-0.62.0/pkg/renovate/renovate_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/melange-0.62.1/pkg/renovate/renovate_test.go 2026-10-11 13:26:29.000000000 +0200 @@ -0,0 +1,153 @@ +// Copyright 2026 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 renovate + +import ( + "os" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" + "gopkg.in/yaml.v3" +) + +// A config whose pipeline has a comment block with a blank line on each +// side. yaml.v3 stores the trailing blank line inside the head comment of +// the next item; with gap expressions yam reproduces the layout, without +// them it emits "comment, blank line, item", which yaml.v3 parses next time +// as a foot comment on that item's first key. +const commentedConfig = `package: + name: example + version: 1.0.0 + epoch: 0 + description: example package + +environment: + contents: + packages: + - busybox + +pipeline: + - runs: | + echo first + + # A note about the next step, kept as a standalone block. + # It spans two lines. + + - runs: | + echo second +` + +const yamConfig = `gap: + - "." + - ".pipeline" +indent: 2 +` + +func writeFile(t *testing.T, path, content string) { + t.Helper() + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte(content), 0o644); err != nil { + t.Fatal(err) + } +} + +// renovateNoop runs Renovate with no renovators, which only re-encodes the +// file, and returns the result. +func renovateNoop(t *testing.T, configPath string) string { + t.Helper() + rctx, err := New(WithConfig(configPath)) + if err != nil { + t.Fatal(err) + } + if err := rctx.Renovate(t.Context()); err != nil { + t.Fatal(err) + } + out, err := os.ReadFile(configPath) + if err != nil { + t.Fatal(err) + } + return string(out) +} + +func TestWriteConfig_UsesRepositoryYamConfig(t *testing.T) { + // The test binary's working directory is this package directory, which + // has no .yam.yaml, so AutomaticConfig would find nothing. + if _, err := os.Stat(".yam.yaml"); err == nil { + t.Fatal("unexpected .yam.yaml in the test working directory") + } + + t.Run("config next to the file", func(t *testing.T) { + dir := t.TempDir() + configPath := filepath.Join(dir, "example.yaml") + writeFile(t, filepath.Join(dir, ".yam.yaml"), yamConfig) + writeFile(t, configPath, commentedConfig) + + got := renovateNoop(t, configPath) + if diff := cmp.Diff(commentedConfig, got); diff != "" { + t.Errorf("layout changed (-want +got):\n%s", diff) + } + }) + + t.Run("config in a parent directory", func(t *testing.T) { + dir := t.TempDir() + configPath := filepath.Join(dir, "os", "example.yaml") + writeFile(t, filepath.Join(dir, ".yam.yaml"), yamConfig) + writeFile(t, configPath, commentedConfig) + + got := renovateNoop(t, configPath) + if diff := cmp.Diff(commentedConfig, got); diff != "" { + t.Errorf("layout changed (-want +got):\n%s", diff) + } + }) + + t.Run("second pass keeps the comment above its step", func(t *testing.T) { + dir := t.TempDir() + configPath := filepath.Join(dir, "example.yaml") + writeFile(t, filepath.Join(dir, ".yam.yaml"), yamConfig) + writeFile(t, configPath, commentedConfig) + + renovateNoop(t, configPath) + got := renovateNoop(t, configPath) + + var root yaml.Node + if err := yaml.Unmarshal([]byte(got), &root); err != nil { + t.Fatalf("second pass produced invalid YAML: %v\n%s", err, got) + } + if diff := cmp.Diff(commentedConfig, got); diff != "" { + t.Errorf("second pass changed the layout (-want +got):\n%s", diff) + } + }) + + t.Run("without any yam config the gaps are lost", func(t *testing.T) { + // Control case documenting the behaviour this change avoids: with no + // .yam.yaml reachable, the encoder has no gap expressions and the blank + // line before the comment block disappears. + dir := t.TempDir() + configPath := filepath.Join(dir, "example.yaml") + writeFile(t, configPath, commentedConfig) + + got := renovateNoop(t, configPath) + if got == commentedConfig { + t.Skip("working directory supplied a yam config; control case not applicable") + } + var root yaml.Node + if err := yaml.Unmarshal([]byte(got), &root); err != nil { + t.Fatalf("no-config pass produced invalid YAML: %v\n%s", err, got) + } + }) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.62.0/pkg/sca/e2e_test.go new/melange-0.62.1/pkg/sca/e2e_test.go --- old/melange-0.62.0/pkg/sca/e2e_test.go 2026-10-07 22:14:27.000000000 +0200 +++ new/melange-0.62.1/pkg/sca/e2e_test.go 2026-10-11 13:26:29.000000000 +0200 @@ -25,29 +25,6 @@ "chainguard.dev/melange/pkg/config" ) -func TestGoFipsBinDeps(t *testing.T) { - ctx := slogtest.Context(t) - th := handleFromApk(ctx, t, "generated/x86_64/go-fips-bin-0.0.1-r0.apk", "go-fips-bin/go-fips-bin.yaml") - defer th.exp.Close() - - got := config.Dependencies{} - if err := Analyze(ctx, th, &got); err != nil { - t.Fatal(err) - } - want := config.Dependencies{ - Runtime: []string{ - "openssl-config-fipshardened", - "so:ld-linux-x86-64.so.2", - "so:libc.so.6", - "so:libcrypto.so.3", - }, - Provides: []string{"cmd:go-fips-bin=0.0.1-r0"}, - } - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("Analyze(): (-want, +got):\n%s", diff) - } -} - func TestAnalyze(t *testing.T) { for _, c := range []struct { apk string diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.62.0/pkg/sca/sca.go new/melange-0.62.1/pkg/sca/sca.go --- old/melange-0.62.0/pkg/sca/sca.go 2026-10-07 22:14:27.000000000 +0200 +++ new/melange-0.62.1/pkg/sca/sca.go 2026-10-11 13:26:29.000000000 +0200 @@ -707,7 +707,6 @@ } var dlopen, fipscrypto, fipsgeomys, geomysv1, geomysesv bool libcrypto := "so:libcrypto.so.3" - fipsexperiments := []string{"boringcrypto", "systemcrypto", "opensslcrypto"} geomysesvs := []string{"openssl+geomys", "geomys"} for _, setting := range buildinfo.Settings { // go-msft-1.26 imlements dlopen with cgo @@ -732,16 +731,15 @@ if setting.Key == "GOFIPS140" && setting.Value == "v1.0.0-c2097c7c" { geomysv1 = true } - // obsolete and now removed, keep for now - if setting.Key == "GOEXPERIMENT" && slices.Contains(fipsexperiments, setting.Value) { - fipscrypto = true - } if setting.Key == "microsoft_systemcrypto" && setting.Value == "1" { fipscrypto = true } if setting.Key == "chainguard_go_package" && goSupportsLibcrypto4(setting.Value) { libcrypto = "so:libcrypto.so.4" } + if setting.Key == "microsoft_toolset_version" && msftToolsetSupportsLibcrypto4(setting.Value) { + libcrypto = "so:libcrypto.so.4" + } } // strong indication of go with openssl compiled binary, will dlopen the below at runtime if dlopen && fipscrypto { @@ -808,6 +806,48 @@ return apk.CompareVersions(ver, minGoMsftLibcrypto4) >= 0 } +// 1.26.9 is the first go-msft-1.26 toolset version where every Chainguard +// build carries the libcrypto.so.4 backport. The backport landed in +// 1.26.8.1-r2, but microsoft_toolset_version can't tell 1.26.8.1-r1 from -r2 +// (both report go1.26.8-microsoft), so 1.26.8 binaries keep libcrypto.so.3. +// That's safe at runtime: the backport probes libcrypto.so.4 first and falls +// back to libcrypto.so.3. +// +// microsoft_toolset_version is emitted by upstream microsoft/go too, while the +// libcrypto.so.4 support in 1.26 is a Chainguard backport. A prebuilt upstream +// Microsoft Go 1.26.9+ FIPS binary vendored into a package would therefore be +// tagged libcrypto.so.4 even though it only loads libcrypto.so.3. +var ( + goMsft126Stream = mustParseVersion("1.26") + minGoMsft126Libcrypto4 = mustParseVersion("1.26.9") +) + +// msftToolsetSupportsLibcrypto4 reports whether the go toolchain recorded in +// the microsoft_toolset_version build setting (e.g. go1.26.9-microsoft) +// supports the libcrypto.so.4 ABI. Only the 1.26 stream is handled here; +// 1.27 and later are detected via chainguard_go_package instead. +func msftToolsetSupportsLibcrypto4(toolset string) bool { + rest, ok := strings.CutPrefix(toolset, "go") + if !ok { + return false + } + version, _, _ := strings.Cut(rest, "-") + major, minor, ok := strings.Cut(version, ".") + if !ok { + return false + } + minor, _, _ = strings.Cut(minor, ".") + streamVer, err := apk.ParseVersion(major + "." + minor) + if err != nil || apk.CompareVersions(streamVer, goMsft126Stream) != 0 { + return false + } + ver, err := apk.ParseVersion(version) + if err != nil { + return false + } + return apk.CompareVersions(ver, minGoMsft126Libcrypto4) >= 0 +} + // TODO(xnox): Note remove this feature flag, once successful // note this can generate depends on pc: files that do not exist in // wolfi, however package install tests will catch that in presubmit diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.62.0/pkg/sca/sca_test.go new/melange-0.62.1/pkg/sca/sca_test.go --- old/melange-0.62.0/pkg/sca/sca_test.go 2026-10-07 22:14:27.000000000 +0200 +++ new/melange-0.62.1/pkg/sca/sca_test.go 2026-10-11 13:26:29.000000000 +0200 @@ -15,7 +15,6 @@ //go:generate go run ./../../ build --generate-index=false --out-dir=./testdata/generated ./testdata/nvidia-driver.yaml --arch=x86_64 //go:generate go run ./../../ build --generate-index=false --out-dir=./testdata/generated ./testdata/ld-so-conf-d-test.yaml --arch=x86_64 //go:generate go run ./../../ build --generate-index=false --out-dir=./testdata/generated ./testdata/shbang-test.yaml --arch=x86_64 -//go:generate go run ./../../ build --generate-index=false --source-dir=./testdata/go-fips-bin/ --out-dir=./testdata/generated ./testdata/go-fips-bin/go-fips-bin.yaml --arch=x86_64 //go:generate curl -s -o ./testdata/py3-seaborn.yaml https://raw.githubusercontent.com/wolfi-dev/os/7a39ac1d0603a3561790ea2201dd8ad7c2b7e51e/py3-seaborn.yaml //go:generate curl -s -o ./testdata/systemd.yaml https://raw.githubusercontent.com/wolfi-dev/os/7a39ac1d0603a3561790ea2201dd8ad7c2b7e51e/systemd.yaml @@ -335,32 +334,6 @@ } } -func TestGoFipsSca(t *testing.T) { - ctx := slogtest.Context(t) - th := handleFromApk(ctx, t, "generated/x86_64/go-fips-bin-0.0.1-r0.apk", "go-fips-bin/go-fips-bin.yaml") - defer th.exp.Close() - - got := config.Dependencies{} - if err := Analyze(ctx, th, &got); err != nil { - t.Fatal(err) - } - - want := config.Dependencies{ - Runtime: []string{ - "openssl-config-fipshardened", - "so:ld-linux-x86-64.so.2", - "so:libc.so.6", - "so:libcrypto.so.3", - }, - Provides: []string{ - "cmd:go-fips-bin=0.0.1-r0", - }, - } - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("Analyze(): (-want, +got):\n%s", diff) - } -} - func TestGoMsftSca(t *testing.T) { ctx := slogtest.Context(t) th := handleFromApk(ctx, t, "generated/x86_64/go-msft-bin-0.0.1-r0.apk", "go-fips-bin/go-msft-bin.yaml") @@ -564,3 +537,26 @@ } } } + +func TestMsftToolsetSupportsLibcrypto4(t *testing.T) { + for _, tc := range []struct { + toolset string + want bool + }{ + {"", false}, + {"go1.26.9", true}, + {"go1.26.8-microsoft", false}, + {"go1.26.9-microsoft", true}, + {"go1.26.10-microsoft", true}, + {"go1.26.9.1-microsoft", true}, + {"go1.25.20-microsoft", false}, + {"go1.27.1-microsoft", false}, + {"go1.260.1-microsoft", false}, + {"1.26.9-microsoft", false}, + {"gogarbage", false}, + } { + if got := msftToolsetSupportsLibcrypto4(tc.toolset); got != tc.want { + t.Errorf("msftToolsetSupportsLibcrypto4(%q) = %v, want %v", tc.toolset, got, tc.want) + } + } +} Binary files old/melange-0.62.0/pkg/sca/testdata/generated/x86_64/go-fips-bin-0.0.1-r0.apk and new/melange-0.62.1/pkg/sca/testdata/generated/x86_64/go-fips-bin-0.0.1-r0.apk differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/melange-0.62.0/pkg/sca/testdata/go-fips-bin/go-fips-bin.yaml new/melange-0.62.1/pkg/sca/testdata/go-fips-bin/go-fips-bin.yaml --- old/melange-0.62.0/pkg/sca/testdata/go-fips-bin/go-fips-bin.yaml 2026-10-07 22:14:27.000000000 +0200 +++ new/melange-0.62.1/pkg/sca/testdata/go-fips-bin/go-fips-bin.yaml 1970-01-01 01:00:00.000000000 +0100 @@ -1,30 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Chainguard, Inc -# SPDX-License-Identifier: Apache-2.0 -# -# This is a sample configuration file to demonstrate how to build a software -# project using melange's built-in go/install pipeline. -# -# For more information about melange's built-in golang support check out: -# -# -# For an equivalent pipeline that uses go/install to build the same project -# please see go-install.yaml in this directory. -package: - name: go-fips-bin - version: 0.0.1 - epoch: 0 - description: "A tiny fips-like go binary" - -environment: - contents: - keyring: - - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub - repositories: - - https://packages.wolfi.dev/os - -pipeline: - - uses: go/install - with: - package: . - # This is an approximation to the real go-fips toolchain - experiments: boringcrypto ++++++ melange.obsinfo ++++++ --- /var/tmp/diff_new_pack.ju0HfL/_old 2026-10-12 22:06:25.862341075 +0200 +++ /var/tmp/diff_new_pack.ju0HfL/_new 2026-10-12 22:06:25.865341200 +0200 @@ -1,5 +1,5 @@ name: melange -version: 0.62.0 -mtime: 1791404067 -commit: 0ae0a5c52b587563b72755df1c7520b082712517 +version: 0.62.1 +mtime: 1791717989 +commit: 88faac23efa1fae08a915518e6782a455d5dfd3f ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/melange/vendor.tar.gz /work/SRC/openSUSE:Factory/.melange.new.3698736/vendor.tar.gz differ: char 30, line 1
