Command line method to validate app metadata.
Build a console script that allows application tarball metadata to be validated directly via command line. Build a console script that will allow individual apps to execute a test via tox, where there is a single entry point. Whenever its updated with additional checks, then tox will also be updated retroactively without the need to update each app. Fixes bug in validate_metadata_file object where a missing app_name or app_version would be allowed. Fixed check for None to check for an empty string instead. Update - Catch edge case where metadata.yaml is not found and validate_metadata_file provides a false positive. Usage: For a person: sysinv-app verify-metadata /path/to/app.tgz For tox automation: sysinv-app tox ./path or ./path/to/metadata.yaml Test Plan: PASS - Manually copy files onto AIO-SX and verify that the code funcitons as expected. Using an existing application tarball verify that it passes checks. PASS - Build AIO-SX ISO and verify that software functions as expected on a fresh install. Story: 2010929 Task: 49194 Change-Id: I97b000489b40b6dae902e8a7b914edb9fbe5558f Signed-off-by: Joshua Reed <joshua.reed@windriver.com>
This commit is contained in:
parent
33cbbaf51f
commit
5265e67cb4
|
@ -18,6 +18,7 @@ usr/bin/cert-mon
|
||||||
usr/bin/kube-cert-rotation.sh
|
usr/bin/kube-cert-rotation.sh
|
||||||
usr/bin/sysinv-agent
|
usr/bin/sysinv-agent
|
||||||
usr/bin/sysinv-api
|
usr/bin/sysinv-api
|
||||||
|
usr/bin/sysinv-app
|
||||||
usr/bin/sysinv-conductor
|
usr/bin/sysinv-conductor
|
||||||
usr/bin/sysinv-dbsync
|
usr/bin/sysinv-dbsync
|
||||||
usr/bin/sysinv-dnsmasq-lease-update
|
usr/bin/sysinv-dnsmasq-lease-update
|
||||||
|
|
|
@ -28,6 +28,7 @@ packages =
|
||||||
[entry_points]
|
[entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
sysinv-api = sysinv.cmd.api:main
|
sysinv-api = sysinv.cmd.api:main
|
||||||
|
sysinv-app = sysinv.cmd.applications:main
|
||||||
sysinv-agent = sysinv.cmd.agent:main
|
sysinv-agent = sysinv.cmd.agent:main
|
||||||
sysinv-dbsync = sysinv.cmd.dbsync:main
|
sysinv-dbsync = sysinv.cmd.dbsync:main
|
||||||
sysinv-conductor = sysinv.cmd.conductor:main
|
sysinv-conductor = sysinv.cmd.conductor:main
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2023 Wind River Systems, Inc.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
"""
|
||||||
|
System Inventory Application Verification & Validation Utility.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from sysinv.common import service
|
||||||
|
from sysinv.common.app_metadata import verify_application
|
||||||
|
from sysinv.common.app_metadata import verify_application_tarball
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
def verify_application_tox(path):
|
||||||
|
"""Verify an application using tox.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: This is the direct path to the metadata.yaml file
|
||||||
|
"""
|
||||||
|
verify_application(path)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_application_metadata(path):
|
||||||
|
"""Verify an application from tarball format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: This is a path to the app tarball.
|
||||||
|
"""
|
||||||
|
verify_application_tarball(path)
|
||||||
|
|
||||||
|
|
||||||
|
def add_action_parsers(subparsers):
|
||||||
|
parser = subparsers.add_parser('verify-metadata')
|
||||||
|
parser.set_defaults(func=verify_application_metadata)
|
||||||
|
parser.add_argument('path', nargs='?')
|
||||||
|
|
||||||
|
parser = subparsers.add_parser('tox')
|
||||||
|
parser.set_defaults(func=verify_application_tox)
|
||||||
|
parser.add_argument('path', nargs='?')
|
||||||
|
|
||||||
|
|
||||||
|
CONF.register_cli_opt(
|
||||||
|
cfg.SubCommandOpt('action',
|
||||||
|
title='actions',
|
||||||
|
help='Perform the application check operation',
|
||||||
|
handler=add_action_parsers))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
service.prepare_service(sys.argv)
|
||||||
|
CONF.action.func(CONF.action.path)
|
|
@ -8,8 +8,11 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
import glob
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import six
|
import six
|
||||||
|
import tempfile
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
@ -22,6 +25,11 @@ from sysinv.common import utils
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _locate_metadata_file(directory):
|
||||||
|
|
||||||
|
return glob.glob(directory + '/**/metadata.yaml', recursive=True)
|
||||||
|
|
||||||
|
|
||||||
def validate_metadata_file(path, metadata_file, upgrade_from_release=None):
|
def validate_metadata_file(path, metadata_file, upgrade_from_release=None):
|
||||||
""" Find and validate the metadata file in a given directory.
|
""" Find and validate the metadata file in a given directory.
|
||||||
|
|
||||||
|
@ -303,6 +311,7 @@ def validate_metadata_file(path, metadata_file, upgrade_from_release=None):
|
||||||
app_version = ''
|
app_version = ''
|
||||||
patches = []
|
patches = []
|
||||||
metadata_path = os.path.join(path, metadata_file)
|
metadata_path = os.path.join(path, metadata_file)
|
||||||
|
|
||||||
if os.path.isfile(metadata_path):
|
if os.path.isfile(metadata_path):
|
||||||
with io.open(metadata_path, 'r', encoding='utf-8') as f:
|
with io.open(metadata_path, 'r', encoding='utf-8') as f:
|
||||||
try:
|
try:
|
||||||
|
@ -313,11 +322,15 @@ def validate_metadata_file(path, metadata_file, upgrade_from_release=None):
|
||||||
# metadata file does not have the key(s)
|
# metadata file does not have the key(s)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if (app_name is None or
|
# Have to check for empty string instead of None.
|
||||||
app_version is None):
|
if app_name == '' or app_name is None:
|
||||||
raise exception.SysinvException(_(
|
raise exception.SysinvException(_(
|
||||||
"Invalid %s: app_name or/and app_version "
|
"Invalid %s: app_name is empty or None." % metadata_file)
|
||||||
"is/are None." % metadata_file))
|
)
|
||||||
|
if app_version == '' or app_version is None:
|
||||||
|
raise exception.SysinvException(_(
|
||||||
|
"Invalid %s: app_version is empty or None." % metadata_file)
|
||||||
|
)
|
||||||
|
|
||||||
behavior = validate_dict_field(doc,
|
behavior = validate_dict_field(doc,
|
||||||
constants.APP_METADATA_BEHAVIOR)
|
constants.APP_METADATA_BEHAVIOR)
|
||||||
|
@ -427,3 +440,125 @@ def validate_metadata_file(path, metadata_file, upgrade_from_release=None):
|
||||||
check_release, release_patches))
|
check_release, release_patches))
|
||||||
|
|
||||||
return app_name, app_version, patches
|
return app_name, app_version, patches
|
||||||
|
|
||||||
|
|
||||||
|
def verify_application_tarball(path: str) -> None:
|
||||||
|
"""Verify metadata withing an application tarball directly.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: str: An absolute path to application tarball.
|
||||||
|
"""
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dirname:
|
||||||
|
|
||||||
|
# Copy tarball
|
||||||
|
shutil.copy(path, temp_dirname)
|
||||||
|
|
||||||
|
if not utils.extract_tarfile(temp_dirname, path):
|
||||||
|
raise Exception("Unable to extract tarball")
|
||||||
|
|
||||||
|
# If checksum file is included in the tarball, verify its contents.
|
||||||
|
if not utils.verify_checksum(temp_dirname):
|
||||||
|
raise Exception("Unable to verify app tarball checksum")
|
||||||
|
|
||||||
|
try:
|
||||||
|
name, version, _ = validate_metadata_file(
|
||||||
|
temp_dirname, constants.APP_METADATA_FILE)
|
||||||
|
|
||||||
|
if name == '' and version == '':
|
||||||
|
message = "Application Metadata file not found! Failure!"
|
||||||
|
LOG.error(message)
|
||||||
|
raise Exception(message)
|
||||||
|
else:
|
||||||
|
LOG.info(
|
||||||
|
f"Application Metadata for App: {name}, "
|
||||||
|
f"Ver: {version} succeeded!"
|
||||||
|
)
|
||||||
|
except exception.SysinvException as e:
|
||||||
|
LOG.info("Application Metadata Verification Failed!")
|
||||||
|
raise exception.SysinvException(_(
|
||||||
|
"metadata verification failed. {}".format(e)))
|
||||||
|
|
||||||
|
|
||||||
|
def verify_application_metadata_file(path: str) -> bool:
|
||||||
|
"""Verify metadata withing an that is in a repository or not in tarball.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: str: An absolute path to application metadata.yaml or an absolute
|
||||||
|
path to the folder it resides in.
|
||||||
|
"""
|
||||||
|
is_verified = False
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dirname:
|
||||||
|
|
||||||
|
# The input may be either a file, or a directory. Depending on which
|
||||||
|
# use the appropriate shutil copy function.
|
||||||
|
|
||||||
|
final_dir_name = temp_dirname
|
||||||
|
|
||||||
|
if os.path.isfile(path):
|
||||||
|
shutil.copy(path, temp_dirname)
|
||||||
|
else:
|
||||||
|
shutil.copytree(path, temp_dirname, dirs_exist_ok=True)
|
||||||
|
|
||||||
|
metadata_path_hits = _locate_metadata_file(temp_dirname)
|
||||||
|
|
||||||
|
if len(metadata_path_hits) == 0:
|
||||||
|
message = \
|
||||||
|
f"Error: Metadata file not found in directory: {path}"
|
||||||
|
LOG.error(message)
|
||||||
|
raise Exception(message)
|
||||||
|
elif len(metadata_path_hits) > 1:
|
||||||
|
message = \
|
||||||
|
"Error: Found More than One Application Metadata File! " \
|
||||||
|
"There should only be one!"
|
||||||
|
LOG.error(message)
|
||||||
|
raise Exception(message)
|
||||||
|
else:
|
||||||
|
final_dir_name = os.path.dirname(metadata_path_hits[-1])
|
||||||
|
|
||||||
|
try:
|
||||||
|
name, version, _ = validate_metadata_file(
|
||||||
|
final_dir_name, constants.APP_METADATA_FILE)
|
||||||
|
|
||||||
|
if name == '' and version == '':
|
||||||
|
message = "Application Metadata file not found! Failure!"
|
||||||
|
LOG.error(message)
|
||||||
|
is_verified = False
|
||||||
|
raise Exception(message)
|
||||||
|
else:
|
||||||
|
LOG.info(
|
||||||
|
f"Application Metadata for App: {name}, "
|
||||||
|
f"Ver: {version} succeeded!"
|
||||||
|
)
|
||||||
|
is_verified = True
|
||||||
|
except exception.SysinvException as e:
|
||||||
|
LOG.info("Application Metadata Verification Failed!")
|
||||||
|
raise exception.SysinvException(_(
|
||||||
|
"metadata verification failed. {}".format(e)))
|
||||||
|
|
||||||
|
return is_verified
|
||||||
|
|
||||||
|
|
||||||
|
def verify_application(path: str) -> bool:
|
||||||
|
"""Wrapper for all possible tests or checks. This is what Tox will use.
|
||||||
|
|
||||||
|
Whenever a new check is needed, that should be added here as another
|
||||||
|
condition.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: str: An absolute path to application metadata.yaml or an absolute
|
||||||
|
path to the folder it resides in.
|
||||||
|
"""
|
||||||
|
is_verified = False
|
||||||
|
|
||||||
|
# For each check, add a try except so there is granularity.
|
||||||
|
# This test will exit on the first failure detected.
|
||||||
|
try:
|
||||||
|
verify_application_metadata_file(path)
|
||||||
|
is_verified = True
|
||||||
|
except exception.SysinvException as e:
|
||||||
|
LOG.info("Application Metadata Verification Failed!")
|
||||||
|
raise exception.SysinvException(_(
|
||||||
|
"metadata verification failed. {}".format(e)))
|
||||||
|
|
||||||
|
return is_verified
|
||||||
|
|
Loading…
Reference in New Issue