SBOM-CycloneDX
view release on metacpan or search on metacpan
examples/rpm-to-sbom.pl view on Meta::CPAN
#!perl
# rpm-to-bom.pl - Generate a BOM file from installed RPM packages
# (C) 2026, Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
# License MIT
use strict;
use warnings;
use utf8;
use v5.16;
use SBOM::CycloneDX;
use SBOM::CycloneDX::Component;
use SBOM::CycloneDX::Metadata::Lifecycle;
use SBOM::CycloneDX::Tool;
use SBOM::CycloneDX::Util qw(cyclonedx_tool);
use Carp;
use URI::PackageURL;
my $packages = `rpm -qa --qf '%{NAME}\t%{VERSION}-%{RELEASE}\t%{ARCH}\t%{SUMMARY}\n'`;
my @packages = split /\n/, $packages;
my %os_release = parse_os_release();
my $bom = SBOM::CycloneDX->new(spec_version => 1.7);
my $os_component = SBOM::CycloneDX::Component->new(
type => 'operating-system',
name => $os_release{NAME},
version => $os_release{VERSION},
bom_ref => sprintf('%s-%s', $os_release{ID}, $os_release{VERSION_ID}),
cpe => $os_release{CPE_NAME},
);
my $root_component = SBOM::CycloneDX::Component->new(
type => 'operating-system',
name => $os_release{NAME},
version => $os_release{VERSION},
lifecycle => {phase => 'operation'},
);
my $this_tool = SBOM::CycloneDX::Tool->new(name => $0, version => '1.0');
my $lifecycle = SBOM::CycloneDX::Metadata::Lifecycle->new(phase => 'operations');
my $metadata = $bom->metadata;
$metadata->lifecycles->add($lifecycle);
$metadata->component($root_component);
$metadata->tools->add(cyclonedx_tool);
$metadata->tools->add($this_tool);
$bom->components->add($os_component);
foreach my $package (@packages) {
my ($name, $version, $arch, $summary) = split /\t/, $package;
my $purl = URI::PackageURL->new(
type => 'rpm',
namespace => $os_release{ID},
name => $name,
version => $version,
qualifiers => {arch => $arch, distro => sprintf('%s-%s', $os_release{ID}, $os_release{VERSION_ID})}
);
my $pkg_component = SBOM::CycloneDX::Component->new(
type => 'application',
name => $name,
description => $summary,
version => $version,
bom_ref => "$name-$version",
purl => $purl
);
$bom->components->add($pkg_component);
$bom->add_dependency($os_component, [$pkg_component]);
}
my @errors = $bom->validate;
say STDERR "[VALIDATION] $_" for @errors;
say $bom;
sub parse_os_release {
open my $fh, '<', '/etc/os-release' or Carp::croak "Failed to open os-release: $!";
my %os_release = ();
while (my $line = <$fh>) {
chomp($line);
my ($key, $value) = ($line =~ /(.*)=(.*)/);
$value =~ s/"//g;
$os_release{$key} = $value;
}
close $fh;
return wantarray ? %os_release : \%os_release;
}
( run in 1.582 second using v1.01-cache-2.11-cpan-d8267643d1d )