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-03-11 20:54:54
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/apko (Old)
 and      /work/SRC/openSUSE:Factory/.apko.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "apko"

Wed Mar 11 20:54:54 2026 rev:100 rq:1338202 version:1.1.14

Changes:
--------
--- /work/SRC/openSUSE:Factory/apko/apko.changes        2026-03-09 
16:15:32.423254213 +0100
+++ /work/SRC/openSUSE:Factory/.apko.new.8177/apko.changes      2026-03-11 
20:56:44.181429289 +0100
@@ -1,0 +2,6 @@
+Wed Mar 11 05:59:40 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 1.1.14:
+  * Add support for custom certificate packages (#2105)
+
+-------------------------------------------------------------------

Old:
----
  apko-1.1.13.obscpio

New:
----
  apko-1.1.14.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ apko.spec ++++++
--- /var/tmp/diff_new_pack.HlU56C/_old  2026-03-11 20:56:46.321517582 +0100
+++ /var/tmp/diff_new_pack.HlU56C/_new  2026-03-11 20:56:46.325517746 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           apko
-Version:        1.1.13
+Version:        1.1.14
 Release:        0
 Summary:        Build OCI images from APK packages directly without Dockerfile
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.HlU56C/_old  2026-03-11 20:56:46.357519067 +0100
+++ /var/tmp/diff_new_pack.HlU56C/_new  2026-03-11 20:56:46.361519232 +0100
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/chainguard-dev/apko</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">v1.1.13</param>
+    <param name="revision">v1.1.14</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.HlU56C/_old  2026-03-11 20:56:46.381520057 +0100
+++ /var/tmp/diff_new_pack.HlU56C/_new  2026-03-11 20:56:46.385520222 +0100
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/chainguard-dev/apko</param>
-              <param 
name="changesrevision">0a1df0f2e7cb29b95680746b8bbb0c03e5a22511</param></service></servicedata>
+              <param 
name="changesrevision">238cd7b8aec624af67bcd08492c7bb4412dc119d</param></service></servicedata>
 (No newline at EOF)
 

++++++ apko-1.1.13.obscpio -> apko-1.1.14.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.1.13/examples/package-certificates.yaml 
new/apko-1.1.14/examples/package-certificates.yaml
--- old/apko-1.1.13/examples/package-certificates.yaml  1970-01-01 
01:00:00.000000000 +0100
+++ new/apko-1.1.14/examples/package-certificates.yaml  2026-03-10 
19:28:46.000000000 +0100
@@ -0,0 +1,24 @@
+contents:
+  keyring:
+    - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
+    - ./internal/cli/testdata/melange.rsa.pub
+  repositories:
+    - https://packages.wolfi.dev/os
+    - ./internal/cli/testdata/packages
+  packages:
+    - ca-certificates-bundle
+    - busybox
+    - apk-tools
+    - java-cacerts
+    - custom-ca-certs-1
+    - custom-ca-certs-2
+
+certificates:
+  providers:
+    - custom-ca-certificates
+
+entrypoint:
+  command: /bin/sh -c
+
+archs:
+  - amd64
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.1.13/hack/test-certificates.sh 
new/apko-1.1.14/hack/test-certificates.sh
--- old/apko-1.1.13/hack/test-certificates.sh   1970-01-01 01:00:00.000000000 
+0100
+++ new/apko-1.1.14/hack/test-certificates.sh   2026-03-10 19:28:46.000000000 
+0100
@@ -0,0 +1,91 @@
+#!/usr/bin/env bash
+
+# Copyright 2025 Chainguard, Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+# Tests that an apko-built image contains expected certificates in both the CA
+# bundle and Java truststore, and that running update-ca-certificates doesn't
+# change them.
+#
+# Usage: hack/test-certificates.sh <yaml> <fingerprint> [<fingerprint> ...]
+#
+# Example:
+#   hack/test-certificates.sh ./examples/certificates.yaml \
+#     "E7:05:70:A9:..." "9B:2A:33:9F:..."
+
+set -euo pipefail
+
+if [ $# -lt 2 ]; then
+    echo "Usage: $0 <yaml> <fingerprint> [<fingerprint> ...]"
+    exit 1
+fi
+
+yaml="$1"
+shift
+fingerprints=("$@")
+
+name=$(basename "${yaml}" .yaml)
+image="${name}:build"
+image_arch="${name}:build-amd64"
+tarball="/tmp/${name}.tar"
+
+# Gets all certificate fingerprints from a PEM stream, sorted, so two outputs 
can
+# be semantically compared.
+get_fingerprints() {
+    local cert=""
+    while IFS= read -r line; do
+        case "$line" in
+            "-----BEGIN CERTIFICATE-----")
+                cert="$line"$'\n' ;;
+            "-----END CERTIFICATE-----")
+                cert+="$line"
+                echo "$cert" | openssl x509 -noout -fingerprint -sha256 
2>/dev/null
+                cert="" ;;
+            *)
+                [[ -n "$cert" ]] && cert+="$line"$'\n' || true ;;
+        esac
+    done | sort
+}
+
+# Verifies that fingerprints file contains at least N certs and all expected 
fingerprints.
+verify_fingerprints() {
+    local file="$1"
+    local min_count="$2"
+    local store_name="$3"
+
+    local count
+    count=$(wc -l < "$file")
+    if [ "$count" -lt "$min_count" ]; then
+        echo "Expected at least $min_count certificates in $store_name, found 
$count"
+        exit 1
+    fi
+
+    for fp in "${fingerprints[@]}"; do
+        grep "$fp" "$file"
+    done
+    echo "$store_name contains all expected certificates."
+}
+
+# Build the image.
+make apko
+./apko build "${yaml}" "${image}" "${tarball}" --arch amd64
+docker load < "${tarball}"
+
+# Get fingerprints from CA bundle and Java truststore before 
update-ca-certificates.
+docker run --rm "${image_arch}" "cat /etc/ssl/certs/ca-certificates.crt" | 
get_fingerprints > /tmp/ca-bundle-fingerprints.txt
+docker run --rm "${image_arch}" "trust extract --filter=ca-anchors 
--purpose=server-auth --format=pem-bundle /tmp/certs.pem && cat /tmp/certs.pem" 
| get_fingerprints > /tmp/java-truststore-fingerprints.txt
+
+# Verify both stores contain base certs and all expected certificates.
+verify_fingerprints /tmp/ca-bundle-fingerprints.txt 10 "CA bundle"
+verify_fingerprints /tmp/java-truststore-fingerprints.txt 10 "Java truststore"
+
+# Run update-ca-certificates and get fingerprints from both stores after.
+docker run --rm "${image_arch}" "apk add ca-certificates && 
update-ca-certificates && cat /etc/ssl/certs/ca-certificates.crt" | 
get_fingerprints > /tmp/ca-bundle-updated-fingerprints.txt
+docker run --rm "${image_arch}" "apk add ca-certificates && 
update-ca-certificates && trust extract --filter=ca-anchors 
--purpose=server-auth --format=pem-bundle /tmp/certs.pem && cat /tmp/certs.pem" 
| get_fingerprints > /tmp/java-truststore-updated-fingerprints.txt
+
+# Verify that the stores are semantically identical before and after 
update-ca-certificates.
+diff /tmp/ca-bundle-fingerprints.txt /tmp/ca-bundle-updated-fingerprints.txt
+echo "CA bundles before and after update-ca-certificates are identical."
+
+diff /tmp/java-truststore-fingerprints.txt 
/tmp/java-truststore-updated-fingerprints.txt
+echo "Java truststores before and after update-ca-certificates are identical."
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.1.13/hack/update-packages.sh 
new/apko-1.1.14/hack/update-packages.sh
--- old/apko-1.1.13/hack/update-packages.sh     2026-03-08 16:29:25.000000000 
+0100
+++ new/apko-1.1.14/hack/update-packages.sh     2026-03-10 19:28:46.000000000 
+0100
@@ -7,6 +7,8 @@
 
 (cd internal/cli/testdata && \
   melange build --arch arm64 --arch amd64 -r https://packages.wolfi.dev/os -k 
https://packages.wolfi.dev/os/wolfi-signing.rsa.pub --signing-key ./melange.rsa 
pretend-baselayout.melange.yaml && \
-  melange build --arch arm64 --arch amd64 -r https://packages.wolfi.dev/os -k 
https://packages.wolfi.dev/os/wolfi-signing.rsa.pub --signing-key ./melange.rsa 
replayout.melange.yaml)
+  melange build --arch arm64 --arch amd64 -r https://packages.wolfi.dev/os -k 
https://packages.wolfi.dev/os/wolfi-signing.rsa.pub --signing-key ./melange.rsa 
replayout.melange.yaml && \
+  melange build --arch arm64 --arch amd64 -r https://packages.wolfi.dev/os -k 
https://packages.wolfi.dev/os/wolfi-signing.rsa.pub --signing-key ./melange.rsa 
custom-ca-certs-1.melange.yaml && \
+  melange build --arch arm64 --arch amd64 -r https://packages.wolfi.dev/os -k 
https://packages.wolfi.dev/os/wolfi-signing.rsa.pub --signing-key ./melange.rsa 
custom-ca-certs-2.melange.yaml)
 (cd internal/cli &&
   apko lock ./testdata/apko.yaml)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.1.13/internal/cli/testdata/apko-certs.yaml 
new/apko-1.1.14/internal/cli/testdata/apko-certs.yaml
--- old/apko-1.1.13/internal/cli/testdata/apko-certs.yaml       1970-01-01 
01:00:00.000000000 +0100
+++ new/apko-1.1.14/internal/cli/testdata/apko-certs.yaml       2026-03-10 
19:28:46.000000000 +0100
@@ -0,0 +1,17 @@
+contents:
+  keyring:
+    - ./testdata/melange.rsa.pub
+  repositories:
+    - ./testdata/packages
+  packages:
+    - pretend-baselayout
+    - custom-ca-certs-1
+    - custom-ca-certs-2
+
+certificates:
+  providers:
+    - custom-ca-certificates
+
+archs:
+- x86_64
+- aarch64
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/apko-1.1.13/internal/cli/testdata/custom-ca-certs-1.melange.yaml 
new/apko-1.1.14/internal/cli/testdata/custom-ca-certs-1.melange.yaml
--- old/apko-1.1.13/internal/cli/testdata/custom-ca-certs-1.melange.yaml        
1970-01-01 01:00:00.000000000 +0100
+++ new/apko-1.1.14/internal/cli/testdata/custom-ca-certs-1.melange.yaml        
2026-03-10 19:28:46.000000000 +0100
@@ -0,0 +1,71 @@
+package:
+  name: custom-ca-certs-1
+  version: 1.0.0
+  epoch: 0
+  description: "custom CA certificates package 1 (test)"
+  copyright:
+    - license: MIT
+  dependencies:
+    provides:
+      - custom-ca-certificates
+
+environment:
+  contents:
+    packages:
+      - busybox
+
+pipeline:
+  - name: Install certificates
+    runs: |
+      mkdir -p ${{targets.destdir}}/usr/local/share/ca-certificates
+      cat >${{targets.destdir}}/usr/local/share/ca-certificates/cert-a.crt 
<<'CERTEOF'
+      -----BEGIN CERTIFICATE-----
+      MIIFmDCCA4CgAwIBAgIQU9C87nMpOIFKYpfvOHFHFDANBgkqhkiG9w0BAQsFADBm
+      MQswCQYDVQQGEwJVUzEzMDEGA1UEChMqKFNUQUdJTkcpIEludGVybmV0IFNlY3Vy
+      aXR5IFJlc2VhcmNoIEdyb3VwMSIwIAYDVQQDExkoU1RBR0lORykgUHJldGVuZCBQ
+      ZWFyIFgxMB4XDTE1MDYwNDExMDQzOFoXDTM1MDYwNDExMDQzOFowZjELMAkGA1UE
+      BhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0eSBSZXNl
+      YXJjaCBHcm91cDEiMCAGA1UEAxMZKFNUQUdJTkcpIFByZXRlbmQgUGVhciBYMTCC
+      AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALbagEdDTa1QgGBWSYkyMhsc
+      ZXENOBaVRTMX1hceJENgsL0Ma49D3MilI4KS38mtkmdF6cPWnL++fgehT0FbRHZg
+      jOEr8UAN4jH6omjrbTD++VZneTsMVaGamQmDdFl5g1gYaigkkmx8OiCO68a4QXg4
+      wSyn6iDipKP8utsE+x1E28SA75HOYqpdrk4HGxuULvlr03wZGTIf/oRt2/c+dYmD
+      oaJhge+GOrLAEQByO7+8+vzOwpNAPEx6LW+crEEZ7eBXih6VP19sTGy3yfqK5tPt
+      TdXXCOQMKAp+gCj/VByhmIr+0iNDC540gtvV303WpcbwnkkLYC0Ft2cYUyHtkstO
+      fRcRO+K2cZozoSwVPyB8/J9RpcRK3jgnX9lujfwA/pAbP0J2UPQFxmWFRQnFjaq6
+      rkqbNEBgLy+kFL1NEsRbvFbKrRi5bYy2lNms2NJPZvdNQbT/2dBZKmJqxHkxCuOQ
+      FjhJQNeO+Njm1Z1iATS/3rts2yZlqXKsxQUzN6vNbD8KnXRMEeOXUYvbV4lqfCf8
+      mS14WEbSiMy87GB5S9ucSV1XUrlTG5UGcMSZOBcEUpisRPEmQWUOTWIoDQ5FOia/
+      GI+Ki523r2ruEmbmG37EBSBXdxIdndqrjy+QVAmCebyDx9eVEGOIpn26bW5LKeru
+      mJxa/CFBaKi4bRvmdJRLAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
+      Af8EBTADAQH/MB0GA1UdDgQWBBS182Xy/rAKkh/7PH3zRKCsYyXDFDANBgkqhkiG
+      9w0BAQsFAAOCAgEAncDZNytDbrrVe68UT6py1lfF2h6Tm2p8ro42i87WWyP2LK8Y
+      nLHC0hvNfWeWmjZQYBQfGC5c7aQRezak+tHLdmrNKHkn5kn+9E9LCjCaEsyIIn2j
+      qdHlAkepu/C3KnNtVx5tW07e5bvIjJScwkCDbP3akWQixPpRFAsnP+ULx7k0aO1x
+      qAeaAhQ2rgo1F58hcflgqKTXnpPM02intVfiVVkX5GXpJjK5EoQtLceyGOrkxlM/
+      sTPq4UrnypmsqSagWV3HcUlYtDinc+nukFk6eR4XkzXBbwKajl0YjztfrCIHOn5Q
+      CJL6TERVDbM/aAPly8kJ1sWGLuvvWYzMYgLzDul//rUF10gEMWaXVZV51KpS9DY/
+      5CunuvCXmEQJHo7kGcViT7sETn6Jz9KOhvYcXkJ7po6d93A/jy4GKPIPnsKKNEmR
+      xUuXY4xRdh45tMJnLTUDdC9FIU0flTeO9/vNpVA8OPU1i14vCz+MU8KX1bV3GXm/
+      fxlB7VBBjX9v5oUep0o/j68R/iDlCOM4VVfRa8gX6T2FU7fNdatvGro7uQzIvWof
+      gN9WUwCbEMBy/YhBSrXycKA8crgGg3x1mIsopn88JKwmMBa68oS7EHM9w7C4y71M
+      7DiA+/9Qdp9RBWJpTS9i/mDnJg1xvo8Xz49mrrgfmcAXTCJqXi24NatI3Oc=
+      -----END CERTIFICATE-----
+      CERTEOF
+      cat >${{targets.destdir}}/usr/local/share/ca-certificates/cert-b.crt 
<<'CERTEOF'
+      -----BEGIN CERTIFICATE-----
+      MIICTjCCAdSgAwIBAgIRAIPgc3k5LlLVLtUUvs4K/QcwCgYIKoZIzj0EAwMwaDEL
+      MAkGA1UEBhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0
+      eSBSZXNlYXJjaCBHcm91cDEkMCIGA1UEAxMbKFNUQUdJTkcpIEJvZ3VzIEJyb2Nj
+      b2xpIFgyMB4XDTIwMDkwNDAwMDAwMFoXDTQwMDkxNzE2MDAwMFowaDELMAkGA1UE
+      BhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0eSBSZXNl
+      YXJjaCBHcm91cDEkMCIGA1UEAxMbKFNUQUdJTkcpIEJvZ3VzIEJyb2Njb2xpIFgy
+      MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOvS+w1kCzAxYOJbA06Aw0HFP2tLBLKPo
+      FQqR9AMskl1nC2975eQqycR+ACvYelA8rfwFXObMHYXJ23XLB+dAjPJVOJ2OcsjT
+      VqO4dcDWu+rQ2VILdnJRYypnV1MMThVxo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
+      VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU3tGjWWQOwZo2o0busBB2766XlWYwCgYI
+      KoZIzj0EAwMDaAAwZQIwRcp4ZKBsq9XkUuN8wfX+GEbY1N5nmCRc8e80kUkuAefo
+      uc2j3cICeXo1cOybQ1iWAjEA3Ooawl8eQyR4wrjCofUE8h44p0j7Yl/kBlJZT8+9
+      vbtH7QiVzeKCOTQPINyRql6P
+      -----END CERTIFICATE-----
+      CERTEOF
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/apko-1.1.13/internal/cli/testdata/custom-ca-certs-2.melange.yaml 
new/apko-1.1.14/internal/cli/testdata/custom-ca-certs-2.melange.yaml
--- old/apko-1.1.13/internal/cli/testdata/custom-ca-certs-2.melange.yaml        
1970-01-01 01:00:00.000000000 +0100
+++ new/apko-1.1.14/internal/cli/testdata/custom-ca-certs-2.melange.yaml        
2026-03-10 19:28:46.000000000 +0100
@@ -0,0 +1,48 @@
+package:
+  name: custom-ca-certs-2
+  version: 1.0.0
+  epoch: 0
+  description: "custom CA certificates package 2 (test)"
+  copyright:
+    - license: MIT
+  dependencies:
+    provides:
+      - custom-ca-certificates
+
+environment:
+  contents:
+    packages:
+      - busybox
+
+pipeline:
+  - name: Install certificates
+    runs: |
+      mkdir -p ${{targets.destdir}}/usr/local/share/ca-certificates
+      cat >${{targets.destdir}}/usr/local/share/ca-certificates/cert-c.crt 
<<'CERTEOF'
+      -----BEGIN CERTIFICATE-----
+      MIIBwjCCAWegAwIBAgIUBKZDifzRAz30jwlcoQLIOxkBPLMwCgYIKoZIzj0EAwIw
+      NTEeMBwGA1UEAwwVVGVzdCBDQSBDZXJ0aWZpY2F0ZSAzMRMwEQYDVQQKDApUZXN0
+      IE9yZyAzMCAXDTI2MDIyNzIwMzk1OVoYDzIxMjYwMjAzMjAzOTU5WjA1MR4wHAYD
+      VQQDDBVUZXN0IENBIENlcnRpZmljYXRlIDMxEzARBgNVBAoMClRlc3QgT3JnIDMw
+      WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARx/10O/q2rOnQtpBXHjARAUryfNWjD
+      UXeshzFk44hrv45loTsGQcyb5vAL6h3FSdBN91njUch4eF1NEYLKoR3Qo1MwUTAd
+      BgNVHQ4EFgQUhLbWEa0IUIixKPBVvuKxhK6UMnMwHwYDVR0jBBgwFoAUhLbWEa0I
+      UIixKPBVvuKxhK6UMnMwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNJADBG
+      AiEAqgTlOPOiNJLPJhMjRl9Zpaq6TTGfh+awe7N3fcEdHVICIQDfgVRRkuv1KTWk
+      44YBh2/IaTSFwFo8cd39Fnv7CYi/2g==
+      -----END CERTIFICATE-----
+      CERTEOF
+      cat >${{targets.destdir}}/usr/local/share/ca-certificates/cert-d.crt 
<<'CERTEOF'
+      -----BEGIN CERTIFICATE-----
+      MIIBwTCCAWegAwIBAgIUPrm4YvABD98JhdU93qPsAgryo0UwCgYIKoZIzj0EAwIw
+      NTEeMBwGA1UEAwwVVGVzdCBDQSBDZXJ0aWZpY2F0ZSA0MRMwEQYDVQQKDApUZXN0
+      IE9yZyA0MCAXDTI2MDIyNzIwNDAwMFoYDzIxMjYwMjAzMjA0MDAwWjA1MR4wHAYD
+      VQQDDBVUZXN0IENBIENlcnRpZmljYXRlIDQxEzARBgNVBAoMClRlc3QgT3JnIDQw
+      WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQbR9hBg7/IeSBYJzUvBUxnnaNmoOJj
+      ESG5CiOa2980CC5aixcLof5kk/9K16B+OLIGSUE+Ya98N0vNP8KmDmvBo1MwUTAd
+      BgNVHQ4EFgQU6ZlpZtkvodhxZX1aRsM44dY0SJ8wHwYDVR0jBBgwFoAU6ZlpZtkv
+      odhxZX1aRsM44dY0SJ8wDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBF
+      AiARCNSY4WZ7Tl1oAmWghJz0Sxzi57JY4pdrvzyzYQNrhgIhAPMAzTOf33fVRhaX
+      wB7TKj2HAGTDpoliTH80SMWJN3jK
+      -----END CERTIFICATE-----
+      CERTEOF
Binary files 
old/apko-1.1.13/internal/cli/testdata/packages/aarch64/APKINDEX.tar.gz and 
new/apko-1.1.14/internal/cli/testdata/packages/aarch64/APKINDEX.tar.gz differ
Binary files 
old/apko-1.1.13/internal/cli/testdata/packages/aarch64/custom-ca-certs-1-1.0.0-r0.apk
 and 
new/apko-1.1.14/internal/cli/testdata/packages/aarch64/custom-ca-certs-1-1.0.0-r0.apk
 differ
Binary files 
old/apko-1.1.13/internal/cli/testdata/packages/aarch64/custom-ca-certs-2-1.0.0-r0.apk
 and 
new/apko-1.1.14/internal/cli/testdata/packages/aarch64/custom-ca-certs-2-1.0.0-r0.apk
 differ
Binary files 
old/apko-1.1.13/internal/cli/testdata/packages/x86_64/APKINDEX.tar.gz and 
new/apko-1.1.14/internal/cli/testdata/packages/x86_64/APKINDEX.tar.gz differ
Binary files 
old/apko-1.1.13/internal/cli/testdata/packages/x86_64/custom-ca-certs-1-1.0.0-r0.apk
 and 
new/apko-1.1.14/internal/cli/testdata/packages/x86_64/custom-ca-certs-1-1.0.0-r0.apk
 differ
Binary files 
old/apko-1.1.13/internal/cli/testdata/packages/x86_64/custom-ca-certs-2-1.0.0-r0.apk
 and 
new/apko-1.1.14/internal/cli/testdata/packages/x86_64/custom-ca-certs-2-1.0.0-r0.apk
 differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.1.13/pkg/build/build_test.go 
new/apko-1.1.14/pkg/build/build_test.go
--- old/apko-1.1.13/pkg/build/build_test.go     2026-03-08 16:29:25.000000000 
+0100
+++ new/apko-1.1.14/pkg/build/build_test.go     2026-03-10 19:28:46.000000000 
+0100
@@ -124,6 +124,63 @@
        require.Equal(t, installed[1].Version, "1.0.0-r0")
 }
 
+func TestBuildImageWithCertPackages(t *testing.T) {
+       ctx := context.Background()
+
+       opts := []build.Option{
+               build.WithConfig("apko-certs.yaml", []string{"testdata"}),
+       }
+
+       fsys := fs.NewMemFS()
+
+       // Pre-create the CA bundle file (in a real image, the ca-certificates
+       // package provides this). installCertificates only appends to existing
+       // bundles.
+       require.NoError(t, fsys.MkdirAll("etc/ssl/certs", 0o755))
+       require.NoError(t, fsys.WriteFile("etc/ssl/certs/ca-certificates.crt", 
[]byte{}, 0o644))
+
+       bc, err := build.New(ctx, fsys, opts...)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       if err := bc.BuildImage(ctx); err != nil {
+               t.Fatal(err)
+       }
+
+       installed, err := bc.InstalledPackages()
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       // Should have pretend-baselayout + custom-ca-certs-1 + 
custom-ca-certs-2.
+       require.Len(t, installed, 3)
+
+       // Verify the CA bundle contains all 4 certificates.
+       bundlePath := "etc/ssl/certs/ca-certificates.crt"
+       bundleData, err := fsys.ReadFile(bundlePath)
+       require.NoError(t, err, "CA bundle should exist at %s", bundlePath)
+
+       bundle := string(bundleData)
+       require.Contains(t, bundle, "-----BEGIN CERTIFICATE-----")
+
+       // Count the number of certificates in the bundle.
+       certCount := strings.Count(bundle, "-----BEGIN CERTIFICATE-----")
+       require.Equal(t, 4, certCount, "expected 4 certificates in the CA 
bundle, got %d", certCount)
+
+       // Verify individual cert files exist in the filesystem (installed by 
packages).
+       certPaths := []string{
+               "usr/local/share/ca-certificates/cert-a.crt",
+               "usr/local/share/ca-certificates/cert-b.crt",
+               "usr/local/share/ca-certificates/cert-c.crt",
+               "usr/local/share/ca-certificates/cert-d.crt",
+       }
+       for _, p := range certPaths {
+               _, err := fsys.Stat(p)
+               require.NoError(t, err, "cert file %s should exist", p)
+       }
+}
+
 func TestBuildImageFromLockFile(t *testing.T) {
        ctx := context.Background()
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.1.13/pkg/build/certificates.go 
new/apko-1.1.14/pkg/build/certificates.go
--- old/apko-1.1.13/pkg/build/certificates.go   2026-03-08 16:29:25.000000000 
+0100
+++ new/apko-1.1.14/pkg/build/certificates.go   2026-03-10 19:28:46.000000000 
+0100
@@ -15,6 +15,7 @@
 package build
 
 import (
+       "archive/tar"
        "bytes"
        "context"
        "crypto/sha256"
@@ -27,9 +28,14 @@
        "io/fs"
        "os"
        "path/filepath"
+       "slices"
+       "sort"
+       "strings"
 
        "github.com/pavlo-v-chernykh/keystore-go/v4"
        "go.opentelemetry.io/otel"
+
+       "chainguard.dev/apko/pkg/apk/apk"
 )
 
 const (
@@ -67,28 +73,119 @@
        ks   keystore.KeyStore
 }
 
-// installCertificates installs inline certificates into the build context.
+// installCertificates installs certificates from two sources into the build 
context:
+//  1. Inline certificates from the image configuration 
(bc.ic.Certificates.Additional).
+//  2. Certificate files from installed packages that provide 
"custom-ca-certificates",
+//     replacing the role of update-ca-certificates post-install scripts.
 func (bc *Context) installCertificates(ctx context.Context) error {
        _, span := otel.Tracer("apko").Start(ctx, "installCertificates")
        defer span.End()
 
-       if bc.ic.Certificates == nil || len(bc.ic.Certificates.Additional) == 0 
{
-               // No configuration, nothing to do.
+       if bc.ic.Certificates == nil {
                return nil
        }
 
+       // certToWrite pairs a parsed certificate with the metadata needed to 
write it.
+       type certToWrite struct {
+               cert  *parsedCertificate
+               alias string // Java truststore alias
+       }
+
+       certs := make([]certToWrite, 0, len(bc.ic.Certificates.Additional))
+
        builtTime, err := bc.GetBuildDateEpoch()
        if err != nil {
                return fmt.Errorf("failed to get build date epoch: %w", err)
        }
 
-       // Create the ca-certificates directory if it doesn't exist
-       if err := bc.fs.MkdirAll(caCertsDir, 0o755); err != nil {
-               return fmt.Errorf("failed to create ca-certificates directory: 
%w", err)
+       // Write inline certificates from the image configuration to individual 
files
+       // and collect them for downstream bundle/truststore appending.
+       if len(bc.ic.Certificates.Additional) > 0 {
+               // Create the ca-certificates directory
+               if err := bc.fs.MkdirAll(caCertsDir, 0o755); err != nil {
+                       return fmt.Errorf("failed to create ca-certificates 
directory: %w", err)
+               }
+               for _, additional := range bc.ic.Certificates.Additional {
+                       cert, err := parseCertificates(additional.Content)
+                       if err != nil {
+                               return fmt.Errorf("failed to parse certificate 
%s: %w", additional.Name, err)
+                       }
+                       // Write individual certificate file for 
update-ca-certificates to pick up.
+                       // Name is validated not to do any path shenanigans on 
configuration validation.
+                       // The fingerprint is controlled to be a hash and so 
also doesn't allow shenanigans.
+                       certPath := filepath.Join(caCertsDir, 
fmt.Sprintf("%s-%s.crt", additional.Name, cert.fingerprint))
+                       if err := bc.fs.WriteFile(certPath, cert.pem, 0o644); 
err != nil {
+                               return fmt.Errorf("failed to write certificate 
file %s: %w", certPath, err)
+                       }
+                       if err := bc.fs.Chtimes(certPath, builtTime, 
builtTime); err != nil {
+                               return fmt.Errorf("failed to change times on 
certificate file %s: %w", certPath, err)
+                       }
+                       certs = append(certs, certToWrite{
+                               cert:  cert,
+                               alias: fmt.Sprintf("%s-%s", additional.Name, 
cert.fingerprint),
+                       })
+               }
+       }
+
+       if len(bc.ic.Certificates.Providers) > 0 {
+               // Filter installed packages to find those that provide 
certificates
+               installed, err := bc.apk.GetInstalled()
+               if err != nil {
+                       return fmt.Errorf("failed to get installed packages: 
%w", err)
+               }
+               var providerPkgs []*apk.InstalledPackage
+               for _, pkg := range installed {
+                       for _, p := range pkg.Provides {
+                               if 
slices.Contains(bc.ic.Certificates.Providers, p) {
+                                       providerPkgs = append(providerPkgs, pkg)
+                                       break
+                               }
+                       }
+               }
+
+               // Collect certificate files from provider packages
+               var pkgCertFiles []string
+               for _, pkg := range providerPkgs {
+                       for _, f := range pkg.Files {
+                               // Directories are explicitly marked; regular 
files from ParseInstalled
+                               // have Typeflag == 0 (not tar.TypeReg).
+                               if f.Typeflag == tar.TypeDir {
+                                       continue
+                               }
+                               // Only consider pem/crt files under the 
caCertsDir
+                               if !strings.HasPrefix(f.Name, caCertsDir+"/") {
+                                       continue
+                               }
+                               ext := filepath.Ext(f.Name)
+                               if ext == ".crt" || ext == ".pem" {
+                                       pkgCertFiles = append(pkgCertFiles, 
f.Name)
+                               }
+                       }
+               }
+               // Sort for deterministic, reproducible builds.
+               sort.Strings(pkgCertFiles)
+               for _, certPath := range pkgCertFiles {
+                       data, err := bc.fs.ReadFile(certPath)
+                       if err != nil {
+                               return fmt.Errorf("failed to read certificate 
file %s: %w", certPath, err)
+                       }
+                       cert, err := parseCertificates(string(data))
+                       if err != nil {
+                               continue
+                       }
+                       certs = append(certs, certToWrite{
+                               cert:  cert,
+                               alias: fmt.Sprintf("pkg-%s", cert.fingerprint),
+                       })
+               }
+       }
+
+       if len(certs) == 0 {
+               return nil
        }
 
        // Open handles for all existing CA bundles to append to.
-       existingBundles := make([]io.WriteSeeker, 0, len(caBundlePaths))
+       existingBundles := make([]io.WriteCloser, 0, len(caBundlePaths))
        for _, caBundlePath := range caBundlePaths {
                file, err := bc.fs.OpenFile(caBundlePath, 
os.O_WRONLY|os.O_APPEND, 0o644)
                if err != nil {
@@ -111,26 +208,11 @@
                return fmt.Errorf("failed to load Java truststores: %w", err)
        }
 
-       for _, additional := range bc.ic.Certificates.Additional {
-               cert, err := parseCertificates(additional.Content)
-               if err != nil {
-                       return fmt.Errorf("failed to parse certificate %s: %w", 
additional.Name, err)
-               }
-
-               // Write individual certificate file for update-ca-certificates 
to pick up.
-               // Name is validated not to do any path shenanigans on 
configuration validation.
-               // The fingerprint is controlled to be a hash and so also 
doesn't allow shenanigans.
-               certPath := filepath.Join(caCertsDir, fmt.Sprintf("%s-%s.crt", 
additional.Name, cert.fingerprint))
-               if err := bc.fs.WriteFile(certPath, cert.pem, 0o644); err != 
nil {
-                       return fmt.Errorf("failed to write certificate file %s: 
%w", certPath, err)
-               }
-               if err := bc.fs.Chtimes(certPath, builtTime, builtTime); err != 
nil {
-                       return fmt.Errorf("failed to change times on 
certificate file %s: %w", certPath, err)
-               }
-
+       // Append all collected certificates to open CA bundles and Java 
truststores.
+       for _, c := range certs {
                // Append to all existing CA bundles.
                for _, bundle := range existingBundles {
-                       if _, err := bundle.Write(cert.pem); err != nil {
+                       if _, err := bundle.Write(c.cert.pem); err != nil {
                                return fmt.Errorf("failed to append certificate 
to bundle: %w", err)
                        }
                        // Put newlines in-between certificates to mimic 
update-ca-certificates behavior.
@@ -145,16 +227,16 @@
                                CreationTime: builtTime,
                                Certificate: keystore.Certificate{
                                        Type:    "X.509",
-                                       Content: cert.structured.Raw,
+                                       Content: c.cert.structured.Raw,
                                },
                        }
-                       alias := fmt.Sprintf("%s-%s", additional.Name, 
cert.fingerprint)
-                       if err := ts.ks.SetTrustedCertificateEntry(alias, 
entry); err != nil {
+                       if err := ts.ks.SetTrustedCertificateEntry(c.alias, 
entry); err != nil {
                                return fmt.Errorf("failed to add certificate to 
Java truststore: %w", err)
                        }
                }
        }
 
+       // Update timestamps on all open CA bundle files.
        for _, caBundlePath := range caBundlePaths {
                if err := bc.fs.Chtimes(caBundlePath, builtTime, builtTime); 
err != nil && !errors.Is(err, fs.ErrNotExist) {
                        return fmt.Errorf("failed to change times on CA bundle 
%s: %w", caBundlePath, err)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.1.13/pkg/build/certificates_test.go 
new/apko-1.1.14/pkg/build/certificates_test.go
--- old/apko-1.1.13/pkg/build/certificates_test.go      2026-03-08 
16:29:25.000000000 +0100
+++ new/apko-1.1.14/pkg/build/certificates_test.go      2026-03-10 
19:28:46.000000000 +0100
@@ -15,15 +15,19 @@
 package build
 
 import (
+       "archive/tar"
        "bytes"
        "context"
        "fmt"
        "io/fs"
        "path/filepath"
+       "strings"
        "testing"
        "time"
 
+       "chainguard.dev/apko/pkg/apk/apk"
        apkfs "chainguard.dev/apko/pkg/apk/fs"
+       apktypes "chainguard.dev/apko/pkg/apk/types"
        "chainguard.dev/apko/pkg/build/types"
        "chainguard.dev/apko/pkg/options"
 
@@ -86,6 +90,38 @@
 -----END CERTIFICATE-----
 `
        testCertPEM2Fingerprint = 
"9b2a339fe6a3e85585c4cd75536cb8c1cf7cd603b9a64bec2521858ae48da85d"
+
+       // Self-signed test certificate 3 (EC P-256, CN=Test CA Certificate 3).
+       testCertPEM3 = `-----BEGIN CERTIFICATE-----
+MIIBwjCCAWegAwIBAgIUBKZDifzRAz30jwlcoQLIOxkBPLMwCgYIKoZIzj0EAwIw
+NTEeMBwGA1UEAwwVVGVzdCBDQSBDZXJ0aWZpY2F0ZSAzMRMwEQYDVQQKDApUZXN0
+IE9yZyAzMCAXDTI2MDIyNzIwMzk1OVoYDzIxMjYwMjAzMjAzOTU5WjA1MR4wHAYD
+VQQDDBVUZXN0IENBIENlcnRpZmljYXRlIDMxEzARBgNVBAoMClRlc3QgT3JnIDMw
+WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARx/10O/q2rOnQtpBXHjARAUryfNWjD
+UXeshzFk44hrv45loTsGQcyb5vAL6h3FSdBN91njUch4eF1NEYLKoR3Qo1MwUTAd
+BgNVHQ4EFgQUhLbWEa0IUIixKPBVvuKxhK6UMnMwHwYDVR0jBBgwFoAUhLbWEa0I
+UIixKPBVvuKxhK6UMnMwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNJADBG
+AiEAqgTlOPOiNJLPJhMjRl9Zpaq6TTGfh+awe7N3fcEdHVICIQDfgVRRkuv1KTWk
+44YBh2/IaTSFwFo8cd39Fnv7CYi/2g==
+-----END CERTIFICATE-----
+`
+       testCertPEM3Fingerprint = 
"347537af7a09d403f19f58f83c3568912af24b7c12e745f1d5557079708c91ad"
+
+       // Self-signed test certificate 4 (EC P-256, CN=Test CA Certificate 4).
+       testCertPEM4 = `-----BEGIN CERTIFICATE-----
+MIIBwTCCAWegAwIBAgIUPrm4YvABD98JhdU93qPsAgryo0UwCgYIKoZIzj0EAwIw
+NTEeMBwGA1UEAwwVVGVzdCBDQSBDZXJ0aWZpY2F0ZSA0MRMwEQYDVQQKDApUZXN0
+IE9yZyA0MCAXDTI2MDIyNzIwNDAwMFoYDzIxMjYwMjAzMjA0MDAwWjA1MR4wHAYD
+VQQDDBVUZXN0IENBIENlcnRpZmljYXRlIDQxEzARBgNVBAoMClRlc3QgT3JnIDQw
+WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQbR9hBg7/IeSBYJzUvBUxnnaNmoOJj
+ESG5CiOa2980CC5aixcLof5kk/9K16B+OLIGSUE+Ya98N0vNP8KmDmvBo1MwUTAd
+BgNVHQ4EFgQU6ZlpZtkvodhxZX1aRsM44dY0SJ8wHwYDVR0jBBgwFoAU6ZlpZtkv
+odhxZX1aRsM44dY0SJ8wDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBF
+AiARCNSY4WZ7Tl1oAmWghJz0Sxzi57JY4pdrvzyzYQNrhgIhAPMAzTOf33fVRhaX
+wB7TKj2HAGTDpoliTH80SMWJN3jK
+-----END CERTIFICATE-----
+`
+       testCertPEM4Fingerprint = 
"12ae34999aa64dcd1a6947e838a53aababfcfaca45abca8dc0cbb8dcb7bd063c"
 )
 
 func TestParseCertificates(t *testing.T) {
@@ -170,13 +206,21 @@
                return buf.Bytes()
        }
 
+       type pkgEntry struct {
+               pkg   apktypes.Package
+               files []tar.Header
+       }
+
        tests := []struct {
                name          string
-               cfg           *types.ImageCertificates
+               cfg           *types.ImageCertificates // inline certs
+               pkgs          []pkgEntry               // package-provided certs
+               certData      map[string][]byte        // cert file contents 
for packages
                existingFiles map[string][]byte
                wantFiles     map[string][]byte
                wantErr       bool
        }{{
+               // Inline certificate tests.
                name: "nil certificates config",
                cfg:  nil,
        }, {
@@ -255,7 +299,7 @@
                        filepath.Join(caCertsDir, 
fmt.Sprintf("test-cert-%s.crt", testCertPEMFingerprint)): []byte(testCertPEM),
                },
        }, {
-               name: "certificate with existing Java truststore",
+               name: "inline certificate with existing Java truststore",
                cfg: &types.ImageCertificates{
                        Additional: []types.AdditionalCertificateEntry{
                                {Name: "test-cert", Content: testCertPEM},
@@ -276,7 +320,7 @@
                        }),
                },
        }, {
-               name: "multiple certificates with existing Java truststore",
+               name: "multiple inline certificates with existing Java 
truststore",
                cfg: &types.ImageCertificates{
                        Additional: []types.AdditionalCertificateEntry{
                                {Name: "test-cert-1", Content: testCertPEM},
@@ -299,19 +343,223 @@
                                "test-cert-2-" + testCertPEM2Fingerprint: 
testCertPEM2,
                        }),
                },
+       }, {
+               // Package-provided certificate tests.
+               name: "no packages with custom-ca-certificates",
+               pkgs: []pkgEntry{{
+                       pkg: apktypes.Package{
+                               Name: "some-package", Version: "1.0.0", Arch: 
"x86_64",
+                               Provides: []string{"something-else"},
+                       },
+               }},
+       }, {
+               name: "package without custom-ca-certificates provide is 
ignored",
+               pkgs: []pkgEntry{{
+                       pkg: apktypes.Package{
+                               Name: "not-a-ca-pkg", Version: "1.0.0", Arch: 
"x86_64",
+                               Provides: []string{"something-else"},
+                       },
+                       files: []tar.Header{
+                               {Name: "usr", Typeflag: tar.TypeDir, Mode: 
0o755},
+                               {Name: "usr/local", Typeflag: tar.TypeDir, 
Mode: 0o755},
+                               {Name: "usr/local/share", Typeflag: 
tar.TypeDir, Mode: 0o755},
+                               {Name: "usr/local/share/ca-certificates", 
Typeflag: tar.TypeDir, Mode: 0o755},
+                               {Name: 
"usr/local/share/ca-certificates/sneaky-cert.crt", Mode: 0o644},
+                       },
+               }},
+               certData: map[string][]byte{
+                       "usr/local/share/ca-certificates/sneaky-cert.crt": 
[]byte(testCertPEM),
+               },
+       }, {
+               name: "single package with two certs appends to bundle",
+               cfg:  &types.ImageCertificates{Providers: 
[]string{"custom-ca-certificates"}},
+               pkgs: []pkgEntry{{
+                       pkg: apktypes.Package{
+                               Name: "ca-certs-1", Version: "1.0.0", Arch: 
"x86_64",
+                               Provides: []string{"custom-ca-certificates"},
+                       },
+                       files: []tar.Header{
+                               {Name: "usr", Typeflag: tar.TypeDir, Mode: 
0o755},
+                               {Name: "usr/local", Typeflag: tar.TypeDir, 
Mode: 0o755},
+                               {Name: "usr/local/share", Typeflag: 
tar.TypeDir, Mode: 0o755},
+                               {Name: "usr/local/share/ca-certificates", 
Typeflag: tar.TypeDir, Mode: 0o755},
+                               {Name: 
"usr/local/share/ca-certificates/cert-a.crt", Mode: 0o644},
+                               {Name: 
"usr/local/share/ca-certificates/cert-b.crt", Mode: 0o644},
+                       },
+               }},
+               certData: map[string][]byte{
+                       "usr/local/share/ca-certificates/cert-a.crt": 
[]byte(testCertPEM),
+                       "usr/local/share/ca-certificates/cert-b.crt": 
[]byte(testCertPEM2),
+               },
+               existingFiles: map[string][]byte{
+                       caBundlePaths[0]: {},
+               },
+               wantFiles: map[string][]byte{
+                       caBundlePaths[0]: []byte(testCertPEM + "\n" + 
testCertPEM2 + "\n"),
+               },
+       }, {
+               name: "two packages with certs each appends to existing bundle",
+               cfg:  &types.ImageCertificates{Providers: 
[]string{"custom-ca-certificates"}},
+               pkgs: []pkgEntry{{
+                       pkg: apktypes.Package{
+                               Name: "ca-certs-1", Version: "1.0.0", Arch: 
"x86_64",
+                               Provides: []string{"custom-ca-certificates"},
+                       },
+                       files: []tar.Header{
+                               {Name: "usr", Typeflag: tar.TypeDir, Mode: 
0o755},
+                               {Name: "usr/local", Typeflag: tar.TypeDir, 
Mode: 0o755},
+                               {Name: "usr/local/share", Typeflag: 
tar.TypeDir, Mode: 0o755},
+                               {Name: "usr/local/share/ca-certificates", 
Typeflag: tar.TypeDir, Mode: 0o755},
+                               {Name: 
"usr/local/share/ca-certificates/cert-a.crt", Mode: 0o644},
+                       },
+               }, {
+                       pkg: apktypes.Package{
+                               Name: "ca-certs-2", Version: "1.0.0", Arch: 
"x86_64",
+                               Provides: []string{"custom-ca-certificates"},
+                       },
+                       files: []tar.Header{
+                               {Name: "usr", Typeflag: tar.TypeDir, Mode: 
0o755},
+                               {Name: "usr/local", Typeflag: tar.TypeDir, 
Mode: 0o755},
+                               {Name: "usr/local/share", Typeflag: 
tar.TypeDir, Mode: 0o755},
+                               {Name: "usr/local/share/ca-certificates", 
Typeflag: tar.TypeDir, Mode: 0o755},
+                               {Name: 
"usr/local/share/ca-certificates/cert-c.crt", Mode: 0o644},
+                       },
+               }},
+               certData: map[string][]byte{
+                       "usr/local/share/ca-certificates/cert-a.crt": 
[]byte(testCertPEM3),
+                       "usr/local/share/ca-certificates/cert-c.crt": 
[]byte(testCertPEM4),
+               },
+               existingFiles: map[string][]byte{
+                       caBundlePaths[0]: []byte("# Existing Bundle\n"),
+               },
+               wantFiles: map[string][]byte{
+                       caBundlePaths[0]: []byte("# Existing Bundle\n" + 
testCertPEM3 + "\n" + testCertPEM4 + "\n"),
+               },
+       }, {
+               name: "non-cert files in package are ignored",
+               cfg:  &types.ImageCertificates{Providers: 
[]string{"custom-ca-certificates"}},
+               pkgs: []pkgEntry{{
+                       pkg: apktypes.Package{
+                               Name: "ca-certs-1", Version: "1.0.0", Arch: 
"x86_64",
+                               Provides: []string{"custom-ca-certificates"},
+                       },
+                       files: []tar.Header{
+                               {Name: "usr", Typeflag: tar.TypeDir, Mode: 
0o755},
+                               {Name: "usr/local", Typeflag: tar.TypeDir, 
Mode: 0o755},
+                               {Name: "usr/local/share", Typeflag: 
tar.TypeDir, Mode: 0o755},
+                               {Name: "usr/local/share/ca-certificates", 
Typeflag: tar.TypeDir, Mode: 0o755},
+                               {Name: 
"usr/local/share/ca-certificates/cert-a.crt", Mode: 0o644},
+                               {Name: 
"usr/local/share/ca-certificates/README.md", Mode: 0o644},
+                       },
+               }},
+               certData: map[string][]byte{
+                       "usr/local/share/ca-certificates/cert-a.crt": 
[]byte(testCertPEM),
+                       "usr/local/share/ca-certificates/README.md":  
[]byte("not a cert"),
+               },
+               existingFiles: map[string][]byte{
+                       caBundlePaths[0]: {},
+               },
+               wantFiles: map[string][]byte{
+                       caBundlePaths[0]: []byte(testCertPEM + "\n"),
+               },
+       }, {
+               name: "package certs with existing Java truststore",
+               cfg:  &types.ImageCertificates{Providers: 
[]string{"custom-ca-certificates"}},
+               pkgs: []pkgEntry{{
+                       pkg: apktypes.Package{
+                               Name: "ca-certs-1", Version: "1.0.0", Arch: 
"x86_64",
+                               Provides: []string{"custom-ca-certificates"},
+                       },
+                       files: []tar.Header{
+                               {Name: "usr", Typeflag: tar.TypeDir, Mode: 
0o755},
+                               {Name: "usr/local", Typeflag: tar.TypeDir, 
Mode: 0o755},
+                               {Name: "usr/local/share", Typeflag: 
tar.TypeDir, Mode: 0o755},
+                               {Name: "usr/local/share/ca-certificates", 
Typeflag: tar.TypeDir, Mode: 0o755},
+                               {Name: 
"usr/local/share/ca-certificates/cert-a.crt", Mode: 0o644},
+                       },
+               }},
+               certData: map[string][]byte{
+                       "usr/local/share/ca-certificates/cert-a.crt": 
[]byte(testCertPEM),
+               },
+               existingFiles: map[string][]byte{
+                       caBundlePaths[0]: {},
+                       javaTruststorePaths[0]: 
createTruststore(map[string]string{
+                               "existing": testCertPEM2,
+                       }),
+               },
+               wantFiles: map[string][]byte{
+                       caBundlePaths[0]: []byte(testCertPEM + "\n"),
+                       javaTruststorePaths[0]: 
createTruststore(map[string]string{
+                               "existing":                      testCertPEM2,
+                               "pkg-" + testCertPEMFingerprint: testCertPEM,
+                       }),
+               },
+       }, {
+               // Combined inline + package-provided certificate test.
+               name: "inline and package certs both appended to bundle and 
truststore",
+               cfg: &types.ImageCertificates{
+                       Additional: []types.AdditionalCertificateEntry{
+                               {Name: "inline-cert", Content: testCertPEM},
+                       },
+                       Providers: []string{"custom-ca-certificates"},
+               },
+               pkgs: []pkgEntry{{
+                       pkg: apktypes.Package{
+                               Name: "ca-certs-1", Version: "1.0.0", Arch: 
"x86_64",
+                               Provides: []string{"custom-ca-certificates"},
+                       },
+                       files: []tar.Header{
+                               {Name: "usr", Typeflag: tar.TypeDir, Mode: 
0o755},
+                               {Name: "usr/local", Typeflag: tar.TypeDir, 
Mode: 0o755},
+                               {Name: "usr/local/share", Typeflag: 
tar.TypeDir, Mode: 0o755},
+                               {Name: "usr/local/share/ca-certificates", 
Typeflag: tar.TypeDir, Mode: 0o755},
+                               {Name: 
"usr/local/share/ca-certificates/cert.crt", Mode: 0o644},
+                       },
+               }},
+               certData: map[string][]byte{
+                       "usr/local/share/ca-certificates/cert.crt": 
[]byte(testCertPEM3),
+               },
+               existingFiles: map[string][]byte{
+                       caBundlePaths[0]: []byte("# Existing Bundle\n"),
+                       javaTruststorePaths[0]: 
createTruststore(map[string]string{
+                               "existing": testCertPEM2,
+                       }),
+               },
+               wantFiles: map[string][]byte{
+                       // Inline certs are processed first, then package certs.
+                       caBundlePaths[0]: []byte("# Existing Bundle\n" + 
testCertPEM + "\n" + testCertPEM3 + "\n"),
+                       filepath.Join(caCertsDir, 
fmt.Sprintf("inline-cert-%s.crt", testCertPEMFingerprint)): []byte(testCertPEM),
+                       javaTruststorePaths[0]: 
createTruststore(map[string]string{
+                               "existing":                              
testCertPEM2,
+                               "inline-cert-" + testCertPEMFingerprint: 
testCertPEM,
+                               "pkg-" + testCertPEM3Fingerprint:        
testCertPEM3,
+                       }),
+               },
        }}
 
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                        fsys := apkfs.NewMemFS()
-                       bc := &Context{
-                               o: options.Options{
-                                       SourceDateEpoch: epoch,
-                               },
-                               ic: types.ImageConfiguration{
-                                       Certificates: tt.cfg,
-                               },
-                               fs: fsys,
+
+                       apkInst, err := apk.New(context.Background(), 
apk.WithFS(fsys), apk.WithIgnoreMknodErrors(true))
+                       if err != nil {
+                               t.Fatalf("failed to create APK: %v", err)
+                       }
+                       if err := apkInst.InitDB(context.Background()); err != 
nil {
+                               t.Fatalf("failed to init APK DB: %v", err)
+                       }
+                       for _, p := range tt.pkgs {
+                               if _, err := 
apkInst.AddInstalledPackage(&p.pkg, p.files); err != nil {
+                                       t.Fatalf("failed to add installed 
package %s: %v", p.pkg.Name, err)
+                               }
+                       }
+                       for path, data := range tt.certData {
+                               if err := fsys.MkdirAll(filepath.Dir(path), 
0o755); err != nil {
+                                       t.Fatalf("failed to create dir for %s: 
%v", path, err)
+                               }
+                               if err := fsys.WriteFile(path, data, 0o644); 
err != nil {
+                                       t.Fatalf("failed to write cert file %s: 
%v", path, err)
+                               }
                        }
 
                        for path, content := range tt.existingFiles {
@@ -323,51 +571,77 @@
                                }
                        }
 
-                       err := bc.installCertificates(context.Background())
+                       bc := &Context{
+                               o: options.Options{
+                                       SourceDateEpoch: epoch,
+                               },
+                               ic: types.ImageConfiguration{
+                                       Certificates: tt.cfg,
+                               },
+                               fs:  fsys,
+                               apk: apkInst,
+                       }
+
+                       err = bc.installCertificates(context.Background())
                        if (err != nil) != tt.wantErr {
                                t.Fatalf("installCertificates() error = %v, 
wantErr %v", err, tt.wantErr)
                        }
                        if tt.wantErr {
-                               // Expected error, nothing further to check
-                               return
-                       }
-                       if tt.cfg == nil || len(tt.cfg.Additional) == 0 {
-                               // Nothing further to check
                                return
                        }
 
-                       // Walk the entire filesystem to ensure we're checking 
contents for all
-                       // expected files.
-                       fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, 
err error) error {
-                               if err != nil {
-                                       t.Fatalf("error walking to %s: %v", 
path, err)
-                               }
-                               if d.IsDir() {
-                                       return nil
-                               }
-
-                               wantContent, ok := tt.wantFiles[path]
-                               if !ok {
-                                       t.Errorf("unexpected file created: %s", 
path)
-                                       return nil
+                       if len(tt.wantFiles) == 0 {
+                               // No-op case: verify primary CA bundle was NOT 
created/modified.
+                               if _, err := fsys.Stat(caBundlePaths[0]); err 
== nil {
+                                       t.Errorf("expected no CA bundle to be 
created, but %s exists", caBundlePaths[0])
                                }
+                               return
+                       }
 
+                       // Verify expected file contents and timestamps.
+                       for path, wantContent := range tt.wantFiles {
                                data, err := fsys.ReadFile(path)
                                if err != nil {
                                        t.Fatalf("failed to read expected file 
%s: %v", path, err)
                                }
-
                                if diff := cmp.Diff(wantContent, data); diff != 
"" {
                                        t.Errorf("file content mismatch for %s 
(-want +got):\n%s", path, diff)
                                }
-
                                stat, err := fsys.Stat(path)
                                if err != nil {
                                        t.Fatalf("failed to stat file %s: %v", 
path, err)
                                }
-                               modTime := stat.ModTime()
-                               if !modTime.Equal(epoch) {
-                                       t.Errorf("file %s has mod time %v, want 
%v", path, modTime, epoch)
+                               if !stat.ModTime().Equal(epoch) {
+                                       t.Errorf("file %s has mod time %v, want 
%v", path, stat.ModTime(), epoch)
+                               }
+                       }
+
+                       // Build a set of files that exist on the filesystem 
but are
+                       // not certificate output: APK DB files from InitDB and
+                       // package cert source files written during test setup.
+                       setupFiles := map[string]bool{}
+                       if apkInst != nil {
+                               for _, h := range apkInst.ListInitFiles() {
+                                       setupFiles[strings.TrimPrefix(h.Name, 
"/")] = true
+                               }
+                       }
+                       for path := range tt.certData {
+                               setupFiles[path] = true
+                       }
+
+                       // Walk the entire filesystem to catch unexpected files.
+                       fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, 
err error) error {
+                               if err != nil {
+                                       t.Fatalf("error walking to %s: %v", 
path, err)
+                               }
+                               if d.IsDir() {
+                                       return nil
+                               }
+                               if setupFiles[path] {
+                                       return nil
+                               }
+                               if _, ok := tt.wantFiles[path]; !ok {
+                                       t.Errorf("unexpected file created: %s", 
path)
                                }
                                return nil
                        })
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.1.13/pkg/build/types/schema.json 
new/apko-1.1.14/pkg/build/types/schema.json
--- old/apko-1.1.13/pkg/build/types/schema.json 2026-03-08 16:29:25.000000000 
+0100
+++ new/apko-1.1.14/pkg/build/types/schema.json 2026-03-10 19:28:46.000000000 
+0100
@@ -84,6 +84,13 @@
           },
           "type": "array",
           "description": "Additional certificates to install in the image"
+        },
+        "providers": {
+          "items": {
+            "type": "string"
+          },
+          "type": "array",
+          "description": "Providers is a list of virtual package names that 
identify packages\ncontaining CA certificate files to be assembled into the 
system CA bundle."
         }
       },
       "additionalProperties": false,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.1.13/pkg/build/types/types.go 
new/apko-1.1.14/pkg/build/types/types.go
--- old/apko-1.1.13/pkg/build/types/types.go    2026-03-08 16:29:25.000000000 
+0100
+++ new/apko-1.1.14/pkg/build/types/types.go    2026-03-10 19:28:46.000000000 
+0100
@@ -455,4 +455,7 @@
 type ImageCertificates struct {
        // Additional certificates to install in the image
        Additional []AdditionalCertificateEntry `json:"additional,omitempty" 
yaml:"additional,omitempty"`
+       // Providers is a list of virtual package names that identify packages
+       // containing CA certificate files to be assembled into the system CA 
bundle.
+       Providers []string `json:"providers,omitempty" 
yaml:"providers,omitempty"`
 }

++++++ apko.obsinfo ++++++
--- /var/tmp/diff_new_pack.HlU56C/_old  2026-03-11 20:56:46.989545142 +0100
+++ /var/tmp/diff_new_pack.HlU56C/_new  2026-03-11 20:56:46.993545308 +0100
@@ -1,5 +1,5 @@
 name: apko
-version: 1.1.13
-mtime: 1772983765
-commit: 0a1df0f2e7cb29b95680746b8bbb0c03e5a22511
+version: 1.1.14
+mtime: 1773167326
+commit: 238cd7b8aec624af67bcd08492c7bb4412dc119d
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/apko/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.apko.new.8177/vendor.tar.gz differ: char 134, line 3

Reply via email to