Skip to content

Commit

Permalink
feat: build commands produces detailed output of what happened (#927)
Browse files Browse the repository at this point in the history
This is a copy of the PR @MateuszMa created but due to how the
repository was previously organized I needed to create another one
myself.

This PR introduces a new option `-v` to the `ucc-gen build` command
which shows detailed information about created/copied/modified/conflict
files after build is complete.

---------

Co-authored-by: Mateusz Macalik <mmacalik@splunk.com>
  • Loading branch information
artemrys and MateuszMa authored Dec 11, 2023
1 parent 19998ad commit 3fad1a2
Show file tree
Hide file tree
Showing 19 changed files with 758 additions and 2 deletions.
18 changes: 18 additions & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,24 @@ It takes the following parameters:
Accepts absolute paths as well.
* `--python-binary-name` - [optional] Python binary name to use when
installing Python libraries. Default: `python3`.
* `-v` / `--verbose` - [optional] show detailed information about
created/copied/modified/conflict files after build is complete.
This option is in experimental mode. Default: `False`.

#### Verbose mode

Available from `v5.33.0`.

Running `ucc-gen build -v` or `ucc-gen build --verbose` prints additional information about
what was exactly created / copied / modified / conflict after the build is complete. It does
not scan `lib` folder due to the nature of the folder.

Below is the explanation on what exactly each state means:

* `created` - file is not in the original package and was created during the build process
* `copied` - file is in the original package and was copied during the build process
* `modified` - file is in the original package and was modified during the build process
* `conflict` - file is in the original package and was copied during the build process but may be generated by UCC itself so incorrect usage can lead to not working add-on

### `ucc-gen init`

Expand Down
124 changes: 124 additions & 0 deletions splunk_add_on_ucc_framework/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
import sys
from typing import Optional, List
import subprocess
import colorama as c
import fnmatch
import filecmp

from openapi3 import OpenAPI

Expand Down Expand Up @@ -327,12 +330,126 @@ def _get_python_version_from_executable(python_binary_name: str) -> str:
)


def summary_report(
source: str,
ta_name: str,
output_directory: str,
verbose_file_summary_report: bool,
) -> None:
# initialising colorama to handle ASCII color in windows cmd
c.init()
color_palette = {
"copied": c.Fore.GREEN,
"conflict": c.Fore.RED,
"modified": c.Fore.YELLOW,
}

# conflicting files from ucc-gen package folder
conflict_path = os.path.join(internal_root_dir, "package")
# conflict files generated through-out the process
conflict_static_list = frozenset(
[
"import_declare_test.py",
f"{ta_name}_rh_*.py",
"app.conf",
"inputs.conf*",
"restmap.conf",
"server.conf",
f"{ta_name}_*.conf*",
"web.conf",
"default.xml",
"configuration.xml",
"dashboard.xml",
"inputs.xml",
"openapi.json",
]
)

def line_print(print_path: str, mod_type: str) -> None:
if verbose_file_summary_report:
logger.info(
color_palette.get(mod_type, "")
+ str(print_path).ljust(80)
+ mod_type
+ c.Style.RESET_ALL,
)
summary[mod_type] += 1

def check_for_conflict(file: str, relative_file_path: str) -> bool:
conflict_path_file = os.path.join(conflict_path, relative_file_path)
if os.path.isfile(conflict_path_file):
return True
for pattern in conflict_static_list:
if fnmatch.fnmatch(file, pattern):
return True
return False

def file_check(
file: str, output_directory: str, relative_file_path: str, source: str
) -> None:
source_path = os.path.join(source, relative_file_path)

if os.path.isfile(source_path):
# file is present in package
output_path = os.path.join(output_directory, relative_file_path)

is_conflict = check_for_conflict(file, relative_file_path)

if not is_conflict:
files_are_same = filecmp.cmp(source_path, output_path)
if not files_are_same:
# output file was modified
line_print(relative_file_path, "modified")
else:
# files are the same
line_print(relative_file_path, "copied")
else:
line_print(relative_file_path, "conflict")
else:
# file does not exist in package
line_print(relative_file_path, "created")

summary = {"created": 0, "copied": 0, "modified": 0, "conflict": 0}

path_len = len(output_directory) + 1

if verbose_file_summary_report:
logger.info("Detailed information about created/copied/modified/conflict files")
logger.info(
"Read more about it here: "
"https://splunk.github.io/addonfactory-ucc-generator/quickstart/#verbose-mode"
)

for path, dir, files in os.walk(output_directory):
relative_path = path[path_len:]
# skipping lib directory
if relative_path[:3] == "lib":
if relative_path == "lib":
line_print("lib", "created")
continue

files = sorted(files, key=str.casefold)

for file in files:
relative_file_path = os.path.join(relative_path, file)
file_check(file, output_directory, relative_file_path, source)

summary_combined = ", ".join(
[
f"{file_type}: {amount_of_files}"
for file_type, amount_of_files in summary.items()
]
)
logger.info(f"File creation summary: {summary_combined}")


def generate(
source: str,
config_path: Optional[str] = None,
addon_version: Optional[str] = None,
output_directory: Optional[str] = None,
python_binary_name: str = "python3",
verbose_file_summary_report: bool = False,
) -> None:
logger.info(f"ucc-gen version {__version__} is used")
logger.info(f"Python binary name to use: {python_binary_name}")
Expand Down Expand Up @@ -583,3 +700,10 @@ def generate(
logger.info(f"Creating {output_openapi_folder} folder")
with open(output_openapi_path, "w") as openapi_file:
json.dump(open_api.raw_element, openapi_file, indent=4)

summary_report(
source,
ta_name,
os.path.join(output_directory, ta_name),
verbose_file_summary_report,
)
11 changes: 11 additions & 0 deletions splunk_add_on_ucc_framework/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
help="Python binary name to use to install requirements",
default="python3",
)
build_parser.add_argument(
"-v",
"--verbose",
action="store_true",
default=False,
help=(
"[experimental] show detailed information about "
"created/copied/modified/conflict files after build is complete"
),
)

package_parser = subparsers.add_parser("package", description="Package an add-on")
package_parser.add_argument(
Expand Down Expand Up @@ -178,6 +188,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
addon_version=args.ta_version,
output_directory=args.output,
python_binary_name=args.python_binary_name,
verbose_file_summary_report=args.verbose,
)
if args.command == "package":
package.package(path_to_built_addon=args.path, output_directory=args.output)
Expand Down
113 changes: 113 additions & 0 deletions tests/smoke/test_ucc_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import tempfile
import sys
import pytest
import logging
import json
from os import path

from tests.smoke import helpers
Expand Down Expand Up @@ -279,3 +281,114 @@ def test_ucc_generate_openapi_with_configuration_files_only():
temp_dir, "Splunk_TA_UCCExample", "appserver", "static", "openapi.json"
)
assert not path.exists(expected_file_path)


def test_ucc_build_verbose_mode(caplog):
"""
Tests results will test both no option and --verbose mode of build command.
No option provides a short summary of file created in manner: File creation summary: <result>
--verbose shows each file specific case and short summary
"""

caplog.set_level(logging.INFO, logger="ucc-gen")

def extract_summary_logs():
return_logs = []
copy_logs = False

message_to_start = (
"Detailed information about created/copied/modified/conflict files"
)
message_to_end = "File creation summary:"

for record in caplog.records:
if record.message == message_to_start:
copy_logs = True

if copy_logs:
return_logs.append(record)

if record.message[:22] == message_to_end:
copy_logs = False

return return_logs

def generate_expected_log():
def append_appserver_content(raw_expected_logs):
path_len = len(app_server_lib_path) + 1
excluded_files = ["redirect_page.js", "redirect.html"]

for full_path, dir, files in os.walk(app_server_lib_path):
if files:
relative_path = full_path[path_len:]
for file in files:
if file not in excluded_files:
relative_file_path = os.path.join(relative_path, file)
key_to_insert = (
str(relative_file_path).ljust(80) + "created\u001b[0m"
)
raw_expected_logs[key_to_insert] = "INFO"

def summarize_types(raw_expected_logs):
summary_counter = {"created": 0, "copied": 0, "modified": 0, "conflict": 0}

for log in raw_expected_logs:
end = log.find("\u001b[0m")
if end > 1:
string_end = end - 10
operation_type = log[string_end:end].strip()
summary_counter[operation_type] += 1

summary_message = (
f'File creation summary: created: {summary_counter.get("created")}, '
f'copied: {summary_counter.get("copied")}, '
f'modified: {summary_counter.get("modified")}, '
f'conflict: {summary_counter.get("conflict")}'
)
raw_expected_logs[summary_message] = "INFO"

with open(expected_logs_path) as f:
raw_expected_logs = json.load(f)

append_appserver_content(raw_expected_logs)
summarize_types(raw_expected_logs)

return raw_expected_logs

with tempfile.TemporaryDirectory() as temp_dir:
package_folder = path.join(
path.dirname(path.realpath(__file__)),
"..",
"testdata",
"test_addons",
"package_files_conflict_test",
"package",
)

expected_logs_path = path.join(
path.dirname(path.realpath(__file__)),
"..",
"testdata",
"expected_addons",
"expected_files_conflict_test",
"expected_log.json",
)

build.generate(
source=package_folder,
output_directory=temp_dir,
verbose_file_summary_report=True,
)

app_server_lib_path = os.path.join(build.internal_root_dir, "package")

summary_logs = extract_summary_logs()

expected_logs = generate_expected_log()

assert len(summary_logs) == len(expected_logs)

for log_line in summary_logs:
# summary messages must be the same but might come in different order
assert log_line.message in expected_logs.keys()
assert log_line.levelname == expected_logs[log_line.message]
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"Detailed information about created/copied/modified/conflict files": "INFO",
"Read more about it here: https://splunk.github.io/addonfactory-ucc-generator/quickstart/#verbose-mode": "INFO",
"\u001b[33mapp.manifest modified\u001b[0m": "INFO",
"\u001b[32mLICENSE.txt copied\u001b[0m": "INFO",
"\u001b[32mREADME.txt copied\u001b[0m": "INFO",
"VERSION created\u001b[0m": "INFO",
"\u001b[31mbin/import_declare_test.py conflict\u001b[0m": "INFO",
"\u001b[32mbin/my_first_input.py copied\u001b[0m": "INFO",
"\u001b[31mbin/test_addon_rh_account.py conflict\u001b[0m": "INFO",
"bin/test_addon_rh_my_first_input.py created\u001b[0m": "INFO",
"bin/test_addon_rh_settings.py created\u001b[0m": "INFO",
"default/app.conf created\u001b[0m": "INFO",
"default/inputs.conf created\u001b[0m": "INFO",
"default/restmap.conf created\u001b[0m": "INFO",
"default/server.conf created\u001b[0m": "INFO",
"default/test_addon_settings.conf created\u001b[0m": "INFO",
"default/web.conf created\u001b[0m": "INFO",
"default/data/ui/nav/default.xml created\u001b[0m": "INFO",
"default/data/ui/views/configuration.xml created\u001b[0m": "INFO",
"default/data/ui/views/dashboard.xml created\u001b[0m": "INFO",
"default/data/ui/views/inputs.xml created\u001b[0m": "INFO",
"\u001b[31mREADME/inputs.conf.spec conflict\u001b[0m": "INFO",
"\u001b[31mREADME/test_addon_account.conf.spec conflict\u001b[0m": "INFO",
"\u001b[31mREADME/test_addon_settings.conf.spec conflict\u001b[0m": "INFO",
"lib created\u001b[0m": "INFO",
"appserver/static/js/build/globalConfig.json created\u001b[0m": "INFO",
"appserver/static/openapi.json created\u001b[0m": "INFO",
"metadata/default.meta created\u001b[0m": "INFO"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# test_addon
Loading

0 comments on commit 3fad1a2

Please sign in to comment.