Skip to content

Commit

Permalink
Add functionality to report repo style and distro
Browse files Browse the repository at this point in the history
This is a fairly large commit that adds functionality for Tern to report
what repo style and distro the container image is based off of. The
following changes were made to the files listed below:

1) tern/command_lib/base.yml
   Add an 'os_guess' section for each package manager entry. os_guess is
   a list of possible OSes for the given pkg manager.

2) tern/classes/image_layer.py
   Add an 'os_guess' attribute to the ImageLayer class. This also
   includes adding a @Property python decorator for it and a setter
   method to access the os_guess value.

3) tests/test_class_image_layer.py
   Add a check to verify the functionality of the getter and setter
   methods for os_guess in the ImageLayer class.

4) tern/command_lib/command_lib.py
   Add a check_os_guess function that will return the list of os_guess
   values associated with a given binary (provided that the binary
   exists in base.yml). If the binary provided does not exist in
   base.yml, return a blank string.

5) tern/helpers/common.py
   - Adds a function called get_os_release that checks the
   /etc/os-release file of the mounted image layer for OS information.
   If no information can be found it returns a blank string.
   - Updates the get_os_style function to call get_os_release and check
   for OS information in the os-release file. If OS info is returned,
   pass this info on to the user with a high degree of certainty. If no
   OS info is returned from get_os_release, make an educated guess as
   to the OS of the image layer based on the package format and package
   manager in base.yml.

6) tern/report/formats.py
   Add two os_style-related reports to format the OS suggestion in the
   final report based on where the OS information was gathered from.

7) tern/utils/constants
   Add the two paths where the os-release file could be.

Resolves tern-tools#161
See also tern-tools#338

Signed-off-by: Rose Judge <[email protected]>
  • Loading branch information
rnjudge committed Aug 28, 2019
1 parent 5a4fb57 commit 5fd784d
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 11 deletions.
9 changes: 9 additions & 0 deletions tern/classes/image_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def __init__(self, diff_id, tar_file=None, created_by=None):
self.__import_image = None
self.__import_str = ''
self.__pkg_format = ''
self.__os_guess = ''

@property
def diff_id(self):
Expand Down Expand Up @@ -98,6 +99,14 @@ def pkg_format(self):
def pkg_format(self, pkg_format):
self.__pkg_format = pkg_format

@property
def os_guess(self):
return self.__os_guess

@os_guess.setter
def os_guess(self, os_guess):
self.__os_guess = os_guess

def add_package(self, package):
if isinstance(package, Package):
if package.name not in self.get_package_names():
Expand Down
14 changes: 14 additions & 0 deletions tern/command_lib/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
# tdnf ---------------------------------------------------------------------------------------------------------------------------------
tdnf:
pkg_format: 'rpm'
os_guess:
- 'Photon'
path:
- 'usr/bin/tdnf' # don't put forward slash here as os.path.join will think it is the root directory on the host
shell: '/usr/bin/bash'
Expand Down Expand Up @@ -57,6 +59,9 @@ tdnf:
# dpkg -------------------------------------------------------------------------------------------------------------------------------
dpkg:
pkg_format: 'deb'
os_guess:
- 'Debian'
- 'Ubuntu'
path:
- 'usr/bin/dpkg'
shell: '/bin/bash'
Expand Down Expand Up @@ -88,6 +93,8 @@ dpkg:
# apk ---------------------------------------------------------------------------------------------------------
apk:
pkg_format: 'apk'
os_guess:
- 'Alpine Linux'
path:
- 'sbin/apk'
shell: '/bin/sh'
Expand Down Expand Up @@ -124,6 +131,8 @@ apk:
# pacman ----------------------------------------------------------------------
pacman:
pkg_format: 'pkg.tar.xz'
os_guess:
- 'Arch Linux'
path:
- 'usr/bin/pacman'
shell: '/usr/bin/sh'
Expand Down Expand Up @@ -155,6 +164,11 @@ pacman:
# rpm ----------------------------------------------------------------------
rpm:
pkg_format: 'rpm'
os_guess:
- 'CentOS'
- 'Fedora'
- 'openSUSE'
- 'RHEL'
path:
- 'usr/bin/rpm'
shell: '/usr/bin/sh'
Expand Down
16 changes: 16 additions & 0 deletions tern/command_lib/command_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,23 @@ def check_sourcable(command, package_name):


def check_pkg_format(binary):
'''Given a binary package manager, return the associated pkg_format from
base.yml. If the binary is not valid in base.yml, return an empty
string.'''
try:
return command_lib['base'][binary]['pkg_format']
except KeyError:
return ''


def check_os_guess(binary):
'''Given a binary package manager, return the associated os_guess from
base.yml. If the binary is not valid in base.yml, return an empty
string.'''
os_list = []
try:
for o in command_lib['base'][binary]['os_guess']:
os_list.append(o)
return ', '.join(os_list)
except KeyError:
return ''
64 changes: 55 additions & 9 deletions tern/helpers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,34 @@ def get_base_bin():
return binary


def get_os_release():
'''Given the base layer object, determine if an os-release file exists and
return the PRETTY_NAME string from it. If no release file exists,
return an empty string. Assume that the layer filesystem is mounted'''
# os-release may exist under /etc/ or /usr/lib. We should first check
# for the preferred /etc/os-release and fall back on /usr/lib/os-release
# if it does not exist under /etc
etc_path = os.path.join(os.getcwd(), constants.temp_folder,
constants.mergedir, constants.etc_release_path)
lib_path = os.path.join(os.getcwd(), constants.temp_folder,
constants.mergedir, constants.lib_release_path)
if not os.path.exists(etc_path):
if not os.path.exists(lib_path):
return ''
etc_path = lib_path
# file exists at this point, try to read it
with open(etc_path, 'r') as f:
lines = f.readlines()
pretty_name = ''
# iterate through os-release file to find OS
for l in lines:
key, val = l.rstrip().split('=', 1)
if key == "PRETTY_NAME":
pretty_name = val
break
return pretty_name.strip('"')


def collate_list_metadata(shell, listing):
'''Given the shell and the listing for the package manager, collect
metadata that gets returned as a list'''
Expand Down Expand Up @@ -383,15 +411,33 @@ def update_master_list(master_list, layer_obj):


def get_os_style(image_layer, binary):
'''Given an ImageLayer object and a binary package manager, determine if
the package manager has a pkg_format associated with it. If the binary
does not exist in base.yml, add a notice to the layer's origins'''
'''Given an ImageLayer object and a binary package manager, check for the
OS identifier in the os-release file first. If the os-release file
is not available, make an educated guess as to what kind of OS the layer
might be based off of given the pkg_format + package manager. If the binary
provided does not exist in base.yml, add a warning notice'''
origin_command_lib = formats.invoking_base_commands
origin_layer = 'Layer: ' + image_layer.fs_hash[:10]
pkg_format = command_lib.check_pkg_format(binary)
if not pkg_format:
# empty string means binary is not found in base.yml
image_layer.origins.add_notice_to_origins(
origin_command_lib, Notice(errors.no_listing_for_base_key.format(
listing_key=binary), 'warning'))
os_guess = command_lib.check_os_guess(binary)
if get_os_release():
# We know with high degree of certainty what the OS is
image_layer.origins.add_notice_to_origins(origin_layer, Notice(
formats.os_release.format(os_style=get_os_release()), 'info'))
else:
image_layer.pkg_format = pkg_format
# We make a guess about the OS based on pkg_format + binary
# First check that binary exists in base.yml
if not pkg_format or not os_guess:
image_layer.origins.add_notice_to_origins(
origin_command_lib, Notice(
errors.no_listing_for_base_key.format(listing_key=binary),
'warning'))
else:
# Assign image layer attributes
image_layer.pkg_format = pkg_format
image_layer.os_guess = os_guess
image_layer.origins.add_notice_to_origins(origin_layer, Notice(
formats.os_style_guess.format(
package_manager=binary,
package_format=image_layer.pkg_format,
os_list=image_layer.os_guess), 'info'))
4 changes: 4 additions & 0 deletions tern/report/formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
'''command_lib/snippets.yml'''
ignored = '''\nIgnored Commands:'''
unrecognized = '''\nUnrecognized Commands:'''
os_style_guess = '''Found {package_manager} package manager with '''\
'''{package_format} package format. Possible OS(es) for this layer '''\
'''might be: {os_list}'''
os_release = '''Found '{os_style}' in /etc/os-release.'''

# report formatting for dockerfiles

Expand Down
3 changes: 1 addition & 2 deletions tern/report/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,7 @@ def mount_overlay_fs(image_obj, top_layer):
return target


def analyze_docker_image(image_obj, redo=False,
dockerfile=False): # pylint: disable=too-many-locals
def analyze_docker_image(image_obj, redo=False, dockerfile=False): # pylint: disable=too-many-locals
'''Given a DockerImage object, for each layer, retrieve the packages, first
looking up in cache and if not there then looking up in the command
library. For looking up in command library first mount the filesystem
Expand Down
3 changes: 3 additions & 0 deletions tern/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
shell = '/bin/sh'
# path where resolv.conf lives
resolv_path = '/etc/resolv.conf'
# paths where os-release could be
etc_release_path = 'etc/os-release'
lib_release_path = 'usr/lib/os-release'
# directory where layer.tar can be extracted to
untar_dir = 'contents'
# rootfs working directory
Expand Down
2 changes: 2 additions & 0 deletions tests/test_class_image_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def testInstance(self):
self.assertEqual(self.layer.created_by, 'some string')
self.layer.pkg_format = 'rpm'
self.assertEqual(self.layer.pkg_format, 'rpm')
self.layer.os_guess = 'operating system'
self.assertEqual(self.layer.os_guess, 'operating system')

def testAddPackage(self):
err = "Object type String, should be Package"
Expand Down

0 comments on commit 5fd784d

Please sign in to comment.