From 7388b0da16f3d8e4bb8e84d89e63390ae969ed03 Mon Sep 17 00:00:00 2001 From: bthornto Date: Mon, 4 Dec 2017 08:49:32 -0800 Subject: [PATCH 1/2] Everything to 1.6 --- Dockerfile | 2 +- now/Dockerfile | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 92263a0..b096c21 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apk add gcc libffi-dev musl-dev openssl-dev sshpass make # RUN apk add py-crypto python-dev # Install td4a -RUN pip install td4a==1.5 +RUN pip install td4a==1.6 # Clear out extras RUN rm -rf /var/cache/apk/* diff --git a/now/Dockerfile b/now/Dockerfile index 93faf48..c16d266 100644 --- a/now/Dockerfile +++ b/now/Dockerfile @@ -1,2 +1,2 @@ -FROM cidrblock/td4a:latest +FROM cidrblock/td4a:1.6 EXPOSE 5000 diff --git a/setup.py b/setup.py index 11e280b..ffa7052 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup setup(name='td4a', - version='1.5', + version='1.6', description='A browser based jinja template renderer', url='http://github.com/cidrblock/td4a', author='Bradley A. Thornton', From ab9858e7b722719bfa5c7dce227c679ef72844f6 Mon Sep 17 00:00:00 2001 From: bthornto Date: Thu, 7 Dec 2017 04:58:33 -0800 Subject: [PATCH 2/2] Initial sensible support --- Dockerfile | 3 +- README.md | 92 +++++++++++++------ my_ansible_inventory/group_vars/all/vars.yml | 1 + .../group_vars/routers/vars.yml | 1 + .../group_vars/servers/vars.yml | 1 + .../host_vars/router00/vars.yml | 29 ++++++ .../host_vars/server00/vars.yml | 61 ++++++++++++ .../host_vars/test00/vars.yml | 24 +++++ my_ansible_inventory/inventory.yml | 9 ++ requirements.txt | 2 +- setup.py | 2 +- td4a-server | 13 ++- td4a/__init__.py | 13 ++- td4a/controllers/config.py | 13 +++ td4a/controllers/enable_link.py | 12 --- td4a/controllers/hosts.py | 13 +++ td4a/controllers/inventory.py | 23 +++++ td4a/controllers/render.py | 76 +++++++++------ td4a/models/exception_handler.py | 27 ++++-- td4a/models/inventory.py | 42 +++++++++ td4a/static/css/app.css | 37 +++++++- td4a/static/index.html | 17 +++- td4a/static/js/app.js | 38 +++++++- 23 files changed, 457 insertions(+), 92 deletions(-) create mode 100644 my_ansible_inventory/group_vars/all/vars.yml create mode 100644 my_ansible_inventory/group_vars/routers/vars.yml create mode 100644 my_ansible_inventory/group_vars/servers/vars.yml create mode 100644 my_ansible_inventory/host_vars/router00/vars.yml create mode 100644 my_ansible_inventory/host_vars/server00/vars.yml create mode 100644 my_ansible_inventory/host_vars/test00/vars.yml create mode 100644 my_ansible_inventory/inventory.yml create mode 100644 td4a/controllers/config.py delete mode 100644 td4a/controllers/enable_link.py create mode 100644 td4a/controllers/hosts.py create mode 100644 td4a/controllers/inventory.py create mode 100644 td4a/models/inventory.py diff --git a/Dockerfile b/Dockerfile index b096c21..c215598 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,5 @@ RUN pip install td4a==1.6 # Clear out extras RUN rm -rf /var/cache/apk/* -RUN mkdir /filter_plugins # Start td4a -CMD [ "td4a-server", "-f", "/filter_plugins" ] +CMD [ "td4a-server" ] diff --git a/README.md b/README.md index 6000172..997f3d0 100644 --- a/README.md +++ b/README.md @@ -13,61 +13,79 @@ All jinja2 filters are supported along with the filter plguins from Ansible vers ### Installation: -#### docker - -If you do not have custom filter plugins. +#### using docker: ``` -docker run -p 5000:5000 cidrblock/td4a +docker pull cidrblock/td4a ``` -TD4A will look for custom plugins at /filter_plugins within the container. Pass your custom filter_plugins directory as a volume and expose port 5000. -``` -docker run -p 5000:5000 -v `pwd`/my_filter_plugins:/filter_plugins cidrblock/td4a -``` -The docker file can be found here: +The docker hub page can be found here: https://hub.docker.com/r/cidrblock/td4a/ -#### pip: +#### using the cli: ``` $ virtualenv venv $ source venv/bin/activate $ pip install td4a -$ td4a-server ``` The pip package can be found here: https://pypi.python.org/pypi/td4a -### Usage +### Starting the TD4A server -The interface is browser based and has been tested using Chrome. If your browser did not automatically open when TD4A was started, you can visit http://127.0.0.1:5000 to see the interface. -The UI is broken into three sections: +#### Simple -1) DATA, this is where the data in yaml format is provided. -2) TEMPLATE, the jinja2 template to be rendered. -3) RESULT, after clicking the render button, the result pane will be populated with the rendered template. +##### using docker: +``` +docker run -p 5000:5000 cidrblock/td4a +``` -### Keyboard shortcuts +##### using the cli: +``` +td4a-server +``` -`cmd+r`: Render the template +##### open your browser to: -`cmd+s`: Save the data in browser local storage +http://127.0.0.1:5000 -`cmd+b`: Begin new, clear the screen +#### Loading custom filter plugins -### Custom filters +##### using docker: + +TD4A will look for custom plugins at /filter_plugins within the container. Pass your custom filter_plugins directory as a volume and expose port 5000. +``` +docker run -p 5000:5000 -v `pwd`/my_filter_plugins:/filter_plugins cidrblock/td4a +``` + +##### using the cli: TD4A can load custom filters from a directory specified from the command line: +``` +td4a-server -f ./my_filter_plugins +``` + +#### Loading an ansible inventory +##### using docker: + +TD4A will look for custom plugins at /filter_plugins within the container. Pass your custom filter_plugins directory as a volume and expose port 5000. ``` -td4a-server -f ./filter_plugins +docker run -p 5000:5000 -v `pwd`/my_filter_plugins:/filter_plugins cidrblock/td4a ``` -### Saving docs and building links +##### using the cli: + +TD4A can load multiple ansible inventories, specifc each with `-i` on the command line: +``` +td4a-server -i ./my_ansible_inventory -v 'my_vault_password' +``` + +#### Enabling storage and links using a couch database TD4A has the ability to store data and templates in a CouchDB. This is disabled by defualt. @@ -75,7 +93,7 @@ The CouchDB needs to previously created. To enable link support, and add the link button to the UI, set the following environ variables: -#### docker: +##### using docker: ``` docker run -p 5000:5000 \ @@ -83,18 +101,34 @@ docker run -p 5000:5000 \ -e "COUCH_USERNAME=admin" \ -e "COUCH_PASSWORD=password" \ -e "COUCH_URL=http://192.168.1.5:5984/td4a" \ - -e "ENABLE_LINK=true" \ - td4a + cidrblock/td4a ``` -#### pip: +##### using the cli: ``` export COUCH_USERNAME=admin export COUCH_PASSWORD=password export COUCH_URL=http://localhost:5984/td4a -export ENABLE_LINK=True ``` +### User Interface + +The interface is browser based and has been tested using Chrome. If your browser did not automatically open when TD4A was started, you can visit http://127.0.0.1:5000 to see the interface. + +The UI is broken into three sections: + +1) DATA, this is where the data in yaml format is provided. +2) TEMPLATE, the jinja2 template to be rendered. +3) RESULT, after clicking the render button, the result pane will be populated with the rendered template. + +#### Keyboard shortcuts + +`cmd+r`: Render the template + +`cmd+s`: Save the data in browser local storage + +`cmd+b`: Begin new, clear the screen + ### Python version To date, this has only been tested with python 2.7. diff --git a/my_ansible_inventory/group_vars/all/vars.yml b/my_ansible_inventory/group_vars/all/vars.yml new file mode 100644 index 0000000..cf11f37 --- /dev/null +++ b/my_ansible_inventory/group_vars/all/vars.yml @@ -0,0 +1 @@ +application_name: td4a diff --git a/my_ansible_inventory/group_vars/routers/vars.yml b/my_ansible_inventory/group_vars/routers/vars.yml new file mode 100644 index 0000000..59832cc --- /dev/null +++ b/my_ansible_inventory/group_vars/routers/vars.yml @@ -0,0 +1 @@ +manufacturer: quagga diff --git a/my_ansible_inventory/group_vars/servers/vars.yml b/my_ansible_inventory/group_vars/servers/vars.yml new file mode 100644 index 0000000..f5a7981 --- /dev/null +++ b/my_ansible_inventory/group_vars/servers/vars.yml @@ -0,0 +1 @@ +manufacturer: whitebox diff --git a/my_ansible_inventory/host_vars/router00/vars.yml b/my_ansible_inventory/host_vars/router00/vars.yml new file mode 100644 index 0000000..cef9845 --- /dev/null +++ b/my_ansible_inventory/host_vars/router00/vars.yml @@ -0,0 +1,29 @@ +ansible_ssh_pass: "{{ lookup('env', 'ANSIBLE_PASSWORD') }}" + +interfaces: + Ethernet4/10: + description: siteassw100-g0/1-siteasrt001-eth4/10 + name: Ethernet4/10 + shutdown: + negate: true + switchport: + mode: + - trunk + switchport: true + Ethernet4/11: + channel_group: + id: 31 + mode: active + description: unity1interface1 + mtu: 9216 + name: Ethernet4/11 + shutdown: + negate: true + switchport: + mode: + - trunk + switchport: true + trunk: + allowed_vlans: + vlans: 3605,3607 + native_vlan: 5 diff --git a/my_ansible_inventory/host_vars/server00/vars.yml b/my_ansible_inventory/host_vars/server00/vars.yml new file mode 100644 index 0000000..eb3869d --- /dev/null +++ b/my_ansible_inventory/host_vars/server00/vars.yml @@ -0,0 +1,61 @@ +--- +# defaults file for ansible-apache2 + +# Defines if Apache2 should be configured +apache2_config: false + +# Defines if php.ini should be configured for Apache2 +apache2_config_php: false + +# Defines if Apache2 virtual hosts should be configured +apache2_config_virtual_hosts: true + +# Defines Apache2 default listen port +apache2_default_port: 80 + +# Defines if php-sqlite should be installed +apache2_install_php_sqlite: false + +# Defines if php should be installed +apache2_install_php: false + +apache2_php_max_execution_time: 30 + +apache2_php_max_input_time: 60 + +# Defines max memory for Apache php +# default is 128M +apache2_php_max_memory: 128M + +apache2_php_post_max_size: 8M + +apache2_php_timezone: "UTC" + +apache2_php_upload_max_filesize: 2M + +apache2_server_admin: webmaster@localhost + +# Define Apache2 virtual hosts +apache2_virtual_hosts: + - documentroot: '/var/www/example.com' + default_site: false + port: 80 + serveradmin: '{{ apache2_server_admin }}' + servername: 'www.example.com' + - documentroot: '/var/www/example.org' + default_site: false + port: 80 + serveradmin: '{{ apache2_server_admin }}' + servername: 'www.example.org' + - documentroot: '/var/www/html' + default_site: true + port: 80 + serveradmin: '{{ apache2_server_admin }}' + servername: '' + +apache2_web_root: /var/www/html +apache2_log_dir: /var/log/apache + +testVar: testing + +anotherTestVar: "{{ testVar }}" diff --git a/my_ansible_inventory/host_vars/test00/vars.yml b/my_ansible_inventory/host_vars/test00/vars.yml new file mode 100644 index 0000000..8a1f5a3 --- /dev/null +++ b/my_ansible_inventory/host_vars/test00/vars.yml @@ -0,0 +1,24 @@ +a: "{{ b }}" +b: + c: this + d: that + +foo: "{{ bar }}" +bar: +- 1 +- 2 +- 3 + +ansible_ssh_pass: "{{ lookup('env', 'FOO') }}" +cisco_xr_cli: + username: "{{ ansible_ssh_user }}" + + +y: "{{ z }}" +z: + aa: + bb: + cc: + dd: dd + +f: "{{ bar + bar }}" diff --git a/my_ansible_inventory/inventory.yml b/my_ansible_inventory/inventory.yml new file mode 100644 index 0000000..dc7c855 --- /dev/null +++ b/my_ansible_inventory/inventory.yml @@ -0,0 +1,9 @@ +router[00:03] +server[00:03] +test[00:03] + +[routers] +router[00:03] + +[servers] +server[00:03] diff --git a/requirements.txt b/requirements.txt index 483704f..8128deb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ Flask==0.12.2 netaddr==0.7.19 Twisted==17.9.0 requests==2.18.4 -ruamel.yaml==0.15.34 +ruamel.yaml==0.15.35 diff --git a/setup.py b/setup.py index ffa7052..979a935 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,6 @@ 'netaddr==0.7.19', 'Twisted==17.9.0', 'requests==2.18.4', - 'ruamel.yaml==0.15.34' + 'ruamel.yaml==0.15.35' ], zip_safe=False) diff --git a/td4a-server b/td4a-server index 141960b..543d6de 100755 --- a/td4a-server +++ b/td4a-server @@ -1,6 +1,7 @@ #! /usr/bin/env python from td4a import app from td4a.models.filters import filters_load +from td4a.models.inventory import inventory_load from argparse import ArgumentParser, RawTextHelpFormatter import os from twisted.internet import reactor @@ -15,16 +16,25 @@ def parse_args(): parser.add_argument('-f', action="store", dest="custom_filters", required=False, help="A folder containing custom filters.") + parser.add_argument('-i', action="append", dest="inventory_source", + required=False, + help="A folder containing the inventory.") + parser.add_argument('-v', action="store", dest="vault_secret", + required=False, + help="A vault secret.") args = parser.parse_args() args.username = os.environ.get('COUCH_USERNAME', False) args.password = os.environ.get('COUCH_PASSWORD', False) - args.enable_links = os.environ.get('ENABLE_LINK', False) args.url = os.environ.get('COUCH_URL', False) return args def main(): + print "Loading..." app.args = parse_args() app.filters = filters_load(app.args.custom_filters) + if app.args.inventory_source: + app.inventory = inventory_load(inventory_sources=app.args.inventory_source, + vault_secret=app.args.vault_secret) reactor_args = {} app.debug = False def wsgi(): @@ -36,6 +46,7 @@ def main(): reactor_args['installSignalHandlers'] = 0 import werkzeug.serving wsgi = werkzeug.serving.run_with_reloader(wsgi) + print "Ready." wsgi() if __name__ == '__main__': diff --git a/td4a/__init__.py b/td4a/__init__.py index a52f4ff..7c10437 100644 --- a/td4a/__init__.py +++ b/td4a/__init__.py @@ -1,14 +1,19 @@ from flask import Flask, request, jsonify -from td4a.controllers.render import api_render +from td4a.controllers.config import api_config +from td4a.controllers.hosts import api_hosts +from td4a.controllers.inventory import api_inventory from td4a.controllers.link import api_link +from td4a.controllers.render import api_render from td4a.controllers.retrieve import api_retrieve -from td4a.controllers.enable_link import api_enable_link + app = Flask(__name__, static_url_path='') # pylint: disable=invalid-name -app.register_blueprint(api_render) +app.register_blueprint(api_config) +app.register_blueprint(api_hosts) +app.register_blueprint(api_inventory) app.register_blueprint(api_link) +app.register_blueprint(api_render) app.register_blueprint(api_retrieve) -app.register_blueprint(api_enable_link) @app.route('/') def root(): diff --git a/td4a/controllers/config.py b/td4a/controllers/config.py new file mode 100644 index 0000000..b944341 --- /dev/null +++ b/td4a/controllers/config.py @@ -0,0 +1,13 @@ +""" /config +""" +from flask import jsonify, Blueprint +from flask import current_app as app + +api_config = Blueprint('api_config', __name__) # pylint: disable=invalid-name + +@api_config.route('/config', methods=['GET']) +def config(): + """ provide some config options to the UI + """ + ui_config = {"link": bool(app.args.url), "inventory": bool(app.args.inventory_source)} + return jsonify(ui_config) diff --git a/td4a/controllers/enable_link.py b/td4a/controllers/enable_link.py deleted file mode 100644 index 8298d59..0000000 --- a/td4a/controllers/enable_link.py +++ /dev/null @@ -1,12 +0,0 @@ -""" /enablelink -""" -from flask import jsonify, Blueprint -from flask import current_app as app - -api_enable_link = Blueprint('api_enable_link', __name__) # pylint: disable=invalid-name - -@api_enable_link.route('/enablelink', methods=['GET']) -def enablelink(): - """ check to see if the link button should be enabled - """ - return jsonify({"enabled": app.args.enable_links}) diff --git a/td4a/controllers/hosts.py b/td4a/controllers/hosts.py new file mode 100644 index 0000000..4f55e33 --- /dev/null +++ b/td4a/controllers/hosts.py @@ -0,0 +1,13 @@ +""" /hosts +""" +from flask import jsonify, Blueprint +from flask import current_app as app + +api_hosts = Blueprint('api_hosts', __name__) # pylint: disable=invalid-name + +@api_hosts.route('/hosts', methods=['GET']) +def hosts(): + """ check to see if the link button should be enabled + """ + devices = app.inventory.keys() + return jsonify({"hosts": sorted(devices)}) diff --git a/td4a/controllers/inventory.py b/td4a/controllers/inventory.py new file mode 100644 index 0000000..6bcee96 --- /dev/null +++ b/td4a/controllers/inventory.py @@ -0,0 +1,23 @@ +""" /inventory +""" +from flask import request, jsonify, Blueprint +from flask import current_app as app +from td4a.models.exception_handler import ExceptionHandler, HandledException +from td4a.models.td4ayaml import Td4aYaml +import json +import collections + +api_inventory = Blueprint('api_inventory', __name__) # pylint: disable=invalid-name + +@api_inventory.route('/inventory', methods=['GET']) +def rest_inventory(): + """ return inventory for host + """ + yaml = Td4aYaml() + inventory = app.inventory.get(request.args.get('host'), "") + data = json.loads(json.dumps(inventory)) + response_text = '' + for section in sorted(data.keys()): + response_text += yaml.dump({section: data[section]}) + response = {"data": response_text} + return jsonify(response) diff --git a/td4a/controllers/render.py b/td4a/controllers/render.py index 6e59af8..5b64afc 100644 --- a/td4a/controllers/render.py +++ b/td4a/controllers/render.py @@ -2,36 +2,40 @@ """ from flask import request, jsonify, Blueprint from flask import current_app as app -from jinja2 import meta, Environment, StrictUndefined +from jinja2 import meta, Environment, StrictUndefined, Undefined from td4a.models.exception_handler import ExceptionHandler, HandledException from td4a.models.td4ayaml import Td4aYaml +from ruamel.yaml import YAML +import re api_render = Blueprint('api_render', __name__) # pylint: disable=invalid-name -def jinja_env(filters): - """ return a jinja env - """ - env = Environment(undefined=StrictUndefined) - env.trim_blocks = True - for entry in filters: - env.filters[entry[0]] = entry[1] - return env - @ExceptionHandler -def jinja_unresolved(template, filters, typ): +def jinja_unresolved(template, typ): """ Check a jinja template for any unresolved vars """ _ = typ - env = jinja_env(filters=filters) + env = Environment() + env.trim_blocks = True unresolved = meta.find_undeclared_variables(env.parse(template)) return unresolved +def lookup(*args, **kwargs): + return "unsupported" + @ExceptionHandler def jinja_render(data, template, filters, typ): """ Render a jinja template """ _ = typ - env = jinja_env(filters=filters) + if typ == 'data': + env = Environment(undefined=Undefined) + else: + env = Environment(undefined=StrictUndefined) + env.trim_blocks = True + for entry in filters: + env.filters[entry[0]] = entry[1] + env.globals.update(lookup=lookup) result = env.from_string(template).render(data) return result @@ -40,9 +44,8 @@ def yaml_parse(string, typ): """ load yaml from string """ _ = typ - yaml = Td4aYaml() - data = yaml.load(string) - return data + yaml = YAML() + yaml.load(string) @ExceptionHandler def render(payload, filters, typ): @@ -50,18 +53,37 @@ def render(payload, filters, typ): """ _ = typ try: - yaml = Td4aYaml() + loader = YAML(typ='unsafe') result = None if payload['data'] and payload['template']: - data = yaml_parse(string=payload['data'], typ="data") - for _ in range(10): - if jinja_unresolved(template=yaml.dump(data), filters=filters, typ="data"): - data_post_jijna = jinja_render(data=data, - template=yaml.dump(data), - filters=filters, - typ="data") - data = yaml_parse(string=data_post_jijna, typ="data") - result = jinja_render(data=data, + # check for error in data + yaml_parse(string=payload['data'], typ="data") + # swap '{{ }}' for "{{ }}" + dq_jinja = re.compile(r"'\{\{([^\{\}]+)\}\}'") + payload['data'] = dq_jinja.sub('"{{\\1}}"', payload['data']) + # remove the quotes around dicts put into jinja + expose_dicts = re.compile(r'"\{([^\{].*)\}"') + # remove the quotes aournd lists put into jinja + expose_lists1 = re.compile(r'"\[(.*)\]"') + # change '{{ }}' to "{{ }}" + dq_jinja = re.compile(r"'\{\{([^\{\}]+)\}\}'") + + raw_data = None + after_jinja = payload['data'] + tvars = loader.load(payload['data']) + + if jinja_unresolved(after_jinja, "data"): + while after_jinja != raw_data: + raw_data = after_jinja + after_jinja = jinja_render(data=tvars, + template=raw_data, + filters=filters, + typ="data") + yaml_ready = expose_dicts.sub("{\\1}", after_jinja) + yaml_ready = expose_lists1.sub("[\\1]", yaml_ready) + yaml_ready = dq_jinja.sub('"{{\\1}}"', yaml_ready) + tvars = loader.load(yaml_ready) + result = jinja_render(data=tvars, template=payload['template'], filters=filters, typ="template") @@ -74,7 +96,9 @@ def rest_render(): """ render path """ try: + print "Checking and parsing data..." response = render(payload=request.json, filters=app.filters, typ="page") + print "Done." return jsonify(response) except HandledException as error: return jsonify(error.json()) diff --git a/td4a/models/exception_handler.py b/td4a/models/exception_handler.py index 01cc7b1..65365aa 100644 --- a/td4a/models/exception_handler.py +++ b/td4a/models/exception_handler.py @@ -75,14 +75,21 @@ def duplicate_key_error(self): def jinja_error(self): message = str(self.error).replace("'ruamel.yaml.comments.CommentedMap object'", 'Object') - line_number = next(x for x in self.tback if re.search('^<.*>$', x[0]))[1] + line_numbers = [x for x in self.tback if re.search('^<.*>$', x[0])] + if line_numbers: + line_number = line_numbers[0][1] + else: + line_number = 'unknown' return self.error_response(message=message, line_number=line_number) def parser_error(self): line_number = self.error.problem_mark.line + 1 - message = next(x for x in str(self.error).splitlines() - if x.startswith('expected')) + messages = [x for x in str(self.error).splitlines() if x.startswith('expected')] + if messages: + message = messages[0] + else: + message = str(self.error) return self.error_response(message=message, line_number=line_number) @@ -99,13 +106,21 @@ def requests_error(self): def type_error(self): message = str(self.error) - line_number = next(x for x in self.tback if re.search('^<.*>$', x[0]))[1] + line_numbers = [x for x in self.tback if re.search('^<.*>$', x[0])] + if line_numbers: + line_number = line_numbers[0][1] + else: + line_number = 'unknown' return self.error_response(message=message, line_number=line_number) def unhandled(self): - print self.exc_type, self.exc_value, self.exc_traceback, self.tback - line_number = next(x for x in self.tback if re.search('^<.*>$', x[0]))[1] + print self.exc_type, self.exc_value, self.exc_traceback, self.tback, self.error + line_numbers = [x for x in self.tback if re.search('^<.*>$', x[0])] + if line_numbers: + line_number = line_numbers[0][1] + else: + line_number = 'unknown' message = "Please see the console for details. %s" % str(self.error) return self.error_response(message=message, line_number=line_number) diff --git a/td4a/models/inventory.py b/td4a/models/inventory.py new file mode 100644 index 0000000..ea7ebbd --- /dev/null +++ b/td4a/models/inventory.py @@ -0,0 +1,42 @@ +from flask import current_app as app +from ansible.parsing.dataloader import DataLoader +from ansible.vars.manager import VariableManager +from ansible.inventory.manager import InventoryManager +from ansible.module_utils._text import to_bytes +from ansible.parsing.vault import VaultSecret + +class TextVaultSecret(VaultSecret): + '''A secret piece of text. ie, a password. Tracks text encoding. + The text encoding of the text may not be the default text encoding so + we keep track of the encoding so we encode it to the same bytes.''' + + def __init__(self, text, encoding=None, errors=None, _bytes=None): + super(TextVaultSecret, self).__init__() + self.text = text + self.encoding = encoding or 'utf-8' + self._bytes = _bytes + self.errors = errors or 'strict' + + @property + def bytes(self): + '''The text encoded with encoding, unless we specifically set _bytes.''' + return self._bytes or to_bytes(self.text, encoding=self.encoding, errors=self.errors) + +def inventory_load(inventory_sources, vault_secret): + """ Load the inventory + """ + loader = DataLoader() + vault_secrets = [('default', TextVaultSecret(vault_secret))] + loader.set_vault_secrets(vault_secrets) + inventory = InventoryManager(loader=loader, sources=inventory_sources) + result = {} + for hostname in inventory.hosts: + host = inventory.get_host(hostname) + variable_manager = VariableManager(loader=loader, inventory=inventory) + magic_vars = ['ansible_playbook_python', 'groups', 'group_names', 'inventory_dir', + 'inventory_file', 'inventory_hostname', 'inventory_hostname_short', + 'omit', 'playbook_dir'] + all_vars = variable_manager.get_vars(host=host, include_hostvars=True) + cleaned = ({k: v for (k, v) in all_vars.items() if k not in magic_vars}) + result[hostname] = cleaned + return result diff --git a/td4a/static/css/app.css b/td4a/static/css/app.css index c484ec1..527d0f1 100644 --- a/td4a/static/css/app.css +++ b/td4a/static/css/app.css @@ -40,8 +40,8 @@ body { .menubar { display: flex; flex-direction: row; - min-height: 35px; - line-height: 35px; + min-height: 40px; + line-height: 40px; } .title { justify-content: center; @@ -67,6 +67,39 @@ body { .md-button.md-ok:not([disabled]):hover { background-color: darkgreen; } +md-autocomplete { + width: 50%; +} +md-autocomplete input:not(.md-input) { + background: black; + color: white !important; + width: 50%; +} +md-autocomplete md-autocomplete-wrap, .md-autocomplete-suggestions, .md-autocomplete-suggestions li { + background: black !important; + color: white !important; +} + +.md-autocomplete-suggestions-container.md-default-theme li.selected, +.md-autocomplete-suggestions-container li.selected, +.md-autocomplete-suggestions-container.md-default-theme li:hover, +.md-autocomplete-suggestions-container li:hover { + background: darkgrey !important; + color: black !important; +} +.md-autocomplete-suggestions-container.md-default-theme li .highlight, +.md-autocomplete-suggestions-container li .highlight { + color: rgb(63,81,181); +} + + +md-select { + margin: 0px 0 0px 0 !important; +} +md-select-value, .md-select-icon { + color: white !important; +} + .column { height: 100%; display: flex; diff --git a/td4a/static/index.html b/td4a/static/index.html index da97a4a..fb42645 100644 --- a/td4a/static/index.html +++ b/td4a/static/index.html @@ -23,8 +23,19 @@
@@ -48,7 +59,7 @@