diff --git a/.coveragerc b/.coveragerc index a6e9f5abf5..85d6c7fe7e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -6,4 +6,4 @@ omit = splunk_add_on_ucc_framework/templates/input.module-template [report] -fail_under = 84.64 +fail_under = 84.70 diff --git a/docs/generated_files.md b/docs/generated_files.md index aab74c463b..09a27cc892 100644 --- a/docs/generated_files.md +++ b/docs/generated_files.md @@ -22,4 +22,5 @@ The following table describes the files generated by UCC framework. | inputs.xml | output/<YOUR_ADDON_NAME>/default/data/ui/views | Generates inputs.xml based on inputs configuration present in globalConfig, in `default/data/ui/views/inputs.xml` folder | | _redirect.xml | output/<YOUR_ADDON_NAME>/default/data/ui/views | Generates ta_name_redirect.xml file, if oauth is mentioned in globalConfig, in `default/data/ui/views/` folder. | | _.html | output/<YOUR_ADDON_NAME>/default/data/ui/alerts | Generates `alert_name.html` file based on alerts configuration present in globalConfig, in `default/data/ui/alerts` folder. | +| globalConfig.json | <source_dir> | Generates globalConfig.json file in the source code if globalConfig is not present in source directory at build time. | diff --git a/splunk_add_on_ucc_framework/commands/build.py b/splunk_add_on_ucc_framework/commands/build.py index 87ce43efcb..8baefc9a14 100644 --- a/splunk_add_on_ucc_framework/commands/build.py +++ b/splunk_add_on_ucc_framework/commands/build.py @@ -442,137 +442,123 @@ def generate( generated_files = [] gc_path = _get_and_check_global_config_path(source, config_path) - if gc_path: - logger.info(f"Using globalConfig file located @ {gc_path}") - global_config = global_config_lib.GlobalConfig.from_file(gc_path) - global_config.cleanup_unwanted_params() - # handle the update of globalConfig before validating - global_config_update.handle_global_config_update(global_config, gc_path) - try: - validator = global_config_validator.GlobalConfigValidator( - internal_root_dir, global_config - ) - validator.validate() - logger.info("globalConfig file is valid") - except exceptions.GlobalConfigValidatorException as e: - logger.error(f"globalConfig file is not valid. Error: {e}") - sys.exit(1) - global_config.update_addon_version(addon_version) - global_config.dump(gc_path) - logger.info( - f"Updated and saved add-on version in the globalConfig file to {addon_version}" + if not gc_path: + # create one in source directory if it doesn't exist + gc_path = os.path.sep.join( + [os.path.abspath(os.path.dirname(source)), "globalConfig.json"] ) - global_config.add_ucc_version(__version__) - global_config.expand() - if ta_name != global_config.product: - logger.error( - "Add-on name mentioned in globalConfig meta tag and that app.manifest are not same," - "please unify them to build the add-on." - ) - sys.exit(1) - global_config.parse_user_defined_handlers() - scheme = global_config_builder_schema.GlobalConfigBuilderSchema(global_config) - if global_config.has_pages(): - utils.recursive_overwrite( - os.path.join(internal_root_dir, "package"), - os.path.join(output_directory, ta_name), - ui_source_map, - ) - global_config_file = ( - "globalConfig.yaml" if gc_path.endswith(".yaml") else "globalConfig.json" - ) - output_build_path = os.path.join( - output_directory, ta_name, "appserver", "static", "js", "build" - ) - if not os.path.isdir(output_build_path): - # this path may not exist for the .conf-only add-ons - os.makedirs(output_build_path) - global_config.dump(os.path.join(output_build_path, global_config_file)) - logger.info("Copied globalConfig to output") - ucc_lib_target = os.path.join(output_directory, ta_name, "lib") - try: - install_python_libraries( - source, - ucc_lib_target, - python_binary_name, - includes_ui=True, - os_libraries=global_config.os_libraries, - pip_version=pip_version, - pip_legacy_resolver=pip_legacy_resolver, - pip_custom_flag=pip_custom_flag, - includes_oauth=global_config.has_oauth(), - ) - except SplunktaucclibNotFound as e: - logger.error(str(e)) - sys.exit(1) - logger.info( - f"Installed add-on requirements into {ucc_lib_target} from {source}" + global_config_lib.GlobalConfig.from_app_manifest(app_manifest).dump(gc_path) + + # no need of the condition as globalConfig would always exist + logger.info(f"Using globalConfig file located @ {gc_path}") + global_config = global_config_lib.GlobalConfig.from_file(gc_path) + global_config.cleanup_unwanted_params() + # handle the update of globalConfig before validating + global_config_update.handle_global_config_update(global_config, gc_path) + try: + validator = global_config_validator.GlobalConfigValidator( + internal_root_dir, global_config ) - generated_files.extend( - begin( - global_config=global_config, - input_dir=source, - output_dir=output_directory, - ucc_dir=internal_root_dir, - addon_name=ta_name, - app_manifest=app_manifest, - addon_version=addon_version, - has_ui=global_config.meta.get("isVisible", True), - ) + validator.validate() + logger.info("globalConfig file is valid") + except exceptions.GlobalConfigValidatorException as e: + logger.error(f"globalConfig file is not valid. Error: {e}") + sys.exit(1) + global_config.update_addon_version(addon_version) + global_config.dump(gc_path) + logger.info( + f"Updated and saved add-on version in the globalConfig file to {addon_version}" + ) + global_config.add_ucc_version(__version__) + global_config.expand() + if ta_name != global_config.product: + logger.error( + "Add-on name mentioned in globalConfig meta tag and that app.manifest are not same," + "please unify them to build the add-on." ) - # TODO: all FILES GENERATED object: generated_files, use it for comparison - if global_config.has_pages(): - builder_obj = RestBuilder(scheme, os.path.join(output_directory, ta_name)) - builder_obj.build() - _modify_and_replace_token_for_oauth_templates( - ta_name, - global_config, - output_directory, - ) - if global_config.has_inputs(): - logger.info("Generating inputs code") - _add_modular_input(ta_name, global_config, output_directory) - if global_config.has_alerts(): - logger.info("Generating alerts code") - alert_builder.generate_alerts(global_config, ta_name, output_directory) - - conf_file_names = [] - conf_file_names.extend(list(scheme.settings_conf_file_names)) - conf_file_names.extend(list(scheme.configs_conf_file_names)) - conf_file_names.extend(list(scheme.oauth_conf_file_names)) - - if global_config.has_dashboard(): - logger.info("Including dashboard") - dashboard_definition_json_path = os.path.join( - output_directory, - ta_name, - "appserver", - "static", - "js", - "build", - "custom", - ) - dashboard.generate_dashboard( - global_config, gc_path, ta_name, dashboard_definition_json_path - ) - - else: - global_config = None - conf_file_names = [] - logger.warning( - "Skipped generating UI components as globalConfig file does not exist" + sys.exit(1) + global_config.parse_user_defined_handlers() + scheme = global_config_builder_schema.GlobalConfigBuilderSchema(global_config) + if global_config.has_pages(): + utils.recursive_overwrite( + os.path.join(internal_root_dir, "package"), + os.path.join(output_directory, ta_name), + ui_source_map, ) - ucc_lib_target = os.path.join(output_directory, ta_name, "lib") + global_config_file = ( + "globalConfig.yaml" if gc_path.endswith(".yaml") else "globalConfig.json" + ) + output_build_path = os.path.join( + output_directory, ta_name, "appserver", "static", "js", "build" + ) + if not os.path.isdir(output_build_path): + # this path may not exist for the .conf-only add-ons + os.makedirs(output_build_path) + global_config.dump(os.path.join(output_build_path, global_config_file)) + logger.info("Copied globalConfig to output") + ucc_lib_target = os.path.join(output_directory, ta_name, "lib") + try: install_python_libraries( source, ucc_lib_target, python_binary_name, + includes_ui=True, + os_libraries=global_config.os_libraries, pip_version=pip_version, pip_legacy_resolver=pip_legacy_resolver, pip_custom_flag=pip_custom_flag, + includes_oauth=global_config.has_oauth(), ) - logger.info( - f"Installed add-on requirements into {ucc_lib_target} from {source}" + except SplunktaucclibNotFound as e: + logger.error(str(e)) + sys.exit(1) + logger.info(f"Installed add-on requirements into {ucc_lib_target} from {source}") + generated_files.extend( + begin( + global_config=global_config, + input_dir=source, + output_dir=output_directory, + ucc_dir=internal_root_dir, + addon_name=ta_name, + app_manifest=app_manifest, + addon_version=addon_version, + has_ui=global_config.meta.get("isVisible", True), + ) + ) + # TODO: all FILES GENERATED object: generated_files, use it for comparison + if global_config.has_pages(): + builder_obj = RestBuilder(scheme, os.path.join(output_directory, ta_name)) + builder_obj.build() + _modify_and_replace_token_for_oauth_templates( + ta_name, + global_config, + output_directory, + ) + if global_config.has_inputs(): + logger.info("Generating inputs code") + _add_modular_input(ta_name, global_config, output_directory) + if global_config.has_alerts(): + logger.info("Generating alerts code") + alert_builder.generate_alerts(global_config, ta_name, output_directory) + + conf_file_names = [] + conf_file_names.extend(list(scheme.settings_conf_file_names)) + conf_file_names.extend(list(scheme.configs_conf_file_names)) + conf_file_names.extend(list(scheme.oauth_conf_file_names)) + + if global_config.has_dashboard(): + logger.info("Including dashboard") + dashboard_definition_json_path = os.path.join( + output_directory, + ta_name, + "appserver", + "static", + "js", + "build", + "custom", + ) + dashboard.generate_dashboard( + global_config, gc_path, ta_name, dashboard_definition_json_path ) ignore_list = _get_ignore_list( diff --git a/splunk_add_on_ucc_framework/generators/doc_generator.py b/splunk_add_on_ucc_framework/generators/doc_generator.py index d51c8db03f..0cbc25b182 100644 --- a/splunk_add_on_ucc_framework/generators/doc_generator.py +++ b/splunk_add_on_ucc_framework/generators/doc_generator.py @@ -42,6 +42,12 @@ def generate_docs() -> None: f"| {f_tup.file_name} | output/<YOUR_ADDON_NAME>/{'/'.join(f_tup.file_path)} " f"| {f_tup.file_description} |" ) + # statically appending this line as we are generating it now + doc_content.append( + "| globalConfig.json | <source_dir> | " + "Generates globalConfig.json file in the source code if globalConfig " + "is not present in source directory at build time. |" + ) doc_content.append("\n") with open( join( diff --git a/tests/smoke/test_ucc_build.py b/tests/smoke/test_ucc_build.py index 14f2b3c203..3e0926880d 100644 --- a/tests/smoke/test_ucc_build.py +++ b/tests/smoke/test_ucc_build.py @@ -445,6 +445,12 @@ def test_ucc_generate_with_configuration_files_only(): "package_no_global_config", "package", ) + global_config_path = path.join( + package_folder, + path.pardir, + "globalConfig.json", + ) + assert not path.exists(global_config_path) build.generate(source=package_folder, output_directory=temp_dir) expected_folder = path.join( @@ -474,6 +480,10 @@ def test_ucc_generate_with_configuration_files_only(): expected_folder, actual_folder, ) + # the globalConfig would now always exist + assert path.exists(global_config_path) + # clean-up for tests + os.remove(global_config_path) def test_ucc_generate_openapi_with_configuration_files_only(): @@ -491,7 +501,16 @@ def test_ucc_generate_openapi_with_configuration_files_only(): actual_file_path = path.join( temp_dir, "Splunk_TA_UCCExample", "appserver", "static", "openapi.json" ) - assert not path.exists(actual_file_path) + # the openapi.json would now exist as globalConfig.json would always exist + assert path.exists(actual_file_path) + # clean-up for tests + os.remove( + path.join( + package_folder, + path.pardir, + "globalConfig.json", + ) + ) def test_ucc_build_verbose_mode(caplog): diff --git a/tests/testdata/expected_addons/expected_output_no_global_config/Splunk_TA_UCCExample/appserver/static/js/build/globalConfig.json b/tests/testdata/expected_addons/expected_output_no_global_config/Splunk_TA_UCCExample/appserver/static/js/build/globalConfig.json new file mode 100644 index 0000000000..3b7fd09f3d --- /dev/null +++ b/tests/testdata/expected_addons/expected_output_no_global_config/Splunk_TA_UCCExample/appserver/static/js/build/globalConfig.json @@ -0,0 +1,11 @@ +{ + "meta": { + "name": "Splunk_TA_UCCExample", + "restRoot": "Splunk_TA_UCCExample", + "displayName": "Splunk Add-on for UCC Example", + "version": "5.59.0+728642d3c", + "checkForUpdates": true, + "schemaVersion": "0.0.9", + "_uccVersion": "5.59.0" + } +} \ No newline at end of file diff --git a/tests/testdata/expected_addons/expected_output_no_global_config/Splunk_TA_UCCExample/appserver/static/openapi.json b/tests/testdata/expected_addons/expected_output_no_global_config/Splunk_TA_UCCExample/appserver/static/openapi.json new file mode 100644 index 0000000000..4ca8a163b9 --- /dev/null +++ b/tests/testdata/expected_addons/expected_output_no_global_config/Splunk_TA_UCCExample/appserver/static/openapi.json @@ -0,0 +1,40 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Splunk_TA_UCCExample", + "version": "5.59.0+728642d3c", + "description": "Splunk Add-on for UCC Example", + "contact": { + "name": "Splunk Inc." + } + }, + "servers": [ + { + "url": "https://{domain}:{port}/servicesNS/-/Splunk_TA_UCCExample", + "variables": { + "domain": { + "default": "localhost" + }, + "port": { + "default": "8089" + } + }, + "description": "Access via management interface" + } + ], + "components": { + "schemas": {}, + "securitySchemes": { + "BasicAuth": { + "type": "http", + "scheme": "basic" + } + } + }, + "paths": {}, + "security": [ + { + "BasicAuth": [] + } + ] +} \ No newline at end of file diff --git a/tests/unit/generators/test_doc_generator.py b/tests/unit/generators/test_doc_generator.py index 820b76b298..d132141663 100644 --- a/tests/unit/generators/test_doc_generator.py +++ b/tests/unit/generators/test_doc_generator.py @@ -40,6 +40,9 @@ def test_generate_docs(mock_open, mock_dirname, mock_realpath): "| ------------ | ------------ | ----------------- |", "| file1.conf | output/<YOUR_ADDON_NAME>/some/path | Conf file |", "| file2.xml | output/<YOUR_ADDON_NAME>/xml/path | XML file |", + "| globalConfig.json | <source_dir> | " + "Generates globalConfig.json file in the source code if globalConfig " + "is not present in source directory at build time. |", "\n", ] ) @@ -75,7 +78,10 @@ def test_generate_docs_empty_list(mock_open_file, mock_dirname, mock_realpath): "", "| File Name | File Location | File Description |", "| ------------ | ------------ | ----------------- |", - "\n", + "| globalConfig.json | <source_dir> | " + "Generates globalConfig.json file in the source code if globalConfig " + "is not present in source directory at build time. |" + "\n\n", ] ) diff --git a/tests/unit/test_install_python_libraries.py b/tests/unit/test_install_python_libraries.py index 303783bc22..cf26abb9e8 100644 --- a/tests/unit/test_install_python_libraries.py +++ b/tests/unit/test_install_python_libraries.py @@ -184,8 +184,8 @@ def test_install_libraries_when_no_splunktaucclib_is_present_but_has_ui(tmp_path with pytest.raises(SplunktaucclibNotFound) as exc: install_python_libraries( - str(tmp_path), - str(tmp_ucc_lib_target), + source_path=str(tmp_path), + ucc_lib_target=str(tmp_ucc_lib_target), python_binary_name="python3", includes_ui=True, )