Boilerplate
All checks were successful
git.cronocide.net/bluebubbles-bot/pipeline/head This commit looks good
All checks were successful
git.cronocide.net/bluebubbles-bot/pipeline/head This commit looks good
This commit is contained in:
parent
2e534fb7aa
commit
4b1962b49a
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@ -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') {
|
||||
|
27
README.md
27
README.md
@ -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
124
bin/bluebubbles_bot
Normal 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!')
|
1
bluebubbles_bot/__init__.py
Normal file
1
bluebubbles_bot/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from bluebubbles_bot.bluebubbles_bot import *
|
1
bluebubbles_bot/bluebubbles_bot.py
Normal file
1
bluebubbles_bot/bluebubbles_bot.py
Normal file
@ -0,0 +1 @@
|
||||
# Write your modular code (classes, functions, etc) here. They'll be automatically imported in bin/bluebubbles_bot
|
0
bluebubbles_bot/plugins/plugin.py
Normal file
0
bluebubbles_bot/plugins/plugin.py
Normal file
62
setup.py
Normal file
62
setup.py
Normal 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
|
||||
)
|
Loading…
Reference in New Issue
Block a user