Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ options:

# Distribution flavor of image.
pkg_manager: 'dnf'
# DNF options to use
dnf_options:
- 'gpgcheck=0'
- 'keepcache=1'

# Starting filesystem of image. 'scratch' means to start with a blank
# filesystem. Currently, only OCI images can be used as parents. In
Expand Down
1 change: 1 addition & 0 deletions src/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def process_args(terminal_args, config_options):
processed_args['scap_benchmark'] = terminal_args.scap_benchmark or config_options.get('scap_benchmark', False)
processed_args['oval_eval'] = terminal_args.oval_eval or config_options.get('oval_eval', False)
processed_args['install_scap'] = terminal_args.install_scap or config_options.get('install_scap', False)
processed_args['dnf_options'] = terminal_args.dnf_options or config_options.get('dnf_options', [])

# If no publish options were passed in either the CLI or the config file, store locally.
if not (processed_args['publish_s3']
Expand Down
1 change: 1 addition & 0 deletions src/image-build
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def main():
parser.add_argument('--scap-benchmark', dest="scap_benchmark", action='store_true', required=False)
parser.add_argument('--oval-eval', dest="oval_eval", action='store_true', required=False)
parser.add_argument('--install-scap', dest="install_scap", action='store_true', required=False)
parser.add_argument('--dnf-options', dest="dnf_options", action='store', nargs='+', type=str, default=[], required=False, help='List of dnf options')


try:
Expand Down
114 changes: 88 additions & 26 deletions src/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import pathmod
import tempfile
import sys
# Written Modules
from utils import cmd

Expand All @@ -22,7 +23,7 @@ def __init__(self, pkg_man, cname, mname, gpgcheck=True):
# DNF complains if the log directory is not present
os.makedirs(os.path.join(self.tdir, "dnf/log"))

def install_scratch_repos(self, repos, repo_dest, proxy):
def install_scratch_repos(self, repos, repo_dest, proxy, dnf_options):
# check if there are repos passed for install
if len(repos) == 0:
logging.info("REPOS: no repos passed to install\n")
Expand All @@ -45,17 +46,24 @@ def install_scratch_repos(self, repos, repo_dest, proxy):
args.append(r['url'])
args.append(r['alias'])
elif self.pkg_man == "dnf":
args.append("--setopt=reposdir="+os.path.join(self.mname, pathmod.sep_strip(repo_dest)))
args.append("--setopt=logdir="+os.path.join(self.tdir, self.pkg_man, "log"))
args.append("--setopt=cachedir="+os.path.join(self.tdir, self.pkg_man, "cache"))
defaults = {
"reposdir": os.path.join(self.mname, pathmod.sep_strip(repo_dest)),
"logdir": os.path.join(self.tdir, self.pkg_man, "log"),
"cachedir": os.path.join(self.tdir, self.pkg_man, "cache")
}
if proxy != "":
args.append("--setopt=proxy="+proxy)
defaults["proxy"] = proxy
config_path = self.generate_config(defaults, dnf_options)

args.append(f"-c={config_path}")
args.append("config-manager")
args.append("--save")
args.append("--add-repo")
args.append(r['url'])

rc = cmd([self.pkg_man] + args)
# Remove Temp config
os.remove(config_path)
if rc != 0:
raise Exception("Failed to install repo", r['alias'], r['url'])

Expand All @@ -66,16 +74,25 @@ def install_scratch_repos(self, repos, repo_dest, proxy):
repo_name = r['url'].split('https://')[1].replace('/','_')
elif r['url'].startswith('http'):
repo_name = r['url'].split('http://')[1].replace('/','_')

defaults = {
"reposdir": os.path.join(self.mname, pathmod.sep_strip(repo_dest)),
"logdir": os.path.join(self.tdir, self.pkg_man, "log"),
"cachedir": os.path.join(self.tdir, self.pkg_man, "cache")
}
if proxy != "":
defaults["proxy"] = proxy
config_path = self.generate_config(defaults, dnf_options)

args = []
args.append('config-manager')
args.append('--save')
args.append("--setopt=reposdir="+os.path.join(self.mname, pathmod.sep_strip(repo_dest)))
args.append("--setopt=logdir="+os.path.join(self.tdir, self.pkg_man, "log"))
args.append("--setopt=cachedir="+os.path.join(self.tdir, self.pkg_man, "cache"))
args.append('--setopt=*.proxy='+proxy)
args.append(f"-c={config_path}")
args.append(repo_name)

rc = cmd([self.pkg_man] + args)
# Remove Temp config
os.remove(config_path)
if rc != 0:
raise Exception("Failed to set proxy for repo", r['alias'], r['url'], proxy)

Expand All @@ -93,7 +110,7 @@ def install_scratch_repos(self, repos, repo_dest, proxy):
if rc != 0:
raise Exception("Failed to install gpg key for", r['alias'], "at URL", r['gpg'])

def install_scratch_packages(self, packages, registry_loc, proxy):
def install_scratch_packages(self, packages, registry_loc, proxy, dnf_options):
# check if there are packages to install
if len(packages) == 0:
logging.warn("PACKAGES: no packages passed to install\n")
Expand All @@ -116,11 +133,17 @@ def install_scratch_packages(self, packages, registry_loc, proxy):
args.append("-l")
args.extend(packages)
elif self.pkg_man == "dnf":
args.append("--setopt=reposdir="+os.path.join(self.mname, pathmod.sep_strip(registry_loc)))
args.append("--setopt=logdir="+os.path.join(self.tdir, self.pkg_man, "log"))
args.append("--setopt=cachedir="+os.path.join(self.tdir, self.pkg_man, "cache"))

defaults = {
"reposdir": os.path.join(self.mname, pathmod.sep_strip(registry_loc)),
"logdir": os.path.join(self.tdir, self.pkg_man, "log"),
"cachedir": os.path.join(self.tdir, self.pkg_man, "cache")
}
if proxy != "":
args.append("--setopt=proxy="+proxy)
defaults["proxy"] = proxy
config_path = self.generate_config(defaults, dnf_options)

args.append(f"-c={config_path}")
args.append("install")
args.append("-y")
args.append("--nogpgcheck")
Expand All @@ -129,13 +152,15 @@ def install_scratch_packages(self, packages, registry_loc, proxy):
args.extend(packages)

rc = cmd([self.pkg_man] + args)
# Remove Temp config
os.remove(config_path)
if rc == 104:
raise Exception("Installing base packages failed")

if rc == 107:
logging.warn("one or more RPM postscripts failed to run")

def install_scratch_package_groups(self, package_groups, registry_loc, proxy):
def install_scratch_package_groups(self, package_groups, registry_loc, proxy, dnf_options):
# check if there are packages groups to install
if len(package_groups) == 0:
logging.warn("PACKAGE GROUPS: no package groups passed to install\n")
Expand All @@ -148,11 +173,16 @@ def install_scratch_package_groups(self, package_groups, registry_loc, proxy):
if self.pkg_man == "zypper":
logging.warn("zypper does not support package groups")
elif self.pkg_man == "dnf":
args.append("--setopt=reposdir="+os.path.join(self.mname, pathmod.sep_strip(registry_loc)))
args.append("--setopt=logdir="+os.path.join(self.tdir, self.pkg_man, "log"))
args.append("--setopt=cachedir="+os.path.join(self.tdir, self.pkg_man, "cache"))
defaults = {
"reposdir": os.path.join(self.mname, pathmod.sep_strip(registry_loc)),
"logdir": os.path.join(self.tdir, self.pkg_man, "log"),
"cachedir": os.path.join(self.tdir, self.pkg_man, "cache")
}
if proxy != "":
args.append("--setopt=proxy="+proxy)
defaults["proxy"] = proxy
config_path = self.generate_config(defaults, dnf_options)

args.append(f"-c={config_path}")
args.append("groupinstall")
args.append("-y")
args.append("--nogpgcheck")
Expand All @@ -161,10 +191,12 @@ def install_scratch_package_groups(self, package_groups, registry_loc, proxy):
args.extend(package_groups)

rc = cmd([self.pkg_man] + args)
# Remove Temp config
os.remove(config_path)
if rc == 104:
raise Exception("Installing base packages failed")

def install_scratch_modules(self, modules, registry_loc, proxy):
def install_scratch_modules(self, modules, registry_loc, proxy, dnf_options):
# check if there are modules groups to install
if len(modules) == 0:
logging.warn("PACKAGE MODULES: no modules passed to install\n")
Expand All @@ -178,11 +210,16 @@ def install_scratch_modules(self, modules, registry_loc, proxy):
logging.warn("zypper does not support package groups")
return
elif self.pkg_man == "dnf":
args.append("--setopt=reposdir="+os.path.join(self.mname, pathmod.sep_strip(registry_loc)))
args.append("--setopt=logdir="+os.path.join(self.tdir, self.pkg_man, "log"))
args.append("--setopt=cachedir="+os.path.join(self.tdir, self.pkg_man, "cache"))
defaults = {
"reposdir": os.path.join(self.mname, pathmod.sep_strip(registry_loc)),
"logdir": os.path.join(self.tdir, self.pkg_man, "log"),
"cachedir": os.path.join(self.tdir, self.pkg_man, "cache")
}
if proxy != "":
args.append("--setopt=proxy="+proxy)
defaults["proxy"] = proxy
config_path = self.generate_config(defaults, dnf_options)

args.append(f"-c={config_path}")
args.append("module")
args.append(mod_cmd)
args.append("-y")
Expand All @@ -191,10 +228,12 @@ def install_scratch_modules(self, modules, registry_loc, proxy):
args.append(self.mname)
args.extend(mod_list)
rc = cmd([self.pkg_man] + args)
# Remove Temp config
os.remove(config_path)
if rc != 0:
raise Exception("Failed to run module cmd", mod_cmd, ' '.join(mod_list))

def install_repos(self, repos, proxy):
def install_repos(self, repos, proxy, dnf_options):
# check if there are repos passed for install
if len(repos) == 0:
logging.info("REPOS: no repos passed to install\n")
Expand Down Expand Up @@ -321,3 +360,26 @@ def install_copyfiles(self, copyfiles):
logging.info(f['src'] + ' -> ' + f['dest'])
args += [ self.cname, f['src'], f['dest'] ]
cmd(["buildah","copy"] + args)

def generate_config(self, defaults, dnf_opts):
_, config_path = tempfile.mkstemp(prefix='temp-dnf-conf-')
dnf_opt_dict = {}

for option in dnf_opts:
key, value = option.split('=')
dnf_opt_dict[key] = value

try:
file = open(config_path, 'w')
file.write('[main]\n')
for key in dnf_opt_dict:
file.write(f'{key}={dnf_opt_dict[key]}\n')

for key in defaults:
if key not in dnf_opt_dict.keys():
file.write(f'{key}={defaults[key]}\n')
file.close()
except Exception as e:
logging.error(f"Could not create a temporary dnf config file: {e}")

return config_path
50 changes: 25 additions & 25 deletions src/layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ def _build_base(self, repos, modules, packages, package_groups, remove_packages,
else:
proxy = ""

dnf_options = self.args['dnf_options']

# container and mount name
def buildah_handler(line):
out.append(line)
Expand All @@ -54,7 +56,12 @@ def buildah_handler(line):
if package_manager == "zypper":
repo_dest = "/etc/zypp/repos.d"
elif package_manager == "dnf":
# When "minimal install" group is installed, it stores .repo files in the default /etc/yum.repos.d location
# this sometimes creates an issue where dnf will start using the latest versions of packages instead of the
# ones defined in the config file. Adding a different repo location to dnf.conf fixes that issue since
# /etc/yum.repos.d is hard coded by packages in "minimal install" group.
repo_dest = os.path.expanduser("~/.pkg_repos/yum.repos.d")

# Create repo dest, if needed
os.makedirs(os.path.join(mname, pathmod.sep_strip(repo_dest)), exist_ok=True)

Expand All @@ -64,27 +71,20 @@ def buildah_handler(line):
os.mknod(os.path.join(mname, "etc/dnf/dnf.conf"), mode=0o644)

# Add repo directory path to dnf.conf, if needed
# Collect the contents of the file
dnf_conf = open(os.path.join(mname, "etc/dnf/dnf.conf"), "r")
dnf_conf_contents = dnf_conf.readlines()
dnf_conf.close()

## If repodir line does not exists, add it
if not str("reposdir=" + repo_dest + "\n") in dnf_conf_contents:
## If there is "[main]" section, add just the repodir
dnf_conf = open(os.path.join(mname, "etc/dnf/dnf.conf"), "a")
line_not_found = True
for line in dnf_conf_contents:
if "[main]\n" == line:
line = line + "reposdir=" + repo_dest + "\n"
line_not_found = False
dnf_conf.write(line)
break
## Otherwise, add "[main]" and reposdir line
if line_not_found:
dnf_conf.write("[main]\n" + "reposdir=" + repo_dest + "\n")

try:
# Collect the contents of the file
dnf_conf = open(os.path.join(mname, "etc/dnf/dnf.conf"), "a+")
dnf_conf_contents = dnf_conf.readlines()
if not str("reposdir=" + repo_dest + "\n") in dnf_conf_contents:
## If there is "[main]" section, add just the repodir
if "[main]\n" in dnf_conf_contents:
dnf_conf.write("reposdir=" + repo_dest + "\n")
## Otherwise, add "[main]" and reposdir line
else:
dnf_conf.write("[main]\n" + "reposdir=" + repo_dest + "\n")
dnf_conf.close()
except Exception as e:
logging.error("Could not update the contents of dnf.conf: {e}")

else:
self.logger.error("unsupported package manager")
Expand All @@ -104,9 +104,9 @@ def buildah_handler(line):
# Install Repos
try:
if parent == "scratch":
inst.install_scratch_repos(repos, repo_dest, proxy)
inst.install_scratch_repos(repos, repo_dest, proxy, dnf_options)
else:
inst.install_repos(repos, proxy)
inst.install_repos(repos, proxy, dnf_options)
except Exception as e:
self.logger.error(f"Error installing repos: {e}")
cmd(["buildah","rm"] + [cname])
Expand All @@ -120,11 +120,11 @@ def buildah_handler(line):
try:
if parent == "scratch":
# Enable modules
inst.install_scratch_modules(modules, repo_dest, self.args['proxy'])
inst.install_scratch_modules(modules, repo_dest, proxy, dnf_options)
# Base Package Groups
inst.install_scratch_package_groups(package_groups, repo_dest, proxy)
inst.install_scratch_package_groups(package_groups, repo_dest, proxy, dnf_options)
# Packages
inst.install_scratch_packages(packages, repo_dest, proxy)
inst.install_scratch_packages(packages, repo_dest, proxy, dnf_options)
else:
inst.install_package_groups(package_groups)
inst.install_packages(packages)
Expand Down