notify on stale metadata files in status command

This commit is contained in:
Jon Ruskin 2023-05-25 17:11:46 -07:00
Родитель 38245e410f
Коммит cf3173f008
6 изменённых файлов: 173 добавлений и 5 удалений

Просмотреть файл

@ -31,6 +31,21 @@ A dependency will fail the status checks if:
- If `license: other` is specified and all of the `licenses` entries match an `allowed` license a failure will not be logged
- A `reviewed` entry must reference a specific version of the depdency, e.g. `<name>@<version>`. The version identifier must specify a specific dependency version, ranges are not allowed.
## Detect and alert on stale cached metadata files
Licensed can alert on any metadata files that don't correlate to a currently used dependency when `licensed status` is run. To configure this behavior, set a root-level `stale_records_action` value in your [licensed configuration file](./../configuration.md).
Available values are:
1. `'error'`: Treat stale cached records as errors. Licensed will output errors for any stale metadata files and will cause `licensed status` to fail.
1. `'warn'`, `''`, or unset (default): Treat stale cached records as warnings. Licensed will output warnings for any stale metadata files but will not cause `licensed status` to fail.
1. `'ignore'`, any other value: Ignore stale cached records. Licensed will not output any notifications about stale metadata files.
```yaml
# in the licensed configuration file
stale_records_action: 'warn'
```
## Options
- `--config`/`-c`: the path to the licensed configuration file

Просмотреть файл

@ -26,6 +26,15 @@ cache_path: 'relative/path/to/cache'
# Defaults to current directory when running `licensed`
source_path: 'relative/path/to/source'
# Whether to take any action when records are detected in the cache paths that don't map to evaluated
# dependencies.
# Available values are:
# - 'error': treat stale cached records as errors. Notify the user and fail status checks
# - 'warn', '', unset: treat stale cached records as warnings. Notify the user but do not fail status checks
# - 'ignore': Ignore stale cached records. Do not notify the user and do not fail status checks
# Optional, when not set this defaults to 'warn' behavior
stale_records_action: 'warn'
# Sources of metadata
sources:
bower: true

Просмотреть файл

@ -23,10 +23,48 @@ module Licensed
# Returns whether the command succeeded based on the call to super
def run_command(report)
super do |result|
next if result
stale_records = stale_cached_records
if stale_records.any?
messages = stale_records.map { |f| "Stale dependency record found: #{f}" }
messages << "Please run the licensed cache command to clean up stale records"
case config["stale_records_action"].to_s
when "error"
report.errors.concat messages
result = false
when "warn", ""
report.warnings.concat messages
end
end
next result if result
report.errors << "Licensed found errors during source enumeration. Please see https://github.com/github/licensed/tree/master/docs/commands/status.md#status-errors-and-resolutions for possible resolutions."
result
end
ensure
cache_paths.clear
files.clear
end
# Run the command for all enumerated dependencies found in a dependency source,
# recording results in a report.
# Enumerating dependencies in the source is skipped if a :sources option
# is provided and the evaluated `source.class.type` is not in the :sources values
#
# app - The application configuration for the source
# source - A dependency source enumerator
#
# Returns whether the command succeeded for the dependency source enumerator
def run_source(app, source, report)
result = super
# add the full cache path to the list of cache paths
# that should be checked for extra files after the command run
cache_paths << app.cache_path.join(source.class.type) unless result == :skipped
result
end
# Evaluates a dependency for any compliance errors.
@ -49,6 +87,9 @@ module Licensed
filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
report["filename"] = filename
record = cached_record(filename)
# add the absolute dependency file path to the list of files seen during this licensed run
files << filename.to_s
end
if record.nil?
@ -133,6 +174,26 @@ module Licensed
licenses.sort_by { |license| license != "other" ? 0 : 1 }.first
end
# Check for cached files that don't match current dependencies
#
# Returns an array of any cached records that do not match a currently used dependency
def stale_cached_records
cache_paths.flat_map do |cache_path|
record_search_glob_pattern = cache_path.join("**/*.#{DependencyRecord::EXTENSION}")
Dir.glob(record_search_glob_pattern).select { |file| !files.include?(file) }
end.uniq
end
# Set of unique cache paths that are evaluted during the run
def cache_paths
@cache_paths ||= Set.new
end
# Set of unique absolute file paths of cached records evaluted during the run
def files
@files ||= Set.new
end
end
end
end

Просмотреть файл

@ -8,6 +8,11 @@ module Licensed
# command - The command being run
# report - A report object containing information about the command run
def end_report_command(command, report)
if report.warnings.any?
shell.newline
report.warnings.each { |e| shell.warn e }
end
if report.errors.any?
shell.newline
report.errors.each { |e| shell.error e }

Просмотреть файл

@ -9,7 +9,8 @@ describe Licensed::Commands::Status do
let(:reporter) { TestReporter.new }
let(:apps) { [] }
let(:source_config) { {} }
let(:config) { Licensed::Configuration.new("apps" => apps, "cache_path" => cache_path, "sources" => { "test" => true }, "test" => source_config) }
let(:command_config) { { "apps" => apps, "cache_path" => cache_path, "sources" => { "test" => true }, "test" => source_config } }
let(:config) { Licensed::Configuration.new(command_config) }
let(:fixtures) { File.expand_path("../../fixtures", __FILE__) }
let(:command) { Licensed::Commands::Status.new(config: config) }
@ -24,11 +25,15 @@ describe Licensed::Commands::Status do
dependency_report&.errors || []
end
def generate_metadata_files
generator_config = Marshal.load(Marshal.dump(config))
generator = Licensed::Commands::Cache.new(config: generator_config)
generator.run(force: true, reporter: TestReporter.new)
end
describe "with cached metadata data source" do
before do
generator_config = Marshal.load(Marshal.dump(config))
generator = Licensed::Commands::Cache.new(config: generator_config)
generator.run(force: true, reporter: TestReporter.new)
generate_metadata_files
end
after do
@ -776,4 +781,65 @@ describe Licensed::Commands::Status do
end
end
end
describe "with stale cached records" do
let(:unused_record_file_path) do
app = config.apps.first
source = app.sources.first
File.join(app.cache_path, source.class.type, "unused.#{Licensed::DependencyRecord::EXTENSION}")
end
before do
# generate artifacts needed for the status command to normally pass
# in order to validate that the command passes or fails depending on
# the stale_records_action config setting
generate_metadata_files
config.apps.each do |app|
app.allow "mit"
end
FileUtils.mkdir_p File.dirname(unused_record_file_path)
File.write(unused_record_file_path, "")
end
after do
config.apps.each do |app|
FileUtils.rm_rf app.cache_path
end
end
it "reports an error on stale cached records when configured" do
command_config["stale_records_action"] = "error"
refute run_command
assert reporter.report.errors.include?("Stale dependency record found: #{unused_record_file_path}")
refute reporter.report.warnings.include?("Stale dependency record found: #{unused_record_file_path}")
end
it "reports a warning on stale cached records when unconfigured" do
assert run_command
refute reporter.report.errors.include?("Stale dependency record found: #{unused_record_file_path}")
assert reporter.report.warnings.include?("Stale dependency record found: #{unused_record_file_path}")
end
it "reports a warning on stale cached records when configured" do
command_config["stale_records_action"] = "warning"
assert run_command
refute reporter.report.errors.include?("Stale dependency record found: #{unused_record_file_path}")
assert reporter.report.warnings.include?("Stale dependency record found: #{unused_record_file_path}")
end
it "ignores stale cached records when configured" do
command_config["stale_records_action"] = "ignore"
assert run_command
refute reporter.report.errors.include?("Stale dependency record found: #{unused_record_file_path}")
refute reporter.report.warnings.include?("Stale dependency record found: #{unused_record_file_path}")
end
end
end

Просмотреть файл

@ -24,6 +24,18 @@ describe Licensed::Reporters::StatusReporter do
style: :error
}
end
it "reports any warnings specified on the report object" do
report.warnings << "command warning"
reporter.end_report_command(command, report)
assert_includes shell.messages,
{
message: "command warning",
newline: true,
style: :warn
}
end
end
describe "#begin_report_app" do