Containerize and refactor image building
* Add new fedora container to run imagefactory * Make architecture class from py attrs and make it raise exceptions * Change build-image script to primarily invoke imagefactory directly. A second wrapper will be added to support running in kubernetes if needed.
This commit is contained in:
parent
957bf5ef3f
commit
361c155481
68
iso/empanadas/Containerfile.imagefactory
Normal file
68
iso/empanadas/Containerfile.imagefactory
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
FROM docker.io/fedora:36
|
||||||
|
|
||||||
|
ADD images/get_arch /get_arch
|
||||||
|
|
||||||
|
ENV TINI_VERSION v0.19.0
|
||||||
|
RUN curl -o /tini -L "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-$(/get_arch)"
|
||||||
|
RUN chmod +x /tini
|
||||||
|
|
||||||
|
RUN dnf install -y \
|
||||||
|
bash \
|
||||||
|
bzip2 \
|
||||||
|
cpio \
|
||||||
|
diffutils \
|
||||||
|
findutils \
|
||||||
|
gawk \
|
||||||
|
gcc \
|
||||||
|
gcc-c++ \
|
||||||
|
git \
|
||||||
|
grep \
|
||||||
|
gzip \
|
||||||
|
info \
|
||||||
|
make \
|
||||||
|
patch \
|
||||||
|
python3 \
|
||||||
|
redhat-rpm-config \
|
||||||
|
rpm-build \
|
||||||
|
scl-utils-build \
|
||||||
|
sed \
|
||||||
|
shadow-utils \
|
||||||
|
tar \
|
||||||
|
unzip \
|
||||||
|
util-linux \
|
||||||
|
which \
|
||||||
|
xz \
|
||||||
|
dnf-plugins-core \
|
||||||
|
createrepo_c \
|
||||||
|
rpm-sign \
|
||||||
|
sudo \
|
||||||
|
mock \
|
||||||
|
python-pip \
|
||||||
|
imagefactory \
|
||||||
|
imagefactory-plugins*
|
||||||
|
|
||||||
|
RUN sed -i -e 's/# memory = 1024/memory = 2048/' /etc/oz/oz.cfg
|
||||||
|
|
||||||
|
RUN ssh-keygen -t rsa -q -f "$HOME/.ssh/id_rsa" -N ""
|
||||||
|
RUN dnf clean all
|
||||||
|
RUN rm -rf /etc/yum.repos.d/*.repo /get_arch
|
||||||
|
# RUN useradd -o -d /var/peridot -u 1002 peridotbuilder && usermod -a -G mock peridotbuilder
|
||||||
|
# RUN chown -R peridotbuilder:mock /etc/dnf && chown -R peridotbuilder:mock /etc/rpm && chown -R peridotbuilder:mock /etc/yum.repos.d && chown -R peridotbuilder:mock /var/lib/imagefactory/storage
|
||||||
|
|
||||||
|
RUN pip install awscli
|
||||||
|
|
||||||
|
ENV BRANCH r9
|
||||||
|
RUN git clone https://git.rockylinux.org/rocky/kickstarts.git --branch $BRANCH /kickstarts
|
||||||
|
RUN cp /kickstarts/Rocky-9-Container.ks /kickstarts/Rocky-9-Container-Base.ks
|
||||||
|
RUN sed -i "s/\$basearch/$(uname -m)/" /kickstarts/Rocky-9-Container-Base.ks
|
||||||
|
|
||||||
|
# devel only
|
||||||
|
COPY . /empanadas
|
||||||
|
RUN pip install -e /empanadas
|
||||||
|
|
||||||
|
# prod
|
||||||
|
#RUN pip install 'git+https://git.rockylinux.org/release-engineering/public/toolkit.git@devel#egg=empanadas&subdirectory=iso/empanadas'
|
||||||
|
|
||||||
|
ENV LIBGUESTFS_BACKEND direct
|
||||||
|
|
||||||
|
ENTRYPOINT ["/tini", "--"]
|
@ -97,24 +97,35 @@ for conf in glob.iglob(f"{_rootdir}/sig/*.yaml"):
|
|||||||
#COMPOSE_ISO_WORKDIR = COMPOSE_ROOT + "work/" + arch + "/" + date_stamp
|
#COMPOSE_ISO_WORKDIR = COMPOSE_ROOT + "work/" + arch + "/" + date_stamp
|
||||||
|
|
||||||
|
|
||||||
def valid_type_variant(_type: str, variant: str="") -> Tuple[bool, str]:
|
ALLOWED_TYPE_VARIANTS = {
|
||||||
ALLOWED_TYPE_VARIANTS = {
|
"Azure": None,
|
||||||
"Container": ["Base", "Minimal"],
|
"Container": ["Base", "Minimal"],
|
||||||
"GenericCloud": [],
|
"EC2": None,
|
||||||
}
|
"GenericCloud": None,
|
||||||
|
"Vagrant": ["Libvirt", "VBox"]
|
||||||
|
}
|
||||||
|
def valid_type_variant(_type: str, variant: str="") -> bool:
|
||||||
if _type not in ALLOWED_TYPE_VARIANTS:
|
if _type not in ALLOWED_TYPE_VARIANTS:
|
||||||
return False, f"Type is invalid: ({_type}, {variant})"
|
raise Exception(f"Type is invalid: ({_type}, {variant})")
|
||||||
elif variant not in ALLOWED_TYPE_VARIANTS[_type]:
|
if ALLOWED_TYPE_VARIANTS[_type] == None:
|
||||||
|
if variant is not None:
|
||||||
|
raise Exception(f"{_type} Type expects no variant type.")
|
||||||
|
return True
|
||||||
|
if variant not in ALLOWED_TYPE_VARIANTS[_type]:
|
||||||
if variant.capitalize() in ALLOWED_TYPE_VARIANTS[_type]:
|
if variant.capitalize() in ALLOWED_TYPE_VARIANTS[_type]:
|
||||||
return False, f"Capitalization mismatch. Found: ({_type}, {variant}). Expected: ({_type}, {variant.capitalize()})"
|
raise Exception(f"Capitalization mismatch. Found: ({_type}, {variant}). Expected: ({_type}, {variant.capitalize()})")
|
||||||
return False, f"Type/Variant Combination is not allowed: ({_type}, {variant})"
|
raise Exception(f"Type/Variant Combination is not allowed: ({_type}, {variant})")
|
||||||
return True, ""
|
return True
|
||||||
|
|
||||||
class Architecture(str):
|
from attrs import define, field
|
||||||
@staticmethod
|
@define
|
||||||
def New(architecture: str, version: int):
|
class Architecture:
|
||||||
|
name: str = field()
|
||||||
|
version: str = field()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def New(cls, architecture: str, version: int):
|
||||||
if architecture not in rldict[version]["allowed_arches"]:
|
if architecture not in rldict[version]["allowed_arches"]:
|
||||||
print("Invalid architecture/version combo, skipping")
|
print("Invalid architecture/version combo, skipping")
|
||||||
exit()
|
exit()
|
||||||
return Architecture(architecture)
|
return cls(architecture, version)
|
||||||
|
@ -3,12 +3,21 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import platform
|
||||||
|
|
||||||
|
from botocore import args
|
||||||
|
from attrs import define, Factory, field, asdict
|
||||||
|
|
||||||
from jinja2 import Environment, FileSystemLoader, Template
|
from jinja2 import Environment, FileSystemLoader, Template
|
||||||
from typing import List, Tuple
|
from typing import Callable, List, NoReturn, Optional, Tuple, IO, Union
|
||||||
|
|
||||||
from empanadas.common import Architecture, rldict, valid_type_variant
|
from empanadas.common import Architecture, rldict, valid_type_variant
|
||||||
from empanadas.common import _rootdir
|
from empanadas.common import _rootdir
|
||||||
@ -22,123 +31,337 @@ parser.add_argument('--debug', action='store_true', help="debug?")
|
|||||||
parser.add_argument('--type', type=str, help="Image type (container, genclo, azure, aws, vagrant)", required=True)
|
parser.add_argument('--type', type=str, help="Image type (container, genclo, azure, aws, vagrant)", required=True)
|
||||||
parser.add_argument('--variant', type=str, help="", required=False)
|
parser.add_argument('--variant', type=str, help="", required=False)
|
||||||
parser.add_argument('--release', type=str, help="Image release for subsequent builds with the same date stamp (rarely needed)", required=False)
|
parser.add_argument('--release', type=str, help="Image release for subsequent builds with the same date stamp (rarely needed)", required=False)
|
||||||
|
parser.add_argument('--kube', action='store_true', help="output as a K8s job(s)", required=False)
|
||||||
|
|
||||||
|
|
||||||
results = parser.parse_args()
|
results = parser.parse_args()
|
||||||
rlvars = rldict[results.version]
|
rlvars = rldict[results.version]
|
||||||
major = rlvars["major"]
|
major = rlvars["major"]
|
||||||
|
|
||||||
|
|
||||||
|
debug = results.debug
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
log.setLevel(logging.INFO if not debug else logging.DEBUG)
|
||||||
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
|
handler.setLevel(logging.INFO if not debug else logging.DEBUG)
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
'%(asctime)s :: %(name)s :: %(message)s',
|
||||||
|
'%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
log.addHandler(handler)
|
||||||
|
|
||||||
STORAGE_DIR = pathlib.Path("/var/lib/imagefactory/storage")
|
STORAGE_DIR = pathlib.Path("/var/lib/imagefactory/storage")
|
||||||
KICKSTART_PATH = pathlib.Path(os.environ.get("KICKSTART_PATH", "/kickstarts"))
|
KICKSTART_PATH = pathlib.Path(os.environ.get("KICKSTART_PATH", "/kickstarts"))
|
||||||
BUILDTIME = datetime.datetime.utcnow()
|
BUILDTIME = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
@define(kw_only=True)
|
||||||
|
class ImageBuild:
|
||||||
|
architecture: Architecture = field()
|
||||||
|
base_uuid: Optional[str] = field(default="")
|
||||||
|
command_args: List[str] = field(factory=list)
|
||||||
|
common_args: List[str] = field(factory=list)
|
||||||
|
debug: bool = field(default=False)
|
||||||
|
image_type: str = field()
|
||||||
|
job_template: Optional[Template] = field(init=False)
|
||||||
|
kickstart_arg: List[str] = field(factory=list)
|
||||||
|
out_type: str = field(init=False)
|
||||||
|
outdir: pathlib.Path = field(init=False)
|
||||||
|
outname: str = field(init=False)
|
||||||
|
package_args: List[str] = field(factory=list)
|
||||||
|
target_uuid: Optional[str] = field(default="")
|
||||||
|
tdl_path: pathlib.Path = field(init=False)
|
||||||
|
template: Template = field()
|
||||||
|
type_variant: str = field(init=False)
|
||||||
|
stage_commands: Optional[List[List[Union[str,Callable]]]] = field(init=False)
|
||||||
|
variant: Optional[str] = field()
|
||||||
|
revision: Optional[int] = field()
|
||||||
|
metadata: pathlib.Path = field(init=False)
|
||||||
|
fedora_release: int = field()
|
||||||
|
|
||||||
def render_icicle_template(template: Template, architecture: Architecture) -> str:
|
def __attrs_post_init__(self):
|
||||||
|
self.tdl_path = self.render_icicle_template()
|
||||||
|
if not self.tdl_path:
|
||||||
|
exit(2)
|
||||||
|
self.type_variant = self.type_variant_name()
|
||||||
|
self.outname = self.output_name()
|
||||||
|
self.outdir = pathlib.Path(f"/tmp/{self.outname}")
|
||||||
|
self.out_type = self.image_format()
|
||||||
|
self.command_args = self._command_args()
|
||||||
|
self.package_args = self._package_args()
|
||||||
|
self.common_args = self._common_args()
|
||||||
|
self.kickstart_arg = self.kickstart_imagefactory_args()
|
||||||
|
|
||||||
|
self.metadata = pathlib.Path(self.outdir, "metadata.json")
|
||||||
|
|
||||||
|
if self.image_type == "Container":
|
||||||
|
self.stage_commands = [
|
||||||
|
["tar", "-C", f"{self.outdir}", "--strip-components=1", "-x", "-f", lambda: f"{STORAGE_DIR}/{self.target_uuid}.body", "*/layer.tar"]
|
||||||
|
]
|
||||||
|
if self.image_type == "GenericCloud":
|
||||||
|
self.stage_commands = [
|
||||||
|
["qemu-img", "convert", "-f", "raw", "-O", "qcow2", lambda: f"{STORAGE_DIR}/{self.target_uuid}.body", f"{self.outdir}/{self.outname}.qcow2"]
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.mkdir(self.outdir)
|
||||||
|
except FileExistsError as e:
|
||||||
|
log.info("Directory already exists for this release. If possible, previously executed steps may be skipped")
|
||||||
|
except Exception as e:
|
||||||
|
log.exception("Some other exception occured while creating the output directory", e)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if os.path.exists(self.metadata):
|
||||||
|
with open(self.metadata, "r") as f:
|
||||||
|
try:
|
||||||
|
o = json.load(f)
|
||||||
|
self.base_uuid = o['base_uuid']
|
||||||
|
self.target_uuid = o['target_uuid']
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
log.exception("Couldn't decode metadata file", e)
|
||||||
|
|
||||||
|
def output_name(self):
|
||||||
|
return f"Rocky-{self.architecture.version}-{self.type_variant}.{BUILDTIME.strftime('%Y%m%d')}.{results.release if results.release else 0}.{self.architecture.name}"
|
||||||
|
|
||||||
|
def type_variant_name(self):
|
||||||
|
return self.image_type if not self.variant else f"{self.image_type}-{self.variant.capitalize()}"
|
||||||
|
|
||||||
|
def _command_args(self):
|
||||||
|
args_mapping = {
|
||||||
|
"debug": "--debug"
|
||||||
|
}
|
||||||
|
return [param for name, param in args_mapping.items() if getattr(results,name)]
|
||||||
|
|
||||||
|
def _package_args(self) -> List[str]:
|
||||||
|
if results.type == "Container":
|
||||||
|
return ["--parameter", "compress", "xz"]
|
||||||
|
return [""]
|
||||||
|
|
||||||
|
def _common_args(self) -> List[str]:
|
||||||
|
args = []
|
||||||
|
if self.image_type == "Container":
|
||||||
|
args = ["--parameter", "offline_icicle", "true"]
|
||||||
|
if self.image_type == "GenericCloud":
|
||||||
|
args = ["--parameter", "generate_icicle", "false"]
|
||||||
|
return args
|
||||||
|
|
||||||
|
def image_format(self) -> str:
|
||||||
|
mapping = {
|
||||||
|
"Container": "docker"
|
||||||
|
}
|
||||||
|
return mapping[self.image_type] if self.image_type in mapping.keys() else ''
|
||||||
|
|
||||||
|
def kickstart_imagefactory_args(self) -> List[str]:
|
||||||
|
kickstart_path = pathlib.Path(f"{KICKSTART_PATH}/Rocky-{self.architecture.version}-{self.type_variant}.ks")
|
||||||
|
|
||||||
|
if not kickstart_path.is_file():
|
||||||
|
log.warn(f"Kickstart file is not available: {kickstart_path}")
|
||||||
|
if not debug:
|
||||||
|
log.warn("Exiting because debug mode is not enabled.")
|
||||||
|
exit(2)
|
||||||
|
|
||||||
|
return ["--file-parameter", "install_script", str(kickstart_path)]
|
||||||
|
|
||||||
|
def render_icicle_template(self) -> pathlib.Path:
|
||||||
handle, output = tempfile.mkstemp()
|
handle, output = tempfile.mkstemp()
|
||||||
if not handle:
|
if not handle:
|
||||||
exit(3)
|
exit(3)
|
||||||
with os.fdopen(handle, "wb") as tmp:
|
with os.fdopen(handle, "wb") as tmp:
|
||||||
_template = template.render(
|
_template = self.template.render(
|
||||||
architecture=architecture,
|
architecture=self.architecture.name,
|
||||||
fedora_version=rlvars["fedora_release"],
|
fedora_version=self.fedora_release,
|
||||||
iso8601date=BUILDTIME.strftime("%Y%m%d"),
|
iso8601date=BUILDTIME.strftime("%Y%m%d"),
|
||||||
installdir="kickstart" if results.kickstartdir else "os",
|
installdir="kickstart" if results.kickstartdir else "os",
|
||||||
major=major,
|
major=self.architecture.version,
|
||||||
release=results.release if results.release else 0,
|
release=results.release if results.release else 0,
|
||||||
size="10G",
|
size="10G",
|
||||||
type=results.type.capitalize(),
|
type=self.image_type,
|
||||||
utcnow=BUILDTIME,
|
utcnow=BUILDTIME,
|
||||||
version_variant=rlvars["revision"] if not results.variant else f"{rlvars['revision']}-{results.variant.capitalize()}",
|
version_variant=self.revision if not self.variant else f"{self.revision}-{self.variant}",
|
||||||
)
|
)
|
||||||
tmp.write(_template.encode())
|
tmp.write(_template.encode())
|
||||||
return output
|
tmp.flush()
|
||||||
|
return pathlib.Path(output)
|
||||||
|
|
||||||
|
def build_command(self) -> List[str]:
|
||||||
def generate_kickstart_imagefactory_args(debug: bool = False) -> str:
|
build_command = ["imagefactory", *self.command_args, "base_image", *self.common_args, *self.kickstart_arg, self.tdl_path
|
||||||
type_variant = results.type if not results.variant else f"{results.type}-{results.variant}" # todo -cleanup
|
# "|", "tee", "-a", f"{outdir}/logs/base_image-{outname}.out",
|
||||||
kickstart_path = pathlib.Path(f"{KICKSTART_PATH}/Rocky-{major}-{type_variant}.ks")
|
# "|", "tail", "-n4", ">", f"{outdir}/base.meta", "||", "exit", "2"
|
||||||
|
|
||||||
if not kickstart_path.is_file():
|
|
||||||
print(f"Kickstart file is not available: {kickstart_path}")
|
|
||||||
if not debug:
|
|
||||||
exit(2)
|
|
||||||
|
|
||||||
return f"--file-parameter install_script {kickstart_path}"
|
|
||||||
|
|
||||||
def get_image_format(_type: str) -> str:
|
|
||||||
mapping = {
|
|
||||||
"Container": "docker"
|
|
||||||
}
|
|
||||||
return mapping[_type] if _type in mapping.keys() else ''
|
|
||||||
|
|
||||||
def generate_imagefactory_commands(tdl_template: Template, architecture: Architecture) -> List[List[str]]:
|
|
||||||
template_path = render_icicle_template(tdl_template, architecture)
|
|
||||||
if not template_path:
|
|
||||||
exit(2)
|
|
||||||
|
|
||||||
args_mapping = {
|
|
||||||
"debug": "--debug"
|
|
||||||
}
|
|
||||||
|
|
||||||
# only supports boolean flags right now?
|
|
||||||
args = [param for name, param in args_mapping.items() if getattr(results,name)]
|
|
||||||
package_args = []
|
|
||||||
|
|
||||||
kickstart_arg = generate_kickstart_imagefactory_args(True) # REMOVE DEBUG ARG
|
|
||||||
|
|
||||||
if results.type == "Container":
|
|
||||||
args += ["--parameter", "offline_icicle", "true"]
|
|
||||||
package_args += ["--parameter", "compress", "xz"]
|
|
||||||
tar_command = ["tar", "-Oxf", f"{STORAGE_DIR}/*.body" "./layer.tar"]
|
|
||||||
|
|
||||||
type_variant = results.type if not results.variant else f"{results.type}-{results.variant}" # todo -cleanup
|
|
||||||
outname = f"Rocky-{rlvars['major']}-{type_variant}.{BUILDTIME.strftime('%Y%m%d')}.{results.release if results.release else 0}.{architecture}"
|
|
||||||
|
|
||||||
outdir = pathlib.Path(f"/tmp/{outname}")
|
|
||||||
|
|
||||||
build_command = (f"imagefactory base_image {kickstart_arg} {' '.join(args)} {template_path}"
|
|
||||||
f" | tee -a {outdir}/logs/base_image-{outname}.out"
|
|
||||||
f" | tail -n4 > {outdir}/base.meta || exit 2"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
out_type = get_image_format(results.type)
|
|
||||||
package_command = ["imagefactory", "target_image", *args, template_path,
|
|
||||||
"--id", "$(awk '$1==\"UUID:\"{print $NF}'"+f" /tmp/{outname}/base.meta)",
|
|
||||||
*package_args,
|
|
||||||
"--parameter", "repository", outname, out_type,
|
|
||||||
"|", "tee", "-a", f"{outdir}/base_image-{outname}.out",
|
|
||||||
"|", "tail", "-n4", ">", f"{outdir}/target.meta", "||", "exit", "3"
|
|
||||||
]
|
]
|
||||||
|
return build_command
|
||||||
|
def package_command(self) -> List[str]:
|
||||||
|
package_command = ["imagefactory", *self.command_args, "target_image", self.out_type, *self.common_args,
|
||||||
|
"--id", f"{self.base_uuid}",
|
||||||
|
*self.package_args,
|
||||||
|
"--parameter", "repository", self.outname,
|
||||||
|
# "|", "tee", "-a", f"{outdir}/base_image-{outname}.out",
|
||||||
|
# "|", "tail", "-n4", ">", f"{outdir}/target.meta", "||", "exit", "3"
|
||||||
|
]
|
||||||
|
return package_command
|
||||||
|
|
||||||
copy_command = (f"aws s3 cp --recursive {outdir}/* s3://resf-empanadas/buildimage-{ outname }/{ BUILDTIME.strftime('%s') }/")
|
def copy_command(self) -> List[str]:
|
||||||
commands = [build_command, package_command, copy_command]
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def run():
|
copy_command = ["aws", "s3", "cp", "--recursive", f"{self.outdir}/", f"s3://resf-empanadas/buildimage-{ self.outname }/{ BUILDTIME.strftime('%s') }/"]
|
||||||
result, error = valid_type_variant(results.type, results.variant)
|
|
||||||
if not result:
|
|
||||||
print(error)
|
|
||||||
exit(2)
|
|
||||||
|
|
||||||
file_loader = FileSystemLoader(f"{_rootdir}/templates")
|
return copy_command
|
||||||
tmplenv = Environment(loader=file_loader)
|
|
||||||
tdl_template = tmplenv.get_template('icicle/tdl.xml.tmpl')
|
|
||||||
job_template = tmplenv.get_template('kube/Job.tmpl')
|
|
||||||
|
|
||||||
for architecture in rlvars["allowed_arches"]:
|
def build(self) -> int:
|
||||||
architecture = Architecture.New(architecture, major)
|
if self.base_uuid:
|
||||||
|
return 0
|
||||||
|
|
||||||
commands = generate_imagefactory_commands(tdl_template, architecture)
|
ret, out, err, uuid = self.runCmd(self.build_command())
|
||||||
|
if ret > 0:
|
||||||
|
#error in build command
|
||||||
|
log.error("Problem during build.")
|
||||||
|
if not uuid:
|
||||||
|
log.error("Build UUID not found in stdout. Dumping stdout and stderr")
|
||||||
|
self.log_subprocess(ret, out, err)
|
||||||
|
return ret
|
||||||
|
self.base_uuid = uuid.rstrip()
|
||||||
|
self.save()
|
||||||
|
return ret
|
||||||
|
|
||||||
print(job_template.render(
|
def package(self) -> int:
|
||||||
architecture=architecture,
|
# Some build types don't need to be packaged by imagefactory
|
||||||
|
if self.image_type == "GenericCloud":
|
||||||
|
self.target_uuid = self.base_uuid if hasattr(self, 'base_uuid') else ""
|
||||||
|
|
||||||
|
if self.target_uuid:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
ret, out, err, uuid = self.runCmd(self.package_command())
|
||||||
|
if ret > 0:
|
||||||
|
log.error("Problem during packaging")
|
||||||
|
if not uuid:
|
||||||
|
log.error("Target Image UUID not found in stdout. Dumping stdout and stderr")
|
||||||
|
self.log_subprocess(ret, out, err)
|
||||||
|
return ret
|
||||||
|
self.target_uuid = uuid.rstrip()
|
||||||
|
self.save()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def stage(self) -> int:
|
||||||
|
""" Stage the artifacst from wherever they are (unpacking and converting if needed)"""
|
||||||
|
if not self.stage_commands:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
returns = []
|
||||||
|
for command in self.stage_commands:
|
||||||
|
ret, out, err, _ = self.runCmd(command, search=False)
|
||||||
|
if ret > 0:
|
||||||
|
log.error("Problem during unpack.")
|
||||||
|
self.log_subprocess(ret, out, err)
|
||||||
|
returns.append(ret)
|
||||||
|
|
||||||
|
return all(ret > 0 for ret in returns)
|
||||||
|
|
||||||
|
def copy(self) -> int:
|
||||||
|
# move or unpack if necessary
|
||||||
|
if (stage := self.stage() > 0):
|
||||||
|
raise Exception(stage)
|
||||||
|
|
||||||
|
ret, out, err, _ = self.runCmd(self.copy_command(), search=False)
|
||||||
|
if ret > 0:
|
||||||
|
#error in build command
|
||||||
|
log.error("Problem during build.")
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def runCmd(self, command: List[Union[str, Callable]], search: bool = True) -> Tuple[int, Union[IO[bytes],None], Union[IO[bytes],None], Union[str,None]]:
|
||||||
|
prepared, _ = self.prepare_command(command)
|
||||||
|
log.info(f"Running command: {' '.join(prepared)}")
|
||||||
|
|
||||||
|
kwargs = {
|
||||||
|
"stderr": subprocess.PIPE,
|
||||||
|
"stdout": subprocess.PIPE
|
||||||
|
}
|
||||||
|
if debug: del kwargs["stderr"]
|
||||||
|
|
||||||
|
with subprocess.Popen(prepared, **kwargs) as p:
|
||||||
|
uuid = None
|
||||||
|
if search:
|
||||||
|
for _, line in enumerate(p.stdout): # type: ignore
|
||||||
|
ln = line.decode()
|
||||||
|
if ln.startswith("UUID: "):
|
||||||
|
uuid = ln.split(" ")[-1]
|
||||||
|
log.debug(f"found uuid: {uuid}")
|
||||||
|
return p.wait(), p.stdout, p.stdin, uuid
|
||||||
|
|
||||||
|
def prepare_command(self, command_list: List[Union[str, Callable]]) -> Tuple[List[str],List[None]]:
|
||||||
|
"""
|
||||||
|
Commands may be a callable, which should be a lambda to be evaluated at
|
||||||
|
preparation time with available locals. This can be used to, among
|
||||||
|
other things, perform lazy evaluations of f-strings which have values
|
||||||
|
not available at assignment time. e.g., filling in a second command
|
||||||
|
with a value extracted from the previous step or command.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
r = []
|
||||||
|
return r, [r.append(c()) if (callable(c) and c.__name__ == '<lambda>') else r.append(str(c)) for c in command_list]
|
||||||
|
|
||||||
|
def log_subprocess(self, return_code: int, stdout: Union[IO[bytes], None], stderr: Union[IO[bytes], None]):
|
||||||
|
def log_lines(title, lines):
|
||||||
|
log.info(f"====={title}=====")
|
||||||
|
for _, line in lines:
|
||||||
|
log.info(line.decode())
|
||||||
|
log.info(f"Command return code: {return_code}")
|
||||||
|
log_lines("Command STDOUT", enumerate(stdout)) # type: ignore
|
||||||
|
log_lines("Command STDERR", enumerate(stderr)) # type: ignore
|
||||||
|
|
||||||
|
def render_kubernetes_job(self):
|
||||||
|
commands = [self.build_command(), self.package_command(), self.copy_command()]
|
||||||
|
if not self.job_template:
|
||||||
|
return None
|
||||||
|
template = self.job_template.render(
|
||||||
|
architecture=self.architecture.name,
|
||||||
backoffLimit=4,
|
backoffLimit=4,
|
||||||
buildTime=datetime.datetime.utcnow().strftime("%s"),
|
buildTime=BUILDTIME.strftime("%s"),
|
||||||
command=commands,
|
command=commands,
|
||||||
imageName="ghcr.io/rockylinux/sig-core-toolkit:latest",
|
imageName="ghcr.io/rockylinux/sig-core-toolkit:latest",
|
||||||
jobname="buildimage",
|
jobname="buildimage",
|
||||||
namespace="empanadas",
|
namespace="empanadas",
|
||||||
major=major,
|
major=major,
|
||||||
restartPolicy="Never",
|
restartPolicy="Never",
|
||||||
))
|
)
|
||||||
|
return template
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
with open(pathlib.Path(self.outdir, "metadata.json"), "w") as f:
|
||||||
|
o = { name: getattr(self, name) for name in ["base_uuid", "target_uuid"] }
|
||||||
|
log.debug(o)
|
||||||
|
json.dump(o, f)
|
||||||
|
|
||||||
|
def run():
|
||||||
|
try:
|
||||||
|
valid_type_variant(results.type, results.variant)
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(e)
|
||||||
|
exit(2)
|
||||||
|
|
||||||
|
file_loader = FileSystemLoader(f"{_rootdir}/templates")
|
||||||
|
tmplenv = Environment(loader=file_loader)
|
||||||
|
tdl_template = tmplenv.get_template('icicle/tdl.xml.tmpl')
|
||||||
|
|
||||||
|
arches = rlvars['allowed_arches'] if results.kube else [platform.uname().machine]
|
||||||
|
|
||||||
|
for architecture in arches:
|
||||||
|
IB = ImageBuild(
|
||||||
|
image_type=results.type,
|
||||||
|
variant=results.variant,
|
||||||
|
architecture=Architecture.New(architecture, major),
|
||||||
|
template=tdl_template,
|
||||||
|
revision=rlvars['revision'],
|
||||||
|
fedora_release=rlvars['fedora_release'],
|
||||||
|
debug=True
|
||||||
|
)
|
||||||
|
if results.kube:
|
||||||
|
IB.job_template = tmplenv.get_template('kube/Job.tmpl')
|
||||||
|
#commands = IB.kube_commands()
|
||||||
|
print(IB.render_kubernetes_job())
|
||||||
|
else:
|
||||||
|
ret = IB.build()
|
||||||
|
ret = IB.package()
|
||||||
|
ret = IB.copy()
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<version>{{fedora_version}}</version>
|
<version>{{fedora_version}}</version>
|
||||||
<arch>{{architecture}}</arch>
|
<arch>{{architecture}}</arch>
|
||||||
<install type='url'>
|
<install type='url'>
|
||||||
<url>https://dl.rockylinux.org/stg/rocky/{{major}}/BaseOS/{{architecture}}/{{installdir}}/</url>
|
<url>https://dl.rockylinux.org/stg/rocky/{{major}}/BaseOS/{{architecture}}/{{installdir}}</url>
|
||||||
</install>
|
</install>
|
||||||
<icicle>
|
<icicle>
|
||||||
<extra_command>rpm -qa --qf '%{NAME},%{VERSION},%{RELEASE},%{ARCH},%{EPOCH},%{SIZE},%{SIGMD5},%{BUILDTIME}
|
<extra_command>rpm -qa --qf '%{NAME},%{VERSION},%{RELEASE},%{ARCH},%{EPOCH},%{SIZE},%{SIGMD5},%{BUILDTIME}
|
||||||
|
0
iso/empanadas/empanadas/util/imagebuild.py
Normal file
0
iso/empanadas/empanadas/util/imagebuild.py
Normal file
54
iso/empanadas/poetry.lock
generated
54
iso/empanadas/poetry.lock
generated
@ -10,7 +10,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|||||||
name = "attrs"
|
name = "attrs"
|
||||||
version = "21.4.0"
|
version = "21.4.0"
|
||||||
description = "Classes Without Boilerplate"
|
description = "Classes Without Boilerplate"
|
||||||
category = "dev"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
@ -22,14 +22,14 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "boto3"
|
name = "boto3"
|
||||||
version = "1.24.14"
|
version = "1.24.22"
|
||||||
description = "The AWS SDK for Python"
|
description = "The AWS SDK for Python"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">= 3.7"
|
python-versions = ">= 3.7"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
botocore = ">=1.27.14,<1.28.0"
|
botocore = ">=1.27.22,<1.28.0"
|
||||||
jmespath = ">=0.7.1,<2.0.0"
|
jmespath = ">=0.7.1,<2.0.0"
|
||||||
s3transfer = ">=0.6.0,<0.7.0"
|
s3transfer = ">=0.6.0,<0.7.0"
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "botocore"
|
name = "botocore"
|
||||||
version = "1.27.14"
|
version = "1.27.22"
|
||||||
description = "Low-level, data-driven core of boto 3."
|
description = "Low-level, data-driven core of boto 3."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@ -62,11 +62,11 @@ python-versions = ">=3.6"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "charset-normalizer"
|
name = "charset-normalizer"
|
||||||
version = "2.0.12"
|
version = "2.1.0"
|
||||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5.0"
|
python-versions = ">=3.6.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
unicode_backport = ["unicodedata2"]
|
unicode_backport = ["unicodedata2"]
|
||||||
@ -89,7 +89,7 @@ python-versions = ">=3.5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "importlib-metadata"
|
name = "importlib-metadata"
|
||||||
version = "4.11.4"
|
version = "4.12.0"
|
||||||
description = "Read metadata from Python packages"
|
description = "Read metadata from Python packages"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@ -102,7 +102,7 @@ zipp = ">=0.5"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
|
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
|
||||||
perf = ["ipython"]
|
perf = ["ipython"]
|
||||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "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)", "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)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "importlib-resources"
|
name = "importlib-resources"
|
||||||
@ -143,7 +143,7 @@ python-versions = ">=3.7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kobo"
|
name = "kobo"
|
||||||
version = "0.24.1"
|
version = "0.24.2"
|
||||||
description = "A pile of python modules used by Red Hat release engineering to build their tools"
|
description = "A pile of python modules used by Red Hat release engineering to build their tools"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@ -267,7 +267,7 @@ python-versions = ">=3.6"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "requests"
|
name = "requests"
|
||||||
version = "2.28.0"
|
version = "2.28.1"
|
||||||
description = "Python HTTP for Humans."
|
description = "Python HTTP for Humans."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@ -275,13 +275,13 @@ python-versions = ">=3.7, <4"
|
|||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
certifi = ">=2017.4.17"
|
certifi = ">=2017.4.17"
|
||||||
charset-normalizer = ">=2.0.0,<2.1.0"
|
charset-normalizer = ">=2,<3"
|
||||||
idna = ">=2.5,<4"
|
idna = ">=2.5,<4"
|
||||||
urllib3 = ">=1.21.1,<1.27"
|
urllib3 = ">=1.21.1,<1.27"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||||
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
|
use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rpm-py-installer"
|
name = "rpm-py-installer"
|
||||||
@ -315,7 +315,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.2.0"
|
version = "4.3.0"
|
||||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@ -365,7 +365,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = ">=3.7,<4"
|
python-versions = ">=3.7,<4"
|
||||||
content-hash = "ccd47ad1b0819968dbad34b68c3f9afd98bd657ee639f9037731fd2a0746bd16"
|
content-hash = "42676fd0ceb350c8cd90246dc688cfcd404e14d22229052d0527fe342c135b95"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
atomicwrites = [
|
atomicwrites = [
|
||||||
@ -377,20 +377,20 @@ attrs = [
|
|||||||
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
||||||
]
|
]
|
||||||
boto3 = [
|
boto3 = [
|
||||||
{file = "boto3-1.24.14-py3-none-any.whl", hash = "sha256:490f5e88f5551b33ae3019a37412158b76426d63d1fb910968ade9b6a024e5fe"},
|
{file = "boto3-1.24.22-py3-none-any.whl", hash = "sha256:c9a9f893561f64f5b81de197714ac4951251a328672a8dba28ad4c4a589c3adf"},
|
||||||
{file = "boto3-1.24.14.tar.gz", hash = "sha256:e284705da36faa668c715ae1f74ebbff4320dbfbe3a733df3a8ab076d1ed1226"},
|
{file = "boto3-1.24.22.tar.gz", hash = "sha256:67d404c643091d4aa37fc485193289ad859f1f65f94d0fa544e13bdd1d4187c1"},
|
||||||
]
|
]
|
||||||
botocore = [
|
botocore = [
|
||||||
{file = "botocore-1.27.14-py3-none-any.whl", hash = "sha256:df1e9b208ff93daac7c645b0b04fb6dccd7f20262eae24d87941727025cbeece"},
|
{file = "botocore-1.27.22-py3-none-any.whl", hash = "sha256:7145d9b7cae87999a9f074de700d02a1b3222ee7d1863aa631ff56c5fc868035"},
|
||||||
{file = "botocore-1.27.14.tar.gz", hash = "sha256:bb56fa77b8fa1ec367c2e16dee62d60000451aac5140dcce3ebddc167fd5c593"},
|
{file = "botocore-1.27.22.tar.gz", hash = "sha256:f57cb33446deef92e552b0be0e430d475c73cf64bc9e46cdb4783cdfe39cb6bb"},
|
||||||
]
|
]
|
||||||
certifi = [
|
certifi = [
|
||||||
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
|
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
|
||||||
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
|
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
|
||||||
]
|
]
|
||||||
charset-normalizer = [
|
charset-normalizer = [
|
||||||
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
|
{file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"},
|
||||||
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
|
{file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"},
|
||||||
]
|
]
|
||||||
colorama = [
|
colorama = [
|
||||||
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
||||||
@ -401,8 +401,8 @@ idna = [
|
|||||||
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
|
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
|
||||||
]
|
]
|
||||||
importlib-metadata = [
|
importlib-metadata = [
|
||||||
{file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"},
|
{file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
|
||||||
{file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"},
|
{file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
|
||||||
]
|
]
|
||||||
importlib-resources = [
|
importlib-resources = [
|
||||||
{file = "importlib_resources-5.8.0-py3-none-any.whl", hash = "sha256:7952325ffd516c05a8ad0858c74dff2c3343f136fe66a6002b2623dd1d43f223"},
|
{file = "importlib_resources-5.8.0-py3-none-any.whl", hash = "sha256:7952325ffd516c05a8ad0858c74dff2c3343f136fe66a6002b2623dd1d43f223"},
|
||||||
@ -417,7 +417,7 @@ jmespath = [
|
|||||||
{file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"},
|
{file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"},
|
||||||
]
|
]
|
||||||
kobo = [
|
kobo = [
|
||||||
{file = "kobo-0.24.1.tar.gz", hash = "sha256:d5a30cc20c323f3e9d9b4b2e511650c4b98929b88859bd8cf57463876686e407"},
|
{file = "kobo-0.24.2.tar.gz", hash = "sha256:1b3c17260a93d933d2238884373fbf3485ecd417d930acf984285dc012410e2b"},
|
||||||
]
|
]
|
||||||
markupsafe = [
|
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_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
|
||||||
@ -558,8 +558,8 @@ pyyaml = [
|
|||||||
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
|
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
|
||||||
]
|
]
|
||||||
requests = [
|
requests = [
|
||||||
{file = "requests-2.28.0-py3-none-any.whl", hash = "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f"},
|
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
||||||
{file = "requests-2.28.0.tar.gz", hash = "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"},
|
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
||||||
]
|
]
|
||||||
rpm-py-installer = [
|
rpm-py-installer = [
|
||||||
{file = "rpm-py-installer-1.1.0.tar.gz", hash = "sha256:66e5f4f9247752ed386345642683103afaee50fb16928878a204bc12504b9bbe"},
|
{file = "rpm-py-installer-1.1.0.tar.gz", hash = "sha256:66e5f4f9247752ed386345642683103afaee50fb16928878a204bc12504b9bbe"},
|
||||||
@ -573,8 +573,8 @@ six = [
|
|||||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||||
]
|
]
|
||||||
typing-extensions = [
|
typing-extensions = [
|
||||||
{file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"},
|
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
|
||||||
{file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"},
|
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
|
||||||
]
|
]
|
||||||
urllib3 = [
|
urllib3 = [
|
||||||
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
|
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
|
||||||
|
@ -16,6 +16,7 @@ boto3 = "^1.24.12"
|
|||||||
xmltodict = "^0.13.0"
|
xmltodict = "^0.13.0"
|
||||||
requests = "^2.28.0"
|
requests = "^2.28.0"
|
||||||
kobo = "^0.24.1"
|
kobo = "^0.24.1"
|
||||||
|
attrs = "^21.4.0"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
pytest = "~5"
|
pytest = "~5"
|
||||||
|
Loading…
Reference in New Issue
Block a user