Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions .github/actions/go-licenses/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# go-licenses

Runs [go-licenses](https://github.com/google/go-licenses) against a Go module in one of two modes:

- **`check`** — scan the module, fail the job (or warn, per `fail-on-error`) if any dependency has an incompatible license.
- **`report`** — render a license report via a Go template and open a PR with the updated file.

Both modes share the same setup (setup-go, install go-licenses, optional private-repo git auth) and differ only in the trailing steps.

## Inputs

| Name | Description | Required | Default |
|------|-------------|----------|---------|
| `mode` | `check` or `report` | yes | — |
| `go-licenses-version` | go-licenses version to install. `v1.0.0` lacks `--ignore`; use `package-mode: go-work` with it | no | `v1.6.0` |
| `go-version-file` | Passed to `actions/setup-go` | no | `go.mod` |
| `ignored-packages` | Comma-separated package path prefixes to skip. In `all` mode → `--ignore` flags; in `go-work` mode → substring-matched against `go.work` DiskPaths | no | `github.com/loft-sh` |
| `package-mode` | `all` (pass `./...` + `--ignore`) or `go-work` (enumerate workspace modules — required for go-licenses < v1.6.0) | no | `all` |
| `fail-on-error` | [check] When `"false"`, non-zero go-licenses exit is surfaced as a warning; the step still succeeds | no | `"true"` |
| `template-path` | [report] Path to the go-licenses .tmpl template | no | `.github/licenses.tmpl` |
| `output-path` | [report] File path to write the rendered report to | required for `report` | `""` |
| `pr-branch` | [report] Branch name for the generated PR | required for `report` | `""` |
| `pr-title` | [report] PR title | required for `report` | `""` |
| `pr-commit-message` | [report] Commit message; defaults to `pr-title` | no | `""` |
| `gh-access-token` | GitHub PAT — fetches private loft-sh modules (both modes) and opens the PR (report mode) | required for `report`, optional for private-module `check` | `""` |

## Usage

### Check (single-module repo, public)

```yaml
name: go-licenses check

on:
pull_request:
paths:
- 'go.mod'
- 'go.sum'

jobs:
check:
runs-on: ubuntu-latest
if: github.repository_owner == 'loft-sh'
permissions:
contents: read
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: loft-sh/github-actions/.github/actions/go-licenses@go-licenses/v1
with:
mode: check
```

### Check (go.work monorepo with private modules, older go-licenses)

```yaml
jobs:
check:
runs-on: ubuntu-latest
if: github.repository_owner == 'loft-sh'
permissions:
contents: read
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: loft-sh/github-actions/.github/actions/go-licenses@go-licenses/v1
with:
mode: check
go-licenses-version: v1.0.0
go-version-file: go.work
package-mode: go-work
ignored-packages: github.com/loft-sh
gh-access-token: ${{ secrets.GH_ACCESS_TOKEN }}
```

### Report (render file + open PR)

```yaml
jobs:
report:
runs-on: ubuntu-latest
if: github.repository_owner == 'loft-sh'
permissions:
contents: write
pull-requests: write
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: loft-sh/github-actions/.github/actions/go-licenses@go-licenses/v1
with:
mode: report
template-path: .github/licenses.tmpl
output-path: docs/pages/licenses/vcluster.mdx
pr-branch: licenses/vcluster
pr-title: "Update vcluster licenses"
gh-access-token: ${{ secrets.GH_ACCESS_TOKEN }}
```

## Testing

```bash
make test-go-licenses
```

Runs the bats suite in `test/run.bats` against `run.sh` with stubbed `go-licenses` and `go work` binaries.
143 changes: 143 additions & 0 deletions .github/actions/go-licenses/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: 'go-licenses'
description: "Run `go-licenses check` or `go-licenses report` against a Go module, with private-module auth and optional PR creation for report mode."
branding:
icon: "file-text"
color: "blue"

inputs:
mode:
description: "`check` — run `go-licenses check` against the module. `report` — render a license report with a template and open a PR with the output."
required: true
go-licenses-version:
description: "Version of go-licenses to install (e.g. v1.6.0, v1.0.0). v1.0.0 does not support `--ignore` — set `package-mode: go-work` when using it."
required: false
# renovate: datasource=go depName=github.com/google/go-licenses
default: v1.6.0
go-version-file:
description: "Path to go.mod or go.work for setup-go."
required: false
default: go.mod
ignored-packages:
description: "Comma-separated package path prefixes to skip. In `all` mode these become `--ignore` flags; in `go-work` mode they are substring-matched against go.work DiskPaths."
required: false
default: github.com/loft-sh
package-mode:
description: "Package selection strategy: `all` (pass `./...` with `--ignore` flags) or `go-work` (enumerate modules via `go work edit` and filter at the package list — required for go-licenses < v1.6.0)."
required: false
default: all
fail-on-error:
description: "[check mode] When `false`, non-zero exit codes from go-licenses are surfaced as a workflow warning instead of a failure. Use only as a temporary escape hatch when upstream go-licenses is broken."
required: false
default: "true"
template-path:
description: "[report mode] Path to the go-licenses .tmpl template used to render the report."
required: false
default: .github/licenses.tmpl
output-path:
description: "[report mode, required] File path in the caller repo to write the rendered report to, e.g. `docs/pages/licenses/vcluster.mdx`."
required: false
default: ""
pr-branch:
description: "[report mode, required] Branch name to push the rendered report onto."
required: false
default: ""
pr-title:
description: "[report mode, required] PR title for the generated pull request."
required: false
default: ""
pr-commit-message:
description: "[report mode] Commit message for the generated pull request. Defaults to `pr-title`."
required: false
default: ""
gh-access-token:
description: "GitHub PAT used to fetch private loft-sh modules (both modes) and to open the generated pull request (report mode, required)."
required: false
default: ""

runs:
using: "composite"
steps:
- name: Validate inputs
shell: bash
env:
MODE: ${{ inputs.mode }}
OUTPUT_PATH: ${{ inputs.output-path }}
PR_BRANCH: ${{ inputs.pr-branch }}
PR_TITLE: ${{ inputs.pr-title }}
GH_ACCESS_TOKEN: ${{ inputs.gh-access-token }}
run: |
case "${MODE}" in
check|report) ;;
*) echo "::error::mode must be 'check' or 'report' (got '${MODE}')"; exit 1 ;;
esac
if [ "${MODE}" = "report" ]; then
missing=()
[ -z "${OUTPUT_PATH}" ] && missing+=("output-path")
[ -z "${PR_BRANCH}" ] && missing+=("pr-branch")
[ -z "${PR_TITLE}" ] && missing+=("pr-title")
[ -z "${GH_ACCESS_TOKEN}" ] && missing+=("gh-access-token")
if [ "${#missing[@]}" -gt 0 ]; then
echo "::error::mode=report requires: ${missing[*]}"
exit 1
fi
fi

- name: Configure git for private repos
if: inputs.gh-access-token != ''
shell: bash
env:
GH_ACCESS_TOKEN: ${{ inputs.gh-access-token }}
run: git config --global "url.https://${GH_ACCESS_TOKEN}@github.com/.insteadOf" https://github.com/

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: ${{ inputs.go-version-file }}

- name: Install go-licenses
shell: bash
env:
GO_LICENSES_VERSION: ${{ inputs.go-licenses-version }}
run: go install "github.com/google/go-licenses@${GO_LICENSES_VERSION}"

- name: Run go-licenses
shell: bash
env:
MODE: ${{ inputs.mode }}
GOPRIVATE: github.com/loft-sh/*
PACKAGE_MODE: ${{ inputs.package-mode }}
IGNORED_PACKAGES: ${{ inputs.ignored-packages }}
FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
TEMPLATE_PATH: ${{ inputs.template-path }}
OUTPUT_PATH: ${{ inputs.output-path }}
run: ${{ github.action_path }}/run.sh "${MODE}"

- name: Resolve PR commit message
if: inputs.mode == 'report'
id: commit
shell: bash
env:
COMMIT_MESSAGE: ${{ inputs.pr-commit-message }}
PR_TITLE: ${{ inputs.pr-title }}
run: |
if [ -z "${COMMIT_MESSAGE}" ]; then
COMMIT_MESSAGE="${PR_TITLE}"
fi
{
echo "message<<EOF"
echo "${COMMIT_MESSAGE}"
echo "EOF"
} >> "${GITHUB_OUTPUT}"

- name: Create pull request
if: inputs.mode == 'report'
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ inputs.gh-access-token }}
committer: Loft Bot <loft-bot@users.noreply.github.com>
branch: ${{ inputs.pr-branch }}
commit-message: ${{ steps.commit.outputs.message }}
title: ${{ inputs.pr-title }}
body: Triggered by ${{ github.repository }}@${{ github.sha }}
signoff: true
delete-branch: true
106 changes: 106 additions & 0 deletions .github/actions/go-licenses/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env bash
set -euo pipefail

# Runs `go-licenses check` or `go-licenses report` with consistent handling of
# package selection and ignored packages across `go.mod` and `go.work`
# projects. Called from the go-licenses-check / go-licenses-report reusable
# workflows so the branching logic lives in a shellcheck-clean script instead
# of inline YAML.
#
# Usage:
# run.sh check
# run.sh report
#
# Required environment variables:
# PACKAGE_MODE "all" — pass ./... and --ignore flags to go-licenses.
# "go-work" — enumerate workspace modules from go.work and
# filter out ignored prefixes at the package list level.
# Use this for go-licenses versions < v1.6.0 (no --ignore
# flag) and for monorepos whose root does not compile.
# IGNORED_PACKAGES Comma-separated list of package path prefixes to skip.
# In "all" mode they become --ignore flags (matching the
# import path prefix). In "go-work" mode they are matched
# as substrings against go.work DiskPaths.
#
# Check-mode variables:
# FAIL_ON_ERROR "true" (default) or "false". When "false", non-zero exit
# codes from go-licenses are logged as a workflow warning
# and the step still succeeds — used only as a temporary
# escape hatch when upstream go-licenses is broken.
#
# Report-mode variables:
# TEMPLATE_PATH Path to the go-licenses .tmpl template (required).
# OUTPUT_PATH File to write the rendered report to (required). Any
# missing parent directories are created.

SUBCOMMAND="${1:?subcommand is required: check or report}"
PACKAGE_MODE="${PACKAGE_MODE:-all}"
IGNORED_PACKAGES="${IGNORED_PACKAGES:-}"

# Parse IGNORED_PACKAGES into an array of trimmed, non-empty prefixes.
IGNORE=()
if [ -n "${IGNORED_PACKAGES}" ]; then
IFS=',' read -ra raw_ignore <<< "${IGNORED_PACKAGES}"
for prefix in "${raw_ignore[@]}"; do
trimmed="${prefix// /}"
if [ -n "${trimmed}" ]; then
IGNORE+=("${trimmed}")
fi
done
fi

# Build the go-licenses argument vector based on PACKAGE_MODE.
ARGS=()
case "${PACKAGE_MODE}" in
all)
ARGS+=("./...")
for prefix in ${IGNORE[@]+"${IGNORE[@]}"}; do
ARGS+=("--ignore" "${prefix}")
done
;;
go-work)
mapfile -t PKGS < <(go work edit -json | jq -r '.Use[].DiskPath + "/..."')
for prefix in ${IGNORE[@]+"${IGNORE[@]}"}; do
FILTERED=()
for pkg in ${PKGS[@]+"${PKGS[@]}"}; do
case "${pkg}" in
*"${prefix}"*) ;;
*) FILTERED+=("${pkg}") ;;
esac
done
PKGS=(${FILTERED[@]+"${FILTERED[@]}"})
done
if [ "${#PKGS[@]}" -eq 0 ]; then
echo "::error::no packages to check after filtering IGNORED_PACKAGES" >&2
exit 1
fi
ARGS+=(${PKGS[@]+"${PKGS[@]}"})
;;
*)
echo "::error::invalid PACKAGE_MODE '${PACKAGE_MODE}' (expected: all, go-work)" >&2
exit 1
;;
esac

case "${SUBCOMMAND}" in
check)
FAIL_ON_ERROR="${FAIL_ON_ERROR:-true}"
if [ "${FAIL_ON_ERROR}" = "true" ]; then
go-licenses check "${ARGS[@]}"
else
if ! go-licenses check "${ARGS[@]}"; then
echo "::warning::go-licenses check reported errors (ignored because fail-on-error=false)"
fi
fi
;;
report)
: "${TEMPLATE_PATH:?TEMPLATE_PATH env var is required for report}"
: "${OUTPUT_PATH:?OUTPUT_PATH env var is required for report}"
mkdir -p "$(dirname "${OUTPUT_PATH}")"
go-licenses report --template "${TEMPLATE_PATH}" "${ARGS[@]}" > "${OUTPUT_PATH}"
;;
*)
echo "::error::invalid subcommand '${SUBCOMMAND}' (expected: check, report)" >&2
exit 1
;;
esac
Loading
Loading