From 7a12e9e90499bad7b37468e9e8aab2e44dd2f5d0 Mon Sep 17 00:00:00 2001 From: cplieger <917744+cplieger@users.noreply.github.com> Date: Sat, 19 Sep 2026 09:27:47 +0200 Subject: [PATCH] feat: ship the license text of every bundled component in the image --- Dockerfile | 4 ++ NOTICE | 50 +-------------- README.md | 4 +- THIRD_PARTY_NOTICES.md | 38 ++++++++++++ scripts/collect-licenses.sh | 118 ++++++++++++++++++++++++++++++++++++ tests/image-smoke.conf | 2 + 6 files changed, 167 insertions(+), 49 deletions(-) create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 scripts/collect-licenses.sh diff --git a/Dockerfile b/Dockerfile index 96411de..a4cdca6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,10 +10,14 @@ COPY internal/ internal/ RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /github-scout . +COPY LICENSE NOTICE THIRD_PARTY_NOTICES.md ./ +COPY scripts/collect-licenses.sh scripts/ +RUN sh scripts/collect-licenses.sh --name github-scout . FROM gcr.io/distroless/static-debian13:nonroot@sha256:e2e927ec666bae08560abb3c55d0659eceabb657f56b6782ab500a9fc7f555e3 COPY --chmod=755 --from=builder /github-scout /github-scout +COPY --from=builder /out/usr/share/licenses /usr/share/licenses USER nonroot:nonroot HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=15s \ CMD ["/github-scout", "health"] diff --git a/NOTICE b/NOTICE index 246829b..cb99c48 100644 --- a/NOTICE +++ b/NOTICE @@ -1,49 +1,3 @@ github-scout -Copyright (c) 2026 cplieger - -This product is licensed under the GNU General Public License v3.0 or -later (see LICENSE). - ------------------------------------------------------------------------- -Third-party acknowledgements ------------------------------------------------------------------------- - -The GitHub REST API client design in internal/github (Bearer-token auth -header set, the X-GitHub-Api-Version pin, and page-count pagination) -follows patterns common to the following MIT-licensed projects. No source -code was copied verbatim; the approach was studied and reimplemented. - - githubexporter/github-exporter - https://github.com/githubexporter/github-exporter - Copyright (c) 2016 Infinity Works Ltd - Licensed under the MIT License. - - xrstf/github_exporter - https://github.com/xrstf/github_exporter - Copyright (c) 2020 Christoph Mewes - Licensed under the MIT License. - -The MIT License permits this use. Their full license text is reproduced -below for attribution. - ------------------------------------------------------------------------- -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to permit -persons to whom the Software is furnished to do so, subject to the -following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +Copyright 2026 cplieger +https://github.com/cplieger/github-scout diff --git a/README.md b/README.md index afefc80..74c3d9a 100644 --- a/README.md +++ b/README.md @@ -490,4 +490,6 @@ This project was built with AI-assisted tooling using [Claude](https://claude.co ## License -GPL-3.0-or-later. See [LICENSE](LICENSE) and [NOTICE](NOTICE). +GPL-3.0-or-later. See [LICENSE](LICENSE). The image carries the license text of every bundled component under `/usr/share/licenses/`. + +Third-party attributions are in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..418d517 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,38 @@ +# Third-party notices + +## Design followed, no code included + +The GitHub REST API client in `internal/github` follows patterns common to two MIT-licensed community exporters. No source code was copied; the approach was studied and reimplemented. Each pattern is named at the line of ours that follows it: + +- The request headers in `internal/github/client.go` (`setHeaders`: the `Authorization: Bearer` set and the `X-GitHub-Api-Version` pin) follow both exporters. +- The page-count pagination in `internal/github/client.go` (`ListRepos`, `ListRuns`, the `/search/issues` paths and `ListCodeScanningAlerts` each request `page` with `per_page` and stop on a short page) follows both exporters. + +The upstreams: + +- [githubexporter/github-exporter](https://github.com/githubexporter/github-exporter), copyright (c) 2016 Infinity Works Ltd, MIT. +- [xrstf/github_exporter](https://github.com/xrstf/github_exporter), copyright (c) 2020 Christoph Mewes, MIT. + +The MIT License permits this use. Their full license text is reproduced below for attribution. + +```text +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +``` diff --git a/scripts/collect-licenses.sh b/scripts/collect-licenses.sh new file mode 100644 index 0000000..362af98 --- /dev/null +++ b/scripts/collect-licenses.sh @@ -0,0 +1,118 @@ +#!/bin/sh +# Copy every linked Go module's license files into the /usr/share/licenses tree of +# attribution.md section 4. usage: collect-licenses.sh --name IMAGE [--out DIR] [PACKAGE ...] +# CANONICAL COPY in cplieger/ci (configs/collect-licenses.sh), synced to each +# root-Dockerfile repo's scripts/collect-licenses.sh: edit it there, never here. +# A module with no license file fails the build rather than being skipped, because a +# missing text is a section 4(a) breach and the fix is a human decision. +set -eu + +OUT=/out/usr/share/licenses +NAME="" +while [ $# -gt 0 ]; do + case "$1" in + --out) + OUT="${2:?--out needs a directory}" + shift 2 + ;; + --name) + NAME="${2:?--name needs an image name}" + shift 2 + ;; + --) + shift + break + ;; + -*) + printf 'collect-licenses: unknown option %s\n' "$1" >&2 + exit 2 + ;; + *) break ;; + esac +done +case "$NAME" in + '') + printf 'collect-licenses: --name IMAGE is required\n' >&2 + exit 2 + ;; + *[!A-Za-z0-9._-]*) + printf 'collect-licenses: --name must be a single path component, got "%s"\n' "$NAME" >&2 + exit 2 + ;; +esac +[ $# -gt 0 ] || set -- ./... +export GOWORK=off + +modules=0 +files=0 +missing="" + +# copy_files SRC_DIR DEST_DIR all|licenses: copy the regular files at SRC_DIR's root +# (all of them, or the license-family names only) into DEST_DIR; sets $copied. +copy_files() { + copied=0 + for f in "$1"/*; do + [ -f "$f" ] || continue + if [ "$3" = licenses ]; then + case "${f##*/}" in + LICENSE* | LICENCE* | COPYING* | NOTICE*) ;; + *) continue ;; + esac + fi + mkdir -p "$2" + cp -f "$f" "$2/" + copied=$((copied + 1)) + done +} + +main_path=$(go list -m -f '{{.Path}}') +main_dir=$(go list -m -f '{{.Dir}}') + +roots=$(go list -f '{{if eq .Name "main"}}{{.ImportPath}}{{end}}' "$@" | sed '/^$/d') +if [ -z "$roots" ]; then + printf 'collect-licenses: no main package matches: %s\n' "$*" >&2 + exit 1 +fi +# shellcheck disable=SC2086 # import paths carry no whitespace +deps=$(go list -deps -f '{{if not .Standard}}{{with .Module}}{{.Path}}|{{.Dir}}{{end}}{{end}}' $roots \ + | sed '/^$/d' | sort -u) + +if [ ! -f "$main_dir/LICENSE" ]; then + printf 'collect-licenses: %s has no LICENSE (the main module, at %s)\n' "$main_path" "$main_dir" >&2 + exit 1 +fi +copy_files "$main_dir" "$OUT/$NAME" licenses +if [ -f "$main_dir/THIRD_PARTY_NOTICES.md" ]; then + cp -f "$main_dir/THIRD_PARTY_NOTICES.md" "$OUT/$NAME/" + copied=$((copied + 1)) +fi +modules=$((modules + 1)) +files=$((files + copied)) + +while IFS='|' read -r path dir; do + [ "$path" = "$main_path" ] && continue + if [ -z "$dir" ]; then + printf 'collect-licenses: %s has no source directory (vendor mode is not supported)\n' "$path" >&2 + exit 1 + fi + copy_files "$dir" "$OUT/$path" licenses + if [ "$copied" -eq 0 ]; then + copy_files "$main_dir/licenses/$path" "$OUT/$path" all + fi + if [ "$copied" -eq 0 ]; then + missing="$missing +$path (at $dir; add licenses/$path/ to the repo or drop the module)" + continue + fi + modules=$((modules + 1)) + files=$((files + copied)) +done <&2 + printf '%s\n' "$missing" | sed '/^$/d; s/^/ /' >&2 + exit 1 +fi +printf 'collect-licenses: %s modules, %s files under %s\n' "$modules" "$files" "$OUT" diff --git a/tests/image-smoke.conf b/tests/image-smoke.conf index bf30386..3a8dce9 100644 --- a/tests/image-smoke.conf +++ b/tests/image-smoke.conf @@ -12,3 +12,5 @@ SMOKE_APP_NAME=github-scout SMOKE_TIMEOUT=60 SMOKE_RUN_ARGS="-e GITHUB_OWNER=smoke-test -e GITHUB_TOKEN=smoke-dummy-token" + +SMOKE_LICENSE_TREE=1