Files
app-gen-tool/app_gen_tool/generator.py
Reed, Joshua fcd0790c03 Refactor paths to use os.path methods.
Instead of using string manipulation and hardcoded
path separators, use the os.path.join method for better
consistency.

Add some initial pytest unit tests. Tests include one
to verify the generator can create a fluxcd application.

Apply flake8 corrections to entire codebase.
Apply pylint corrections to entire codebase.
Diabled bandit.

Update tox.ini file and test-requirements.txt

Make updates to .zuul.yaml to enable coverage tests
with helm. Coverage check will fail if coverage
falls below 55% for now. Zuul is using the same
version of helm as is current in STX; v3.12.2.

TODO - Make sure flake8, pylint, etc.. are good.

Test Plan:
PASS - Verify the generator can still build an example
       fluxcd app
TODO - Verify the generator can still buld an example
       armada app

Story: 2010937
Task: 48918
Task: 48917

Change-Id: I18a52cd98b730cc96b2b51cd4c1ab16785ad9857
Signed-off-by: Reed, Joshua <Joshua.Reed@windriver.com>
2023-11-27 07:46:00 -07:00

246 lines
8.5 KiB
Python

"""Generator Main Module."""
import getopt
import os
import re
import shutil
import sys
import yaml
from app_gen_tool.application import Application
def parse_yaml(yaml_in):
"""Pare generator input yaml file."""
yaml_data = ''
try:
with open(yaml_in, 'r', encoding='utf-8') as f:
yaml_data = yaml.safe_load(f)
except FileNotFoundError:
print('Error: {yaml_in} not found')
except Exception:
print('Error: Invalid yaml file')
return yaml_data
def check_manifest(manifest_data): # pylint: disable=too-many-return-statements
"""Check generator input yaml file for correct inputs."""
# TODO: check more mandatory key/values in manifest yaml
# check app values
if 'appName' not in manifest_data['appManifestFile-config']:
print('Error: \'appName\' is missing.')
return False
if 'namespace' not in manifest_data['appManifestFile-config']:
print('Error: \'namespace\' is missing.')
return False
if 'appVersion' not in manifest_data['appManifestFile-config']:
print('Error: \'appVersion\' is missing.')
return False
# check chartGroup values
if 'chartGroup' not in manifest_data['appManifestFile-config']:
print('Error: \'chartGroup\' is missing.')
return False
# check chart values
if 'chart' not in manifest_data['appManifestFile-config']:
print('Error: \'chart\' is missing.')
return False
for chart in manifest_data['appManifestFile-config']['chart']:
# check chart name
if 'name' not in chart:
print('Error: Chart attribute \'name\' is missing.')
return False
# check chart version
if 'version' not in chart:
print('Error: Chart attribute \'version\' is missing.')
return False
# check chart path, supporting: dir, git, tarball
if 'path' not in chart:
print(f'Error: Chart attribute \'path\' is missing in chart {chart["name"]}.')
return False
else:
# TODO: To support branches/tags in git repo
if chart['path'].endswith('.git'):
if 'subpath' not in chart:
print(
'Error: Chart attribute \'subpath\' is missing in '
f'chart {chart["name"]}.'
)
return False
chart['_pathType'] = 'git'
gitname = re.search(r'[^/]+(?=\.git$)', chart['path']).group()
if gitname:
chart['_gitname'] = gitname
else:
print(f'Error: Invalid \'path\' in chart {chart["name"]}.')
print(
' only \'local dir\', \'.git\', \'.tar.gz\', \'.tgz\' are supported'
)
return False
elif chart['path'].endswith('.tar.gz') or chart['path'].endswith('.tgz'):
if 'subpath' not in chart:
print(
'Error: Chart attribute \'subpath\' is missing in '
f'chart {chart["name"]}.'
)
return False
chart['_pathType'] = 'tarball'
tarname = \
re.search(
r'[^/]+(?=\.tgz)|[^/]+(?=\.tar\.gz)',
chart['path']
).group()
if tarname:
chart['_tarname'] = tarname
else:
print(f'Error: Invalid \'path\' in chart {chart["name"]}.')
print(
' only \'local dir\', \'.git\', \'.tar.gz\', \'.tgz\' are supported'
)
return False
else:
if not os.path.isdir(chart['path']):
print(f'Error: Invalid \'path\' in chart {chart["name"]}.')
print(
' only \'local dir\', \'.git\', \'.tar.gz\', \'.tgz\' are supported'
)
return False
chart['_pathType'] = 'dir'
return True
def print_help():
"""Print CLI Helm Menu."""
print('StarlingX User Application Generator')
print('')
print('Usage:')
print(' python app-gen.py [Option]')
print('')
print('Options:')
print(' -i, --input yaml_file generate app from yaml_file')
print(' -o, --output folder generate app to output folder')
print(' -t, --type package select Armada, Fluxcd or Both '
'packaging')
print(' --overwrite overwrite the output dir')
print(' --no-package does not create app tarball')
print(' --package-only only creates tarball from dir')
print(' -h, --help help for StarlingX User Application '
'Generator')
def check_input_file(app_data) -> bool:
"""Check if input file passed by user is valid."""
if not app_data:
print('Parse yaml error')
return False
if not check_manifest(app_data):
print('Application manifest is not valid')
return False
return True
def check_app_directory(app_out, overwrite, package_only) -> bool:
"""Checks if the user gave enough information to modify or create app folder directory."""
if os.path.exists(app_out) and not overwrite and not package_only:
print(f'Output folder {app_out} exists, please remove it, use '
f'--overwrite, or use --package-only.')
return False
if not os.path.exists(app_out) and package_only:
print(f'Output folder {app_out} does not exist, cannot package it.')
return False
return True
def create_app_directories(app_out, overwrite):
"""Creates or/and removes the app generated directories"""
if os.path.exists(app_out) and overwrite:
shutil.rmtree(app_out)
if not os.path.exists(app_out):
os.makedirs(app_out)
def generate_app(app, package_type, no_package, package_only):
"""Generate app based on application packaging type."""
if package_type == 'armada' or package_type == 'both': # pylint: disable=consider-using-in
app.gen_armada_app(app.output_folder, no_package, package_only)
if package_type == 'fluxcd' or package_type == 'both': # pylint: disable=consider-using-in
app.gen_fluxcd_app(app.output_folder, no_package, package_only)
def main(argv):
"""Main Method with argument parsing."""
input_file = ''
output_folder = '.'
package_type = ''
overwrite = False
package_only = False
no_package = False
try:
options, _ = \
getopt.getopt(
argv,
'hi:o:t:',
[
'help',
'input=',
'output=',
'type=',
'overwrite',
'no-package',
'package-only'
]
)
except getopt.GetoptError:
print('Error: Invalid argument')
sys.exit(1)
for option, value in options:
if option in ('-h', '--help'):
print_help()
if option in ('--overwrite'):
overwrite = True
if option in ('-i', '--input', '--input='):
input_file = value
if option in ('-o', '--output', '--output='):
output_folder = value
if option in ('-t', '--type', '--type='):
package_type = value.lower()
if option in ('--no-package'):
no_package = True
if option in ('--package-only'):
package_only = True
if overwrite and package_only:
print('Error: Selecting both overwrite and package-only is not allowed'
'. Please consult our README if further clarification is needed')
sys.exit(1)
if not package_type:
print('Error: Select type of packaging (armada/fluxcd/both)')
sys.exit(1)
if not os.path.isfile(os.path.abspath(input_file)):
print('Error: input file not found')
sys.exit(1)
if input_file:
app_data = parse_yaml(input_file)
if not check_input_file(app_data):
sys.exit(1)
output_folder = os.path.abspath(output_folder)
app = Application(app_data, package_type, output_folder)
if not check_app_directory(app.output_folder, overwrite, package_only):
sys.exit(1)
create_app_directories(app.output_folder, overwrite)
generate_app(app, package_type, no_package, package_only)