Skip to content

Add version and upstream license links to THIRD_PARTY_NOTICES.md - #1

Draft
abrarshivani wants to merge 2 commits into
mainfrom
tpn-version-location
Draft

abrarshivani wants to merge 2 commits into
mainfrom
tpn-version-location

Conversation

@abrarshivani

@abrarshivani abrarshivani commented Aug 27, 2026 •

Copy link
Copy Markdown
Owner

Adds Version and Location columns to THIRD_PARTY_NOTICES.md, replacing the Dependency
column. Location links to the license file in the dependency's own upstream repository, pinned
to the version we redistribute:

Package Version License Location
github.com/NVIDIA/go-nvlib/pkg v0.12.0 Apache-2.0 LICENSE

This is the same change as NVIDIA/gpu-operator, applied here.

Version was dropped in d28112a ("Remove Go module versions from rendered TPN document") on the
grounds that the notices identify dependencies and their licenses rather than an exact build.
That is reversed here, and I want to flag it rather than bury it. Two reasons: a notices file
that does not say which version it describes cannot be matched to a release, and a link into
upstream needs a ref to point at. The churn is real but small, one index row and two bullets per
bump.

Every URL in the file was verified by fetching it and comparing its sha256 against the copy under
vendor/. A URL that does not match is never written, so there are no dead links and none point
at the wrong license. 74 URLs.

That includes the secondary license files a module ships alongside its main one, which are easy
to lose: nine golang.org/x/* modules carry PATENTS, four modules carry AUTHORS, and
sigs.k8s.io/yaml/goyaml.v2 carries LICENSE.libyaml (MIT, for the embedded libyaml port) on
top of its Apache-2.0 LICENSE. Nineteen license files that were previously dropped are now
reproduced.

What to review

Hand-written:

File Lines
scripts/verify-license-urls.sh 226
scripts/generate-third-party-notices_test.sh 194
scripts/resolve-module-repos.sh 173
scripts/license-url-lib.sh 156
scripts/license-url-lib_test.sh 91
.github/workflows/third-party-notices-links.yaml 62
scripts/test-helpers.sh 45
scripts/generate-third-party-notices.sh +178/-49
scripts/license-overrides.tsv 15
Makefile +20
.github/workflows/third-party-notices-check.yaml +9

Generated, do not read: THIRD_PARTY_NOTICES.md, scripts/license-urls.tsv (79),
scripts/module-repos.tsv (238).

scripts/verify-license-urls.sh is the one to read. Everything else supports it.

How it works

vendor/ gives the module, the version and the license file names for free, but it does not
record the upstream repository. k8s.io/api actually lives at github.com/kubernetes/api,
sigs.k8s.io/yaml at github.com/kubernetes-sigs/yaml. Scraping that out of vendored sources is
wrong more often than right, so two committed maps carry it instead, both machine-generated:

scripts/module-repos.tsv maps module to repository. Resolution is the Go module proxy's
Origin, then the go-import meta tag that go get itself uses, then the
github.com/<org>/<repo> path shape. Nothing is hand-written. It is keyed by module and not by
version, so a bump does not invalidate it. It covers all 234 vendored modules rather than only
the shipped ones, so adding a dependency rarely requires re-running it.

scripts/license-urls.tsv maps module, version and license path to a verified URL. A row is
written only when the bytes at that URL hash identically to the vendored copy. Probing for a 200
is not enough: it cannot tell a correct link from one that returns 200 for the wrong license.

Both are produced out of band by make third-party-notices-repos and make third-party-notices-urls, which need network. make third-party-notices reads them offline, so
make check-third-party-notices stays hermetic and cannot flap on a proxy that withholds
Origin.

Scope is unchanged. The verifier reuses the generator's own collection, so it covers what
go-licenses attributes to ./cmd/...: 56 packages out of 234 vendored modules. The rest are
golangci-lint and its plugin tree, vendored for linting and never redistributed.

License files are now enumerated from vendor/ rather than from the go-licenses save output,
because that output keeps only the one file it classifies as the license per package and drops
the rest.

scripts/license-overrides.tsv corrects the License column where go-licenses under-reports it.
Two modules here ship a file holding more than one license: gopkg.in/yaml.v3 carries a
full-text MIT section plus a short-form Apache-2.0 grant in one LICENSE, and
sigs.k8s.io/yaml/goyaml.v2 carries LICENSE (Apache-2.0) alongside LICENSE.libyaml (MIT).
go-licenses classifies each as a single license, so both read Apache-2.0 / MIT from the
override instead. Each was confirmed by reading the vendored file by eye rather than by scanning
license text, because scanning cannot tell BSD-2-Clause from BSD-3-Clause and a wrong addition to
this file is worse than an omission. Generation fails if an override names a package that is no
longer in the index, so the file cannot rot unnoticed.

.github/workflows/third-party-notices-links.yaml re-verifies every URL weekly. Links are proven
correct when written, but upstream can retag or archive a repository afterwards and no offline
gate can see that.

Note for dependency bumps

A version change now needs two commands, because a verified URL contains the version:

make third-party-notices-urls   # network
make third-party-notices        # offline

Dependabot cannot do the first on its own. Its bump job needs wiring, or a human runs it. This is
the direct cost of requiring every link to be verified rather than derived.

Testing

make test-tools runs the new bash suites, 54 assertions across two files. This repo has no
make check aggregate, so it runs as a step in the existing Third-Party Notices workflow
alongside the staleness check.

Verified by hand:

  • 56 index rows, four columns, no floating HEAD or branch refs
  • all 56 package versions match vendor/modules.txt
  • 74 license-file sections, each carrying its verified URL
  • regeneration is byte-deterministic across runs
  • the gate fails closed two ways: removing the go-nvlib rows from scripts/license-urls.tsv
    makes make third-party-notices exit non-zero naming that module, and a
    license-overrides.tsv row for a package not in the index does the same

THIRD_PARTY_NOTICES.md now carries a Version and a Location column instead of
the Dependency column. Location links to the license file in the dependency's
own upstream repository, pinned to the version we redistribute:

| Package | Version | License | Location |
|---------|---------|---------|----------|
| `github.com/NVIDIA/go-nvlib/pkg` | v0.12.0 | Apache-2.0 | [LICENSE](https://github.com/NVIDIA/go-nvlib/blob/v0.12.0/LICENSE) |

Version was dropped in d28112a on the grounds that the notices identify
dependencies and their licenses rather than an exact build. That is reversed
here: a notices file that does not say which version it describes cannot be
matched to a release, and a link into upstream needs a ref to point at. The
churn per bump is one index row and two bullets.

Every URL was verified by fetching it and comparing its sha256 against the copy
under vendor/. A URL that does not match is never written, so no link is dead
and none points at the wrong license. 74 URLs across the shipped dependency set.

vendor/ gives the module, the version and the license file names for free, but
not the upstream repository: k8s.io/api lives at github.com/kubernetes/api,
sigs.k8s.io/yaml at github.com/kubernetes-sigs/yaml. Two committed maps carry
that instead, both machine-generated.

scripts/module-repos.tsv maps module to repository, resolved from the Go module
proxy's Origin, then the go-import meta tag that go get itself uses, then the
github.com/<org>/<repo> path shape. It is keyed by module and not by version,
so a bump does not invalidate it. It covers all 234 vendored modules, not just
the shipped ones, so adding a dependency rarely requires re-running it.

scripts/license-urls.tsv maps module, version and license path to a verified
URL. A row is written only when the bytes at that URL hash identically to the
vendored copy. Probing for a 200 is not enough: it cannot tell a correct link
from one that returns 200 for the wrong license.

Both are produced out of band by 'make third-party-notices-repos' and 'make
third-party-notices-urls', which need network. 'make third-party-notices' reads
them offline, so 'make check-third-party-notices' stays hermetic.

Scope is unchanged. The verifier reuses the generator's own collection, so it
covers what go-licenses attributes to ./cmd/...: 56 packages out of 234
vendored modules. The rest are golangci-lint and its plugin tree, vendored for
linting and never redistributed.

License files are now enumerated from vendor/ rather than from the go-licenses
save output, because that output keeps only the one file it classifies as the
license per package and drops the rest. That recovers nineteen files the
document previously omitted, including nine PATENTS files, four AUTHORS files
and the LICENSE.libyaml that sigs.k8s.io/yaml/goyaml.v2 ships alongside its
Apache-2.0 LICENSE.

scripts/license-overrides.tsv corrects the License column where go-licenses
under-reports it. gopkg.in/yaml.v3 ships one LICENSE holding a full-text MIT
section plus a short-form Apache-2.0 grant, and sigs.k8s.io/yaml/goyaml.v2
ships LICENSE (Apache-2.0) alongside LICENSE.libyaml (MIT); go-licenses reports
a single identifier for each, so both read Apache-2.0 / MIT from the override.
Each was confirmed by reading the vendored file rather than by scanning license
text, because scanning cannot tell BSD-2-Clause from BSD-3-Clause and a wrong
addition is worse than an omission. Generation fails if an override names a
package no longer in the index, so the file cannot rot unnoticed.

third-party-notices-links.yaml re-verifies every URL weekly. Links are proven
correct when written, but upstream can retag or archive a repository afterwards
and no offline gate can see that.

A version change now needs two commands, because a verified URL contains the
version:

    make third-party-notices-urls   # network
    make third-party-notices        # offline

Dependabot cannot do the first on its own; its bump job needs wiring, or a
human runs it. That is the direct cost of requiring every link to be verified
rather than derived.

'make test-tools' runs the new bash suites, 67 assertions across two files.
This repo has no 'make check' aggregate, so it runs as a step in the existing
third-party-notices workflow alongside the staleness check.

Transient failures fetching a license blob are retried. go.googlesource.com
returns 503 and 429 under the per-file loop this drives, and the fail-closed
gate would otherwise treat a rate-limited response as link rot; the weekly link
check drives the same loop. A 404 still fails on the first request, so a real
miss costs one request per candidate. fetch_retry moves into license-url-lib.sh
as http_fetch_to_file so both resolvers share one retry and status policy, and
it writes to a file because command substitution strips the trailing newline
that a license file's sha256 depends on.

Signed-off-by: Abrar Shivani <ashivani@nvidia.com>
The image copies a prebuilt gpu-feature-discovery in from the k8s-device-plugin
image, and the header said its modules were not covered here. They are
redistributed all the same: the binary ships in the image whoever built it, and
the third-party code linked into it ships with it.

Nothing in this repo records what that binary links, so the binary is the source
of truth. Go writes every dependency into the build info at link time, which is
what actually ships rather than what a tag implies. go-licenses classifies
packages rather than modules, so the package set still has to be resolved from
source, and the two are then required to agree — a rebuilt or patched image
would otherwise be attributed to the wrong sources without a word.

The binary records its own module version as "(devel)", which resolves to
nothing, so the version comes from the image tag instead. That mapping is a
convention nothing enforces, which is exactly what the dependency cross-check
covers: the resolved set has to equal what the binary records, or the run stops.

Runtime and bundled entries merge into one index. The two trees are a build-time
detail; what ships is one filesystem, so a module both binaries link at
different versions is two honest rows rather than a second table the reader has
to reconcile. The source tree moves into the row as a sixth field, since it is
now a property of the row rather than of the table, and the resolver, verifier
and both emit functions read it from there.

56 rows become 91: 13 modules the document did not mention at all, and 22
packages that ship at two versions and now say so.

Signed-off-by: Abrar Shivani <ashivani@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant