Skip to content

Commit

Permalink
Merge pull request #220 from ismet55555/fix-history-file-corruption
Browse files Browse the repository at this point in the history
  • Loading branch information
ismet55555 committed Jul 4, 2023
2 parents dad560a + 9ab6436 commit d63426c
Show file tree
Hide file tree
Showing 13 changed files with 672 additions and 675 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.0.85
current_version = 0.0.86
commit = True
tag = True
allow_dirty = False
Expand Down
53 changes: 34 additions & 19 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,38 @@
##############################################################################

repos:
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes
# - repo: meta
# hooks:
# - id: check-hooks-apply
# - id: check-useless-excludes

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-docstring-first
- id: detect-private-key
- id: check-ast
- id: check-json
- id: check-yaml
- id: check-toml
- id: name-tests-test
- id: check-yaml
files: (.yaml|.yml)
- id: check-added-large-files
args: [--maxkb=500]
- id: no-commit-to-branch
args: [--branch, main]

- repo: https://github.com/gitleaks/gitleaks
rev: v8.16.1
hooks:
- id: gitleaks

- repo: https://github.com/adrienverge/yamllint.git
rev: v1.29.0
hooks:
- id: yamllint
args: [--strict, -c=.yamllint]

- repo: https://github.com/pre-commit/mirrors-yapf
rev: v0.31.0
Expand All @@ -45,7 +61,7 @@ repos:
verbose: true

- repo: https://github.com/PyCQA/isort
rev: 5.9.3
rev: 5.12.0
hooks:
- id: isort
name: Python Imports Sorting (isort)
Expand All @@ -54,19 +70,18 @@ repos:
types_or: [python]
verbose: true

# - repo: https://github.com/PyCQA/bandit
# rev: 1.7.0
# hooks:
# - id: bandit
# verbose: true
# name: Python Code Security Scan (bandit)
# FIXME: File and test exclusion not working in pyproject.toml

- repo: https://github.com/PyCQA/pylint
rev: pylint-2.6.0
- repo: local
hooks:
- id: pylint
name: Python Code Linting (pylint)
args: ["--rcfile=.pylintrc", "--fail-under=8.0"]
entry: pylint
language: python
verbose: false
types: [python]
args:
[
"-rn", # Only display messages
"--rcfile=.pylintrc", # Link to your config file
"--load-plugins=pylint.extensions.docparams", # Load an extension
"--output-format=colorized", # Load an extension
"--fail-under=8.0"
]
12 changes: 12 additions & 0 deletions .yamllint
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# -*- mode: yaml -*-
# vim: set filetype=yaml
---
rules:
line-length:
level: error
max: 120

yaml-files:
- '*.yaml'
- '*.yml'
- '.yamllint'
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ setuptools = "*"
twine = "*"
wheel = "*"
yapf = "*"
yamllint = "*"
yojenkins = {editable = true, path = "."}

[packages]
Expand Down
1,104 changes: 543 additions & 561 deletions Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.85
0.0.86
2 changes: 1 addition & 1 deletion docs/source/cli_outline.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Below is a complete outline of the `yojenkins` CLI command structure.

!!! note
As of Version: **0.0.85**
As of Version: **0.0.86**

```text
yojenkins
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import setuptools

# Package version number (Updated via bump2version tool)
__version__ = "0.0.85"
__version__ = "0.0.86"

def check_py_version():
"""Check the python version"""
Expand Down
2 changes: 1 addition & 1 deletion yojenkins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

__title__ = 'yojenkins'
__summary__ = "A CLI tool to interface with Jenkins server"
__version__ = "0.0.85"
__version__ = "0.0.86"
__author__ = "Ismet Handzic"
__copyright__ = "Copyright 2022 %s" % __author__
38 changes: 19 additions & 19 deletions yojenkins/cli/cli_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import os
import sys
from pathlib import Path
from typing import NoReturn, Union
from typing import Any, Dict, List, NoReturn, Union

import click

Expand Down Expand Up @@ -76,7 +76,7 @@ def remove() -> None:

@log_to_history
def bug_report() -> None:
"""TODO Docstring
"""Report a bug for yojenkins.
Details: TODO
Expand All @@ -93,7 +93,7 @@ def bug_report() -> None:

@log_to_history
def feature_request() -> None:
"""TODO Docstring
"""Request a new feature for yojenkins.
Details: TODO
Expand All @@ -109,20 +109,15 @@ def feature_request() -> None:


def history(profile: str, clear: bool) -> None:
"""Displaying the command history and clearing the history file if requested.
"""Display the command history and clearing the history file if requested.
### TODO: Ability to clear only for a specific profile.
Args:
profile: The name of the profile to to filter history with
clear: Clearing the history file
"""
# Load contents from history file
history_file_path = os.path.join(os.path.join(Path.home(), cu.CONFIG_DIR_NAME), cu.HISTORY_FILE_NAME)
contents = load_contents_from_local_file('json', history_file_path)
if not contents:
click.secho('No history found', fg='bright_red', bold=True)
sys.exit(1)

# Clearing the history file if requested
if clear:
Expand All @@ -135,32 +130,37 @@ def history(profile: str, clear: bool) -> None:
click.secho('success', fg='bright_green', bold=True)
sys.exit(0)

# Load contents from history file
contents = load_contents_from_local_file('jsonl', history_file_path)
if not contents:
click.secho('No history found', fg='bright_red', bold=True)
sys.exit(1)

# Displaying the command history
logger.debug(f'Displaying command history for profile "{profile}" ...')

def output_history_to_console(command_list: list, profile_name: str) -> None:
"""Helper function to format and output to console"""
def output_history_to_console(command_list: List, profile_name: str = "") -> None:
"""Help to format and output to console."""
for command_info in command_list:
profile_str = f'{click.style("[" + profile_name + "]", fg="yellow", bold=True)}'
if profile_name:
if command_info["profile"] != profile_name:
continue
profile_str = f'{click.style("[" + command_info["profile"] + "]", fg="yellow", bold=True)}'
datetime_str = f'{click.style("[" + command_info["datetime"] + "]", fg="green", bold=False)}'
tool_version = f'{click.style("[" + "v" + command_info["tool_version"] + "]", fg="green", bold=False)}'

command_info = f'{profile_str} {datetime_str} {tool_version} - {command_info["tool_path"]} {command_info["arguments"]}'
click.echo(command_info)

if profile:
if profile in contents:
output_history_to_console(contents[profile], profile)
else:
fail_out(f'No history found for profile: {profile}')
output_history_to_console(contents, profile)
else:
for profile_name in contents:
output_history_to_console(contents[profile_name], profile_name)
output_history_to_console(contents)


@log_to_history
def rest_request(profile: str, token: str, request_text: str, request_type: str, raw: bool, clean_html: bool) -> None:
"""Send a generic REST request to Jenkins Server using the loaded credentials
"""Send a generic REST request to Jenkins Server using the loaded credentials.
Args:
profile: The name of the credentials profile
Expand Down
32 changes: 9 additions & 23 deletions yojenkins/cli/cli_utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
logger = logging.getLogger()

CONFIG_DIR_NAME = '.yojenkins'
HISTORY_FILE_NAME = 'history'
COMMAND_HISTORY_FORMAT = 'json'
HISTORY_FILE_NAME = 'history.jsonl'
COMMAND_HISTORY_FORMAT = 'jsonl'
DEFAULT_PROFILE_NAME = 'default'
MAX_PROFILE_HISTORY_LENGTH = 1000

Expand Down Expand Up @@ -195,7 +195,7 @@ def server_target_check(target: str) -> bool:


def log_to_history(decorated_function) -> Callable:
"""This function decorates a function that is a cli command.
"""Decorate/wrap a function that is a cli command.
The function will log the CLI command and its info to the command history.
Details: Add this function like "@log_to_history" to a function
Expand All @@ -204,7 +204,7 @@ def log_to_history(decorated_function) -> Callable:
decorated_function : Function that is decorated
Returns:
None
The decorated function
"""
# Get the profile argument index
argspec = getfullargspec(decorated_function)
Expand All @@ -227,39 +227,25 @@ def wrapper(*args, **kwargs) -> None:
# If function has profile argument, but none was passed, use the default profile name
profile_name = DEFAULT_PROFILE_NAME

# Check if history file exists
# Check if history file exists, if not create it
history_file_path = os.path.join(os.path.join(Path.home(), CONFIG_DIR_NAME), HISTORY_FILE_NAME)
if not os.path.isfile(history_file_path):
create_new_history_file(history_file_path)

# Load the history file content
file_contents = load_contents_from_local_file(COMMAND_HISTORY_FORMAT, history_file_path)
if not file_contents:
logger.debug("Command history file is blank")

# If profile history length is too long, remove the oldest history item
if profile_name in file_contents:
if len(file_contents[profile_name]) > MAX_PROFILE_HISTORY_LENGTH:
file_contents[profile_name].pop(0)

# Add the command to the history file
logger.debug(f'Logging command to command history file: "{history_file_path}" ...')
if profile_name not in file_contents:
file_contents[profile_name] = []

command_info = {
'profile': profile_name,
'tool_path': CLI_CMD_PATH,
'arguments': CLI_CMD_ARGS,
'timestamp': datetime.now().timestamp(),
'datetime': datetime.now().strftime("%A, %B %d, %Y %I:%M:%S"),
'tool_version': __version__
}
file_contents[profile_name].append(command_info)

# Add to file, overwritting entire file
# Add line to history file
try:
with open(history_file_path, 'w') as outfile:
json.dump(file_contents, outfile, indent=4)
with open(history_file_path, 'a', encoding="utf-8") as outfile:
outfile.write(json.dumps(command_info) + '\n')
except Exception as error:
logger.debug(f'Failed to write command history file: {error}')

Expand Down
2 changes: 1 addition & 1 deletion yojenkins/cli_sub_commands/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def feature_request(debug):
@click.option('--profile', type=str, required=False, is_flag=False, help='Filter by profile name')
@click.option('--clear', type=bool, required=False, default=False, is_flag=True, help='Clear the history file')
def history(debug, **kwargs):
"""Show detailed command usage history"""
"""Show detailed command usage history."""
set_debug_log_level(debug)
cli_tools.history(**kwargs)

Expand Down
Loading

0 comments on commit d63426c

Please sign in to comment.