diff --git a/.github/workflows/validate-cg-manifest.sh b/.github/workflows/validate-cg-manifest.sh
index 3a775b55f0..1a509c425e 100755
--- a/.github/workflows/validate-cg-manifest.sh
+++ b/.github/workflows/validate-cg-manifest.sh
@@ -23,6 +23,7 @@ ignore_multiple_sources=" \
# List of ignored specs due to no source tarball to scan.
ignore_no_source_tarball=" \
+ azurelinux-sysinfo \
ca-certificates \
check-restart \
core-packages \
diff --git a/SPECS/LICENSES-AND-NOTICES/LICENSES-MAP.md b/SPECS/LICENSES-AND-NOTICES/LICENSES-MAP.md
index 8e15c41d6d..018bd1237f 100644
--- a/SPECS/LICENSES-AND-NOTICES/LICENSES-MAP.md
+++ b/SPECS/LICENSES-AND-NOTICES/LICENSES-MAP.md
@@ -9,7 +9,7 @@ The CBL-Mariner SPEC files originated from a variety of sources with varying lic
| Fedora (Copyright Remi Collet) | [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) | libmemcached-awesome
librabbitmq |
| Fedora (ISC) | [ISC License](https://github.com/sarugaku/resolvelib/blob/main/LICENSE) | python-resolvelib |
| Magnus Edenhill Open Source | [Magnus Edenhill Open Source BSD License](https://github.com/jemalloc/jemalloc/blob/dev/COPYING) | librdkafka |
-| Microsoft | [Microsoft MIT License](/LICENSES-AND-NOTICES/LICENSE.md) | application-gateway-kubernetes-ingress
asc
azcopy
azure-iot-sdk-c
azure-storage-cpp
bazel
blobfuse
blobfuse2
bmon
bpftrace
ccache
cert-manager
cf-cli
check-restart
clamav
cloud-hypervisor
cmake-fedora
coredns
csi-driver-lvm
dcos-cli
debugedit
dejavu-fonts
distroless-packages
doxygen
dtc
elixir
espeak-ng
espeakup
flannel
fluent-bit
freefont
gflags
gh
go-md2man
grpc
grub2-efi-binary-signed
GSL
gtk-update-icon-cache
helm
hvloader
hvloader-signed
installkernel
intel-pf-bb-config
ivykis
jsonbuilder
jx
kata-containers-cc
kata-packages-uvm
keda
keras
kernel-azure-signed
kernel-hci-signed
kernel-mos-signed
kernel-mshv-signed
kernel-signed
KeysInUse-OpenSSL
kpatch
kube-vip-cloud-provider
kubernetes
libacvp
libconfini
libconfuse
libgdiplus
libmaxminddb
libmetalink
libsafec
libuv
libxml++
livepatch-5.15.102.1-1.cm2
livepatch-5.15.102.1-3.cm2
livepatch-5.15.107.1-1.cm2
livepatch-5.15.110.1-1.cm2
livepatch-5.15.111.1-1.cm2
livepatch-5.15.112.1-1.cm2
livepatch-5.15.112.1-2.cm2
livepatch-5.15.116.1-1.cm2
livepatch-5.15.116.1-2.cm2
livepatch-5.15.122.1-2.cm2
livepatch-5.15.125.1-1.cm2
livepatch-5.15.125.1-2.cm2
livepatch-5.15.126.1-1.cm2
livepatch-5.15.131.1-1.cm2
livepatch-5.15.131.1-3.cm2
livepatch-5.15.94.1-1.cm2
livepatch-5.15.94.1-1.cm2-signed
livepatch-5.15.95.1-1.cm2
livepatch-5.15.98.1-1.cm2
livepatching
lld
lld16
local-path-provisioner
lsb-release
ltp
lttng-consume
mariner-release
mariner-repos
mariner-rpm-macros
maven3
mm-common
moby-buildx
moby-cli
moby-compose
moby-containerd
moby-containerd-cc
moby-engine
moby-runc
msgpack
ncompress
networkd-dispatcher
nlohmann-json
nmap
nmi
node-problem-detector
ntopng
opentelemetry-cpp
osslsigncode
packer
pcaudiolib
pcre2
perl-Test-Warnings
perl-Text-Template
pigz
prebuilt-ca-certificates
prebuilt-ca-certificates-base
prometheus-adapter
python-cachetools
python-cherrypy
python-cstruct
python-execnet
python-google-pasta
python-libclang
python-logutils
python-nocasedict
python-opt-einsum
python-pecan
python-pyrpm
python-remoto
python-repoze-lru
python-routes
python-rsa
python-sphinxcontrib-websupport
python-tensorboard
python-tensorboard-plugin-wit
python-tensorflow-estimator
python-yamlloader
R
rabbitmq-server
reaper
rocksdb
rubygem-addressable
rubygem-asciidoctor
rubygem-async
rubygem-async-http
rubygem-async-io
rubygem-async-pool
rubygem-aws-eventstream
rubygem-aws-partitions
rubygem-aws-sdk-core
rubygem-aws-sdk-kms
rubygem-aws-sdk-s3
rubygem-aws-sdk-sqs
rubygem-aws-sigv4
rubygem-bigdecimal
rubygem-bindata
rubygem-concurrent-ruby
rubygem-connection_pool
rubygem-console
rubygem-cool.io
rubygem-deep_merge
rubygem-digest-crc
rubygem-elastic-transport
rubygem-elasticsearch
rubygem-elasticsearch-api
rubygem-eventmachine
rubygem-excon
rubygem-faraday
rubygem-faraday-em_http
rubygem-faraday-em_synchrony
rubygem-faraday-excon
rubygem-faraday-httpclient
rubygem-faraday-multipart
rubygem-faraday-net_http
rubygem-faraday-net_http_persistent
rubygem-faraday-patron
rubygem-faraday-rack
rubygem-faraday-retry
rubygem-ffi
rubygem-fiber-local
rubygem-fluent-config-regexp-type
rubygem-fluent-logger
rubygem-fluent-plugin-elasticsearch
rubygem-fluent-plugin-kafka
rubygem-fluent-plugin-prometheus
rubygem-fluent-plugin-prometheus_pushgateway
rubygem-fluent-plugin-record-modifier
rubygem-fluent-plugin-rewrite-tag-filter
rubygem-fluent-plugin-s3
rubygem-fluent-plugin-systemd
rubygem-fluent-plugin-td
rubygem-fluent-plugin-webhdfs
rubygem-fluent-plugin-windows-exporter
rubygem-fluentd
rubygem-hirb
rubygem-hocon
rubygem-hoe
rubygem-http_parser.rb
rubygem-httpclient
rubygem-io-event
rubygem-jmespath
rubygem-ltsv
rubygem-mini_portile2
rubygem-minitest
rubygem-mocha
rubygem-msgpack
rubygem-multi_json
rubygem-multipart-post
rubygem-net-http-persistent
rubygem-nio4r
rubygem-nokogiri
rubygem-oj
rubygem-parallel
rubygem-power_assert
rubygem-prometheus-client
rubygem-protocol-hpack
rubygem-protocol-http
rubygem-protocol-http1
rubygem-protocol-http2
rubygem-public_suffix
rubygem-puppet-resource_api
rubygem-rdiscount
rubygem-rdkafka
rubygem-rexml
rubygem-ruby-kafka
rubygem-ruby-progressbar
rubygem-rubyzip
rubygem-semantic_puppet
rubygem-serverengine
rubygem-sigdump
rubygem-strptime
rubygem-systemd-journal
rubygem-td
rubygem-td-client
rubygem-td-logger
rubygem-test-unit
rubygem-thor
rubygem-timers
rubygem-tzinfo
rubygem-tzinfo-data
rubygem-webhdfs
rubygem-webrick
rubygem-yajl-ruby
rubygem-zip-zip
sdbus-cpp
sgx-backwards-compatability
shim
shim-unsigned
shim-unsigned-aarch64
shim-unsigned-x64
skopeo
span-lite
sriov-network-device-plugin
swupdate
SymCrypt
SymCrypt-OpenSSL
tensorflow
terraform
tinyxml2
toml11
tracelogging
umoci
usrsctp
vala
verity-read-only-root
vnstat
zstd |
+| Microsoft | [Microsoft MIT License](/LICENSES-AND-NOTICES/LICENSE.md) | application-gateway-kubernetes-ingress
asc
azcopy
azure-iot-sdk-c
azure-storage-cpp
azurelinux-sysinfo
bazel
blobfuse
blobfuse2
bmon
bpftrace
ccache
cert-manager
cf-cli
check-restart
clamav
cloud-hypervisor
cmake-fedora
coredns
csi-driver-lvm
dcos-cli
debugedit
dejavu-fonts
distroless-packages
doxygen
dtc
elixir
espeak-ng
espeakup
flannel
fluent-bit
freefont
gflags
gh
go-md2man
grpc
grub2-efi-binary-signed
GSL
gtk-update-icon-cache
helm
hvloader
hvloader-signed
installkernel
intel-pf-bb-config
ivykis
jsonbuilder
jx
kata-containers-cc
kata-packages-uvm
keda
keras
kernel-azure-signed
kernel-hci-signed
kernel-mos-signed
kernel-mshv-signed
kernel-signed
KeysInUse-OpenSSL
kpatch
kube-vip-cloud-provider
kubernetes
libacvp
libconfini
libconfuse
libgdiplus
libmaxminddb
libmetalink
libsafec
libuv
libxml++
livepatch-5.15.102.1-1.cm2
livepatch-5.15.102.1-3.cm2
livepatch-5.15.107.1-1.cm2
livepatch-5.15.110.1-1.cm2
livepatch-5.15.111.1-1.cm2
livepatch-5.15.112.1-1.cm2
livepatch-5.15.112.1-2.cm2
livepatch-5.15.116.1-1.cm2
livepatch-5.15.116.1-2.cm2
livepatch-5.15.122.1-2.cm2
livepatch-5.15.125.1-1.cm2
livepatch-5.15.125.1-2.cm2
livepatch-5.15.126.1-1.cm2
livepatch-5.15.131.1-1.cm2
livepatch-5.15.131.1-3.cm2
livepatch-5.15.94.1-1.cm2
livepatch-5.15.94.1-1.cm2-signed
livepatch-5.15.95.1-1.cm2
livepatch-5.15.98.1-1.cm2
livepatching
lld
lld16
local-path-provisioner
lsb-release
ltp
lttng-consume
mariner-release
mariner-repos
mariner-rpm-macros
maven3
mm-common
moby-buildx
moby-cli
moby-compose
moby-containerd
moby-containerd-cc
moby-engine
moby-runc
msgpack
ncompress
networkd-dispatcher
nlohmann-json
nmap
nmi
node-problem-detector
ntopng
opentelemetry-cpp
osslsigncode
packer
pcaudiolib
pcre2
perl-Test-Warnings
perl-Text-Template
pigz
prebuilt-ca-certificates
prebuilt-ca-certificates-base
prometheus-adapter
python-cachetools
python-cherrypy
python-cstruct
python-execnet
python-google-pasta
python-libclang
python-logutils
python-nocasedict
python-opt-einsum
python-pecan
python-pyrpm
python-remoto
python-repoze-lru
python-routes
python-rsa
python-sphinxcontrib-websupport
python-tensorboard
python-tensorboard-plugin-wit
python-tensorflow-estimator
python-yamlloader
R
rabbitmq-server
reaper
rocksdb
rubygem-addressable
rubygem-asciidoctor
rubygem-async
rubygem-async-http
rubygem-async-io
rubygem-async-pool
rubygem-aws-eventstream
rubygem-aws-partitions
rubygem-aws-sdk-core
rubygem-aws-sdk-kms
rubygem-aws-sdk-s3
rubygem-aws-sdk-sqs
rubygem-aws-sigv4
rubygem-bigdecimal
rubygem-bindata
rubygem-concurrent-ruby
rubygem-connection_pool
rubygem-console
rubygem-cool.io
rubygem-deep_merge
rubygem-digest-crc
rubygem-elastic-transport
rubygem-elasticsearch
rubygem-elasticsearch-api
rubygem-eventmachine
rubygem-excon
rubygem-faraday
rubygem-faraday-em_http
rubygem-faraday-em_synchrony
rubygem-faraday-excon
rubygem-faraday-httpclient
rubygem-faraday-multipart
rubygem-faraday-net_http
rubygem-faraday-net_http_persistent
rubygem-faraday-patron
rubygem-faraday-rack
rubygem-faraday-retry
rubygem-ffi
rubygem-fiber-local
rubygem-fluent-config-regexp-type
rubygem-fluent-logger
rubygem-fluent-plugin-elasticsearch
rubygem-fluent-plugin-kafka
rubygem-fluent-plugin-prometheus
rubygem-fluent-plugin-prometheus_pushgateway
rubygem-fluent-plugin-record-modifier
rubygem-fluent-plugin-rewrite-tag-filter
rubygem-fluent-plugin-s3
rubygem-fluent-plugin-systemd
rubygem-fluent-plugin-td
rubygem-fluent-plugin-webhdfs
rubygem-fluent-plugin-windows-exporter
rubygem-fluentd
rubygem-hirb
rubygem-hocon
rubygem-hoe
rubygem-http_parser.rb
rubygem-httpclient
rubygem-io-event
rubygem-jmespath
rubygem-ltsv
rubygem-mini_portile2
rubygem-minitest
rubygem-mocha
rubygem-msgpack
rubygem-multi_json
rubygem-multipart-post
rubygem-net-http-persistent
rubygem-nio4r
rubygem-nokogiri
rubygem-oj
rubygem-parallel
rubygem-power_assert
rubygem-prometheus-client
rubygem-protocol-hpack
rubygem-protocol-http
rubygem-protocol-http1
rubygem-protocol-http2
rubygem-public_suffix
rubygem-puppet-resource_api
rubygem-rdiscount
rubygem-rdkafka
rubygem-rexml
rubygem-ruby-kafka
rubygem-ruby-progressbar
rubygem-rubyzip
rubygem-semantic_puppet
rubygem-serverengine
rubygem-sigdump
rubygem-strptime
rubygem-systemd-journal
rubygem-td
rubygem-td-client
rubygem-td-logger
rubygem-test-unit
rubygem-thor
rubygem-timers
rubygem-tzinfo
rubygem-tzinfo-data
rubygem-webhdfs
rubygem-webrick
rubygem-yajl-ruby
rubygem-zip-zip
sdbus-cpp
sgx-backwards-compatability
shim
shim-unsigned
shim-unsigned-aarch64
shim-unsigned-x64
skopeo
span-lite
sriov-network-device-plugin
swupdate
SymCrypt
SymCrypt-OpenSSL
tensorflow
terraform
tinyxml2
toml11
tracelogging
umoci
usrsctp
vala
verity-read-only-root
vnstat
zstd |
| Netplan source | [GPLv3](https://github.com/canonical/netplan/blob/main/COPYING) | netplan |
| Numad source | [LGPLv2 License](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt) | numad |
| NVIDIA | [ASL 2.0 License and spec specific licenses](http://www.apache.org/licenses/LICENSE-2.0) | knem
libnvidia-container
mlnx-ofa_kernel
mlnx-tools
mlx-bootctl
nvidia-container-runtime
nvidia-container-toolkit
nvidia-docker2
ofed-scripts
perftest |
diff --git a/SPECS/LICENSES-AND-NOTICES/data/licenses.json b/SPECS/LICENSES-AND-NOTICES/data/licenses.json
index 012261b1ae..eff809859d 100644
--- a/SPECS/LICENSES-AND-NOTICES/data/licenses.json
+++ b/SPECS/LICENSES-AND-NOTICES/data/licenses.json
@@ -2152,6 +2152,7 @@
"azcopy",
"azure-iot-sdk-c",
"azure-storage-cpp",
+ "azurelinux-sysinfo",
"bazel",
"blobfuse",
"blobfuse2",
diff --git a/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.service b/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.service
new file mode 100644
index 0000000000..5434f98f45
--- /dev/null
+++ b/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Azure Linux Sysinfo Service
+After=cloud-init.target multi-user.target
+
+[Service]
+Environment=PYTHONUNBUFFERED=1
+Type=simple
+ExecStart=/usr/bin/python3 /usr/bin/collect-sysinfo
+StandardOutput=journal
+StandardError=journal
+
+[Install]
+WantedBy=multi-user.target
diff --git a/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.signatures.json b/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.signatures.json
new file mode 100644
index 0000000000..0ca8108b64
--- /dev/null
+++ b/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.signatures.json
@@ -0,0 +1,9 @@
+{
+ "Signatures": {
+ "collect-sysinfo": "b47df8a856c49e4bc02b36d1c3dd2825b75b9d8449b5dae8af401fc6818131c9",
+ "sysinfo-schema-v1.json": "67b541239416bd5f9a77a0799881f21c2e5eea686dc7a3ccaffe6bd7219a4798",
+ "azurelinux-sysinfo.service": "c719ab2238d0412b7ac6a793cd83e5be7879023161f86fb29d1c0ca18e70631c",
+ "sysinfo-selinuxpolicies.cil": "1f0df94a09f4db09093743339b6162735b6f1c81108cd3b857a6dbc729630400"
+ }
+}
+
\ No newline at end of file
diff --git a/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.spec b/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.spec
new file mode 100644
index 0000000000..07e5f7b0a5
--- /dev/null
+++ b/SPECS/azurelinux-sysinfo/azurelinux-sysinfo.spec
@@ -0,0 +1,69 @@
+Summary: Package to deploy azurelinux-sysinfo service
+Name: azurelinux-sysinfo
+Version: 2.0
+Release: 1%{?dist}
+License: MIT
+Vendor: Microsoft Corporation
+Distribution: Azure Linux
+Group: System Environment/Base
+URL: https://aka.ms/azurelinux
+Source0: collect-sysinfo
+Source1: sysinfo-schema-v1.json
+Source2: azurelinux-sysinfo.service
+Source3: sysinfo-selinuxpolicies.cil
+Requires: systemd
+Requires: python3-psutil
+
+%description
+Deploys a systemd service that gathers system information related to the device, operating system, cloud-init, boot
+time, resource utilization, installed packages, and SELinux mode. Collected information is written in JSON format to
+a log file on the user's system for easy access and analysis. The systemd service runs at boot time if installed during
+image creation.
+
+%install
+# Copy collection python script to /usr/bin/
+mkdir -p %{buildroot}%{_bindir}/
+install -m 755 %{SOURCE0} %{buildroot}%{_bindir}/
+
+# Copy data schema to /usr/share/azurelinux-sysinfo/
+mkdir -p %{buildroot}%{_datadir}/azurelinux-sysinfo/
+install -m 755 %{SOURCE1} %{buildroot}%{_datadir}/azurelinux-sysinfo/
+
+# Copy service to /etc/systemd/system/
+mkdir -p %{buildroot}%{_sysconfdir}/systemd/system/
+install -m 755 %{SOURCE2} %{buildroot}%{_sysconfdir}/systemd/system/
+
+# Copy the sysinfo-selinuxpolicies file to /usr/share/selinux/packages/
+mkdir -p %{buildroot}%{_datadir}/selinux/packages/
+install -m 755 %{SOURCE3} %{buildroot}%{_datadir}/selinux/packages/
+
+%files
+%{_bindir}/collect-sysinfo
+%dir %{_datadir}/azurelinux-sysinfo/
+%{_datadir}/azurelinux-sysinfo/sysinfo-schema-v1.json
+%{_sysconfdir}/systemd/system/azurelinux-sysinfo.service
+%{_datadir}/selinux/packages/sysinfo-selinuxpolicies.cil
+
+%post
+#!/bin/sh
+# Enable the systemd service
+systemctl enable azurelinux-sysinfo.service
+
+# Apply required SElinux policies only if selinux-policy is present
+if rpm -q selinux-policy &> /dev/null; then
+ semodule -i %{_datadir}/selinux/packages/sysinfo-selinuxpolicies.cil
+fi
+
+%postun
+# If selinux-policy is present, remove the sysinfo-selinuxpolicies module
+if rpm -q selinux-policy &> /dev/null; then
+ semodule -r sysinfo-selinuxpolicies
+fi
+
+%changelog
+* Thu Apr 04 2024 Amrita Kohli - 2.0-1
+- License verified.
+- Implementation of package that deploys azurelinux-sysinfo service.
+- Original version for CBL-Mariner.
+
+
diff --git a/SPECS/azurelinux-sysinfo/collect-sysinfo b/SPECS/azurelinux-sysinfo/collect-sysinfo
new file mode 100755
index 0000000000..2188c0561e
--- /dev/null
+++ b/SPECS/azurelinux-sysinfo/collect-sysinfo
@@ -0,0 +1,293 @@
+#!/usr/bin/python3
+
+import argparse
+import json
+import shutil
+import jsonschema
+import psutil
+import os
+import re
+import subprocess
+
+DATA_SCHEMA_DIR = "/usr/share/azurelinux-sysinfo"
+DATA_SCHEMA_VERSION = "v1"
+DATA_SCHEMA_FILENAME = f"sysinfo-schema-{DATA_SCHEMA_VERSION}.json"
+LOG_FILE_PATH = "/var/log/azurelinux-sysinfo.log"
+SERVICE_NAME = "azurelinux-sysinfo-service"
+
+
+# This function converts a string that matches
+# regex = r"(\d+(\.\d+)?)(min|s|ms)" to seconds
+def convert_to_secs(line):
+ regex = r"(\d+(?:\.\d+)?)(min|s|ms)"
+ time_secs = 0
+ for match in re.findall(regex, line):
+ time = float(match[0])
+ unit = match[1]
+ if unit == "min":
+ time *= 60
+ elif unit == "ms":
+ time /= 1000
+ time_secs += time
+ return time_secs
+
+
+def collect_os_info():
+ print("Collecting os info...")
+
+ release_data = {}
+ release_info = subprocess.run(
+ ["cat", "/etc/os-release"], capture_output=True, text=True
+ )
+
+ kernel_info = subprocess.run(["uname", "-r"], capture_output=True, text=True)
+
+ for line in release_info.stdout.strip().splitlines():
+ name, value = line.split("=", maxsplit=1)
+ release_data[name] = value.strip('"')
+
+
+ os_info = {
+ "kernel_version": kernel_info.stdout.strip(),
+ "release_version": release_data["VERSION"],
+ "release_version_id": release_data["VERSION_ID"],
+ }
+
+ return os_info
+
+
+def collect_boot_info():
+ print("Collecting boot info...")
+ # Known issue: In SELinux enforcing mode, systemd-analyze commands are expected to fail until required policies are added.
+ # In this case, the boot times will be 0 and longest running processes will be empty.
+
+ # Collect boot time
+ result = subprocess.run(["systemd-analyze", "time"], capture_output=True, text=True)
+
+ # Sample output for livecd image:
+ # Startup finished in 153ms (firmware) + 554ms (loader) + 1.413s (kernel) + 908ms (userspace) = 3.030s
+ # multi-user.target reached after 897ms in userspace
+ # Sample output for host images:
+ # Startup finished in 12.688s (kernel) + 8.082s (initrd) + 1min 1.458s (userspace) = 1min 22.230s
+ # multi-user.target reached after 1min 966ms in userspace
+
+ lines = result.stdout.strip().splitlines()
+
+ # In a test setup on qemu, systemd-analyze returns empty
+ if len(lines) < 1 or not(lines[0].startswith("Startup finished in")):
+ boot_info = {
+ "boot_time": {
+ "kernel_boot_time_secs": 0,
+ "userspace_boot_time_secs": 0,
+ "total_boot_time_secs": 0,
+ },
+ "longest_running_processes": [],
+ }
+ return boot_info
+
+ # Define regular expression to extract times
+ timeRegex = r"((?:\d+)(?:\d*min\s?)?(?:\d*\.?\d*s\s?)?(?:\d*\.?\d*ms)?)"
+ # Define regular expression to extract values between parentheses
+ betweenParenthesesRegex = r"\((.*?)\)"
+
+ boot_time_keys = re.findall(betweenParenthesesRegex, lines[0])
+ boot_times = re.findall(timeRegex, lines[0])
+ boot_times = [t.strip() for t in boot_times]
+ boot_times_secs = [convert_to_secs(time) for time in boot_times]
+
+ boot_time = dict()
+ suffix = "_boot_time_secs"
+ for i in range(len(boot_time_keys)):
+ bootTimeKey = boot_time_keys[i] + suffix
+ bootTimeValue = boot_times_secs[i]
+ boot_time[bootTimeKey] = bootTimeValue
+ boot_time["total_boot_time_secs"] = boot_times_secs[-1]
+
+ # Collect boot time longest running processes
+ top_n = 3
+ result = subprocess.run(
+ ["systemd-analyze", "blame"], capture_output=True, text=True
+ )
+ filtered_result = subprocess.run(
+ ["head", f"-{top_n}"], input=result.stdout, capture_output=True, text=True
+ )
+
+ # Sample output:
+ # 43.642s systemd-networkd-wait-online.service
+ lines = filtered_result.stdout.strip().splitlines()
+
+ process_list = []
+ for line in lines:
+ process = re.search(r"\S+\s*$", line).group().strip()
+ process_list.append({process: convert_to_secs(line)})
+
+ boot_info = {"boot_time": boot_time, "longest_running_processes": process_list}
+
+ return boot_info
+
+
+def collect_resource_utilization():
+ print("Collecting disk and memory usage...")
+
+ # disk
+ os_disk_usage = shutil.disk_usage("/")
+ disk_usage = {
+ "disk_size_gib": f"{os_disk_usage.total/1024**3:.2f}",
+ "disk_usage_gib": f"{os_disk_usage.used/1024**3:.2f}",
+ }
+
+ # memory
+ memory_info = psutil.virtual_memory()
+ total_memory = memory_info.total // (1024**3)
+ available_memory = memory_info.available // (1024**3)
+
+ memory_usage = {
+ "total_memory_gib": total_memory,
+ "available_memory_gib": available_memory,
+ }
+
+ physical_cpu_count = psutil.cpu_count(logical=False)
+ logical_cpu_count = psutil.cpu_count(logical=True)
+ cpu_percent = psutil.cpu_percent()
+
+ cpu_usage = {
+ "physical_cpu_count": physical_cpu_count,
+ "logical_cpu_count": logical_cpu_count,
+ "cpu_percent": cpu_percent,
+ }
+
+ resource_utilization = {
+ "disk_usage": disk_usage,
+ "memory_usage": memory_usage,
+ "cpu_usage": cpu_usage,
+ }
+
+ return resource_utilization
+
+
+def collect_package_info():
+ print("Collecting package info...")
+ get_package_list = subprocess.run(
+ ["rpm", "-qa"], capture_output=True, text=True, check=True
+ )
+
+ package_list = get_package_list.stdout.strip().splitlines()
+
+ # TASK 4917: Adding package list resulted in hitting the size limit for the log,
+ # so only logging package count until an alternative is implemented.
+ package_info = {"package_count": len(package_list)}
+
+ return package_info
+
+
+def collect_cloud_init_info():
+ print("Collecting cloud-init info...")
+
+ # Collect cloud-init longest running processes
+ result = subprocess.run(
+ ["cloud-init", "analyze", "blame"], capture_output=True, text=True, check=True
+ )
+
+ lines = result.stdout.strip().splitlines()
+ process_list = []
+ top_n = 5
+
+ # Skipping the first line as it is "-- Boot Record 01 --"
+ # Skipping the last line as it is "x boot records analyzed"
+ range = min(top_n + 1, len(lines) - 1)
+
+ for line in lines[1:range]:
+ record_details = line.split()
+ if len(record_details) > 1:
+ process_info = {}
+ process_info["time"], process_info["process"] = record_details
+ process_list.append(process_info)
+
+ get_hostname = subprocess.run(
+ ["hostname"], capture_output=True, text=True, check=True
+ )
+
+ cloud_init_info = {
+ "hostname": get_hostname.stdout.strip(),
+ "longest_running_processes": process_list,
+ }
+
+ return cloud_init_info
+
+
+def get_selinux_mode():
+ return subprocess.run(
+ ["getenforce"], capture_output=True, text=True, check=True
+ ).stdout.strip()
+
+
+def collect_system_info():
+ print("Collecting system info...")
+ system_info = {"selinux_mode": get_selinux_mode()}
+ return system_info
+
+
+def get_asset_id():
+ print("Collecting asset id...")
+
+ return subprocess.run(
+ ["cat", "/sys/devices/virtual/dmi/id/product_uuid"], capture_output=True, text=True
+ ).stdout.lower().strip()
+
+
+def has_valid_schema(data):
+ schema_file = os.path.join(DATA_SCHEMA_DIR, DATA_SCHEMA_FILENAME)
+ with open(schema_file, "r") as file:
+ schema = json.load(file)
+
+ try:
+ jsonschema.validate(data, schema)
+ except jsonschema.exceptions.ValidationError as err:
+ print(f"Schema validation failed: {err}")
+ return False
+ return True
+
+
+def main():
+ print("Running azurelinux sysinfo collection...")
+ asset_id = get_asset_id()
+ os_info = collect_os_info()
+ cloud_init_info = collect_cloud_init_info()
+ boot_info = collect_boot_info()
+ resource_utilization = collect_resource_utilization()
+ package_info = collect_package_info()
+ system_info = collect_system_info()
+
+ # Use json as a data structure to store the data
+ # since it is supported by Kusto
+ data = {
+ "$schema": f"{DATA_SCHEMA_VERSION}",
+ "source": f"{SERVICE_NAME}",
+ "asset_id": asset_id,
+ "os_info": os_info,
+ "cloud_init_info": cloud_init_info,
+ "boot_info": boot_info,
+ "resource_utilization": resource_utilization,
+ "package_info": package_info,
+ "system_info": system_info,
+ }
+
+ print(data)
+
+ if has_valid_schema(data):
+ # Dump the data to a log file, this path is added to fluentd config
+ # and will be picked up by fluentd and sent through Geneva Agents
+ with open(LOG_FILE_PATH, "w") as file:
+ json.dump(data, file, separators=(',', ':'))
+
+ # Add newline so that the fluentd tail plug-in consumes the log
+ # line.
+ file.write("\n")
+ print("Azure Linux sysinfo collection completed successfully.")
+ else:
+ print("Azure Linux sysinfo collection failed.")
+ exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/SPECS/azurelinux-sysinfo/sysinfo-schema-v1.json b/SPECS/azurelinux-sysinfo/sysinfo-schema-v1.json
new file mode 100644
index 0000000000..64377ab4f5
--- /dev/null
+++ b/SPECS/azurelinux-sysinfo/sysinfo-schema-v1.json
@@ -0,0 +1,272 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "SPECS/azurelinux-sysinfo/sysinfo-schema-v1.json",
+ "title": "Azure Linux Sysinfo Schema",
+ "description": "This defines the schema for the collected Azure Linux system information",
+ "type": "object",
+ "properties": {
+ "source": {
+ "$id": "#/properties/source",
+ "type": "string",
+ "title": "Name of the service",
+ "description": "Service the logs are obtained from",
+ "required": [
+ "source"
+ ],
+ "additionalProperties": false
+ },
+ "asset_id": {
+ "$id": "#/properties/asset_id",
+ "type": "string",
+ "title": "Asset ID",
+ "description": "Unique identifier (uuid) of the device",
+ "required": [
+ "asset_id"
+ ],
+ "additionalProperties": false
+ },
+ "os_info": {
+ "$id": "#/properties/os_info",
+ "type": "object",
+ "title": "Operating system information",
+ "description": "Information about the Azure Linux operating system",
+ "properties": {
+ "kernel_version": {
+ "$id": "#/properties/os_info/properties/kernel_version",
+ "type": "string",
+ "title": "Linux kernel version",
+ "description": "The kernel version of Linux"
+ },
+ "release_version": {
+ "$id": "#/properties/os_info/properties/release_version",
+ "type": "string",
+ "title": "Azure Linux operating system release version",
+ "description": "The release version of upstream Azure Linux"
+ },
+ "release_version_id": {
+ "$id": "#/properties/os_info/properties/release_version_id",
+ "type": "string",
+ "title": "Azure Linux operating system release version id",
+ "description": "The release version id of upstream Azure Linux"
+ }
+ },
+ "required": [
+ "kernel_version",
+ "release_version",
+ "release_version_id"
+ ],
+ "additionalProperties": false
+ },
+ "cloud_init_info": {
+ "$id": "#/properties/cloud_init_info",
+ "type": "object",
+ "title": "Cloud-init information",
+ "description": "Information about cloud-init applied to the Azure Linux operating system",
+ "properties": {
+ "hostname": {
+ "$id": "#/properties/cloud_init_info/properties/hostname",
+ "type": "string",
+ "title": "Hostname",
+ "description": "The hostname of the system"
+ },
+ "longest_running_processes": {
+ "$id": "#/properties/cloud_init_info/properties/longest_running_processes",
+ "type": "array",
+ "title": "Longest Running Processes",
+ "description": "List of top running processes that took the most time during cloud-init"
+ }
+ },
+ "required": [
+ "hostname",
+ "longest_running_processes"
+ ],
+ "additionalProperties": false
+ },
+ "boot_info": {
+ "$id": "#/properties/boot_info",
+ "type": "object",
+ "title": "Operating system boot information",
+ "description": "Information about Azure Linux operating system booting",
+ "properties": {
+ "boot_time": {
+ "$id": "#/properties/boot_info/properties/boot_time",
+ "type": "object",
+ "title": "Boot time",
+ "description": "Boot time information",
+ "properties": {
+ "kernel_boot_time_secs": {
+ "$id": "#/properties/boot_info/properties/boot_time/properties/kernel_boot_time_secs",
+ "type": "number",
+ "title": "Kernel boot time in seconds",
+ "description": "Time spent in the kernel before userspace has been reached"
+ },
+ "initrd_boot_time_secs": {
+ "$id": "#/properties/boot_info/properties/boot_time/properties/initrd_boot_time_secs",
+ "type": "number",
+ "title": "Initrd boot time in seconds",
+ "description": "Time spent in the initrd before userspace has been reached"
+ },
+ "userspace_boot_time_secs": {
+ "$id": "#/properties/boot_info/properties/boot_time/properties/userspace_boot_time_secs",
+ "type": "number",
+ "title": "Userspace boot time in seconds",
+ "description": "Time spent in userspace before the system is ready to use"
+ },
+ "firmware_boot_time_secs": {
+ "$id": "#/properties/boot_info/properties/boot_time/properties/firmware_boot_time_secs",
+ "type": "number",
+ "title": "Firmware boot time in seconds",
+ "description": "Time spent in firmware before the system is ready to use"
+ },
+ "loader_boot_time_secs": {
+ "$id": "#/properties/boot_info/properties/boot_time/properties/loader_boot_time_secs",
+ "type": "number",
+ "title": "Loader boot time in seconds",
+ "description": "Time spent in loader before the system is ready to use"
+ },
+ "total_boot_time_secs": {
+ "$id": "#/properties/boot_info/properties/boot_time/properties/total_boot_time_secs",
+ "type": "number",
+ "title": "Total boot time in seconds",
+ "description": "Total time spent in the boot process"
+ }
+ },
+ "required": [
+ "kernel_boot_time_secs",
+ "userspace_boot_time_secs",
+ "total_boot_time_secs"
+ ]
+ },
+ "longest_running_processes": {
+ "$id": "#/properties/boot_info/properties/longest_running_processes",
+ "type": "array",
+ "title": "Longest running processes",
+ "description": "List of top running processes that took the most time during boot"
+ }
+ },
+ "required": [
+ "boot_time",
+ "longest_running_processes"
+ ]
+ },
+ "resource_utilization": {
+ "$id": "#/properties/resource_utilization",
+ "type": "object",
+ "title": "System resources utilization",
+ "description": "Information about resources usage",
+ "properties": {
+ "disk_usage": {
+ "$id": "#/properties/resource_utilization/properties/disk_usage",
+ "type": "object",
+ "title": "Disk usage",
+ "description": "Disk usage information",
+ "properties": {
+ "disk_size_gib": {
+ "$id": "#/properties/resource_utilization/properties/disk_usage/properties/disk_size_gib",
+ "type": "string",
+ "title": "Os disk size",
+ "description": "Os disk size in GiB when the system was booted"
+ },
+ "disk_usage_gib": {
+ "$id": "#/properties/resource_utilization/properties/disk_usage/properties/disk_usage_gib",
+ "type": "string",
+ "title": "Os disk usage",
+ "description": "Os disk usage in GiB when the system was booted"
+ }
+ },
+ "required": [
+ "disk_size_gib",
+ "disk_usage_gib"
+ ]
+ },
+ "memory_usage": {
+ "$id": "#/properties/resource_utilization/properties/memory_usage",
+ "type": "object",
+ "title": "Memory usage",
+ "description": "Memory usage information",
+ "properties": {
+ "total_memory_gib": {
+ "$id": "#properties/resource_utilization/properties/memory_usage/properties/total_memory_gib",
+ "type": "integer",
+ "title": "Total memory",
+ "description": "Total memory in GiB when the system was booted"
+ },
+ "available_memory_gib": {
+ "$id": "#properties/resource_utilization/properties/memory_usage/properties/available_memory_gib",
+ "type": "integer",
+ "title": "Available memory",
+ "description": "Available memory in GiB when the system was booted"
+ }
+ }
+ },
+ "cpu_usage": {
+ "$id": "#/properties/resource_utilization/properties/memory_usage",
+ "type": "object",
+ "title": "cpu usage & info",
+ "description": "Cpu usage information",
+ "properties": {
+ "physical_cpu_count": {
+ "$id": "#properties/resource_utilization/properties/cpu_usage/properties/physical_cpu_count",
+ "type": "integer",
+ "title": "Physical cpu count",
+ "description": "Physical cpu count"
+ },
+ "logical_cpu_count": {
+ "$id": "#properties/resource_utilization/properties/cpu_usage/properties/logical_cpu_count",
+ "type": "integer",
+ "title": "Logical cpu count",
+ "description": "Logical cpu count"
+ },
+ "cpu_usage_percent": {
+ "$id": "#properties/resource_utilization/properties/cpu_usage/properties/cpu_usage_percent",
+ "type": "number",
+ "title": "Cpu usage percent",
+ "description": "Cpu usage percent"
+ }
+ }
+ }
+ },
+ "required": [
+ "disk_usage",
+ "memory_usage",
+ "cpu_usage"
+ ]
+ },
+ "package_info": {
+ "$id": "#/properties/package_info",
+ "type": "object",
+ "title": "Package Information",
+ "description": "Information about the packages installed on Azure Linux",
+ "properties": {
+ "package_count": {
+ "$id": "#/properties/package_info/properties/package_count",
+ "type": "integer",
+ "title": "Package Count",
+ "description": "The number of packages installed on Azure Linux"
+ }
+ },
+ "required": [
+ "package_count"
+ ],
+ "additionalProperties": false
+ },
+ "system_info": {
+ "$id": "#/properties/system_info",
+ "type": "object",
+ "title": "System Information",
+ "description": "Information about the system-wide settings",
+ "properties": {
+ "selinux_mode": {
+ "$id": "#/properties/package_info/properties/package_count",
+ "type": "string",
+ "title": "SELinux Mode",
+ "description": "Enforced or Permissive"
+ }
+ },
+ "required": [
+ "selinux_mode"
+ ],
+ "additionalProperties": false
+ }
+ }
+}
diff --git a/SPECS/azurelinux-sysinfo/sysinfo-selinuxpolicies.cil b/SPECS/azurelinux-sysinfo/sysinfo-selinuxpolicies.cil
new file mode 100644
index 0000000000..d66205f386
--- /dev/null
+++ b/SPECS/azurelinux-sysinfo/sysinfo-selinuxpolicies.cil
@@ -0,0 +1,14 @@
+(allow systemd_analyze_t sysctl_kernel_t (dir (search)))
+(allow systemd_analyze_t locale_t (dir (search)))
+(allow systemd_analyze_t init_runtime_t (dir (search)))
+(allow systemd_analyze_t sysctl_kernel_t (file (read)))
+(allow systemd_analyze_t locale_t (file (read)))
+(allow systemd_analyze_t systemd_analyze_t (capability (net_admin)))
+(allow systemd_analyze_t init_t (unix_stream_socket (connectto)))
+(allow systemd_analyze_t system_dbusd_runtime_t (dir (search)))
+(allow systemd_analyze_t security_t (filesystem (getattr)))
+(allow systemd_analyze_t selinux_config_t (dir (search)))
+(allow systemd_analyze_t init_t (system (status)))
+(allow systemd_analyze_t init_t (service (status)))
+(allow systemd_analyze_t systemdunit (service (status)))
+(allow systemd_analyze_t etc_t (service (status)))