Boilerplate
All checks were successful
git.cronocide.net/bluebubbles-bot/pipeline/head This commit looks good

This commit is contained in:
Daniel Dayley 2023-04-07 22:48:34 -06:00
parent 2e534fb7aa
commit 4b1962b49a
7 changed files with 208 additions and 9 deletions

2
Jenkinsfile vendored
View File

@ -16,7 +16,7 @@ pipeline {
}
environment {
WORKSPACE_PATH = "/opt/nomad/alloc/${NOMAD_ALLOC_ID}/${NOMAD_TASK_NAME}${WORKSPACE}"
DESCRIPTION = "Another amazing piece of software written by Cronocide."
DESCRIPTION = "A chatbot for a local BlueBlubbles server."
}
stages {
stage('Prepare') {

View File

@ -1,12 +1,23 @@
# python-template
# bluebubbles_bot
## A chatbot for a local BlueBlubbles server.
Template repository for Docker image creation and deployment.
## Usage
```
# Deployment Checklist
```
## Installation
1.
2.
3.
## Configuration
```
```
## Justification
* Add Jenkins user to project as a Developer in Git
* Write the description in the Jenkinsfile env variable
* Add a private Github push mirror
* Rename the project in the README
* Delete this checklist from the README

124
bin/bluebubbles_bot Normal file
View File

@ -0,0 +1,124 @@
#!python3
import os
import sys
import yaml
import logging
import argparse
import bluebubbles_bot
import inspect
import importlib
# Import your custom libraries here
_plugin_map = {}
_delegate_map = {}
_initialized_plugins = []
# Plugin loading code
plugin_class = 'bluebubbles_bot'.strip('-').capitalize() + 'Plugin'
def add_plugin(plugin,reload) :
"""Adds a given plugin and instance, reinitializing one if it already exists and such is specified."""
plugin_name = plugin.__module__.split('.')[-1]
if not reload and plugin_name in _plugin_map.keys():
pass
else :
# We can't startup the plugin here because it hasn't been configured. We'll handle that at runtime.
try:
# Remove any intialized objects of the same name, forcing a reinitialization
_initialized_plugins.remove(_plugin_map[plugin_name])
except:
pass
_plugin_map.update({plugin_name:plugin})
def use_plugins(plugins,reload=False) :
"""Defines plugins that should be used in a lookup, optionally forcing them to reload."""
# Verify data
if type(plugins) != list :
raise ValueError('argument \'plugins\' should be of type list')
for plugin in plugins :
# Check if the plugin is a string or a descendent of a bluebubbles_bot-plugin class
if type(plugin) != str and plugin_class not in [x.__name__ for x in inspect.getmro(plugin)] :
raise ValueError('unkown type for plugin')
# Find plugins by name using a default path
if type(plugin) == str :
available_plugins = [y for x,y in search_plugins().items() if x == plugin and plugin_class in [z.__name__ for z in inspect.getmro(y)]]
if len(available_plugins) == 0 :
raise FileNotFoundError(plugin + '.py not found')
plugin = available_plugins[0]
if plugin_class in [x.__name__ for x in inspect.getmro(plugin)] :
add_plugin(plugin,reload)
continue
def get_plugins() :
"""Returns a map of plugins configured and loaded."""
return _plugin_map
def search_plugins(directory=None) :
"""Searches a given directory for compatible plugins and returns a map of available plugin names and classes."""
if not directory :
directory = '/'.join(os.path.realpath(__file__).split('/')[:-1]) + '/' + 'plugins'
directory = os.path.normpath(os.path.expanduser(os.path.expandvars(directory)))
name_map = {}
candidates = {x.split('.')[0]:x for x in os.listdir(directory) if x.endswith('.py')}
for name,filename in candidates.items() :
try :
spec = importlib.util.spec_from_file_location(name, directory + '/' + filename)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
instance = getattr(mod,plugin_class)
name_map.update({filename.split('.')[0]:instance})
except Exception as e :
# Handle plugin loading issues if desired
print("Unable to load plugin from " + filename + ": " + str(e))
return name_map
class LoggingFormatter(logging.Formatter):
def format(self, record):
module_max_width = 30
datefmt='%Y/%m/%d/ %H:%M:%S'
level = f'[{record.levelname}]'.ljust(9)
if 'log_module' not in dir(record) :
modname = str(record.module)+'.'+str(record.name)
else :
modname = record.log_module
modname = (f'{modname}'[:module_max_width-1] + ']').ljust(module_max_width)
final = "%-7s %s [%s %s" % (self.formatTime(record, self.datefmt), level, modname, record.getMessage())
return final
if __name__ == '__main__':
# Command-line client
# Define constants
config_template = {'bluebubbles_bot': {}}
# Gather Argument options
EXAMPLE_TEXT='Example:\n\tbluebubbles_bot -h'
parser = argparse.ArgumentParser(epilog=EXAMPLE_TEXT,formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('-H', '--hosts', action='append', default=None, help='Collects arguments in an array.')
parser.add_argument('-d', '--dry-run', action='store_true', help='Store the existence of a variable.')
parser.add_argument('-l', '--log', action='store', help='Specify a file to log to.')
parser.add_argument('-v', '--verbose', action='count', help='Include verbose information in the output. Add \'v\'s for more output.',default=0)
args = parser.parse_args()
log = logging.getLogger(__name__)
log = logging.LoggerAdapter(log,{'log_module':'bluebubbles_bot'})
# Configure logging
log_options = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
if not args.verbose :
args.verbose = 0
if args.verbose > 3 :
args.verbose = 3
if args.log :
logging.basicConfig(level=log_options[args.verbose],filename=args.log)
logging.getLogger().addHandler(logging.StreamHandler(sys.stderr))
else :
logging.basicConfig(level=log_options[args.verbose])
logging.getLogger().handlers[0].setFormatter(LoggingFormatter())
logging.propagate=True
# Load plugins
available_plugins = search_plugins(directory='/'.join(os.path.realpath(__file__).split('/')[:-2]) + '/bluebubbles_bot/' + 'plugins')
use_plugins([x for x in available_plugins.values()])
# Main functions
print('Hello World!')

View File

@ -0,0 +1 @@
from bluebubbles_bot.bluebubbles_bot import *

View File

@ -0,0 +1 @@
# Write your modular code (classes, functions, etc) here. They'll be automatically imported in bin/bluebubbles_bot

View File

62
setup.py Normal file
View File

@ -0,0 +1,62 @@
from setuptools import setup, find_packages
from setuptools.command.install_scripts import install_scripts
from setuptools.command.install import install
from setuptools.command.develop import develop
from setuptools.command.egg_info import egg_info
from setuptools.command.build_ext import build_ext
import subprocess
import os
import glob
# From https://stackoverflow.com/questions/5932804/set-file-permissions-in-setup-py-file
# https://blog.niteo.co/setuptools-run-custom-code-in-setup-py/
def customize(command) :
command_name = str(command.mro()[1].__name__).strip()
original_run = command.run
def run(self) :
# Run the rest of the installer first
original_run(self)
# Create a new subprocess to run the included shell script
print("Running " + command_name + " commands...")
current_dir_path = os.path.dirname(os.path.realpath(__file__))
create_service_script_path = os.path.join(current_dir_path, 'setup.sh')
# stdout and stderr are combined in shell output
output=subprocess.run([create_service_script_path,command_name],stdout=subprocess.PIPE,stderr=subprocess.STDOUT).stdout
print(output.decode('UTF-8'))
command.run = run
return command
@customize
class CustomInstallCommand(install) :
pass
@customize
class CustomDevelopCommand(develop) :
pass
@customize
class CustomEggInfoCommand(egg_info) :
pass
@customize
class CustomBuildExtCommand(build_ext) :
pass
files = glob.glob('bluebubbles_bot/plugins/*.py')
setup(name='bluebubbles_bot',
version='1.0.0',
url='',
license='Apache2',
author='Daniel Dayley',
author_email='github@cronocide.com',
description='A chatbot for a local BlueBlubbles server.',
packages=find_packages(exclude=['tests']),
package_data={"": ['plugins/*.py']},
install_requires=['pyyaml',],
scripts=['bin/bluebubbles_bot'],
long_description=open('README.md').read(),
zip_safe=True
)