Bump main to 0.6.0

This commit is contained in:
Louis Abel 2023-03-29 01:13:43 -07:00
commit 166ee1d38a
Signed by: label
GPG Key ID: B37E62D143879B36
36 changed files with 1171 additions and 467 deletions

View File

@ -1 +1 @@
__version__ = '0.5.0'
__version__ = '0.6.0'

View File

@ -109,6 +109,7 @@
XFCE: rocky-live-xfce.ks
KDE: rocky-live-kde.ks
MATE: rocky-live-mate.ks
Cinnamon: rocky-live-cinnamon.ks
allowed_arches:
- x86_64
- aarch64

View File

@ -9,6 +9,7 @@ from empanadas.util.check import (
from empanadas.util.shared import (
Shared,
ArchCheck,
Idents,
)
from empanadas.util.dnf_utils import (

View File

@ -289,7 +289,9 @@ class RepoSync:
Shared.deploy_extra_files(self.extra_files, sync_root, global_work_root, self.log)
self.deploy_treeinfo(self.repo, sync_root, self.arch)
self.tweak_treeinfo(self.repo, sync_root, self.arch)
self.symlink_to_latest(generated_dir)
#self.symlink_to_latest(generated_dir)
Shared.symlink_to_latest(self.shortname, self.major_version,
generated_dir, self.compose_latest_dir, self.log)
if self.repoclosure:
self.repoclosure_work(sync_root, work_root, log_root)
@ -323,7 +325,7 @@ class RepoSync:
if self.parallel:
self.podman_sync(repo, sync_root, work_root, log_root, global_work_root, arch)
else:
Shared.dnf_sync(repo, sync_root, work_root, arch, self.log)
Shared.norm_dnf_sync(self, repo, sync_root, work_root, arch, self.log)
def podman_sync(
self,
@ -709,22 +711,6 @@ class RepoSync:
'No issues detected.'
)
def symlink_to_latest(self, generated_dir):
"""
Emulates pungi and symlinks latest-Rocky-X
This link will be what is updated in full runs. Whatever is in this
'latest' directory is what is rsynced on to staging after completion.
This link should not change often.
"""
try:
os.remove(self.compose_latest_dir)
except:
pass
self.log.info('Symlinking to latest-{}-{}...'.format(self.shortname, self.major_version))
os.symlink(generated_dir, self.compose_latest_dir)
def repoclosure_work(self, sync_root, work_root, log_root):
"""
This is where we run repoclosures, based on the configuration of each

View File

@ -35,7 +35,7 @@ import productmd.treeinfo
from jinja2 import Environment, FileSystemLoader
from empanadas.common import Color, _rootdir
from empanadas.util import Shared, ArchCheck
from empanadas.util import Shared, ArchCheck, Idents
class IsoBuild:
"""
@ -138,10 +138,7 @@ class IsoBuild:
self.compose_latest_dir = os.path.join(
config['compose_root'],
major,
"latest-{}-{}".format(
self.shortname,
self.profile
)
f"latest-{self.shortname}-{self.profile}"
)
self.compose_latest_sync = os.path.join(
@ -371,11 +368,7 @@ class IsoBuild:
source_path = latest_artifacts[arch]
full_drop = '{}/lorax-{}-{}.tar.gz'.format(
lorax_arch_dir,
self.release,
arch
)
full_drop = f'{lorax_arch_dir}/lorax-{self.release}-{arch}.tar.gz'
if not os.path.exists(lorax_arch_dir):
os.makedirs(lorax_arch_dir, exist_ok=True)
@ -403,10 +396,7 @@ class IsoBuild:
self.log.info(Color.INFO + 'Beginning unpack phase...')
for arch in arches_to_unpack:
tarname = 'lorax-{}-{}.tar.gz'.format(
self.release,
arch
)
tarname = f'lorax-{self.release}-{arch}.tar.gz'
tarball = os.path.join(
self.lorax_work_dir,
@ -523,22 +513,13 @@ class IsoBuild:
if self.release_candidate:
rclevel = '-' + self.rclvl
discname = '{}-{}.{}{}-{}-{}.iso'.format(
self.shortname,
self.major_version,
self.minor_version,
rclevel,
arch,
'boot'
)
discname = f'{self.shortname}-{self.major_version}.{self.minor_version}{rclevel}-{arch}-boot.iso'
isobootpath = os.path.join(iso_to_go, discname)
manifest = '{}.manifest'.format(isobootpath)
link_name = '{}-{}-boot.iso'.format(self.shortname, arch)
manifest = f'{isobootpath}.manifest'
link_name = f'{self.shortname}-{arch}-boot.iso'
link_manifest = link_name + '.manifest'
latest_link_name = '{}-{}-latest-{}-boot.iso'.format(self.shortname,
self.major_version,
arch)
latest_link_name = f'{self.shortname}-{self.major_version}-latest-{arch}-boot.iso'
latest_link_manifest = latest_link_name + '.manifest'
isobootpath = os.path.join(iso_to_go, discname)
linkbootpath = os.path.join(iso_to_go, link_name)
@ -813,11 +794,11 @@ class IsoBuild:
xorriso_template = self.tmplenv.get_template('xorriso.tmpl.txt')
iso_readme_template = self.tmplenv.get_template('ISOREADME.tmpl')
mock_iso_path = '/var/tmp/lorax-{}.cfg'.format(self.major_version)
mock_sh_path = '{}/extraisobuild-{}-{}.sh'.format(entries_dir, arch, image)
iso_template_path = '{}/buildExtraImage-{}-{}.sh'.format(entries_dir, arch, image)
xorriso_template_path = '{}/xorriso-{}-{}.txt'.format(entries_dir, arch, image)
iso_readme_path = '{}/{}/README'.format(self.iso_work_dir, arch)
mock_iso_path = f'/var/tmp/lorax-{self.major_version}.cfg'
mock_sh_path = f'{entries_dir}/extraisobuild-{arch}-{image}.sh'
iso_template_path = f'{entries_dir}/buildExtraImage-{arch}-{image}.sh'
xorriso_template_path = f'{entries_dir}/xorriso-{arch}-{image}.txt'
iso_readme_path = f'{self.iso_work_dir}/{arch}/README'
#print(iso_readme_path)
log_root = os.path.join(
@ -829,7 +810,7 @@ class IsoBuild:
if not os.path.exists(log_root):
os.makedirs(log_root, exist_ok=True)
log_path_command = '| tee -a {}/{}-{}.log'.format(log_root, arch, image)
log_path_command = f'| tee -a {log_root}/{arch}-{image}.log'
# This is kind of a hack. Installing xorrisofs sets the alternatives to
# it, so backwards compatibility is sort of guaranteed. But we want to
@ -850,31 +831,10 @@ class IsoBuild:
if self.updated_image:
datestamp = '-' + self.updated_image_date
volid = '{}-{}-{}{}-{}-{}'.format(
self.shortname,
self.major_version,
self.minor_version,
rclevel,
arch,
volname
)
isoname = '{}-{}{}{}-{}-{}.iso'.format(
self.shortname,
self.revision,
rclevel,
datestamp,
arch,
image
)
generic_isoname = '{}-{}-{}.iso'.format(self.shortname, arch, image)
latest_isoname = '{}-{}-latest-{}-{}.iso'.format(
self.shortname,
self.major_version,
arch,
image
)
volid = f'{self.shortname}-{self.major_version}-{self.minor_version}{rclevel}-{arch}-{volname}'
isoname = f'{self.shortname}-{self.revision}{rclevel}{datestamp}-{arch}-{image}.iso'
generic_isoname = f'{self.shortname}-{arch}-{image}.iso'
latest_isoname = f'{self.shortname}-{self.major_version}-latest-{arch}-{image}.iso'
lorax_pkg_cmd = '/usr/bin/dnf install {} -y {}'.format(
' '.join(required_pkgs),
@ -986,7 +946,7 @@ class IsoBuild:
have mock available.
"""
entries_dir = os.path.join(work_root, "entries")
extra_iso_cmd = '/bin/bash {}/extraisobuild-{}-{}.sh'.format(entries_dir, arch, image)
extra_iso_cmd = f'/bin/bash {entries_dir}/extraisobuild-{arch}-{image}.sh'
self.log.info('Starting mock build...')
p = subprocess.call(shlex.split(extra_iso_cmd))
if p != 0:
@ -1022,7 +982,7 @@ class IsoBuild:
arch_sync = arches.copy()
for a in arch_sync:
entry_name = 'buildExtraImage-{}-{}.sh'.format(a, i)
entry_name = f'buildExtraImage-{a}-{i}.sh'
entry_name_list.append(entry_name)
rclevel = ''
@ -1080,10 +1040,7 @@ class IsoBuild:
join_all_pods = ' '.join(entry_name_list)
time.sleep(3)
self.log.info(Color.INFO + 'Building ' + i + ' ...')
pod_watcher = '{} wait {}'.format(
cmd,
join_all_pods
)
pod_watcher = f'{cmd} wait {join_all_pods}'
watch_man = subprocess.call(
shlex.split(pod_watcher),
@ -1095,10 +1052,7 @@ class IsoBuild:
# code.
pattern = "Exited (0)"
for pod in entry_name_list:
checkcmd = '{} ps -f status=exited -f name={}'.format(
cmd,
pod
)
checkcmd = f'{cmd} ps -f status=exited -f name={pod}'
podcheck = subprocess.Popen(
checkcmd,
stdout=subprocess.PIPE,
@ -1111,10 +1065,7 @@ class IsoBuild:
self.log.error(Color.FAIL + pod)
bad_exit_list.append(pod)
rmcmd = '{} rm {}'.format(
cmd,
join_all_pods
)
rmcmd = f'{cmd} rm {join_all_pods}'
rmpod = subprocess.Popen(
rmcmd,
@ -1202,32 +1153,41 @@ class IsoBuild:
for k, v in self._get_grafts([rd_for_var]).items():
files[os.path.join(repo, "repodata", k)] = v
grafts = '{}/{}-{}-grafts'.format(
lorax_base_dir,
iso,
arch
)
grafts = f'{lorax_base_dir}/{iso}-{arch}-grafts'
xorrs = '{}/xorriso-{}.txt'.format(
lorax_base_dir,
arch
)
xorrs = f'{lorax_base_dir}/xorriso-{iso}-{arch}.txt'
# Generate exclusion list/dict from boot.iso manifest
boot_manifest = f'{lorax_base_dir}/lorax/images/boot.iso.manifest'
# Boot configs and images that may change
# It's unlikely these will be changed in empanadas, they're used as is
# and it works fine. This is a carry over from a recent pungi commit,
# based on an issue I had filed. The above was the original part, the
# below is a pungi "buildinstall" thing that we don't do, but may
# include as a feature if it ever happens.
updatable_files = set(ArchCheck.boot_configs + ArchCheck.boot_images + ['.discinfo'])
ignores = set()
updatables = set()
try:
with open(boot_manifest) as i:
# ignores = set(line.lstrip("/").rstrip("\n") for line in i)
for line in i:
path = line.lstrip("/").rstrip("\n")
if path in updatable_files:
updatables.add(path)
else:
ignores.add(path)
except Exception as e:
self.log.error(Color.FAIL + 'File was likely not found.')
raise SystemExit(e)
self._write_grafts(
grafts,
xorrs,
files,
exclude=[
"*/lost+found",
"*/boot.iso",
"*/boot.iso.manifest",
"EFI/*",
"images/*",
"isolinux/*",
"boot/*",
"ppc/*",
"generic.ins"
]
exclude=ignores,
update=updatables
)
if self.iso_map['xorrisofs']:
@ -1249,12 +1209,12 @@ class IsoBuild:
if isinstance(p, dict):
tree = p
else:
tree = self._scanning(p)
result = self._merging(result, tree)
tree = Idents.scanning(p)
result = Idents.merging(result, tree)
for p in exclusive_paths:
tree = self._scanning(p)
result = self._merging(result, tree, exclusive=True)
tree = Idents.scanning(p)
result = Idents.merging(result, tree, exclusive=True)
# Resolves possible symlinks
for key in result.keys():
@ -1267,12 +1227,17 @@ class IsoBuild:
return result
def _write_grafts(self, filepath, xorrspath, u, exclude=None):
def _write_grafts(self, filepath, xorrspath, u, exclude=None, update=None):
"""
Write out the graft points
"""
seen = set()
# There are files that are on the exclude list typically.
exclude = exclude or []
# There is a chance files may get updated before being placed in a
# variant ISO - it's rare though. most that will be different is
# .discinfo
update = update or []
result = {}
for zl in sorted(u, reverse=True):
dirn = os.path.dirname(zl)
@ -1291,119 +1256,43 @@ class IsoBuild:
result[zl] = u[zl]
seen.add(dirn)
# We check first if a file needs to be updated first before relying on
# the boot.iso manifest to exclude a file
if self.iso_map['xorrisofs']:
fx = open(xorrspath, "w")
for zm in sorted(result, key=self._sorting):
for zm in sorted(result, key=Idents.sorting):
found = False
replace = False
for upda in update:
if fnmatch(zm, upda):
#print(f'updating: {zm} {upda}')
replace = True
break
for excl in exclude:
if fnmatch(zm, excl):
#print(f'ignoring: {zm} {excl}')
found = True
break
if found:
continue
fx.write("-map %s %s\n" % (u[zm], zm))
mcmd = "-update" if replace else "-map"
fx.write("%s %s %s\n" % (mcmd, u[zm], zm))
fx.close()
else:
fh = open(filepath, "w")
for zl in sorted(result, key=self._sorting):
found = False
for excl in exclude:
if fnmatch(zl, excl):
found = True
break
if found:
continue
self.log.info(Color.WARN + 'Nothing should be excluded in legacy ' +
'genisoimage. Ignoring exclude list.')
for zl in sorted(result, key=Idents.sorting):
#found = False
#for excl in exclude:
# if fnmatch(zl, excl):
# found = True
# break
#if found:
# continue
fh.write("%s=%s\n" % (zl, u[zl]))
fh.close()
def _scanning(self, p):
"""
Scan tree
"""
path = os.path.abspath(p)
result = {}
for root, dirs, files in os.walk(path):
for file in files:
abspath = os.path.join(root, file)
relpath = kobo.shortcuts.relative_path(abspath, path.rstrip("/") + "/")
result[relpath] = abspath
# Include empty directories too
if root != path:
abspath = os.path.join(root, "")
relpath = kobo.shortcuts.relative_path(abspath, path.rstrip("/") + "/")
result[relpath] = abspath
return result
def _merging(self, tree_a, tree_b, exclusive=False):
"""
Merge tree
"""
result = tree_b.copy()
all_dirs = set(
[os.path.dirname(dirn).rstrip("/") for dirn in result if os.path.dirname(dirn) != ""]
)
for dirn in tree_a:
dn = os.path.dirname(dirn)
if exclusive:
match = False
for x in all_dirs:
if dn == x or dn.startswith("%s/" % x):
match = True
break
if match:
continue
if dirn in result:
continue
result[dirn] = tree_a[dirn]
return result
def _sorting(self, k):
"""
Sorting using the is_rpm and is_image funcs. Images are first, extras
next, rpm's last.
"""
rolling = (0 if self._is_image(k) else 2 if self._is_rpm(k) else 1, k)
return rolling
def _is_rpm(self, k):
"""
Is this an RPM? :o
"""
result = k.endswith(".rpm")
return result
def _is_image(self, k):
"""
Is this an image? :o
"""
if (
k.startswith("images/") or
k.startswith("isolinux/") or
k.startswith("EFI/") or
k.startswith("etc/") or
k.startswith("ppc/")
):
return True
if (
k.endswith(".img") or
k.endswith(".ins")
):
return True
return False
def _get_vol_id(self):
"""
Gets a volume ID
"""
def run_pull_generic_images(self):
"""
Pulls generic images built in peridot and places them where they need
@ -1500,15 +1389,9 @@ class IsoBuild:
drop_name = source_path.split('/')[-3] + fsuffix
checksum_name = drop_name + '.CHECKSUM'
full_drop = '{}/{}'.format(
image_arch_dir,
drop_name
)
full_drop = f'{image_arch_dir}/{drop_name}'
checksum_drop = '{}/{}.CHECKSUM'.format(
image_arch_dir,
drop_name
)
checksum_drop = f'{image_arch_dir}/{drop_name}.CHECKSUM'
if not os.path.exists(image_arch_dir):
os.makedirs(image_arch_dir, exist_ok=True)
@ -1694,10 +1577,7 @@ class LiveBuild:
self.compose_latest_dir = os.path.join(
config['compose_root'],
major,
"latest-{}-{}".format(
self.shortname,
self.profile
)
f"latest-{self.shortname}-{self.profile}"
)
self.compose_latest_sync = os.path.join(
@ -1820,17 +1700,9 @@ class LiveBuild:
if self.peridot:
kloc = 'peridot'
mock_iso_path = '/var/tmp/live-{}.cfg'.format(self.major_version)
mock_sh_path = '{}/liveisobuild-{}-{}.sh'.format(
entries_dir,
self.current_arch,
image
)
iso_template_path = '{}/buildLiveImage-{}-{}.sh'.format(
entries_dir,
self.current_arch,
image
)
mock_iso_path = f'/var/tmp/live-{self.major_version}.cfg'
mock_sh_path = f'{entries_dir}/liveisobuild-{self.current_arch}-{image}.sh'
iso_template_path = f'{entries_dir}/buildLiveImage-{self.current_arch}-{image}.sh'
log_root = os.path.join(
work_root,
@ -1843,27 +1715,12 @@ class LiveBuild:
if not os.path.exists(log_root):
os.makedirs(log_root, exist_ok=True)
log_path_command = '| tee -a {}/{}-{}.log'.format(
log_root,
self.current_arch,
image
)
log_path_command = f'| tee -a {log_root}/{self.current_arch}-{image}.log'
required_pkgs = self.livemap['required_pkgs']
volid = '{}-{}-{}-{}'.format(
self.shortname,
self.major_version,
self.minor_version,
image
)
volid = f'{self.shortname}-{self.major_version}-{self.minor_version}-{image}'
isoname = '{}-{}-{}-{}-{}.iso'.format(
self.shortname,
self.release,
image,
self.current_arch,
self.date
)
isoname = f'{self.shortname}-{self.release}-{image}-{self.current_arch}-{self.date}.iso'
live_pkg_cmd = '/usr/bin/dnf install {} -y {}'.format(
' '.join(required_pkgs),
@ -1960,17 +1817,10 @@ class LiveBuild:
self.log.warn(Color.WARN + 'This mode does not work properly. It will fail.')
for i in images:
image_name = i
entry_name = 'buildLiveImage-{}-{}.sh'.format(arch, i)
entry_name = f'buildLiveImage-{arch}-{i}.sh'
entry_name_list.append(entry_name)
isoname = '{}/{}-{}-{}-{}-{}.iso'.format(
arch,
self.shortname,
i,
self.major_version,
arch,
self.date
)
isoname = f'{arch}/{self.shortname}-{i}-{self.major_version}-{arch}-{self.date}.iso'
checksum_list.append(isoname)
@ -1998,10 +1848,7 @@ class LiveBuild:
time.sleep(3)
self.log.info(Color.INFO + 'Building requested live images ...')
pod_watcher = '{} wait {}'.format(
cmd,
join_all_pods
)
pod_watcher = f'{cmd} wait {join_all_pods}'
watch_man = subprocess.call(
shlex.split(pod_watcher),
@ -2013,10 +1860,7 @@ class LiveBuild:
# code.
pattern = "Exited (0)"
for pod in entry_name_list:
checkcmd = '{} ps -f status=exited -f name={}'.format(
cmd,
pod
)
checkcmd = f'{cmd} ps -f status=exited -f name={pod}'
podcheck = subprocess.Popen(
checkcmd,
stdout=subprocess.PIPE,
@ -2029,10 +1873,7 @@ class LiveBuild:
self.log.error(Color.FAIL + pod)
bad_exit_list.append(pod)
rmcmd = '{} rm {}'.format(
cmd,
join_all_pods
)
rmcmd = f'{cmd} rm {join_all_pods}'
rmpod = subprocess.Popen(
rmcmd,
@ -2072,25 +1913,9 @@ class LiveBuild:
"""
entries_dir = os.path.join(work_root, "entries")
live_dir_arch = os.path.join(self.live_work_dir, arch)
isoname = '{}-{}-{}-{}-{}.iso'.format(
self.shortname,
self.release,
image,
arch,
self.date
)
isolink = '{}-{}-{}-{}-{}.iso'.format(
self.shortname,
self.major_version,
image,
arch,
'latest'
)
live_res_dir = '/var/lib/mock/{}-{}-{}/result'.format(
self.shortname.lower(),
self.major_version,
arch
)
isoname = f'{self.shortname}-{self.release}-{image}-{arch}-{self.date}.iso'
isolink = f'{self.shortname}-{self.major_version}-{image}-{arch}-latest.iso'
live_res_dir = f'/var/lib/mock/{self.shortname.lower()}-{self.major_version}-{arch}/result'
if self.justcopyit:
if os.path.exists(os.path.join(live_dir_arch, isoname)):
@ -2101,7 +1926,7 @@ class LiveBuild:
self.log.warn(Color.WARN + 'Skipping.')
return
live_iso_cmd = '/bin/bash {}/liveisobuild-{}-{}.sh'.format(entries_dir, arch, image)
live_iso_cmd = f'/bin/bash {entries_dir}/liveisobuild-{arch}-{image}.sh'
self.log.info('Starting mock build...')
p = subprocess.call(shlex.split(live_iso_cmd))
if p != 0:

View File

@ -40,6 +40,18 @@ class ArchCheck:
]
}
# These are files that can potentially change on an image.
boot_configs = [
"isolinux/isolinux.cfg",
"etc/yaboot.conf",
"ppc/ppc64/yaboot.conf",
"EFI/BOOT/BOOTX64.conf",
"EFI/BOOT/grub.cfg"
]
boot_images = [
"images/efiboot.img"
]
class Shared:
"""
Quick utilities that may be commonly used
@ -73,7 +85,7 @@ class Shared:
base = os.path.basename(path)
# This emulates our current syncing scripts that runs stat and
# sha256sum and what not with a very specific output.
return "%s: %s bytes\n%s (%s) = %s\n" % (
return "# %s: %s bytes\n%s (%s) = %s\n" % (
base,
stat.st_size,
hashtype.upper(),
@ -1141,3 +1153,208 @@ class Shared:
logger.error('DNF syncing has been removed.')
logger.error('Please install podman and enable parallel')
raise SystemExit()
@staticmethod
def norm_dnf_sync(data, repo, sync_root, work_root, arch, logger):
"""
This is for normal dnf syncs. This is very slow.
"""
cmd = Shared.reposync_cmd(logger)
sync_single_arch = False
arches_to_sync = data.arches
if arch:
sync_single_arch = True
arches_to_sync = [arch]
logger.info(
Color.BOLD + '!! WARNING !! ' + Color.END + 'You are performing a '
'local reposync, which will incur delays in your compose.'
)
if data.fullrun:
logger.info(
Color.BOLD + '!! WARNING !! ' + Color.END + 'This is a full '
'sync. Expect a few days for it to complete.'
)
for r in repos_to_sync:
for a in arches_to_sync:
repo_name = r
if r in data.repo_renames:
repo_name = data.repo_renames[r]
os_sync_path = os.path.join(
sync_root,
repo_name,
a,
'os'
)
debug_sync_path = os.path.join(
sync_root,
repo_name,
a,
'debug/tree'
)
sync_cmd = "{} -c {} --download-metadata --repoid={} -p {} --forcearch {} --norepopath".format(
cmd,
data.dnf_config,
r,
os_sync_path,
a
)
debug_sync_cmd = "{} -c {} --download-metadata --repoid={}-debug -p {} --forcearch {} --norepopath".format(
cmd,
data.dnf_config,
r,
debug_sync_path,
a
)
logger.info('Syncing {} {}'.format(r, a))
#logger.info(sync_cmd)
# Try to figure out where to send the actual output of this...
# Also consider on running a try/except here? Basically if
# something happens (like a repo doesn't exist for some arch,
# eg RT for aarch64), make a note of it somehow (but don't
# break the entire sync). As it stands with this
# implementation, if something fails, it just continues on.
process = subprocess.call(
shlex.split(sync_cmd),
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
if not data.ignore_debug:
logger.info('Syncing {} {} (debug)'.format(r, a))
process_debug = subprocess.call(
shlex.split(debug_sync_cmd),
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
# There should be a check here that if it's "all" and multilib
# is on, i686 should get synced too.
if not data.ignore_source:
source_sync_path = os.path.join(
sync_root,
repo_name,
'source/tree'
)
source_sync_cmd = "{} -c {} --download-metadata --repoid={}-source -p {} --norepopath".format(
cmd,
data.dnf_config,
r,
source_sync_path
)
logger.info('Syncing {} source'.format(r))
process_source = subprocess.call(
shlex.split(source_sync_cmd),
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
logger.info('Syncing complete')
class Idents:
"""
Identifiers or locators
"""
@staticmethod
def scanning(p):
"""
Scan tree
"""
path = os.path.abspath(p)
result = {}
for root, dirs, files in os.walk(path):
for file in files:
abspath = os.path.join(root, file)
relpath = kobo.shortcuts.relative_path(abspath, path.rstrip("/") + "/")
result[relpath] = abspath
# Include empty directories too
if root != path:
abspath = os.path.join(root, "")
relpath = kobo.shortcuts.relative_path(abspath, path.rstrip("/") + "/")
result[relpath] = abspath
return result
@staticmethod
def merging(tree_a, tree_b, exclusive=False):
"""
Merge tree
"""
result = tree_b.copy()
all_dirs = set(
[os.path.dirname(dirn).rstrip("/") for dirn in result if os.path.dirname(dirn) != ""]
)
for dirn in tree_a:
dn = os.path.dirname(dirn)
if exclusive:
match = False
for x in all_dirs:
if dn == x or dn.startswith("%s/" % x):
match = True
break
if match:
continue
if dirn in result:
continue
result[dirn] = tree_a[dirn]
return result
@staticmethod
def sorting(k):
"""
Sorting using the is_rpm and is_image funcs. Images are first, extras
next, rpm's last.
"""
rolling = (0 if Idents.is_image(k) else 2 if Idents.is_rpm(k) else 1, k)
return rolling
@staticmethod
def is_rpm(k):
"""
Is this an RPM? :o
"""
result = k.endswith(".rpm")
return result
@staticmethod
def is_image(k):
"""
Is this an image? :o
"""
if (
k.startswith("images/") or
k.startswith("isolinux/") or
k.startswith("EFI/") or
k.startswith("etc/") or
k.startswith("ppc/")
):
return True
if (
k.endswith(".img") or
k.endswith(".ins")
):
return True
return False
@staticmethod
def get_vol_id(opts):
"""
Gets a volume ID
"""

View File

@ -1,6 +1,6 @@
[[package]]
name = "atomicwrites"
version = "1.4.0"
version = "1.4.1"
description = "Atomic file writes."
category = "dev"
optional = false
@ -22,14 +22,14 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>
[[package]]
name = "boto3"
version = "1.24.22"
version = "1.26.89"
description = "The AWS SDK for Python"
category = "main"
optional = false
python-versions = ">= 3.7"
[package.dependencies]
botocore = ">=1.27.22,<1.28.0"
botocore = ">=1.29.89,<1.30.0"
jmespath = ">=0.7.1,<2.0.0"
s3transfer = ">=0.6.0,<0.7.0"
@ -38,7 +38,7 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]]
name = "botocore"
version = "1.27.22"
version = "1.29.89"
description = "Low-level, data-driven core of boto 3."
category = "main"
optional = false
@ -50,11 +50,11 @@ python-dateutil = ">=2.1,<3.0.0"
urllib3 = ">=1.25.4,<1.27"
[package.extras]
crt = ["awscrt (==0.13.8)"]
crt = ["awscrt (==0.16.9)"]
[[package]]
name = "certifi"
version = "2022.6.15"
version = "2022.12.7"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
@ -62,26 +62,23 @@ python-versions = ">=3.6"
[[package]]
name = "charset-normalizer"
version = "2.1.0"
version = "3.1.0"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
python-versions = ">=3.6.0"
[package.extras]
unicode_backport = ["unicodedata2"]
python-versions = ">=3.7.0"
[[package]]
name = "colorama"
version = "0.4.5"
version = "0.4.6"
description = "Cross-platform colored terminal text."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
[[package]]
name = "idna"
version = "3.3"
version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
@ -89,7 +86,7 @@ python-versions = ">=3.5"
[[package]]
name = "importlib-metadata"
version = "4.12.0"
version = "6.0.0"
description = "Read metadata from Python packages"
category = "dev"
optional = false
@ -100,13 +97,13 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"]
perf = ["ipython"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8", "importlib-resources (>=1.3)"]
[[package]]
name = "importlib-resources"
version = "5.8.0"
version = "5.12.0"
description = "Read resources from Python packages"
category = "main"
optional = false
@ -116,8 +113,8 @@ python-versions = ">=3.7"
zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"]
[[package]]
name = "jinja2"
@ -162,22 +159,19 @@ python-versions = ">=3.6"
[[package]]
name = "more-itertools"
version = "8.13.0"
version = "9.1.0"
description = "More routines for operating on iterables, beyond itertools"
category = "dev"
optional = false
python-versions = ">=3.5"
python-versions = ">=3.7"
[[package]]
name = "packaging"
version = "21.3"
version = "23.0"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
python-versions = ">=3.7"
[[package]]
name = "pluggy"
@ -212,17 +206,6 @@ category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pyparsing"
version = "3.0.9"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
category = "dev"
optional = false
python-versions = ">=3.6.8"
[package.extras]
diagrams = ["railroad-diagrams", "jinja2"]
[[package]]
name = "pytest"
version = "5.4.3"
@ -267,7 +250,7 @@ python-versions = ">=3.6"
[[package]]
name = "requests"
version = "2.28.1"
version = "2.28.2"
description = "Python HTTP for Humans."
category = "main"
optional = false
@ -275,7 +258,7 @@ python-versions = ">=3.7, <4"
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<3"
charset-normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<1.27"
@ -315,7 +298,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "typing-extensions"
version = "4.3.0"
version = "4.5.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "dev"
optional = false
@ -323,20 +306,20 @@ python-versions = ">=3.7"
[[package]]
name = "urllib3"
version = "1.26.9"
version = "1.26.15"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[package.extras]
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "wcwidth"
version = "0.2.5"
version = "0.2.6"
description = "Measures the displayed width of unicode strings in a terminal"
category = "dev"
optional = false
@ -352,15 +335,15 @@ python-versions = ">=3.4"
[[package]]
name = "zipp"
version = "3.8.0"
version = "3.15.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "jaraco.functools", "more-itertools", "big-o", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"]
[metadata]
lock-version = "1.1"
@ -368,46 +351,19 @@ python-versions = ">=3.7,<4"
content-hash = "42676fd0ceb350c8cd90246dc688cfcd404e14d22229052d0527fe342c135b95"
[metadata.files]
atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
]
atomicwrites = []
attrs = [
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
]
boto3 = [
{file = "boto3-1.24.22-py3-none-any.whl", hash = "sha256:c9a9f893561f64f5b81de197714ac4951251a328672a8dba28ad4c4a589c3adf"},
{file = "boto3-1.24.22.tar.gz", hash = "sha256:67d404c643091d4aa37fc485193289ad859f1f65f94d0fa544e13bdd1d4187c1"},
]
botocore = [
{file = "botocore-1.27.22-py3-none-any.whl", hash = "sha256:7145d9b7cae87999a9f074de700d02a1b3222ee7d1863aa631ff56c5fc868035"},
{file = "botocore-1.27.22.tar.gz", hash = "sha256:f57cb33446deef92e552b0be0e430d475c73cf64bc9e46cdb4783cdfe39cb6bb"},
]
certifi = [
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
]
charset-normalizer = [
{file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"},
{file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"},
]
colorama = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
importlib-metadata = [
{file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
{file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
]
importlib-resources = [
{file = "importlib_resources-5.8.0-py3-none-any.whl", hash = "sha256:7952325ffd516c05a8ad0858c74dff2c3343f136fe66a6002b2623dd1d43f223"},
{file = "importlib_resources-5.8.0.tar.gz", hash = "sha256:568c9f16cb204f9decc8d6d24a572eeea27dacbb4cee9e6b03a8025736769751"},
]
boto3 = []
botocore = []
certifi = []
charset-normalizer = []
colorama = []
idna = []
importlib-metadata = []
importlib-resources = []
jinja2 = [
{file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"},
{file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"},
@ -416,9 +372,7 @@ jmespath = [
{file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"},
{file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"},
]
kobo = [
{file = "kobo-0.24.2.tar.gz", hash = "sha256:1b3c17260a93d933d2238884373fbf3485ecd417d930acf984285dc012410e2b"},
]
kobo = []
markupsafe = [
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
@ -490,14 +444,8 @@ markupsafe = [
{file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
{file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
]
more-itertools = [
{file = "more-itertools-8.13.0.tar.gz", hash = "sha256:a42901a0a5b169d925f6f217cd5a190e32ef54360905b9c39ee7db5313bfec0f"},
{file = "more_itertools-8.13.0-py3-none-any.whl", hash = "sha256:c5122bffc5f104d37c1626b8615b511f3427aa5389b94d61e5ef8236bfbc3ddb"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
more-itertools = []
packaging = []
pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
@ -510,10 +458,6 @@ py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
pytest = [
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
@ -557,10 +501,7 @@ pyyaml = [
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
requests = [
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
]
requests = []
rpm-py-installer = [
{file = "rpm-py-installer-1.1.0.tar.gz", hash = "sha256:66e5f4f9247752ed386345642683103afaee50fb16928878a204bc12504b9bbe"},
]
@ -572,23 +513,11 @@ six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
typing-extensions = [
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
]
urllib3 = [
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
{file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
]
wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
]
typing-extensions = []
urllib3 = []
wcwidth = []
xmltodict = [
{file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"},
{file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"},
]
zipp = [
{file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"},
{file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"},
]
zipp = []

68
mangle/fix-aws-quotas.sh Normal file
View File

<
@ -0,0 +1,68 @@
#!/bin/bash
# Source common variables
# shellcheck disable=SC2046,1091,1090
source "$(dirname "${BASH_SOURCE[0]}")/common"
usage() {
echo "usage: $0"
}
aws() {
# shellcheck disable=SC2068
command aws --profile resf-ami --output json $@
}
# Get the quota code for Public AMIs once
quota_code=$(aws service-quotas list-service-quotas --service-code ec2 --region us-east-1 --query "Quotas[*].{QuotaCode:QuotaCode,QuotaName:QuotaName}" | jq '.[] | select(.QuotaName == "Public AMIs") | .QuotaCode' | tr -d '"')
function get_current_quota() {
region=$1
# Get the current value of the quota
current_value=$(aws service-quotas get-service-quota --service-code ec2 --quota-code "$quota_code" --region "$region" 2>/dev/null | jq .Quota.Value 2>/dev/null)
# shellcheck disable=SC2181
if [[ $? -gt 0 ]]; then
echo "ERR"