Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
datadog / dogshell / timeboard.py
Size: Mime:
# stdlib
import os.path
import platform
import sys
import webbrowser

# 3p
import argparse

# datadog
from datadog import api
from datadog.dogshell.common import report_errors, report_warnings, print_err
from datadog.util.compat import json
from datadog.util.format import pretty_json
from datetime import datetime


class TimeboardClient(object):

    @classmethod
    def setup_parser(cls, subparsers):
        parser = subparsers.add_parser('timeboard', help="Create, edit, and delete timeboards")
        parser.add_argument('--string_ids', action='store_true', dest='string_ids',
                            help="Represent timeboard IDs as strings instead of ints in JSON")

        verb_parsers = parser.add_subparsers(title='Verbs', dest='verb')
        verb_parsers.required = True

        post_parser = verb_parsers.add_parser('post', help="Create timeboards")
        post_parser.add_argument('title', help="title for the new timeboard")
        post_parser.add_argument('description', help="short description of the timeboard")
        post_parser.add_argument('graphs', help="graph definitions as a JSON string. if unset,"
                                 " reads from stdin.", nargs="?")
        post_parser.add_argument('--template_variables', type=_template_variables, default=[],
                                 help="a json list of template variable dicts, e.g. "
                                 "[{'name': 'host', 'prefix': 'host', "
                                 "'default': 'host:my-host'}]\'")

        post_parser.set_defaults(func=cls._post)

        update_parser = verb_parsers.add_parser('update', help="Update existing timeboards")
        update_parser.add_argument('timeboard_id', help="timeboard to replace"
                                   " with the new definition")
        update_parser.add_argument('title', help="new title for the timeboard")
        update_parser.add_argument('description', help="short description of the timeboard")
        update_parser.add_argument('graphs', help="graph definitions as a JSON string."
                                   " if unset, reads from stdin", nargs="?")
        update_parser.add_argument('--template_variables', type=_template_variables, default=[],
                                   help="a json list of template variable dicts, e.g. "
                                   "[{'name': 'host', 'prefix': 'host', "
                                   "'default': 'host:my-host'}]\'")
        update_parser.set_defaults(func=cls._update)

        show_parser = verb_parsers.add_parser('show', help="Show a timeboard definition")
        show_parser.add_argument('timeboard_id', help="timeboard to show")
        show_parser.set_defaults(func=cls._show)

        show_all_parser = verb_parsers.add_parser('show_all', help="Show a list of all timeboards")
        show_all_parser.set_defaults(func=cls._show_all)

        pull_parser = verb_parsers.add_parser('pull', help="Pull a timeboard on the server"
                                              " into a local file")
        pull_parser.add_argument('timeboard_id', help="ID of timeboard to pull")
        pull_parser.add_argument('filename', help="file to pull timeboard into")
        pull_parser.set_defaults(func=cls._pull)

        pull_all_parser = verb_parsers.add_parser('pull_all', help="Pull all timeboards"
                                                  " into files in a directory")
        pull_all_parser.add_argument('pull_dir', help="directory to pull timeboards into")
        pull_all_parser.set_defaults(func=cls._pull_all)

        push_parser = verb_parsers.add_parser('push', help="Push updates to timeboards"
                                              " from local files to the server")
        push_parser.add_argument('--append_auto_text', action='store_true', dest='append_auto_text',
                                 help="When pushing to the server, appends filename"
                                 " and timestamp to the end of the timeboard description")
        push_parser.add_argument('file', help="timeboard files to push to the server",
                                 nargs='+', type=argparse.FileType('r'))
        push_parser.set_defaults(func=cls._push)

        new_file_parser = verb_parsers.add_parser('new_file', help="Create a new timeboard"
                                                  " and put its contents in a file")
        new_file_parser.add_argument('filename', help="name of file to create with empty timeboard")
        new_file_parser.add_argument('graphs', help="graph definitions as a JSON string."
                                     " if unset, reads from stdin.", nargs="?")
        new_file_parser.set_defaults(func=cls._new_file)

        web_view_parser = verb_parsers.add_parser('web_view',
                                                  help="View the timeboard in a web browser")
        web_view_parser.add_argument('file', help="timeboard file", type=argparse.FileType('r'))
        web_view_parser.set_defaults(func=cls._web_view)

        delete_parser = verb_parsers.add_parser('delete', help="Delete timeboards")
        delete_parser.add_argument('timeboard_id', help="timeboard to delete")
        delete_parser.set_defaults(func=cls._delete)

    @classmethod
    def _pull(cls, args):
        cls._write_dash_to_file(
            args.timeboard_id, args.filename,
            args.timeout, args.format, args.string_ids)

    @classmethod
    def _pull_all(cls, args):
        api._timeout = args.timeout

        def _title_to_filename(title):
            # Get a lowercased version with most punctuation stripped out...
            no_punct = ''.join([c for c in title.lower() if c.isalnum() or c in [" ", "_", "-"]])
            # Now replace all -'s, _'s and spaces with "_", and strip trailing _
            return no_punct.replace(" ", "_").replace("-", "_").strip("_")

        format = args.format
        res = api.Timeboard.get_all()
        report_warnings(res)
        report_errors(res)

        if not os.path.exists(args.pull_dir):
            os.mkdir(args.pull_dir, 0o755)

        used_filenames = set()
        for dash_summary in res['dashes']:
            filename = _title_to_filename(dash_summary['title'])
            if filename in used_filenames:
                filename = filename + "-" + dash_summary['id']
            used_filenames.add(filename)

            cls._write_dash_to_file(
                dash_summary['id'], os.path.join(args.pull_dir, filename + ".json"),
                args.timeout, format, args.string_ids)
        if format == 'pretty':
            print(("\n### Total: {0} dashboards to {1} ###"
                  .format(len(used_filenames), os.path.realpath(args.pull_dir))))

    @classmethod
    def _new_file(cls, args):
        api._timeout = args.timeout
        format = args.format
        graphs = args.graphs
        if args.graphs is None:
            graphs = sys.stdin.read()
        try:
            graphs = json.loads(graphs)
        except:
            raise Exception('bad json parameter')
        res = api.Timeboard.create(
            title=args.filename,
            description="Description for {0}".format(args.filename),
            graphs=[graphs])
        report_warnings(res)
        report_errors(res)

        cls._write_dash_to_file(res['dash']['id'], args.filename,
                                args.timeout, format, args.string_ids)

        if format == 'pretty':
            print(pretty_json(res))
        else:
            print(json.dumps(res))

    @classmethod
    def _write_dash_to_file(cls, dash_id, filename, timeout, format='raw', string_ids=False):
        with open(filename, "w") as f:
            res = api.Timeboard.get(dash_id)
            report_warnings(res)
            report_errors(res)

            dash_obj = res["dash"]
            if "resource" in dash_obj:
                del dash_obj["resource"]
            if "url" in dash_obj:
                del dash_obj["url"]

            if string_ids:
                dash_obj["id"] = str(dash_obj["id"])

            json.dump(dash_obj, f, indent=2)

            if format == 'pretty':
                print(u"Downloaded dashboard {0} to file {1}".format(dash_id, filename))
            else:
                print(u"{0} {1}".format(dash_id, filename))

    @classmethod
    def _push(cls, args):
        api._timeout = args.timeout
        for f in args.file:
            try:
                dash_obj = json.load(f)
            except Exception as err:
                raise Exception("Could not parse {0}: {1}".format(f.name, err))

            if args.append_auto_text:
                datetime_str = datetime.now().strftime('%x %X')
                auto_text = ("<br/>\nUpdated at {0} from {1} ({2}) on {3}"
                             .format(datetime_str, f.name, dash_obj["id"], platform.node()))
                dash_obj["description"] += auto_text
            tpl_vars = dash_obj.get("template_variables", [])

            if 'id' in dash_obj:
                # Always convert to int, in case it was originally a string.
                dash_obj["id"] = int(dash_obj["id"])
                res = api.Timeboard.update(dash_obj["id"], title=dash_obj["title"],
                                           description=dash_obj["description"],
                                           graphs=dash_obj["graphs"], template_variables=tpl_vars)
            else:
                res = api.Timeboard.create(title=dash_obj["title"],
                                           description=dash_obj["description"],
                                           graphs=dash_obj["graphs"], template_variables=tpl_vars)

            if 'errors' in res:
                print_err('Upload of dashboard {0} from file {1} failed.'
                          .format(dash_obj["id"], f.name))

            report_warnings(res)
            report_errors(res)

            if format == 'pretty':
                print(pretty_json(res))
            else:
                print(json.dumps(res))

            if args.format == 'pretty':
                print("Uploaded file {0} (dashboard {1})".format(f.name, dash_obj["id"]))

    @classmethod
    def _post(cls, args):
        api._timeout = args.timeout
        format = args.format
        graphs = args.graphs
        if args.graphs is None:
            graphs = sys.stdin.read()
        try:
            graphs = json.loads(graphs)
        except:
            raise Exception('bad json parameter')
        res = api.Timeboard.create(title=args.title, description=args.description, graphs=[graphs],
                                   template_variables=args.template_variables)
        report_warnings(res)
        report_errors(res)
        if format == 'pretty':
            print(pretty_json(res))
        else:
            print(json.dumps(res))

    @classmethod
    def _update(cls, args):
        api._timeout = args.timeout
        format = args.format
        graphs = args.graphs
        if args.graphs is None:
            graphs = sys.stdin.read()
        try:
            graphs = json.loads(graphs)
        except:
            raise Exception('bad json parameter')

        res = api.Timeboard.update(args.timeboard_id, title=args.title,
                                   description=args.description, graphs=graphs,
                                   template_variables=args.template_variables)
        report_warnings(res)
        report_errors(res)
        if format == 'pretty':
            print(pretty_json(res))
        else:
            print(json.dumps(res))

    @classmethod
    def _show(cls, args):
        api._timeout = args.timeout
        format = args.format
        res = api.Timeboard.get(args.timeboard_id)
        report_warnings(res)
        report_errors(res)

        if args.string_ids:
            res["dash"]["id"] = str(res["dash"]["id"])

        if format == 'pretty':
            print(pretty_json(res))
        else:
            print(json.dumps(res))

    @classmethod
    def _show_all(cls, args):
        api._timeout = args.timeout
        format = args.format
        res = api.Timeboard.get_all()
        report_warnings(res)
        report_errors(res)

        if args.string_ids:
            for d in res["dashes"]:
                d["id"] = str(d["id"])

        if format == 'pretty':
            print(pretty_json(res))
        elif format == 'raw':
            print(json.dumps(res))
        else:
            for d in res["dashes"]:
                print("\t".join([(d["id"]),
                                 (d["resource"]),
                                 (d["title"]),
                                 cls._escape(d["description"])]))

    @classmethod
    def _delete(cls, args):
        api._timeout = args.timeout
        res = api.Timeboard.delete(args.timeboard_id)
        if res is not None:
            report_warnings(res)
            report_errors(res)

    @classmethod
    def _web_view(cls, args):
        dash_id = json.load(args.file)['id']
        url = api._api_host + "/dash/dash/{0}".format(dash_id)
        webbrowser.open(url)

    @classmethod
    def _escape(cls, s):
        return s.replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t")


def _template_variables(tpl_var_input):
    if '[' not in tpl_var_input:
        return [v.strip() for v in tpl_var_input.split(',')]
    else:
        try:
            return json.loads(tpl_var_input)
        except Exception:
            raise argparse.ArgumentTypeError('bad template_variable json parameter')