6bd2a45eac
Fedora 35 image was move to the archives, hence is no more available on the container URL. The podman jobs are failing due to the 404 error. Container image version needs to be updated to use the Fedora 36. Closes-Bug: #2012903 Signed-off-by: Veronika Fisarova <vfisarov@redhat.com> Change-Id: I5d6c67e309709ff363575189ebc8c53541bbe7ff
293 lines
12 KiB
Python
Executable File
293 lines
12 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Copyright 2022 Red Hat, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import argparse
|
|
from distutils import spawn
|
|
import logging
|
|
import os
|
|
import pwd
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
DESCRIPTION = "Build and execute Validations from a container."
|
|
EPILOG = "Example: ./validation --run --cmd run --validation check-ftype,512e"
|
|
|
|
LOCAL_USER = os.environ.get('SUDO_USER', os.environ.get('USER'))
|
|
VALIDATIONS_LOG_BASEDIR = os.path.expanduser(f'~{LOCAL_USER}/validations')
|
|
CONTAINER_INVENTORY_PATH = '/tmp/inventory.yaml'
|
|
COMMUNITY_VALIDATION_PATH = \
|
|
os.path.expanduser(f'~{LOCAL_USER}/community-validations')
|
|
|
|
CONTAINERFILE_TEMPLATE = """
|
|
FROM %(image)s
|
|
|
|
LABEL name="VF dockerfile"
|
|
|
|
RUN groupadd -g %(gid)s -o %(user)s
|
|
RUN useradd -m -u %(uid)s -g %(gid)s -o -s /bin/bash %(user)s
|
|
|
|
RUN dnf install -y python3-pip gcc python3-devel libffi-devel jq openssh openssh-clients %(extra_pkgs)s
|
|
|
|
# Clone the Framework and common Validations
|
|
RUN python3 -m pip install validations-libs validations-common
|
|
|
|
# Clone user repository if provided
|
|
%(clone_user_repo)s
|
|
%(install_user_repo)s
|
|
|
|
#Setting up the default directory structure for both ansible,
|
|
#and the VF
|
|
RUN ln -s /usr/local/share/ansible /usr/share/ansible
|
|
|
|
ENV ANSIBLE_HOST_KEY_CHECKING false
|
|
ENV ANSIBLE_RETRY_FILES_ENABLED false
|
|
ENV ANSIBLE_KEEP_REMOTE_FILES 1
|
|
ENV ANSIBLE_REMOTE_USER %(user)s
|
|
ENV ANSIBLE_PRIVATE_KEY_FILE %(user_dir)s/containerhost_private_key
|
|
|
|
USER %(user)s
|
|
%(entrypoint)s
|
|
"""
|
|
|
|
|
|
class Validation(argparse.ArgumentParser):
|
|
"""Validation client implementation class"""
|
|
|
|
log = logging.getLogger(__name__ + ".Validation")
|
|
|
|
def __init__(self, description=DESCRIPTION, epilog=EPILOG):
|
|
"""Init validation paser"""
|
|
super(Validation, self).__init__(description=DESCRIPTION,
|
|
epilog=EPILOG)
|
|
|
|
def parser(self, parser):
|
|
"""Argument parser for validation"""
|
|
user_entry = pwd.getpwuid(int(os.environ.get('SUDO_UID', os.getuid())))
|
|
parser.add_argument('--run', '-R', action='store_true',
|
|
help=('Run Validation command. '
|
|
'Defaults to False'))
|
|
parser.add_argument('--interactive', '-i', action='store_true',
|
|
help=('Execute interactive Validation shell. '
|
|
'Defaults to False'))
|
|
parser.add_argument('--build', '-B', action='store_true',
|
|
help=('Build container even if it exists. '
|
|
'Defaults to False'))
|
|
parser.add_argument('--cmd', type=str, nargs=argparse.REMAINDER,
|
|
default=None,
|
|
help='Validation command you want to execute, '
|
|
'use --help to get more information. '
|
|
'Only available in non-interactive mode. ')
|
|
parser.add_argument('--user', '-u', type=str, default='validation',
|
|
help=('Set user in the container. '))
|
|
parser.add_argument('--user-home', type=str, default='/home/validation',
|
|
help=('User home path in the container. '
|
|
'Example: --user-home /home/validation '))
|
|
parser.add_argument('--uid', '-U', type=int, default=user_entry.pw_uid,
|
|
help=('User UID in container. '))
|
|
parser.add_argument('--gid', '-G', type=int, default=user_entry.pw_gid,
|
|
help=('Group UID in container. '))
|
|
parser.add_argument('--image', type=str, default='fedora:36',
|
|
help='Container base image. Defaults to fedora:36')
|
|
parser.add_argument('--extra-pkgs', type=str, default='',
|
|
help=('Extra packages to install in the container.'
|
|
'Comma or space separated list. '
|
|
'Defaults to empty string.'))
|
|
parser.add_argument('--volumes', '-v', type=str, action='append',
|
|
default=[],
|
|
help=('Volumes you want to add to the container. '
|
|
'Can be provided multiple times. '
|
|
'Defaults to []'))
|
|
parser.add_argument('--keyfile', '-K', type=str,
|
|
default=os.path.join(os.path.expanduser('~'),
|
|
'.ssh/id_rsa'),
|
|
help=('Keyfile path to bind-mount in container. '))
|
|
parser.add_argument('--engine', '-e', type=str, default='podman',
|
|
choices=['docker', 'podman'],
|
|
help='Container engine. Defaults to podman.')
|
|
parser.add_argument('--validation-log-dir', '-l', type=str,
|
|
default=VALIDATIONS_LOG_BASEDIR,
|
|
help=('Path where the log files and artifacts '
|
|
'will be located. '))
|
|
parser.add_argument('--repository', '-r', type=str,
|
|
default=None,
|
|
help=('Remote repository to clone validations '
|
|
'role from.'))
|
|
parser.add_argument('--branch', '-b', type=str, default='master',
|
|
help=('Remote repository branch to clone '
|
|
'validations from. Defaults to master'))
|
|
|
|
parser.add_argument('--inventory', '-I', type=str,
|
|
default=None,
|
|
help=('Path of the Ansible inventory. '
|
|
'It will be pulled to {} inside the '
|
|
'container. '.format(
|
|
CONTAINER_INVENTORY_PATH)))
|
|
parser.add_argument('--debug', '-D', action='store_true',
|
|
help='Toggle debug mode. Defaults to False.')
|
|
|
|
return parser.parse_args()
|
|
|
|
def take_action(self, parsed_args):
|
|
"""Take validation action"""
|
|
# Container params
|
|
self.image = parsed_args.image
|
|
self.extra_pkgs = parsed_args.extra_pkgs
|
|
self.engine = parsed_args.engine
|
|
self.validation_log_dir = parsed_args.validation_log_dir
|
|
self.keyfile = parsed_args.keyfile
|
|
self.interactive = parsed_args.interactive
|
|
self.cmd = parsed_args.cmd
|
|
self.user = parsed_args.user
|
|
self.user_home = parsed_args.user_home
|
|
self.uid = parsed_args.uid
|
|
self.gid = parsed_args.gid
|
|
self.repository = parsed_args.repository
|
|
self.branch = parsed_args.branch
|
|
self.debug = parsed_args.debug
|
|
|
|
build = parsed_args.build
|
|
run = parsed_args.run
|
|
# Validation params
|
|
self.inventory = parsed_args.inventory
|
|
self.volumes = parsed_args.volumes
|
|
|
|
if build:
|
|
self.build()
|
|
if run:
|
|
self.run()
|
|
|
|
def _print(self, string, debug=True):
|
|
if self.debug:
|
|
print(string)
|
|
|
|
def _generate_containerfile(self):
|
|
self._print('Generating "Containerfile"')
|
|
clone_user_repo, install_user_repo, entrypoint = "", "", ""
|
|
if self.repository:
|
|
clone_user_repo = ("RUN git clone {} -b {} "
|
|
"{}/user_repo").format(self.repository,
|
|
self.branch,
|
|
self.user_home)
|
|
install_user_repo = ("RUN cd {}/user_repo && \\"
|
|
"python3 -m pip install .").format(
|
|
self.user_home)
|
|
if self.interactive:
|
|
entrypoint = "ENTRYPOINT /usr/local/bin/validation"
|
|
param = {'image': self.image, 'extra_pkgs': self.extra_pkgs,
|
|
'clone_user_repo': clone_user_repo,
|
|
'install_user_repo': install_user_repo,
|
|
'entrypoint': entrypoint,
|
|
'user': self.user, 'uid': self.uid, 'gid': self.gid,
|
|
'user_dir': self.user_home}
|
|
with open('./Containerfile', 'w+') as containerfile:
|
|
containerfile.write(CONTAINERFILE_TEMPLATE % param)
|
|
|
|
def _check_container_cli(self, cli):
|
|
if not spawn.find_executable(cli):
|
|
raise RuntimeError(
|
|
"The container cli {} doesn't exist on this host".format(cli))
|
|
|
|
def _build_container(self):
|
|
self._print('Building image')
|
|
self._check_container_cli(self.engine)
|
|
cmd = [
|
|
self.engine,
|
|
'build',
|
|
'-t',
|
|
'localhost/validation',
|
|
'-f',
|
|
'Containerfile',
|
|
'.'
|
|
]
|
|
if os.getuid() != 0:
|
|
# build user needs to have sudo rights.
|
|
cmd.insert(0, 'sudo')
|
|
try:
|
|
subprocess.check_call(cmd)
|
|
except subprocess.CalledProcessError:
|
|
print('An error occurred!')
|
|
sys.exit(1)
|
|
|
|
def _create_volume(self, path):
|
|
try:
|
|
self._print("Attempt to create {}.".format(path))
|
|
os.mkdir(path)
|
|
except (OSError, FileExistsError) as e:
|
|
self._print(e)
|
|
pass
|
|
|
|
def _build_run_cmd(self):
|
|
self._check_container_cli(self.engine)
|
|
if self.interactive:
|
|
container_args = '-ti'
|
|
else:
|
|
container_args = '--rm'
|
|
cmd = [self.engine, 'run', container_args]
|
|
# Keyfile
|
|
cmd.append('-v%s:%s/containerhost_private_key:z' %
|
|
(self.keyfile, self.user_home))
|
|
# log path
|
|
self._create_volume(self.validation_log_dir)
|
|
if os.path.isdir(os.path.abspath(self.validation_log_dir)):
|
|
cmd.append('-v%s:%s/validations:z' %
|
|
(self.validation_log_dir, self.user_home))
|
|
# community validation path
|
|
self._create_volume(COMMUNITY_VALIDATION_PATH)
|
|
if os.path.isdir(os.path.abspath(COMMUNITY_VALIDATION_PATH)):
|
|
cmd.append('-v%s:%s/community-validations:z' %
|
|
(COMMUNITY_VALIDATION_PATH, self.user_home))
|
|
# Volumes
|
|
if self.volumes:
|
|
self._print('Adding volumes:')
|
|
for volume in self.volumes:
|
|
self._print(volume)
|
|
cmd.extend(['-v%s:z' % volume])
|
|
# Inventory
|
|
if self.inventory:
|
|
if os.path.isfile(os.path.abspath(self.inventory)):
|
|
cmd.append('-v%s:%s:z' % (
|
|
os.path.abspath(self.inventory),
|
|
CONTAINER_INVENTORY_PATH))
|
|
# Map host network config
|
|
cmd.append('--network=host')
|
|
# Container name
|
|
cmd.append('localhost/validation')
|
|
# Validation binary
|
|
cmd.append('validation')
|
|
if not self.interactive and self.cmd:
|
|
cmd.extend(self.cmd)
|
|
return cmd
|
|
|
|
def build(self):
|
|
self._generate_containerfile()
|
|
self._build_container()
|
|
|
|
def run(self):
|
|
self._print('Starting container')
|
|
cmd = self._build_run_cmd()
|
|
self._print('Running %s' % ' '.join(cmd))
|
|
try:
|
|
subprocess.check_call(cmd)
|
|
except subprocess.CalledProcessError:
|
|
print('An error occurred!')
|
|
sys.exit(2)
|
|
|
|
if __name__ == "__main__":
|
|
validation = Validation()
|
|
args = validation.parser(validation)
|
|
validation.take_action(args)
|