App-CPAN-SBOM
view release on metacpan or search on metacpan
lib/App/CPAN/SBOM.pm view on Meta::CPAN
use SBOM::CycloneDX;
our $VERSION = '1.04';
sub DEBUG { $ENV{SBOM_DEBUG} || 0 }
sub cli_error {
my ($error, $code) = @_;
$error =~ s/ at .* line \d+.*//;
say STDERR "ERROR: $error";
return $code || 1;
}
sub run {
my (@args) = @_;
my %options = ();
GetOptionsFromArray(
lib/App/CPAN/SBOM.pm view on Meta::CPAN
pod2usage(-exitstatus => 0, -verbose => 2) if defined $options{man};
pod2usage(-exitstatus => 0, -verbose => 0) if defined $options{help};
$options{'project-meta'} //= $options{meta};
if (defined $options{v}) {
return show_version();
}
if ($options{'list-spdx-licenses'}) {
say $_ for (sort @{SBOM::CycloneDX::Enum->SPDX_LICENSES});
return 0;
}
unless ($options{distribution} || $options{'project-meta'} || $options{'project-directory'}) {
pod2usage(-exitstatus => 0, -verbose => 0);
}
$options{maxdepth} //= 1;
$options{validate} //= 1;
lib/App/CPAN/SBOM.pm view on Meta::CPAN
if (defined $options{'project-directory'} || defined $options{'project-meta'}) {
make_sbom_from_project(bom => $bom, options => \%options);
}
$bom->metadata->timestamp(time);
$bom->metadata->tools->push(cyclonedx_tool());
my $output_file = $options{output} // 'bom.json';
say STDERR "Save SBOM to $output_file";
open my $fh, '>', $output_file or Carp::croak "Failed to open file: $!";
say $fh $bom->to_string;
close $fh;
if ($options{validate}) {
my @errors = $bom->validate;
say STDERR $_ foreach (@errors);
}
if (defined $options{'server-url'} && defined $options{'api-key'}) {
submit_bom(bom => $bom, options => \%options);
}
}
sub show_version {
(my $progname = $0) =~ s/.*\///;
say <<"VERSION";
$progname version $VERSION
Copyright 2025-2026, Giuseppe Di Terlizzi <gdt\@cpan.org>
This program is part of the "App-CPAN-SBOM" distribution and is free software;
you can redistribute it and/or modify it under the same terms as Perl itself.
Complete documentation for $progname can be found using 'man $progname'
or on the internet at <https://metacpan.org/dist/App-CPAN-SBOM>.
VERSION
lib/App/CPAN/SBOM.pm view on Meta::CPAN
my (%params) = @_;
my $audit_discover = CPAN::Audit::Discover->new;
my $bom = $params{bom};
my $options = $params{options} || {};
my @META_FILES = (qw[META.json META.yml MYMETA.json MYMETA.yml]);
say STDERR 'Generate SBOM';
my $project_type = $options->{'project-type'} || 'library';
my $project_directory = File::Spec->rel2abs($options->{'project-directory'});
my $project_meta = $options->{'project-meta'} || $options->{'meta'};
my $project_name = $options->{'project-name'} || basename($project_directory);
my $project_version = $options->{'project-version'} || 0;
my $project_description = $options->{'project-description'};
my $project_license = $options->{'project-license'};
my $project_author = $options->{'project-author'} || [];
lib/App/CPAN/SBOM.pm view on Meta::CPAN
sub make_sbom_from_dist {
my (%params) = @_;
my $distribution = $params{distribution};
my $version = $params{version};
my $bom = $params{bom};
my $options = $params{options} || {};
say STDERR "Generate SBOM for $distribution\@$version";
my $mcpan = MetaCPAN::Client->new;
my $release_data = $mcpan->release({all => [{distribution => $distribution}, {version => $version}]});
my $dist_data = $release_data->next;
unless ($dist_data) {
Carp::carp("Unable to find release ($distribution\@$version) in Meta::CPAN");
return;
}
lib/App/CPAN/SBOM.pm view on Meta::CPAN
my $maxdepth = $params{maxdepth} || 1;
my $add_vulns = $params{add_vulns} || 0;
my $mcpan = MetaCPAN::Client->new;
if ($module) {
$module = 'perl' if ($module eq 'Perl');
DEBUG
and say STDERR sprintf '-- %s[%d] Collect module %s@%s info (parent component %s)',
(" " x ($depth - 1)), $depth, $module, $version, $parent_component->bom_ref;
my $module_data = $mcpan->module($module);
unless ($module_data) {
Carp::carp("Unable to find module ($module) in Meta::CPAN");
return;
}
$author //= $module_data->author;
lib/App/CPAN/SBOM.pm view on Meta::CPAN
my $release_data = $mcpan->release({
either => [
{all => [{distribution => $distribution}, {version => $version}]},
{all => [{distribution => $distribution}, {version => "v$version"}]},
]
});
my $dist_data = $release_data->next;
DEBUG
and say STDERR sprintf '-- %s[%d] Collect distribution %s@%s info (parent component %s)',
(" " x ($depth - 1)), $depth, $distribution, $version, $parent_component->bom_ref;
unless ($dist_data) {
Carp::carp("Unable to find release ($distribution\@$version) in Meta::CPAN");
return;
}
my $metadata = $dist_data->metadata;
$author //= $dist_data->author;
lib/App/CPAN/SBOM.pm view on Meta::CPAN
$bom_payload->{parentUUID} = $options->{'parent-project-id'};
}
my $verify_ssl = (defined $options->{'skip-tls-check'}) ? 0 : 1;
my $ua = HTTP::Tiny->new(
verify_SSL => $verify_ssl,
default_headers => {'Content-Type' => 'application/json', 'X-Api-Key' => $options->{'api-key'}}
);
say STDERR "Upload BOM in OSWASP Dependency Track ($server_url)";
my $response = $ua->put($server_url, {content => encode_json($bom_payload)});
DEBUG and say STDERR "-- Response <-- " . Dumper($response);
unless ($response->{success}) {
return cli_error(sprintf(
'Failed to upload BOM file to OWASP Dependency Track: (%s) %s - %s',
$response->{status}, $response->{reason}, $response->{content}
));
}
}
( run in 1.418 second using v1.01-cache-2.11-cpan-d7f47b0818f )