Templating: working implementation
- Includes a sample custom.jinja file. - Supports any number of templates. Change-Id: Iae199170ea9ab8c75258720070ea7e5f2200d8a2
This commit is contained in:
parent
4b77d11ffb
commit
b80866bac5
|
@ -1,3 +1,4 @@
|
|||
iso8601>=0.1.9
|
||||
Jinja2
|
||||
pbr>=0.5.21,<1.0
|
||||
python-novaclient==2.15.0
|
||||
|
|
|
@ -16,12 +16,13 @@ import datetime
|
|||
import logging
|
||||
import socket
|
||||
|
||||
import dateutil.parser
|
||||
import pythonwhois
|
||||
from six.moves.urllib import parse as urlparse
|
||||
import tldextract
|
||||
|
||||
from satori import errors
|
||||
from satori import utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -64,9 +65,12 @@ def domain_info(domain):
|
|||
if (isinstance(result['expiration_date'], list)
|
||||
and len(result['expiration_date']) > 0):
|
||||
expires = result['expiration_date'][0]
|
||||
if not isinstance(expires, datetime.datetime):
|
||||
expires = dateutil.parser.parse(expires)
|
||||
days_until_expires = (expires - datetime.datetime.now()).days
|
||||
if isinstance(expires, datetime.datetime):
|
||||
days_until_expires = (expires - datetime.datetime.now()).days
|
||||
expires = utils.get_time_string(time_obj=expires)
|
||||
else:
|
||||
days_until_expires = (utils.parse_time_string(expires) -
|
||||
datetime.datetime.now()).days
|
||||
return {
|
||||
'name': domain,
|
||||
'whois': result['raw'],
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{#
|
||||
|
||||
This is a jinja template used to customize the output of satori. You can add
|
||||
as many of those as you'd like to your setup. You can reference them using the
|
||||
--format (or -F) argument. satori takes the format you asked for and appends
|
||||
".jinja" to it to look up the file from this dirfectory.
|
||||
|
||||
You have some global variables available to you:
|
||||
|
||||
- target: that's the address or URL supplied at the command line
|
||||
- data: all the discovery data
|
||||
|
||||
|
||||
For example, to use this template:
|
||||
|
||||
$ satori openstack.org -F custo
|
||||
|
||||
Hi! localhost
|
||||
|
||||
Happy customizing :-)
|
||||
|
||||
#}
|
||||
Hi! {{ target }}
|
|
@ -23,6 +23,7 @@ findings back to the user.
|
|||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
@ -108,10 +109,18 @@ def main():
|
|||
)
|
||||
|
||||
# Output formatting
|
||||
parser.add_argument(
|
||||
'--format', '-F',
|
||||
dest='format',
|
||||
default='text',
|
||||
help='Format for output (json or text)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--logconfig",
|
||||
help="Optional logging configuration file"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-d", "--debug",
|
||||
action="store_true",
|
||||
|
@ -156,9 +165,13 @@ def main():
|
|||
"tenant. Either provide all of these settings or none of "
|
||||
"them.")
|
||||
|
||||
if not (args.format == 'json' or check_format(args.format or "text")):
|
||||
sys.exit("Output format file (%s) not found or accessible. Try "
|
||||
"specifying raw JSON format using `--format json`" %
|
||||
get_template_path(args.format))
|
||||
try:
|
||||
results = discovery.run(args.netloc, args)
|
||||
print(format_output(args.netloc, results))
|
||||
print(format_output(args.netloc, results, template_name=args.format))
|
||||
except Exception as exc: # pylint: disable=W0703
|
||||
if args.debug:
|
||||
LOG.exception(exc)
|
||||
|
@ -166,6 +179,18 @@ def main():
|
|||
return 0
|
||||
|
||||
|
||||
def get_template_path(name):
|
||||
"""Get template path from name."""
|
||||
root_dir = os.path.dirname(__file__)
|
||||
return os.path.join(root_dir, "formats", "%s.jinja" % name)
|
||||
|
||||
|
||||
def check_format(name):
|
||||
"""Verify that we have the requested format template."""
|
||||
template_path = get_template_path(name)
|
||||
return os.path.exists(template_path)
|
||||
|
||||
|
||||
def get_template(name):
|
||||
"""Get template text fromtemplates directory by name."""
|
||||
root_dir = os.path.dirname(__file__)
|
||||
|
@ -175,10 +200,14 @@ def get_template(name):
|
|||
return template
|
||||
|
||||
|
||||
def format_output(discovered_target, results):
|
||||
def format_output(discovered_target, results, template_name="text"):
|
||||
"""Format results in CLI format."""
|
||||
template = get_template("text")
|
||||
return templating.parse(template, target=discovered_target, data=results)
|
||||
if template_name == 'json':
|
||||
return(json.dumps(results, indent=2))
|
||||
else:
|
||||
template = get_template(template_name)
|
||||
return templating.parse(template, target=discovered_target,
|
||||
data=results)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -19,6 +19,7 @@ import unittest
|
|||
from freezegun import freeze_time
|
||||
import mock
|
||||
import pythonwhois
|
||||
import six
|
||||
|
||||
from satori import dns
|
||||
from satori import errors
|
||||
|
@ -234,7 +235,7 @@ class TestDNS(utils.TestCase):
|
|||
data = dns.domain_info(self.domain)
|
||||
self.assertIsInstance(data['whois'][0], str)
|
||||
|
||||
def test_domain_info_returns_datetime_for_expiry_string(self):
|
||||
def test_domain_info_returns_string_date_for_expiry(self):
|
||||
small_whois = ["""
|
||||
Domain : example.io
|
||||
Status : Live
|
||||
|
@ -245,17 +246,11 @@ class TestDNS(utils.TestCase):
|
|||
"""]
|
||||
self.mynet.get_whois_raw.return_value = small_whois
|
||||
data = dns.domain_info(self.domain)
|
||||
self.assertIsInstance(
|
||||
data['expiration_date'],
|
||||
datetime.datetime
|
||||
)
|
||||
self.assertIsInstance(data['expiration_date'], six.string_types)
|
||||
|
||||
def test_domain_info_returns_datetime_for_expiration_date_string(self):
|
||||
def test_domain_info_returns_string_for_expiration_date_string(self):
|
||||
data = dns.domain_info(self.domain)
|
||||
self.assertIsInstance(
|
||||
data['expiration_date'],
|
||||
datetime.datetime
|
||||
)
|
||||
self.assertIsInstance(data['expiration_date'], six.string_types)
|
||||
|
||||
def test_domain_info_returns_none_for_missing_expiration_date(self):
|
||||
small_whois = ["""
|
||||
|
@ -267,10 +262,7 @@ class TestDNS(utils.TestCase):
|
|||
"""]
|
||||
self.mynet.get_whois_raw.return_value = small_whois
|
||||
data = dns.domain_info(self.domain)
|
||||
self.assertEqual(
|
||||
data['expiration_date'],
|
||||
None
|
||||
)
|
||||
self.assertIsNone(data['expiration_date'])
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Tests for utils module."""
|
||||
|
||||
import datetime
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from satori import utils
|
||||
|
||||
|
||||
class SomeTZ(datetime.tzinfo):
|
||||
|
||||
"""A random timezone."""
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return datetime.timedelta(minutes=45)
|
||||
|
||||
def tzname(self, dt):
|
||||
return "STZ"
|
||||
|
||||
def dst(self, dt):
|
||||
return datetime.timedelta(0)
|
||||
|
||||
|
||||
class TestTimeUtils(unittest.TestCase):
|
||||
|
||||
"""Test time formatting functions."""
|
||||
|
||||
def test_get_formatted_time_string(self):
|
||||
some_time = time.gmtime(0)
|
||||
with mock.patch.object(utils.time, 'gmtime') as mock_gmt:
|
||||
mock_gmt.return_value = some_time
|
||||
result = utils.get_time_string()
|
||||
self.assertEqual(result, "1970-01-01 00:00:00 +0000")
|
||||
|
||||
def test_get_formatted_time_string_time_struct(self):
|
||||
result = utils.get_time_string(time_obj=time.gmtime(0))
|
||||
self.assertEqual(result, "1970-01-01 00:00:00 +0000")
|
||||
|
||||
def test_get_formatted_time_string_datetime(self):
|
||||
result = utils.get_time_string(
|
||||
time_obj=datetime.datetime(1970, 2, 1, 1, 2, 3, 0))
|
||||
self.assertEqual(result, "1970-02-01 01:02:03 +0000")
|
||||
|
||||
def test_get_formatted_time_string_datetime_tz(self):
|
||||
result = utils.get_time_string(
|
||||
time_obj=datetime.datetime(1970, 2, 1, 1, 2, 3, 0, SomeTZ()))
|
||||
self.assertEqual(result, "1970-02-01 01:47:03 +0000")
|
||||
|
||||
def test_parse_time_string(self):
|
||||
result = utils.parse_time_string("1970-02-01 01:02:03 +0000")
|
||||
self.assertEqual(result, datetime.datetime(1970, 2, 1, 1, 2, 3, 0))
|
||||
|
||||
def test_parse_time_string_with_tz(self):
|
||||
result = utils.parse_time_string("1970-02-01 01:02:03 +1000")
|
||||
self.assertEqual(result, datetime.datetime(1970, 2, 1, 11, 2, 3, 0))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -10,12 +10,21 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
"""General utilities."""
|
||||
"""General utilities.
|
||||
|
||||
- Class and module import/export
|
||||
- Time utilities (we standardize on UTC)
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
|
||||
import iso8601
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
STRING_FORMAT = "%Y-%m-%d %H:%M:%S +0000"
|
||||
|
||||
|
||||
def import_class(import_str):
|
||||
|
@ -37,3 +46,35 @@ def import_object(import_str, *args, **kw):
|
|||
except ImportError:
|
||||
cls = import_class(import_str)
|
||||
return cls(*args, **kw)
|
||||
|
||||
|
||||
def get_time_string(time_obj=None):
|
||||
"""The canonical time string format (in UTC).
|
||||
|
||||
:param time_obj: an optional datetime.datetime or timestruct (defaults to
|
||||
gm_time)
|
||||
|
||||
Note: Changing this function will change all times that this project uses
|
||||
in the returned data.
|
||||
"""
|
||||
if isinstance(time_obj, datetime.datetime):
|
||||
if time_obj.tzinfo:
|
||||
offset = time_obj.tzinfo.utcoffset(time_obj)
|
||||
utc_dt = time_obj + offset
|
||||
return datetime.datetime.strftime(utc_dt, STRING_FORMAT)
|
||||
return datetime.datetime.strftime(time_obj, STRING_FORMAT)
|
||||
elif isinstance(time_obj, time.struct_time):
|
||||
return time.strftime(STRING_FORMAT, time_obj)
|
||||
elif time_obj is not None:
|
||||
raise TypeError("get_time_string takes only a time_struct, none, or a "
|
||||
"datetime. It was given a %s" % type(time_obj))
|
||||
return time.strftime(STRING_FORMAT, time.gmtime())
|
||||
|
||||
|
||||
def parse_time_string(time_string):
|
||||
"""Return naive datetime object from string in standard time format."""
|
||||
parsed = time_string.replace(" +", "+").replace(" -", "-")
|
||||
dt_with_tz = iso8601.parse_date(parsed)
|
||||
offset = dt_with_tz.tzinfo.utcoffset(dt_with_tz)
|
||||
result = dt_with_tz + offset
|
||||
return result.replace(tzinfo=None)
|
||||
|
|
Loading…
Reference in New Issue