-
Notifications
You must be signed in to change notification settings - Fork 170
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
utilities and case scripts are provided, and cover Image type:ami vmdk qcow2 anaconda-iso raw container image reference: quay.io/centos-bootc/centos-bootc:stream9, quay.io/centos-bootc/fedora-bootc:eln, localhost/bootc:eln Signed-off-by: Chunfu Wen <[email protected]>
- Loading branch information
Showing
5 changed files
with
663 additions
and
0 deletions.
There are no files selected for viewing
346 changes: 346 additions & 0 deletions
346
provider/bootc_image_builder/bootc_image_build_utils.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,346 @@ | ||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
# | ||
# Copyright Red Hat | ||
# | ||
# SPDX-License-Identifier: GPL-2.0 | ||
# | ||
# Author: [email protected] | ||
# | ||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
"""Helper functions for bootc image builder""" | ||
|
||
import logging | ||
import json | ||
import os | ||
import shutil | ||
import time | ||
import textwrap | ||
import pathlib | ||
|
||
from avocado.utils import path, process | ||
from avocado.core import exceptions | ||
from virttest import utils_package | ||
|
||
LOG = logging.getLogger('avocado.' + __name__) | ||
|
||
|
||
def install_bib_packages(): | ||
""" | ||
install necessary bootc image builder necessary packages | ||
""" | ||
package_list = ["podman", "skopeo", "virt-install", "curl", "virt-manager"] | ||
for pkg in package_list: | ||
try: | ||
path.find_command(pkg) | ||
except path.CmdNotFoundError: | ||
utils_package.package_install(pkg) | ||
|
||
|
||
def podman_command_build(bib_image_url, disk_image_type, image_ref, config=None, local_container=False, tls_verify="true", chownership=None, options=None, **dargs): | ||
""" | ||
Use podman run command to launch bootc image builder | ||
:param bib_image_url: bootc image builder url | ||
:param disk_image_type: image type to build [qcow2, ami] (default "qcow2") | ||
:param image_ref: image reference | ||
:param config: config file | ||
:param local_container: whether use local container image | ||
:param tls_verify: whether verify tls connection | ||
:param local_container: whether use local container image | ||
:param chownership: whether change output ownership | ||
:param options: additional options if needed | ||
:param dargs: standardized virsh function API keywords | ||
:return: CmdResult object | ||
""" | ||
if not os.path.exists("/var/lib/libvirt/images/output"): | ||
os.makedirs("/var/lib/libvirt/images/output") | ||
cmd = "sudo podman run --rm -it --privileged --pull=newer --security-opt label=type:unconfined_t -v /var/lib/libvirt/images/output:/output" | ||
if config: | ||
cmd += " -v %s:/config.json " % config | ||
|
||
if local_container: | ||
cmd += " -v /var/lib/containers/storage:/var/lib/containers/storage " | ||
|
||
cmd += " %s " \ | ||
" --type %s --tls-verify=%s " % (bib_image_url, disk_image_type, tls_verify) | ||
|
||
if config: | ||
cmd += " --config /config.json " | ||
|
||
if local_container: | ||
cmd += " --local %s " % image_ref | ||
else: | ||
cmd += " %s " % image_ref | ||
|
||
if chownership: | ||
cmd += " --chown %s " % chownership | ||
|
||
if options is not None: | ||
cmd += " %s" % options | ||
|
||
debug = dargs.get("debug", True) | ||
|
||
ignore_status = dargs.get("ignore_status", False) | ||
timeout = int(dargs.get("timeout", "1800")) | ||
LOG.debug("the whole podman command: %s\n" % cmd) | ||
|
||
ret = process.run( | ||
cmd, timeout=timeout, verbose=debug, ignore_status=ignore_status, shell=True) | ||
|
||
ret.stdout = ret.stdout_text | ||
ret.stderr = ret.stderr_text | ||
return ret | ||
|
||
|
||
def podman_login(podman_username, podman_password, registry): | ||
""" | ||
Use podman to login in registry | ||
:param podman_username: podman username | ||
:param podman_password: podman password | ||
:param registry: registry to login | ||
:return: CmdResult object | ||
""" | ||
command = "sudo podman login -u='%s' -p='%s' %s " % (podman_username, podman_password, registry) | ||
process.run( | ||
command, timeout=60, verbose=True, ignore_status=False, shell=True) | ||
|
||
|
||
def podman_push(podman_username, podman_password, registry, container_url): | ||
""" | ||
Use podman image to registry | ||
:param podman_username: podman username | ||
:param podman_password: podman password | ||
:param registry: registry to login | ||
:param container_url: image url | ||
:return: CmdResult object | ||
""" | ||
podman_login(podman_username, podman_password, registry) | ||
command = "sudo podman push %s " % container_url | ||
process.run( | ||
command, timeout=1200, verbose=True, ignore_status=False, shell=True) | ||
|
||
|
||
def create_config_json_file(folder, username, password): | ||
""" | ||
install necessary bootc image builder necessary packages | ||
:param folder: the folder that config.json reside in | ||
:param username: user name | ||
:param password: user password | ||
""" | ||
public_key_path = os.path.join(os.path.expanduser("~/.ssh/"), "id_rsa.pub") | ||
if not os.path.exists(public_key_path): | ||
LOG.debug("public key doesn't exist, please create one") | ||
key_gen_cmd = "ssh-keygen -q -t rsa -N '' <<< $'\ny' >/dev/null 2>&1" | ||
process.run(key_gen_cmd, shell=True, ignore_status=False) | ||
|
||
with open(public_key_path, 'r') as ssh: | ||
key_value = ssh.read().rstrip() | ||
cfg = { | ||
"blueprint": { | ||
"customizations": { | ||
"user": [ | ||
{ | ||
"name": username, | ||
"password": password, | ||
"groups": ["wheel"], | ||
"key": "%s" % key_value, | ||
}, | ||
], | ||
}, | ||
}, | ||
} | ||
LOG.debug("what is cfg:%s", cfg) | ||
config_json_path = pathlib.Path(folder) / "config.json" | ||
config_json_path.write_text(json.dumps(cfg), encoding="utf-8") | ||
return os.path.join(folder, "config.json") | ||
|
||
|
||
def create_and_build_container_file(folder, build_container, container_tag, add_vmware_tool=False): | ||
""" | ||
Create container file and build container tag | ||
:param folder: the folder that config.json reside in | ||
:param build_container: the base container image | ||
:param container_tag: container tag | ||
:param add_vmware_tool: whether add vmware tool into container | ||
""" | ||
# clean up existed image | ||
clean_image_cmd = "sudo podman rmi %s" % container_tag | ||
process.run(clean_image_cmd, shell=True, ignore_status=True) | ||
etc_config = '' | ||
dnf_vmware_tool = '' | ||
|
||
# create VmWare tool folder | ||
if add_vmware_tool: | ||
vmware_tool_path = os.path.join(folder, "etc/vmware-tools/") | ||
if not os.path.exists(vmware_tool_path): | ||
os.makedirs(vmware_tool_path) | ||
etc_config = "COPY etc/ /etc/" | ||
dnf_vmware_tool = "dnf -y install open-vm-tools && dnf clean all && systemctl enable vmtoolsd.service && " | ||
|
||
download_vmware_config_cmd = "curl https://gitlab.com/bootc-org/" \ | ||
"examples/-/raw/main/vmware/etc/vmware-tools/tools.conf > %s/tools.conf" % vmware_tool_path | ||
process.run(download_vmware_config_cmd, shell=True, verbose=True, ignore_status=True) | ||
|
||
container_path = pathlib.Path(folder) / "Containerfile_tmp" | ||
shutil.copy("/etc/yum.repos.d/beaker-BaseOS.repo", folder) | ||
shutil.copy("/etc/yum.repos.d/beaker-AppStream.repo", folder) | ||
container_path.write_text(textwrap.dedent(f"""\n | ||
FROM {build_container} | ||
{etc_config} | ||
COPY beaker-BaseOS.repo /etc/yum.repos.d/ | ||
COPY beaker-AppStream.repo /etc/yum.repos.d/ | ||
RUN {dnf_vmware_tool} dnf install -y vim && dnf clean all | ||
"""), encoding="utf8") | ||
build_cmd = "sudo podman build -t %s -f %s" % (container_tag, str(container_path)) | ||
process.run(build_cmd, shell=True, ignore_status=False) | ||
|
||
|
||
def create_and_start_vmware_vm(params): | ||
""" | ||
prepare environment, upload vmdk, create and start vm | ||
@param params: one dictionary wrapping various parameter | ||
""" | ||
install_vmware_govc_tool() | ||
setup_vCenter_env(params) | ||
delete_vm_if_existed(params) | ||
import_vmdk_to_vCenter(params) | ||
create_vm_in_vCenter(params) | ||
attach_disk_to_vm(params) | ||
power_on_vm(params) | ||
|
||
|
||
def install_vmware_govc_tool(): | ||
""" | ||
Download VmWare govc tool and install it | ||
""" | ||
govc_install_cmd = "curl -L -o - 'https://github.com/vmware/govmomi/releases/latest/download/govc_Linux_x86_64.tar.gz' " \ | ||
"| tar -C /usr/local/bin -xvzf - govc" | ||
if not os.path.exists("/usr/local/bin/govc"): | ||
process.run(govc_install_cmd, shell=True, ignore_status=False) | ||
|
||
|
||
def setup_vCenter_env(params): | ||
""" | ||
Download VmWare govc tool and install it | ||
@param params: one dictionary wrapping various parameter | ||
""" | ||
# vCenter information | ||
os.environ["GOVC_URL"] = params.get("GOVC_URL") | ||
os.environ["GOVC_USERNAME"] = params.get("GOVC_USERNAME") | ||
os.environ["GOVC_PASSWORD"] = params.get("GOVC_PASSWORD") | ||
os.environ["DATA_CENTER"] = params.get("DATA_CENTER") | ||
os.environ["DATA_STORE"] = params.get("DATA_STORE") | ||
os.environ["GOVC_INSECURE"] = "true" | ||
process.run("govc about", shell=True, ignore_status=False) | ||
|
||
|
||
def import_vmdk_to_vCenter(params): | ||
""" | ||
import vmdk into vCenter | ||
@param params: one dictionary wrapping various parameter | ||
""" | ||
import_cmd = f"govc import.vmdk -force {params.get('vm_disk_image_path')}" | ||
process.run(import_cmd, shell=True, verbose=True, ignore_status=False) | ||
|
||
|
||
def create_vm_in_vCenter(params): | ||
""" | ||
create VM in vCenter | ||
@param params: one dictionary wrapping various parameter | ||
""" | ||
create_cmd = "govc vm.create -net='VM Network' -on=false -c=2 " \ | ||
"-m=4096 -g=centos9_64Guest -firmware=%s %s" % (params.get("firmware"), params.get("vm_name_bootc")) | ||
process.run(create_cmd, shell=True, verbose=True, ignore_status=False) | ||
|
||
|
||
def attach_disk_to_vm(params): | ||
""" | ||
attach disk to VM in vCenter | ||
@param params: one dictionary wrapping various parameter | ||
""" | ||
vm_name = params.get("vm_name_bootc") | ||
attach_cmd = "govc vm.disk.attach -vm %s" \ | ||
" -controller %s -link=false -disk=%s/%s.vmdk" % (vm_name, params.get("controller"), vm_name, vm_name) | ||
process.run(attach_cmd, shell=True, verbose=True, ignore_status=False) | ||
|
||
|
||
def power_on_vm(params): | ||
""" | ||
power on VM in vCenter | ||
@param params: one dictionary wrapping various parameter | ||
""" | ||
vm_name = params.get("vm_name_bootc") | ||
power_on_cmd = "govc vm.power -on=true %s" % vm_name | ||
process.run(power_on_cmd, shell=True, verbose=True, ignore_status=False) | ||
time.sleep(40) | ||
state_cmd = "govc vm.info -json %s |jq -r .virtualMachines[0].summary.runtime.powerState" % vm_name | ||
result = process.run(state_cmd, shell=True, verbose=True, ignore_status=False).stdout_text.strip() | ||
if result not in "poweredOn": | ||
raise exceptions.TestFail('The VM state is not powered on,actually is: %s') % result | ||
|
||
|
||
def delete_vm_if_existed(params): | ||
""" | ||
delete vm if existed | ||
@param params: one dictionary wrapping various parameter | ||
""" | ||
vm_name = params.get("vm_name_bootc") | ||
find_cmd = "govc find / -type m -name %s" % vm_name | ||
cmd_result = process.run(find_cmd, shell=True, verbose=True, ignore_status=True).stdout_text | ||
if cmd_result: | ||
vm_path = cmd_result.strip() | ||
delete_cmd = "govc vm.destroy %s && govc datastore.rm -f %s" % (vm_name, vm_name) | ||
process.run(delete_cmd, shell=True, verbose=True, ignore_status=True) | ||
|
||
|
||
def parse_container_url(params): | ||
""" | ||
Parse repository information from container url | ||
@param params: wrapped dictionary containing url | ||
""" | ||
container_url = params.get("container_url") | ||
repository_info = container_url.split('/')[-1] | ||
repository_name = repository_info.split(':')[0] | ||
if "localhost" in container_url: | ||
repository_name = "localhost-%s" % repository_name | ||
return repository_name | ||
|
||
|
||
def convert_disk_image_name(params): | ||
""" | ||
Convert disk type image name | ||
@param params: wrapped dictionary containing parameters | ||
""" | ||
repository_name = parse_container_url(params) | ||
origin_disk_name, extension = os.path.splitext(params.get("output_name")) | ||
dest_disk_name = "%s-%s%s" % (origin_disk_name, repository_name, extension) | ||
return dest_disk_name | ||
|
||
|
||
def get_group_and_user_ids(folder_path): | ||
try: | ||
stat_info = os.stat(folder_path) | ||
gid = stat_info.st_gid | ||
uid = stat_info.st_uid | ||
return gid, uid | ||
except FileNotFoundError: | ||
LOG.debug(f"Folder '{folder_path}' not found.") | ||
return None, None | ||
except Exception as ex: | ||
LOG.debug(f"Error occurred: {ex}") | ||
return None, None |
Oops, something went wrong.