Skip to content
2 changes: 1 addition & 1 deletion coreos-assembler
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ if [ -e /sys/fs/selinux/status ]; then
fi

cmd=${1:-}
build_commands="init fetch build run clean"
build_commands="init fetch analyze build run clean"
other_commands="shell"
utility_commands="gf-oemid virt-install"
if [ -z "${cmd}" ]; then
Expand Down
3 changes: 3 additions & 0 deletions deps.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ rpm-ostree
# rpmdistro-gitoverlay deps
dnf-plugins-core createrepo_c dnf-utils fedpkg openssh-clients rpmdistro-gitoverlay

# coreos-assembler analyze
python3-dnf

# Currently a transitive req of rpmdistro-gitoverlay via mock, but we
# expect people to use these explicitly in their repo configurations.
distribution-gpg-keys
Expand Down
41 changes: 41 additions & 0 deletions src/cmd-analyze
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail

dn=$(dirname $0)
. ${dn}/cmdlib.sh

print_help() {
cat 1>&2 <<'EOF'
Usage: coreos-assembler analyze --help

EOF
}

# Call getopt to validate the provided input.
options=$(getopt --options hf --longoptions help,force -- "$@")
[ $? -eq 0 ] || {
print_help
exit 1
}
eval set -- "$options"
while true; do
case "$1" in
-h | --help)
print_help
exit 0
;;
*)
print_help
fatal "init: unrecognized option: $1"
exit 1
;;
esac
shift
done

# LOOP: if rpm-ostree-entrypoint.yaml exist; do
# append PKG[] with "*.yaml"."packages*"
# if "*.yaml"."include" exist; LOOP
# else; improper coreos-assembler init

# analyze.py $PKG $REPOS $FLAGS
135 changes: 135 additions & 0 deletions src/dnf-analyze
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/python3

# GLOBAL
import dnf,argparse,os,math
interpretedLabel = ["python", "perl"]
interpretedPkg = []
query = dnf.query.Query

# CLI ENTRYPOINT
parser = argparse.ArgumentParser()
parser.add_argument('--packages',help='Comma Separated list of packages to analyze.',action='store', required=True)
parser.add_argument('--repo-dir',help='Directory which stores all .repo definitions.',action='store', required=True)
parser.add_argument('--log-dir',help='If set, log to file and stdout.',action='store')
parser.add_argument('--size',help="Display total dependency tree size.",action='store_true')
parser.add_argument('--exit-on',help="Emphasize interpreted dependencies through 'exit 1'",action='store_true')
args = parser.parse_args()

# CLI VERIFY
def directoryExists(path):
if os.path.isdir(path):
if os.access(path, os.W_OK):
return True
return False

if not directoryExists(args.repo_dir):
print('Bad Repo Directory.')
exit(1)
if not directoryExists(args.log_dir):
print('Bad Log Directory: Defaulting to /var/log')
args.log_dir = '/var/log'
if (args.size != True) or (args.size != False):
print('Size must be set to True or False.')
exit(1)
if (args.exit_on != True) or (args.exit_on != False):
print('Exit On must be set to True or False.')
exit(1)

# CLASS
class Package(object):
def printTree(self):
# Make an indentation format for easier read.
ind = '-' * self.generation
# Print dnf.package.Package.name.
print(ind + self.pkg_meta.name)
# Print size in MB if args.size.
if args.size:
print(ind + 'size: ' + (self.pkg_meta.downloadsize / 2^20))
# Print number of depends.
print(ind + 'depends: ' + len(self.children))
# Print if it is interpreted.
print(ind + 'isInterpreted: ' + str(self.interpreted))
for x in self.children:
x.printTree()

def findChildren(self):
# First, prevent recursive scans i.e. glib requires glib translations which require glib.
# However, this cannot be done in a way where each node is aware of the rest of the tree; this would result in an incomplete dependency tree.
toScan = []
for x in self.pkg_meta.requires:
# Convert hawkey.ReqDep to string.
i = str(x)
if i not in self.scanned:
self.scanned.append(i)
toScan.append(i)
# Scan the packages which do not already exist in this tree path but pass in the whole tree path to the next child.
for x in toScan:
j = query.filter(name=x,latest=True)
self.children.append(Package(query,self.generation,j[0],self.scanned))

def interpreted(self):
# For each package listed as examples for interpreted languages...
for x in interpretedLabel:
# If one is found in the Package.pkg_meta.name, then label this Package object as an interpreted object.
if x in self.pkg_meta.name:
self.interpreted = True
# If this package already hasn't been scanned, labeled interpreted, and placed in interpretedPkg, place in interpretedPkg.
if self.pkg_meta.name not in interpretedPkg:
interpretedPkg.append(self.pkg_meta.name)

def __init__(self,generation,pkg,scanned):
# ATTR
self.generation = generation + 1
self.pkg_meta = pkg
self.children = []
self.scanned = scanned
self.interpreted = False

# METHOD
interpreted()
findChildren()

# Split comma delimited packages into an array.
base_packages = args.packages.split(",")

# Init DNF object.
base = dnf.Base()

# DNF config.
base_config = base.conf
base_config.assumeyes = "true"
base_config.best = "true"
base_config.cachedir = "/var/cache/dnf"
base_config.logdir = args.log
base_config.reposdir = args.repo_dir

# Parse repos in dir and fill sack without system rpm scanning.
base.read_all_repos()
base.fill_sack(load_system_repo=False,load_available_repos=True)

# Init query object on new sack. From here on out, no changes are made to query.
query = base.sack.query()

# Replace each element in the base_packages array with a Packages object.
for x in range(0,len(base_packages)):
i = base_packages[x]
# Assuming best first, get latest package.
j = query.filter(name=i,latest=True)
# Use first item in j JIC.
base_packages[x] = Package(query,0,j[0],"")

# Major print functions.
for x in base_backages:
x.printTree()

if len(interpreted) > 0:
for x in interpreted:
print('Interpreted Package: ' + x)
print('Total Interpreted Packages: ' + len(interpreted))
if args.exit_on:
exit(1)

# TODO: print which base packages introduce interpreted dependencies.
# TODO: print which packages require interpreted dependencies.

exit(0)