Repository: chainguard-dev/apko
Pull request #1356 (2024-10-23 14:47:17)

build(deps): bump github/codeql-action from 3.26.13 to 3.27.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.13 to 3.27.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/github/codeql-action/releases">github/codeql-action's releases</a>.</em></p>
<blockquote>
<h2>v3.27.0</h2>
<h1>CodeQL Action Changelog</h1>
<p>See the <a href="https://github.com/github/codeql-action/releases">releases page</a> for the relevant changes to the CodeQL CLI and language packs.</p>
<p>Note that the only difference between <code>v2</code> and <code>v3</code> of the CodeQL Action is the node version they support, with <code>v3</code> running on node 20 while we continue to release <code>v2</code> to support running on node 16. For example <code>3.22.11</code> was the first <code>v3</code> release and is functionally identical to <code>2.22.11</code>. This approach ensures an easy way to track exactly which features are included in different versions, indicated by the minor and patch version numbers.</p>
<h2>3.27.0 - 22 Oct 2024</h2>
<ul>
<li>Bump the minimum CodeQL bundle version to 2.14.6. <a href="https://redirect.github.com/github/codeql-action/pull/2549">#2549</a></li>
<li>Fix an issue where the <code>upload-sarif</code> Action would fail with &quot;upload-sarif post-action step failed: Input required and not supplied: token&quot; when called in a composite Action that had a different set of inputs to the ones expected by the <code>upload-sarif</code> Action. <a href="https://redirect.github.com/github/codeql-action/pull/2557">#2557</a></li>
<li>Update default CodeQL bundle version to 2.19.2. <a href="https://redirect.github.com/github/codeql-action/pull/2552">#2552</a></li>
</ul>
<p>See the full <a href="https://github.com/github/codeql-action/blob/v3.27.0/CHANGELOG.md">CHANGELOG.md</a> for more information.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action's changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a href="https://github.com/github/codeql-action/releases">releases page</a> for the relevant changes to the CodeQL CLI and language packs.</p>
<p>Note that the only difference between <code>v2</code> and <code>v3</code> of the CodeQL Action is the node version they support, with <code>v3</code> running on node 20 while we continue to release <code>v2</code> to support running on node 16. For example <code>3.22.11</code> was the first <code>v3</code> release and is functionally identical to <code>2.22.11</code>. This approach ensures an easy way to track exactly which features are included in different versions, indicated by the minor and patch version numbers.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>3.27.0 - 22 Oct 2024</h2>
<ul>
<li>Bump the minimum CodeQL bundle version to 2.14.6. <a href="https://redirect.github.com/github/codeql-action/pull/2549">#2549</a></li>
<li>Fix an issue where the <code>upload-sarif</code> Action would fail with &quot;upload-sarif post-action step failed: Input required and not supplied: token&quot; when called in a composite Action that had a different set of inputs to the ones expected by the <code>upload-sarif</code> Action. <a href="https://redirect.github.com/github/codeql-action/pull/2557">#2557</a></li>
<li>Update default CodeQL bundle version to 2.19.2. <a href="https://redirect.github.com/github/codeql-action/pull/2552">#2552</a></li>
</ul>
<h2>3.26.13 - 14 Oct 2024</h2>
<p>No user facing changes.</p>
<h2>3.26.12 - 07 Oct 2024</h2>
<ul>
<li>
<p><em>Upcoming breaking change</em>: Add a deprecation warning for customers using CodeQL version 2.14.5 and earlier. These versions of CodeQL were discontinued on 24 September 2024 alongside GitHub Enterprise Server 3.10, and will be unsupported by CodeQL Action versions 3.27.0 and later and versions 2.27.0 and later. <a href="https://redirect.github.com/github/codeql-action/pull/2520">#2520</a></p>
<ul>
<li>
<p>If you are using one of these versions, please update to CodeQL CLI version 2.14.6 or later. For instance, if you have specified a custom version of the CLI using the 'tools' input to the 'init' Action, you can remove this input to use the default version.</p>
</li>
<li>
<p>Alternatively, if you want to continue using a version of the CodeQL CLI between 2.13.5 and 2.14.5, you can replace <code>github/codeql-action/*@v3</code> by <code>github/codeql-action/*@v3.26.11</code> and <code>github/codeql-action/*@v2</code> by <code>github/codeql-action/*@v2.26.11</code> in your code scanning workflow to ensure you continue using this version of the CodeQL Action.</p>
</li>
</ul>
</li>
</ul>
<h2>3.26.11 - 03 Oct 2024</h2>
<ul>
<li>
<p><em>Upcoming breaking change</em>: Add support for using <code>actions/download-artifact@v4</code> to programmatically consume CodeQL Action debug artifacts.</p>
<p>Starting November 30, 2024, GitHub.com customers will <a href="https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/">no longer be able to use <code>actions/download-artifact@v3</code></a>. Therefore, to avoid breakage, customers who programmatically download the CodeQL Action debug artifacts should set the <code>CODEQL_ACTION_ARTIFACT_V4_UPGRADE</code> environment variable to <code>true</code> and bump <code>actions/download-artifact@v3</code> to <code>actions/download-artifact@v4</code> in their workflows. The CodeQL Action will enable this behavior by default in early November and workflows that have not yet bumped to <code>actions/download-artifact@v3</code> to <code>actions/download-artifact@v4</code> will begin failing then.</p>
<p>This change is currently unavailable for GitHub Enterprise Server customers, as <code>actions/upload-artifact@v4</code> and <code>actions/download-artifact@v4</code> are not yet compatible with GHES.</p>
</li>
<li>
<p>Update default CodeQL bundle version to 2.19.1. <a href="https://redirect.github.com/github/codeql-action/pull/2519">#2519</a></p>
</li>
</ul>
<h2>3.26.10 - 30 Sep 2024</h2>
<ul>
<li>We are rolling out a feature in September/October 2024 that sets up CodeQL using a bundle compressed with <a href="http://facebook.github.io/zstd/">Zstandard</a>. Our aim is to improve the performance of setting up CodeQL. <a href="https://redirect.github.com/github/codeql-action/pull/2502">#2502</a></li>
</ul>
<h2>3.26.9 - 24 Sep 2024</h2>
<p>No user facing changes.</p>
<h2>3.26.8 - 19 Sep 2024</h2>
<ul>
<li>Update default CodeQL bundle version to 2.19.0. <a href="https://redirect.github.com/github/codeql-action/pull/2483">#2483</a></li>
</ul>
<h2>3.26.7 - 13 Sep 2024</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/github/codeql-action/commit/662472033e021d55d94146f66f6058822b0b39fd"><code>6624720</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2561">#2561</a> from github/update-v3.27.0-b35b023d9</li>
<li><a href="https://github.com/github/codeql-action/commit/ce7c2b560da6747133b72eb2f33503a6c4da9c15"><code>ce7c2b5</code></a> Update changelog for v3.27.0</li>
<li><a href="https://github.com/github/codeql-action/commit/b35b023d9b1296658ca1bcb95dcd0336f9d23f0b"><code>b35b023</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2552">#2552</a> from github/update-bundle/codeql-bundle-v2.19.2</li>
<li><a href="https://github.com/github/codeql-action/commit/dafc762411c1755f00abee84283eaa85d438af2e"><code>dafc762</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2560">#2560</a> from github/aeisenberg/fix-required-checks</li>
<li><a href="https://github.com/github/codeql-action/commit/0d1eb88b60733b6720210d591c48ebc9e961d42c"><code>0d1eb88</code></a> Remove ESLint from required checks</li>
<li><a href="https://github.com/github/codeql-action/commit/0a30541440699f21b772e5ef95c912f097514855"><code>0a30541</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2558">#2558</a> from github/dependabot/npm_and_yarn/npm-6515e6e328</li>
<li><a href="https://github.com/github/codeql-action/commit/2a6a6ad1c809216132b8a6a8c1f5873fff3f400a"><code>2a6a6ad</code></a> Update checked-in dependencies</li>
<li><a href="https://github.com/github/codeql-action/commit/26c18c2c1f382ccd402aec5dd18d9ff6d0097891"><code>26c18c2</code></a> Bump the npm group with 3 updates</li>
<li><a href="https://github.com/github/codeql-action/commit/7080a68cbca9319f3cf869f12f34acea853ad72d"><code>7080a68</code></a> Merge branch 'main' into update-bundle/codeql-bundle-v2.19.2</li>
<li><a href="https://github.com/github/codeql-action/commit/63eb7bbf1f65c025536b3e7d083d89c1c161043c"><code>63eb7bb</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2551">#2551</a> from github/cklin/diff-informed-queries-feature</li>
<li>Additional commits viewable in <a href="https://github.com/github/codeql-action/compare/f779452ac5af1c261dce0346a8f964149f49322b...662472033e021d55d94146f66f6058822b0b39fd">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action&package-manager=github_actions&previous-version=3.26.13&new-version=3.27.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/build-samples.yml b/.github/workflows/build-samples.yml
index 00a8e97..04b4e2f 100644
--- a/.github/workflows/build-samples.yml
+++ b/.github/workflows/build-samples.yml
@@ -25,7 +25,7 @@ jobs:
       - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
         with:
           egress-policy: audit
-      - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v2.1.5
         with:
           go-version-file: 'go.mod'
@@ -62,7 +62,7 @@ jobs:
       - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
         with:
           egress-policy: audit
-      - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v2.1.5
         with:
           go-version-file: 'go.mod'
@@ -97,7 +97,7 @@ jobs:
       - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
         with:
           egress-policy: audit
-      - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v2.1.5
         with:
           go-version-file: 'go.mod'
@@ -127,7 +127,7 @@ jobs:
       - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
         with:
           egress-policy: audit
-      - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v2.1.5
         with:
           go-version-file: 'go.mod'
@@ -166,7 +166,7 @@ jobs:
       - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
         with:
           egress-policy: audit
-      - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v2.1.5
         with:
           go-version-file: 'go.mod'
@@ -205,7 +205,7 @@ jobs:
       - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
         with:
           egress-policy: audit
-      - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v2.1.5
         with:
           go-version-file: 'go.mod'
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 0bab3b8..83a3f07 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -19,7 +19,7 @@ jobs:
         with:
           egress-policy: audit
 
-      - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
 
       - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v2.1.5
         with:
diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml
index 0cffd80..8703414 100644
--- a/.github/workflows/codeql.yaml
+++ b/.github/workflows/codeql.yaml
@@ -22,7 +22,7 @@ jobs:
       with:
         egress-policy: audit
 
-    - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
 
     - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v3.0.0
       with:
@@ -30,7 +30,7 @@ jobs:
         check-latest: true
 
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b
+      uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd
       with:
         languages: go
 
@@ -38,4 +38,4 @@ jobs:
       run: make apko
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b
+      uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index b0e6119..62effbb 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -23,7 +23,7 @@ jobs:
     - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
       with:
         egress-policy: audit
-    - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
     - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v2.2.0
       with:
         go-version-file: 'go.mod'
diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml
index 6b40ab6..4ddcc72 100644
--- a/.github/workflows/verify.yaml
+++ b/.github/workflows/verify.yaml
@@ -15,7 +15,7 @@ jobs:
       contents: read
 
     steps:
-      - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v2.2.0
         with:
           go-version-file: 'go.mod'
diff --git a/go.mod b/go.mod
index 1184a2d..6060cb3 100644
--- a/go.mod
+++ b/go.mod
@@ -31,7 +31,7 @@ require (
 	golang.org/x/sync v0.8.0
 	golang.org/x/sys v0.26.0
 	golang.org/x/time v0.7.0
-	google.golang.org/api v0.201.0
+	google.golang.org/api v0.203.0
 	gopkg.in/ini.v1 v1.67.0
 	gopkg.in/yaml.v3 v3.0.1
 	k8s.io/apimachinery v0.31.1
@@ -40,7 +40,7 @@ require (
 
 require (
 	chainguard.dev/go-grpc-kit v0.17.6 // indirect
-	cloud.google.com/go/auth v0.9.8 // indirect
+	cloud.google.com/go/auth v0.9.9 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
 	cloud.google.com/go/compute/metadata v0.5.2 // indirect
 	dario.cat/mergo v1.0.1 // indirect
@@ -157,8 +157,8 @@ require (
 	golang.org/x/oauth2 v0.23.0 // indirect
 	golang.org/x/term v0.25.0 // indirect
 	golang.org/x/text v0.19.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
 	google.golang.org/grpc v1.67.1 // indirect
 	google.golang.org/protobuf v1.35.1 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
diff --git a/go.sum b/go.sum
index b061f0f..2f536d6 100644
--- a/go.sum
+++ b/go.sum
@@ -3,8 +3,8 @@ chainguard.dev/go-grpc-kit v0.17.6/go.mod h1:ZNaXn2KEO++2u2WveHs65krYiHmAEGjYLeE
 chainguard.dev/sdk v0.1.28 h1:xLQv0JxiGhqVKOL059DmTReTjrKFhUsP5U1W6cgr+jQ=
 chainguard.dev/sdk v0.1.28/go.mod h1:9EvGI9GY5UPDbZ5AhGbMO8865ixNu36afQYCZ5M95NM=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go/auth v0.9.8 h1:+CSJ0Gw9iVeSENVCKJoLHhdUykDgXSc4Qn+gu2BRtR8=
-cloud.google.com/go/auth v0.9.8/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI=
+cloud.google.com/go/auth v0.9.9 h1:BmtbpNQozo8ZwW2t7QJjnrQtdganSdmqeIBxHxNkEZQ=
+cloud.google.com/go/auth v0.9.9/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI=
 cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
 cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
 cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
@@ -523,18 +523,18 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.201.0 h1:+7AD9JNM3tREtawRMu8sOjSbb8VYcYXJG/2eEOmfDu0=
-google.golang.org/api v0.201.0/go.mod h1:HVY0FCHVs89xIW9fzf/pBvOEm+OolHa86G/txFezyq4=
+google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU=
+google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f h1:jTm13A2itBi3La6yTGqn8bVSrc3ZZ1r8ENHlIXBfnRA=
-google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f/go.mod h1:CLGoBuH1VHxAUXVPP8FfPwPEVJB6lz3URE5mY2SuayE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
+google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg=
+google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
 google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
diff --git a/pkg/apk/apk/cache.go b/pkg/apk/apk/cache.go
index f1a204f..83ee7f2 100644
--- a/pkg/apk/apk/cache.go
+++ b/pkg/apk/apk/cache.go
@@ -209,7 +209,7 @@ func (t *cacheTransport) get(ctx context.Context, request *http.Request, cacheFi
 }
 
 func (t *cacheTransport) fetchAndCache(ctx context.Context, request *http.Request, cacheFile string) (*http.Response, error) {
-	initialEtag := request.Header.Get("If-None-Match")
+	initialEtag := request.Header.Get("I-Cant-Believe-Its-Not-If-None-Match")
 	if initialEtag == "" {
 		resp, err := t.head(request, cacheFile)
 		if err != nil {
@@ -232,7 +232,7 @@ func (t *cacheTransport) fetchAndCache(ctx context.Context, request *http.Reques
 	// Since we already send a HEAD request to get that etag, we want to avoid sending a redundant HEAD request above if we can.
 	// However, we don't actually want to pass along this header because it's using the "parsed" version of the Etag from
 	// etagFromResponse and doesn't actually attempt to follow HTTP semantics, so we remove it here to avoid any confusion.
-	request.Header.Del("If-None-Match")
+	request.Header.Del("I-Cant-Believe-Its-Not-If-None-Match")
 
 	etagFile, err := t.get(ctx, request, cacheFile, initialEtag)
 	if err != nil {
diff --git a/pkg/apk/apk/implementation.go b/pkg/apk/apk/implementation.go
index 00f0b2d..ba34e8a 100644
--- a/pkg/apk/apk/implementation.go
+++ b/pkg/apk/apk/implementation.go
@@ -927,6 +927,11 @@ func (a *APK) fetchChainguardKeys(ctx context.Context, repository string) error
 
 	log := clog.FromContext(ctx)
 
+	if !strings.HasPrefix(repository, "https://") && !strings.HasPrefix(repository, "http://") {
+		log.Debugf("ignoring non-http(s) repository %s", repository)
+		return nil
+	}
+
 	keys, err := a.DiscoverKeys(ctx, repository)
 	if err != nil {
 		log.Warnf("ignoring missing keys for %s: %v", repository, err)
diff --git a/pkg/apk/apk/index.go b/pkg/apk/apk/index.go
index 8d127e3..98ad3da 100644
--- a/pkg/apk/apk/index.go
+++ b/pkg/apk/apk/index.go
@@ -289,7 +289,7 @@ func fetchRepositoryIndex(ctx context.Context, u string, etag string, opts *inde
 		// also really wants to do a HEAD request in order to do etag-based caching itself. To avoid the double HEAD,
 		// I'm stuffing the etag into the If-None-Match header, which isn't exactly correct semantics but it rhymes.
 		// The alternative is to rewrite everything, which I don't have time to do right now, so it is what it is.
-		req.Header.Set("If-None-Match", etag)
+		req.Header.Set("I-Cant-Believe-Its-Not-If-None-Match", etag)
 	}
 
 	if opts.auth == nil {
diff --git a/pkg/build/build.go b/pkg/build/build.go
index 7cbabf6..76cb47d 100644
--- a/pkg/build/build.go
+++ b/pkg/build/build.go
@@ -305,6 +305,18 @@ func New(ctx context.Context, fs apkfs.FullFS, opts ...Option) (*Context, error)
 	return &bc, nil
 }
 
+type notAFile struct {
+	rc *os.File
+}
+
+func (f *notAFile) Read(p []byte) (int, error) {
+	return f.rc.Read(p)
+}
+
+func (f *notAFile) Close() error {
+	return f.rc.Close()
+}
+
 // layer implements v1.Layer from go-containerregistry to avoid re-computing
 // digests and diffids.
 type layer struct {
@@ -322,7 +334,14 @@ func (l *layer) Digest() (v1.Hash, error) {
 }
 
 func (l *layer) Compressed() (io.ReadCloser, error) {
-	return os.Open(l.filename)
+	f, err := os.Open(l.filename)
+	if err != nil {
+		return nil, err
+	}
+
+	// There is a bug in how go uses sendfile on macos, so we need to make this not a file.
+	// See https://github.com/golang/go/issues/70000
+	return &notAFile{f}, nil
 }
 
 func (l *layer) Uncompressed() (io.ReadCloser, error) {
diff --git a/pkg/build/oci/image.go b/pkg/build/oci/image.go
index 1cfb08f..43db805 100644
--- a/pkg/build/oci/image.go
+++ b/pkg/build/oci/image.go
@@ -39,9 +39,15 @@ import (
 	"chainguard.dev/apko/pkg/options"
 )
 
-func BuildImageFromLayer(ctx context.Context, baseImage v1.Image, layer v1.Layer, ic types.ImageConfiguration, created time.Time, arch types.Architecture) (oci.SignedImage, error) {
+func BuildImageFromLayer(ctx context.Context, baseImage v1.Image, layer v1.Layer, oic types.ImageConfiguration, created time.Time, arch types.Architecture) (oci.SignedImage, error) {
 	log := clog.FromContext(ctx)
 
+	// Create a copy to avoid modifying the original ImageConfiguration.
+	ic := &types.ImageConfiguration{}
+	if err := oic.MergeInto(ic); err != nil {
+		return nil, err
+	}
+
 	mediaType, err := layer.MediaType()
 	if err != nil {
 		return nil, fmt.Errorf("accessing layer MediaType: %w", err)
diff --git a/pkg/sbom/generator/spdx/spdx.go b/pkg/sbom/generator/spdx/spdx.go
index 3cbf81a..c37cbca 100644
--- a/pkg/sbom/generator/spdx/spdx.go
+++ b/pkg/sbom/generator/spdx/spdx.go
@@ -24,6 +24,7 @@ import (
 	"time"
 	"unicode/utf8"
 
+	"github.com/charmbracelet/log"
 	purl "github.com/package-url/packageurl-go"
 	"sigs.k8s.io/release-utils/version"
 
@@ -137,6 +138,18 @@ func (sx *SPDX) Generate(opts *options.Options, path string) error {
 		}
 	}
 
+	dedupedPackages := make([]Package, 0, len(doc.Packages))
+	seenIDs := make(map[string]struct{})
+	for i := range doc.Packages {
+		if _, ok := seenIDs[doc.Packages[i].ID]; !ok {
+			seenIDs[doc.Packages[i].ID] = struct{}{}
+			dedupedPackages = append(dedupedPackages, doc.Packages[i])
+		} else {
+			log.Info("duplicate package ID found in SBOM, deduplicating package...", "ID", doc.Packages[i].ID)
+		}
+	}
+	doc.Packages = dedupedPackages
+
 	if err := renderDoc(doc, path); err != nil {
 		return fmt.Errorf("rendering document: %w", err)
 	}
@@ -178,7 +191,9 @@ func replacePackage(doc *Document, originalID, newID string) {
 	}
 }
 
-// locateApkSBOM returns the SBOM
+// locateApkSBOM returns the path to the SBOM in the given filesystem, using the
+// given Package's name and version. It returns an empty string if the SBOM is
+// not found.
 func locateApkSBOM(fsys apkfs.FullFS, p *Package) (string, error) {
 	re := regexp.MustCompile(`-r\d+$`)
 	for _, s := range []string{
@@ -209,51 +224,53 @@ func (sx *SPDX) ProcessInternalApkSBOM(opts *options.Options, doc *Document, p *
 		return fmt.Errorf("inspecting FS for internal apk SBOM: %w", err)
 	}
 	if path == "" {
+		// The SBOM does not exist.
+		// (So just ignore that the package was specified to the SPDX Generate method?)
 		return nil
 	}
 
-	internalDoc, err := sx.ParseInternalSBOM(opts, path)
+	apkSBOMDoc, err := sx.ParseInternalSBOM(opts, path)
 	if err != nil {
 		// TODO: Log error parsing apk SBOM
 		return nil
 	}
 
 	// Cycle the top level elements...
-	elementIDs := map[string]struct{}{}
-	for _, elementID := range internalDoc.DocumentDescribes {
-		elementIDs[elementID] = struct{}{}
+	idsDescribedByAPKSBOM := map[string]struct{}{}
+	for _, elementID := range apkSBOMDoc.DocumentDescribes {
+		idsDescribedByAPKSBOM[elementID] = struct{}{}
 	}
 
 	// ... searching for a 1st level package
 	targetElementIDs := map[string]struct{}{}
-	for _, pkg := range internalDoc.Packages {
+	for _, pkg := range apkSBOMDoc.Packages {
 		// that matches the name
 		if p.Name != pkg.Name {
 			continue
 		}
 
-		if _, ok := elementIDs[pkg.ID]; !ok {
+		if _, ok := idsDescribedByAPKSBOM[pkg.ID]; !ok {
 			continue
 		}
 
 		targetElementIDs[pkg.ID] = struct{}{}
-		if len(targetElementIDs) == len(elementIDs) {
+		if len(targetElementIDs) == len(idsDescribedByAPKSBOM) {
 			// Exit early if we found them all.
 			break
 		}
 	}
 
 	// Copy the targetElementIDs
-	todo := make(map[string]struct{}, len(internalDoc.Relationships))
+	todo := make(map[string]struct{}, len(apkSBOMDoc.Relationships))
 	for id := range targetElementIDs {
 		todo[id] = struct{}{}
 	}
 
-	if err := copySBOMElements(internalDoc, doc, todo); err != nil {
+	if err := copySBOMElements(apkSBOMDoc, doc, todo); err != nil {
 		return fmt.Errorf("copying element: %w", err)
 	}
 
-	if err := mergeLicensingInfos(internalDoc, doc); err != nil {
+	if err := mergeLicensingInfos(apkSBOMDoc, doc); err != nil {
 		return fmt.Errorf("merging LicensingInfos: %w", err)
 	}
 
diff --git a/pkg/sbom/options/options.go b/pkg/sbom/options/options.go
index ebaacaf..a74a26f 100644
--- a/pkg/sbom/options/options.go
+++ b/pkg/sbom/options/options.go
@@ -51,7 +51,7 @@ type Options struct {
 	// Formats dictates which SBOM formats we will output
 	Formats []string
 
-	// Packages is alist of packages which will be listed in the SBOM
+	// Packages is a list of packages which will be listed in the SBOM
 	Packages []*apk.InstalledPackage
 }
 


Test patch:
diff --git a/.github/workflows/go-tests.yaml b/.github/workflows/go-tests.yaml
index 1e0a6ab..4dc0864 100644
--- a/.github/workflows/go-tests.yaml
+++ b/.github/workflows/go-tests.yaml
@@ -19,7 +19,7 @@ jobs:
           egress-policy: audit
 
       - name: Checkout code
-        uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
 
       - name: Install Go
         uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v3.0.0
diff --git a/pkg/sbom/generator/spdx/spdx_test.go b/pkg/sbom/generator/spdx/spdx_test.go
index fd82f50..99c4fe0 100644
--- a/pkg/sbom/generator/spdx/spdx_test.go
+++ b/pkg/sbom/generator/spdx/spdx_test.go
@@ -15,8 +15,10 @@
 package spdx
 
 import (
+	"encoding/json"
 	"fmt"
 	"os"
+	"path"
 	"path/filepath"
 	"regexp"
 	"testing"
@@ -30,12 +32,9 @@ import (
 	"chainguard.dev/apko/pkg/sbom/options"
 )
 
+// TODO: clean this up and make consistent with the other test cases
 var testOpts = &options.Options{
-	OS: struct {
-		Name    string
-		ID      string
-		Version string
-	}{
+	OS: options.OSInfo{
 		Name:    "unknown",
 		ID:      "unknown",
 		Version: "3.0",
@@ -60,56 +59,7 @@ var testOpts = &options.Options{
 	},
 }
 
-var testCustomLicenseOpts = &options.Options{
-	OS: struct {
-		Name    string
-		ID      string
-		Version string
-	}{
-		Name:    "unknown",
-		ID:      "unknown",
-		Version: "3.0",
-	},
-	FileName: "sbom",
-	Packages: []*apk.InstalledPackage{
-		{
-			Package: apk.Package{
-				Name:        "font-ubuntu",
-				Version:     "0.869-r1",
-				Arch:        "x86_64",
-				Description: "Ubuntu font family",
-				License:     "LicenseRef-ubuntu-font",
-				Origin:      "font-ubuntu",
-			},
-		},
-	},
-}
-
-var testNoSupplierOpts = &options.Options{
-	OS: struct {
-		Name    string
-		ID      string
-		Version string
-	}{
-		Name:    "Apko Images, Plc",
-		ID:      "apko-images",
-		Version: "3.0",
-	},
-	FileName: "sbom",
-	Packages: []*apk.InstalledPackage{
-		{
-			Package: apk.Package{
-				Name:        "libattr1",
-				Version:     "2.5.1-r2",
-				Arch:        "x86_64",
-				Description: "library for managing filesystem extended attributes",
-				License:     "GPL-2.0-or-later",
-				Origin:      "attr",
-			},
-		},
-	},
-}
-
+// TODO: clean this up and make consistent with the other test cases
 func TestGenerate(t *testing.T) {
 	dir := t.TempDir()
 	fsys := apkfs.NewMemFS()
@@ -120,48 +70,130 @@ func TestGenerate(t *testing.T) {
 	require.FileExists(t, path)
 }
 
-func TestGenerateCustomLicense(t *testing.T) {
-	spdx, err := os.ReadFile("testdata/font-ubuntu.spdx.json")
-	require.NoError(t, err)
+func TestSPDX_Generate(t *testing.T) {
+	tests := []struct {
+		name string
+		opts *options.Options
+	}{
+		{
+			name: "custom-license",
+			opts: &options.Options{
+				OS: options.OSInfo{
+					Name:    "unknown",
+					ID:      "unknown",
+					Version: "3.0",
+				},
+				FileName: "sbom",
+				Packages: []*apk.InstalledPackage{
+					{
+						Package: apk.Package{
+							Name:    "font-ubuntu",
+							Version: "0.869-r1",
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "no-supplier",
+			opts: &options.Options{
+				OS: options.OSInfo{
+					Name:    "Apko Images, Plc",
+					ID:      "apko-images",
+					Version: "3.0",
+				},
+				FileName: "sbom",
+				Packages: []*apk.InstalledPackage{
+					{
+						Package: apk.Package{
+							Name:    "libattr1",
+							Version: "2.5.1-r2",
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "package-deduplicating",
+			opts: &options.Options{
+				OS: options.OSInfo{
+					Name:    "unknown",
+					ID:      "unknown",
+					Version: "3.0",
+				},
+				FileName: "sbom",
+				Packages: []*apk.InstalledPackage{
+					{
+						Package: apk.Package{
+							Name:    "logstash-8",
+							Version: "8.15.3-r4",
+						},
+					},
+					{
+						Package: apk.Package{
+							Name:    "logstash-8-compat",
+							Version: "8.15.3-r4",
+						},
+					},
+				},
+			},
+		},
+	}
 
-	fsys := apkfs.NewMemFS()
-	fsys.MkdirAll("/var/lib/db/sbom", 0750)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			fsys := apkfs.NewMemFS()
+			sbomDir := path.Join("var", "lib", "db", "sbom")
+			err := fsys.MkdirAll(sbomDir, 0750)
+			require.NoError(t, err)
 
-	err = fsys.WriteFile("/var/lib/db/sbom/font-ubuntu.spdx.json", spdx, 0644)
-	require.NoError(t, err)
+			for _, apkPkg := range tt.opts.Packages {
+				apkSBOMName := fmt.Sprintf("%s-%s.spdx.json", apkPkg.Name, apkPkg.Version)
+				apkSBOMTestdataPath := filepath.Join("testdata", "apk_sboms", apkSBOMName)
+				apkSBOMBytes, err := os.ReadFile(apkSBOMTestdataPath)
+				require.NoError(t, err)
 
-	sx := New(fsys)
-	path := filepath.Join(t.TempDir(), testCustomLicenseOpts.FileName+"."+sx.Ext())
-	err = sx.Generate(testCustomLicenseOpts, path)
-	require.NoError(t, err)
+				sbomDestPath := path.Join(sbomDir, apkSBOMName)
+				err = fsys.WriteFile(sbomDestPath, apkSBOMBytes, 0644)
+				require.NoError(t, err)
+			}
 
-	got, err := os.ReadFile(path)
-	require.NoError(t, err)
-	expected, err := os.ReadFile("testdata/expected.ubuntu-font.spdx.json")
-	require.NoError(t, err)
-	require.Equal(t, expected, got, "CustomLicense SPDX")
-}
+			sx := New(fsys)
+			imageSBOMName := fmt.Sprintf("%s.spdx.json", tt.name)
+			imageSBOMDestPath := filepath.Join(t.TempDir(), imageSBOMName)
+			err = sx.Generate(tt.opts, imageSBOMDestPath)
+			require.NoError(t, err)
 
-func TestNoSupplier(t *testing.T) {
-	spdx, err := os.ReadFile("testdata/libattr1.spdx.json")
-	require.NoError(t, err)
+			actual, err := os.ReadFile(imageSBOMDestPath)
+			require.NoError(t, err)
 
-	fsys := apkfs.NewMemFS()
-	fsys.MkdirAll("/var/lib/db/sbom", 0750)
+			expectedImageSBOMPath := filepath.Join("testdata", "expected_image_sboms", imageSBOMName)
+			expected, err := os.ReadFile(expectedImageSBOMPath)
+			require.NoError(t, err)
 
-	err = fsys.WriteFile("/var/lib/db/sbom/libattr1.spdx.json", spdx, 0644)
-	require.NoError(t, err)
+			t.Run("goldenfile diff", func(t *testing.T) {
+				if diff := cmp.Diff(expected, actual); diff != "" {
+					t.Errorf("Unexpected image SBOM (-want, +got): \n%s", diff)
+				}
+			})
 
-	sx := New(fsys)
-	path := filepath.Join(t.TempDir(), testNoSupplierOpts.FileName+"."+sx.Ext())
-	err = sx.Generate(testNoSupplierOpts, path)
-	require.NoError(t, err)
+			t.Run("unique SPDX IDs", func(t *testing.T) {
+				doc := new(Document)
+				err := json.Unmarshal(actual, doc)
+				if err != nil {
+					t.Fatalf("unmarshalling SBOM: %v", err)
+				}
 
-	got, err := os.ReadFile(path)
-	require.NoError(t, err)
-	expected, err := os.ReadFile("testdata/expected.libattr1.spdx.json")
-	require.NoError(t, err)
-	require.Equal(t, expected, got, "NoSupplier SPDX")
+				ids := make(map[string]struct{})
+				for _, p := range doc.Packages {
+					if _, ok := ids[p.ID]; ok {
+						t.Errorf("duplicate SPDX ID found: %s", p.ID)
+					}
+					ids[p.ID] = struct{}{}
+				}
+			})
+		})
+	}
 }
 
 func TestReproducible(t *testing.T) {
diff --git a/pkg/sbom/generator/spdx/testdata/apk_sboms/_generate.sh b/pkg/sbom/generator/spdx/testdata/apk_sboms/_generate.sh
new file mode 100755
index 0000000..31cfd9d
--- /dev/null
+++ b/pkg/sbom/generator/spdx/testdata/apk_sboms/_generate.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -euo pipefail
+
+# Define an array of package name-version strings
+packages=(
+  "font-ubuntu-0.869-r1"
+  "libattr1-2.5.1-r2"
+  "logstash-8-8.15.3-r4"
+  "logstash-8-compat-8.15.3-r4"
+)
+
+# Base URL for downloading APKs
+base_url="https://packages.wolfi.dev/os/x86_64"
+
+# Loop through the array and process each package
+for pkg in "${packages[@]}"; do
+  url="${base_url}/${pkg}.apk"
+  output_path="${pkg}.spdx.json"
+  curl -q "$url" | tar Ozx "var/lib/db/sbom/${pkg}.spdx.json" >"$output_path" 2>/dev/null
+done
diff --git a/pkg/sbom/generator/spdx/testdata/font-ubuntu.spdx.json b/pkg/sbom/generator/spdx/testdata/apk_sboms/font-ubuntu-0.869-r1.spdx.json
similarity index 100%
rename from pkg/sbom/generator/spdx/testdata/font-ubuntu.spdx.json
rename to pkg/sbom/generator/spdx/testdata/apk_sboms/font-ubuntu-0.869-r1.spdx.json
diff --git a/pkg/sbom/generator/spdx/testdata/libattr1.spdx.json b/pkg/sbom/generator/spdx/testdata/apk_sboms/libattr1-2.5.1-r2.spdx.json
similarity index 100%
rename from pkg/sbom/generator/spdx/testdata/libattr1.spdx.json
rename to pkg/sbom/generator/spdx/testdata/apk_sboms/libattr1-2.5.1-r2.spdx.json
diff --git a/pkg/sbom/generator/spdx/testdata/apk_sboms/logstash-8-8.15.3-r4.spdx.json b/pkg/sbom/generator/spdx/testdata/apk_sboms/logstash-8-8.15.3-r4.spdx.json
new file mode 100644
index 0000000..9e75628
--- /dev/null
+++ b/pkg/sbom/generator/spdx/testdata/apk_sboms/logstash-8-8.15.3-r4.spdx.json
@@ -0,0 +1,87 @@
+{
+  "SPDXID": "SPDXRef-DOCUMENT",
+  "name": "apk-logstash-8-8.15.3-r4",
+  "spdxVersion": "SPDX-2.3",
+  "creationInfo": {
+    "created": "2024-10-24T15:16:49Z",
+    "creators": [
+      "Tool: melange (v0.14.7)",
+      "Organization: Chainguard, Inc"
+    ],
+    "licenseListVersion": "3.22"
+  },
+  "dataLicense": "CC0-1.0",
+  "documentNamespace": "https://spdx.org/spdxdocs/chainguard/melange/0434ffabb9f0343f5dada78abdb2e419b2be48fb",
+  "documentDescribes": [
+    "SPDXRef-Package-logstash-8-8.15.3-r4"
+  ],
+  "packages": [
+    {
+      "SPDXID": "SPDXRef-Package-logstash-8-8.15.3-r4",
+      "name": "logstash-8",
+      "versionInfo": "8.15.3-r4",
+      "filesAnalyzed": false,
+      "licenseConcluded": "NOASSERTION",
+      "licenseDeclared": "Apache-2.0",
+      "downloadLocation": "NOASSERTION",
+      "originator": "Organization: Wolfi",
+      "supplier": "Organization: Wolfi",
+      "copyrightText": "\n",
+      "externalRefs": [
+        {
+          "referenceCategory": "PACKAGE-MANAGER",
+          "referenceLocator": "pkg:apk/wolfi/logstash-8@8.15.3-r4?arch=x86_64",
+          "referenceType": "purl"
+        }
+      ]
+    },
+    {
+      "SPDXID": "SPDXRef-Package-logstash-8.yaml-c7d40faa38ddffa98700cfa4c2f9bde196acc504",
+      "name": "logstash-8.yaml",
+      "versionInfo": "c7d40faa38ddffa98700cfa4c2f9bde196acc504",
+      "filesAnalyzed": false,
+      "licenseConcluded": "NOASSERTION",
+      "licenseDeclared": "Apache-2.0",
+      "downloadLocation": "NOASSERTION",
+      "originator": "Organization: Wolfi",
+      "supplier": "Organization: Wolfi",
+      "externalRefs": [
+        {
+          "referenceCategory": "PACKAGE-MANAGER",
+          "referenceLocator": "pkg:github/wolfi-dev/os@c7d40faa38ddffa98700cfa4c2f9bde196acc504#logstash-8.yaml",
+          "referenceType": "purl"
+        }
+      ]
+    },
+    {
+      "SPDXID": "SPDXRef-Package-github.com-elastic-logstash-v8.15.3-8364c8e89cfb113e38ec3f966df7eb1e9abe9d33-0",
+      "name": "logstash",
+      "versionInfo": "v8.15.3",
+      "filesAnalyzed": false,
+      "licenseConcluded": "NOASSERTION",
+      "licenseDeclared": "Apache-2.0",
+      "downloadLocation": "NOASSERTION",
+      "originator": "Organization: Elastic",
+      "supplier": "Organization: Elastic",
+      "externalRefs": [
+        {
+          "referenceCategory": "PACKAGE-MANAGER",
+          "referenceLocator": "pkg:github/elastic/logstash@v8.15.3",
+          "referenceType": "purl"
+        }
+      ]
+    }
+  ],
+  "relationships": [
+    {
+      "spdxElementId": "SPDXRef-Package-logstash-8-8.15.3-r4",
+      "relationshipType": "DESCRIBED_BY",
+      "relatedSpdxElement": "SPDXRef-Package-logstash-8.yaml-c7d40faa38ddffa98700cfa4c2f9bde196acc504"
+    },
+    {
+      "spdxElementId": "SPDXRef-Package-logstash-8-8.15.3-r4",
+      "relationshipType": "GENERATED_FROM",
+      "relatedSpdxElement": "SPDXRef-Package-github.com-elastic-logstash-v8.15.3-8364c8e89cfb113e38ec3f966df7eb1e9abe9d33-0"
+    }
+  ]
+}
diff --git a/pkg/sbom/generator/spdx/testdata/apk_sboms/logstash-8-compat-8.15.3-r4.spdx.json b/pkg/sbom/generator/spdx/testdata/apk_sboms/logstash-8-compat-8.15.3-r4.spdx.json
new file mode 100644
index 0000000..b83d59c
--- /dev/null
+++ b/pkg/sbom/generator/spdx/testdata/apk_sboms/logstash-8-compat-8.15.3-r4.spdx.json
@@ -0,0 +1,87 @@
+{
+  "SPDXID": "SPDXRef-DOCUMENT",
+  "name": "apk-logstash-8-compat-8.15.3-r4",
+  "spdxVersion": "SPDX-2.3",
+  "creationInfo": {
+    "created": "2024-10-24T15:16:49Z",
+    "creators": [
+      "Tool: melange (v0.14.7)",
+      "Organization: Chainguard, Inc"
+    ],
+    "licenseListVersion": "3.22"
+  },
+  "dataLicense": "CC0-1.0",
+  "documentNamespace": "https://spdx.org/spdxdocs/chainguard/melange/0434ffabb9f0343f5dada78abdb2e419b2be48fb",
+  "documentDescribes": [
+    "SPDXRef-Package-logstash-8-compat-8.15.3-r4"
+  ],
+  "packages": [
+    {
+      "SPDXID": "SPDXRef-Package-logstash-8-compat-8.15.3-r4",
+      "name": "logstash-8-compat",
+      "versionInfo": "8.15.3-r4",
+      "filesAnalyzed": false,
+      "licenseConcluded": "NOASSERTION",
+      "licenseDeclared": "Apache-2.0",
+      "downloadLocation": "NOASSERTION",
+      "originator": "Organization: Wolfi",
+      "supplier": "Organization: Wolfi",
+      "copyrightText": "\n",
+      "externalRefs": [
+        {
+          "referenceCategory": "PACKAGE-MANAGER",
+          "referenceLocator": "pkg:apk/wolfi/logstash-8-compat@8.15.3-r4?arch=x86_64",
+          "referenceType": "purl"
+        }
+      ]
+    },
+    {
+      "SPDXID": "SPDXRef-Package-logstash-8.yaml-c7d40faa38ddffa98700cfa4c2f9bde196acc504",
+      "name": "logstash-8.yaml",
+      "versionInfo": "c7d40faa38ddffa98700cfa4c2f9bde196acc504",
+      "filesAnalyzed": false,
+      "licenseConcluded": "NOASSERTION",
+      "licenseDeclared": "Apache-2.0",
+      "downloadLocation": "NOASSERTION",
+      "originator": "Organization: Wolfi",
+      "supplier": "Organization: Wolfi",
+      "externalRefs": [
+        {
+          "referenceCategory": "PACKAGE-MANAGER",
+          "referenceLocator": "pkg:github/wolfi-dev/os@c7d40faa38ddffa98700cfa4c2f9bde196acc504#logstash-8.yaml",
+          "referenceType": "purl"
+        }
+      ]
+    },
+    {
+      "SPDXID": "SPDXRef-Package-github.com-elastic-logstash-v8.15.3-8364c8e89cfb113e38ec3f966df7eb1e9abe9d33-0",
+      "name": "logstash",
+      "versionInfo": "v8.15.3",
+      "filesAnalyzed": false,
+      "licenseConcluded": "NOASSERTION",
+      "licenseDeclared": "Apache-2.0",
+      "downloadLocation": "NOASSERTION",
+      "originator": "Organization: Elastic",
+      "supplier": "Organization: Elastic",
+      "externalRefs": [
+        {
+          "referenceCategory": "PACKAGE-MANAGER",
+          "referenceLocator": "pkg:github/elastic/logstash@v8.15.3",
+          "referenceType": "purl"
+        }
+      ]
+    }
+  ],
+  "relationships": [
+    {
+      "spdxElementId": "SPDXRef-Package-logstash-8-compat-8.15.3-r4",
+      "relationshipType": "DESCRIBED_BY",
+      "relatedSpdxElement": "SPDXRef-Package-logstash-8.yaml-c7d40faa38ddffa98700cfa4c2f9bde196acc504"
+    },
+    {
+      "spdxElementId": "SPDXRef-Package-logstash-8-compat-8.15.3-r4",
+      "relationshipType": "GENERATED_FROM",
+      "relatedSpdxElement": "SPDXRef-Package-github.com-elastic-logstash-v8.15.3-8364c8e89cfb113e38ec3f966df7eb1e9abe9d33-0"
+    }
+  ]
+}
diff --git a/pkg/sbom/generator/spdx/testdata/expected.ubuntu-font.spdx.json b/pkg/sbom/generator/spdx/testdata/expected_image_sboms/custom-license.spdx.json
similarity index 100%
rename from pkg/sbom/generator/spdx/testdata/expected.ubuntu-font.spdx.json
rename to pkg/sbom/generator/spdx/testdata/expected_image_sboms/custom-license.spdx.json
diff --git a/pkg/sbom/generator/spdx/testdata/expected.libattr1.spdx.json b/pkg/sbom/generator/spdx/testdata/expected_image_sboms/no-supplier.spdx.json
similarity index 100%
rename from pkg/sbom/generator/spdx/testdata/expected.libattr1.spdx.json
rename to pkg/sbom/generator/spdx/testdata/expected_image_sboms/no-supplier.spdx.json
diff --git a/pkg/sbom/generator/spdx/testdata/expected_image_sboms/package-deduplicating.spdx.json b/pkg/sbom/generator/spdx/testdata/expected_image_sboms/package-deduplicating.spdx.json
new file mode 100644
index 0000000..3db3159
--- /dev/null
+++ b/pkg/sbom/generator/spdx/testdata/expected_image_sboms/package-deduplicating.spdx.json
@@ -0,0 +1,132 @@
+{
+  "SPDXID": "SPDXRef-DOCUMENT",
+  "name": "sbom",
+  "spdxVersion": "SPDX-2.3",
+  "creationInfo": {
+    "created": "0001-01-01T00:00:00Z",
+    "creators": [
+      "Tool: apko (devel)",
+      "Organization: Chainguard, Inc"
+    ],
+    "licenseListVersion": "3.16"
+  },
+  "dataLicense": "CC0-1.0",
+  "documentNamespace": "https://spdx.org/spdxdocs/apko/",
+  "documentDescribes": [
+    "SPDXRef-Package-"
+  ],
+  "packages": [
+    {
+      "SPDXID": "SPDXRef-Package-",
+      "name": "",
+      "versionInfo": "3.0",
+      "filesAnalyzed": false,
+      "description": "apko operating system layer",
+      "downloadLocation": "NOASSERTION",
+      "supplier": "Organization: unknown",
+      "externalRefs": [
+        {
+          "referenceCategory": "PACKAGE-MANAGER",
+          "referenceLocator": "pkg:oci/image?mediaType=\u0026os=linux",
+          "referenceType": "purl"
+        }
+      ]
+    },
+    {
+      "SPDXID": "SPDXRef-Package-logstash-8-8.15.3-r4",
+      "name": "logstash-8",
+      "versionInfo": "8.15.3-r4",
+      "filesAnalyzed": false,
+      "licenseConcluded": "NOASSERTION",
+      "licenseDeclared": "Apache-2.0",
+      "downloadLocation": "NOASSERTION",
+      "originator": "Organization: Wolfi",
+      "supplier": "Organization: Wolfi",
+      "copyrightText": "\n",
+      "externalRefs": [
+        {
+          "referenceCategory": "PACKAGE-MANAGER",
+          "referenceLocator": "pkg:apk/wolfi/logstash-8@8.15.3-r4?arch=x86_64",
+          "referenceType": "purl"
+        }
+      ]
+    },
+    {
+      "SPDXID": "SPDXRef-Package-logstash-8.yaml-c7d40faa38ddffa98700cfa4c2f9bde196acc504",
+      "name": "logstash-8.yaml",
+      "versionInfo": "c7d40faa38ddffa98700cfa4c2f9bde196acc504",
+      "filesAnalyzed": false,
+      "licenseConcluded": "NOASSERTION",
+      "licenseDeclared": "Apache-2.0",
+      "downloadLocation": "NOASSERTION",
+      "originator": "Organization: Wolfi",
+      "supplier": "Organization: Wolfi",
+      "externalRefs": [
+        {
+          "referenceCategory": "PACKAGE-MANAGER",
+          "referenceLocator": "pkg:github/wolfi-dev/os@c7d40faa38ddffa98700cfa4c2f9bde196acc504#logstash-8.yaml",
+          "referenceType": "purl"
+        }
+      ]
+    },
+    {
+      "SPDXID": "SPDXRef-Package-github.com-elastic-logstash-v8.15.3-8364c8e89cfb113e38ec3f966df7eb1e9abe9d33-0",
+      "name": "logstash",
+      "versionInfo": "v8.15.3",
+      "filesAnalyzed": false,
+      "licenseConcluded": "NOASSERTION",
+      "licenseDeclared": "Apache-2.0",
+      "downloadLocation": "NOASSERTION",
+      "originator": "Organization: Elastic",
+      "supplier": "Organization: Elastic",
+      "externalRefs": [
+        {
+          "referenceCategory": "PACKAGE-MANAGER",
+          "referenceLocator": "pkg:github/elastic/logstash@v8.15.3",
+          "referenceType": "purl"
+        }
+      ]
+    },
+    {
+      "SPDXID": "SPDXRef-Package-logstash-8-compat-8.15.3-r4",
+      "name": "logstash-8-compat",
+      "versionInfo": "8.15.3-r4",
+      "filesAnalyzed": false,
+      "licenseConcluded": "NOASSERTION",
+      "licenseDeclared": "Apache-2.0",
+      "downloadLocation": "NOASSERTION",
+      "originator": "Organization: Wolfi",
+      "supplier": "Organization: Wolfi",
+      "copyrightText": "\n",
+      "externalRefs": [
+        {
+          "referenceCategory": "PACKAGE-MANAGER",
+          "referenceLocator": "pkg:apk/wolfi/logstash-8-compat@8.15.3-r4?arch=x86_64",
+          "referenceType": "purl"
+        }
+      ]
+    }
+  ],
+  "relationships": [
+    {
+      "spdxElementId": "SPDXRef-Package-logstash-8-8.15.3-r4",
+      "relationshipType": "DESCRIBED_BY",
+      "relatedSpdxElement": "SPDXRef-Package-logstash-8.yaml-c7d40faa38ddffa98700cfa4c2f9bde196acc504"
+    },
+    {
+      "spdxElementId": "SPDXRef-Package-logstash-8-8.15.3-r4",
+      "relationshipType": "GENERATED_FROM",
+      "relatedSpdxElement": "SPDXRef-Package-github.com-elastic-logstash-v8.15.3-8364c8e89cfb113e38ec3f966df7eb1e9abe9d33-0"
+    },
+    {
+      "spdxElementId": "SPDXRef-Package-logstash-8-compat-8.15.3-r4",
+      "relationshipType": "DESCRIBED_BY",
+      "relatedSpdxElement": "SPDXRef-Package-logstash-8.yaml-c7d40faa38ddffa98700cfa4c2f9bde196acc504"
+    },
+    {
+      "spdxElementId": "SPDXRef-Package-logstash-8-compat-8.15.3-r4",
+      "relationshipType": "GENERATED_FROM",
+      "relatedSpdxElement": "SPDXRef-Package-github.com-elastic-logstash-v8.15.3-8364c8e89cfb113e38ec3f966df7eb1e9abe9d33-0"
+    }
+  ]
+}
diff --git a/pkg/sbom/generator/spdx/testdata/generate.sh b/pkg/sbom/generator/spdx/testdata/generate.sh
deleted file mode 100755
index d2acb23..0000000
--- a/pkg/sbom/generator/spdx/testdata/generate.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-curl -q https://packages.wolfi.dev/os/x86_64/font-ubuntu-0.869-r1.apk | tar Ozx var/lib/db/sbom/font-ubuntu-0.869-r1.spdx.json >font-ubuntu.spdx.json 2>/dev/null
-curl -q https://packages.wolfi.dev/os/x86_64/libattr1-2.5.1-r2.apk | tar Ozx var/lib/db/sbom/libattr1-2.5.1-r2.spdx.json >libattr1.spdx.json 2>/dev/null