def run(self):
     url = 'https://api.travis-ci.org/repos/{repo}/builds'.format(repo=self.repo)
     request_handler = RequestHandler()
     req = request_handler.get(url)
     if log.isEnabledFor(logging.DEBUG):
         log.debug("\n%s", jsonpp(req.content))
     try:
         self.parse_results(req.content)
     except (KeyError, ValueError):
         exception = traceback.format_exc().split('\n')[-2]
         # this covers up the traceback info and makes it harder to debug
         #raise UnknownError('failed to parse expected json response from Travis CI API: {0}'.format(exception))
         qquit('UNKNOWN', 'failed to parse expected json response from Travis CI API: {0}. {1}'.
               format(exception, support_msg_api()))
Exemple #2
0
 def __init__(self):
     super(ConsulPeerCount, self).__init__()
     self.name = 'Consul'
     self.default_port = 8500
     self.host = None
     self.port = None
     self.request_handler = RequestHandler()
     self.msg = 'NO MESSAGE DEFINED'
 def __init__(self):
     # Python 2.x
     super(TravisDebugSession, self).__init__()
     # Python 3.x
     # super().__init__()
     self.timeout_default = 600
     self.verbose_default = 2
     self.job_id = None
     self.travis_token = None
     self.repo = None
     self.headers = {
         'Content-Type': 'application/json',
         'Accept': 'application/json',
         'Travis-API-Version': '3',
         'User-Agent': prog
     }
     self.request_handler = RequestHandler()
Exemple #4
0
 def test_request_handler_failure():
     try:
         RequestHandler().get('127.0.0.1:1')
         raise AssertionError(
             'failed to raise exception for RequestHandler.get(127.0.0.1:1)'
         )
     except CriticalError:
         pass
class ConsulKeyCheck(KeyCheckNagiosPlugin):
    def __init__(self):
        super(ConsulKeyCheck, self).__init__()
        self.name = "Consul"
        self.default_port = 8500
        self.request_handler = RequestHandler()

    def extract_value(self, content):  # pylint: disable=no-self-use
        json_data = None
        try:
            json_data = json.loads(content)
        except ValueError:
            raise UnknownError("non-json data returned by consul: '%s'. %s" % (content, support_msg_api()))
        value = None
        if not isList(json_data):
            raise UnknownError("non-list returned by consul: '%s'. %s" % (content, support_msg_api()))
        if not json_data:
            raise UnknownError("blank list returned by consul! '%s'. %s" % (content, support_msg_api()))
        if len(json_data) > 1:
            raise UnknownError(
                "more than one key returned by consul! response = '%s'. %s" % (content, support_msg_api())
            )
        try:
            value = json_data[0]["Value"]
        except KeyError:
            raise UnknownError(
                "couldn't find field 'Value' in response from consul: '%s'. %s" % (content, support_msg_api())
            )
        try:
            value = base64.decodestring(value)
        except TypeError as _:
            raise UnknownError(
                "invalid data returned for key '{0}' value = '{1}', failed to base64 decode".format(self.key, value)
            )
        return value

    # closure factory
    @staticmethod
    def check_response_code(msg):
        def tmp(req):
            if req.status_code != 200:
                err = ""
                if req.content and isStr(req.content) and len(req.content.split("\n")) < 2:
                    err += ": " + req.content
                raise CriticalError("{0}: '{1}' {2}{3}".format(msg, req.status_code, req.reason, err))

        return tmp

    def read(self):
        req = None
        # could use ?raw to get the value without base64 but leaving base64 encoding as it's safer
        url = "http://%(host)s:%(port)s/v1/kv/%(key)s" % self.__dict__
        self.request_handler.check_response_code = self.check_response_code(
            "failed to retrieve Consul key '{0}'".format(self.key)
        )
        req = self.request_handler.get(url)
        value = self.extract_value(req.content)
        return value
 def run_host(self, host, url):
     log.info('querying %s', host)
     req = RequestHandler().get(url)
     json_data = json.loads(req.text)
     beans = json_data['beans']
     for bean in beans:
         log.debug('processing Regions bean')
         if bean['name'] == 'Hadoop:service=HBase,name=RegionServer,sub=Regions':
             self.process_bean(host, bean)
Exemple #7
0
 def __init__(self):
     # Python 2.x
     super(CheckDockerhubRepoBuildStatus, self).__init__()
     # Python 3.x
     # super().__init__()
     self.request = RequestHandler()
     self.statuses = {
         '0': 'Queued',
         '3': 'Building',
         '10': 'Success',
         '-1': 'Error',
         '-4': 'Cancelled',
     }
     self.msg = 'DockerHub repo '
     self.repo = None
     self.tag = None
     self.max_pages = None
     self.start_time = None
     self.ok()
class ConsulKeyCheck(KeyCheckNagiosPlugin):

    def __init__(self):
        super(ConsulKeyCheck, self).__init__()
        self.name = 'Consul'
        self.default_port = 8500
        self.request_handler = RequestHandler()

    def extract_value(self, content):
        json_data = None
        try:
            json_data = json.loads(content)
        except ValueError:
            raise UnknownError("non-json data returned by consul: '%s'. %s" % (content, support_msg_api()))
        value = None
        if not isList(json_data):
            raise UnknownError("non-list returned by consul: '%s'. %s" % (content, support_msg_api()))
        if not json_data:
            raise UnknownError("blank list returned by consul! '%s'. %s" % (content, support_msg_api()))
        if len(json_data) > 1:
            raise UnknownError("more than one key returned by consul! response = '%s'. %s" \
                  % (content, support_msg_api()))
        try:
            value = json_data[0]['Value']
        except KeyError:
            raise UnknownError("couldn't find field 'Value' in response from consul: '%s'. %s"
                               % (content, support_msg_api()))
        try:
            # decodestring might be deprecated but decodebytes isn't available on Python 2.7
            #value = base64.decodebytes(value)
            value = base64.decodestring(value)
        except TypeError:
            raise UnknownError("invalid data returned for key '{0}' value = '{1}', failed to base64 decode"
                               .format(self.key, value))
        return value

    # closure factory
    @staticmethod
    def check_response_code(msg):
        def tmp(req):
            if req.status_code != 200:
                err = ''
                if req.content and isStr(req.content) and len(req.content.split('\n')) < 2:
                    err += ': ' + req.content
                raise CriticalError("{0}: '{1}' {2}{3}".format(msg, req.status_code, req.reason, err))
        return tmp

    def read(self):
        # could use ?raw to get the value without base64 but leaving base64 encoding as it's safer
        url = 'http://%(host)s:%(port)s/v1/kv/%(key)s' % self.__dict__
        self.request_handler.check_response_code = \
            self.check_response_code("failed to retrieve Consul key '{0}'".format(self.key))
        req = self.request_handler.get(url)
        value = self.extract_value(req.content)
        return value
Exemple #9
0
 def __init__(self):
     # Python 2.x
     super(TravisLastBuildLog, self).__init__()
     # Python 3.x
     # super().__init__()
     self.timeout_default = 600
     self.verbose_default = 1
     self.travis_token = None
     self.repo = None
     self.job_id = None
     self.num = None
     self.completed = False
     self.failed = False
     self.plaintext = False
     self.color = False
     self.headers = {
         'Content-Type': 'application/json',
         'Accept': 'application/json',
         'Travis-API-Version': '3',
         'User-Agent': prog
     }
     self.request_handler = RequestHandler()
 def __init__(self):
     # Python 2.x
     super(RestNagiosPlugin, self).__init__()
     # Python 3.x
     # super().__init__()
     self.name = None
     self.default_host = 'localhost'
     self.default_port = 80
     self.default_user = None
     self.default_password = None
     self.host = None
     self.port = None
     self.user = None
     self.password = None
     self.protocol = 'http'
     self.msg = 'rest msg not defined yet'
     self.request = RequestHandler()
     self.req = None
     self.json_data = None
     self.path = None
     self.json = False
     self.auth = True
     self.ok()
 def run_host(self, host, url):
     req = RequestHandler().get(url)
     json_data = json.loads(req.text)
     uptime = None
     beans = json_data['beans']
     if self.since_uptime:
         for bean in beans:
             if bean['name'] == 'java.lang:type=Runtime':
                 uptime = int(bean['Uptime'] / 1000)
                 break
         if not uptime:
             raise UnknownError("failed to find uptime in JMX stats for host '{}'. {}"\
                                .format(host, support_msg_api()))
     for bean in beans:
         if bean['name'] == 'Hadoop:service=HBase,name=RegionServer,sub=Regions':
             self.process_bean(host, bean, uptime)
 def __init__(self):
     # Python 2.x
     super(CheckDockerhubRepoBuildStatus, self).__init__()
     # Python 3.x
     # super().__init__()
     self.request = RequestHandler()
     self.statuses = {
         '0': 'Queued',
         '3': 'Building',
         '10': 'Success',
         '-1': 'Error',
         '-4': 'Cancelled',
     }
     self.msg = 'DockerHub repo '
     self.repo = None
     self.tag = None
     self.max_pages = None
     self.start_time = None
     self.ok()
Exemple #13
0
    def print_region_stats(self, host):
        req = RequestHandler().get('http://{host}:{port}/jmx'.format(host=host, port=self.port))
        json_data = json.loads(req.text)
        uptime = None
        beans = json_data['beans']
        for bean in beans:
            if bean['name'] == 'java.lang:type=Runtime':
                uptime = int(bean['Uptime'] / 1000)
                break
        if not uptime:
            raise UnknownError("failed to find uptime in JMX stats for host '{}'. {}".format(host, support_msg_api()))

        region_regex = re.compile('^Namespace_{namespace}_table_{table}_region_(.+)_metric_(.+)RequestCount'\
                                  .format(namespace=self.namespace, table=self.table))
        for bean in beans:
            if bean['name'] == 'Hadoop:service=HBase,name=RegionServer,sub=Regions':
                for key in sorted(bean):
                    match = region_regex.match(key)
                    if match:
                        region = match.group(1)
                        metric = match.group(2)
                        print('{:20s}\t{:20s}\t\t{:10s}\t{:.1f}'.format(host, region, metric, bean[key] / uptime))
Exemple #14
0
 def __init__(self):
     # Python 2.x
     super(RestNagiosPlugin, self).__init__()
     # Python 3.x
     # super().__init__()
     self.name = None
     self.default_host = 'localhost'
     self.default_port = 80
     self.default_user = None
     self.default_password = None
     self.host = None
     self.port = None
     self.user = None
     self.password = None
     self.protocol = 'http'
     self.msg = 'rest msg not defined yet'
     self.request = RequestHandler()
     self.req = None
     self.json_data = None
     self.path = None
     self.json = False
     self.ok()
 def get_status(self):
     if self.get_opt('ssl'):
         self.protocol = 'https'
     url = '%(protocol)s://%(host)s:%(port)s/api/atlas/admin/status' % self.__dict__
     req = RequestHandler().get(url)
     return self.parse(req)
 def get_status(self):
     url = 'http://{0}:{1}/status'.format(self.host, self.port)
     req = RequestHandler().get(url)
     status = self.parse(req)
     return status
Exemple #17
0
class RestNagiosPlugin(NagiosPlugin):

    __version__ = __version__
    # abstract class
    __metaclass__ = ABCMeta

    def __init__(self):
        # Python 2.x
        super(RestNagiosPlugin, self).__init__()
        # Python 3.x
        # super().__init__()
        self.name = None
        self.default_host = 'localhost'
        self.default_port = 80
        self.default_user = None
        self.default_password = None
        self.host = None
        self.port = None
        self.user = None
        self.password = None
        self.protocol = 'http'
        self.msg = 'rest msg not defined yet'
        self.request = RequestHandler()
        self.request_method = 'get'
        self.req = None
        self.json_data = None
        self.path = None
        self.json = False
        self.headers = {}
        self.auth = True
        self.ok()

    def add_options(self):
        self.add_hostoption(name=self.name,
                            default_host=self.default_host,
                            default_port=self.default_port)
        if self.auth:
            self.add_useroption(name=self.name,
                                default_user=self.default_user,
                                default_password=self.default_password)
        self.add_ssl_option()

    def process_options(self):
        self.no_args()
        self.host = self.get_opt('host')
        self.port = self.get_opt('port')
        validate_host(self.host)
        validate_port(self.port)
        if self.auth:
            self.user = self.get_opt('user')
            self.password = self.get_opt('password')
            if self.auth == 'optional':
                if self.user and self.password:
                    validate_user(self.user)
                    validate_password(self.password)
            else:
                validate_user(self.user)
                validate_password(self.password)
        ssl = self.get_opt('ssl')
        log_option('ssl', ssl)
        if ssl and self.protocol == 'http':
            self.protocol = 'https'
        if self.json:
            # recommended for many systems like CouchDB
            self.headers['Accept'] = 'application/json'

    def run(self):
        start_time = time.time()
        self.req = self.query()
        query_time = time.time() - start_time
        if self.json:
            log.info('parsing json response')
            self.process_json(self.req.content)
        else:
            log.info('parsing response')
            self.parse(self.req)
        if '|' not in self.msg:
            self.msg += ' |'
        if ' query_time=' not in self.msg:
            self.msg += ' query_time={0:.4f}s'.format(query_time)

    def query(self):
        url = '{proto}://{host}:{port}/'.format(proto=self.protocol,
                                                host=self.host,
                                                port=self.port)
        if self.path:
            url += self.path.lstrip('/')
        auth = None
        if self.user and self.password:
            log.info('authenticating to Rest API')
            auth = (self.user, self.password)
        req = self.request.req(self.request_method, url, auth=auth, headers=self.headers)
        return req

    #@abstractmethod
    def parse(self, req):
        pass

    #@abstractmethod
    def parse_json(self, json_data):
        pass

    def process_json(self, content):
        try:
            self.json_data = json.loads(content)
            if log.isEnabledFor(logging.DEBUG):
                log.debug('JSON prettified:\n\n%s\n%s', jsonpp(self.json_data), '='*80)
            return self.parse_json(self.json_data)
        #except (KeyError, ValueError) as _:
            #raise UnknownError('{0}: {1}. {2}'.format(type(_).__name__, _, support_msg_api()))
        except (KeyError, ValueError):
            raise UnknownError('{0}. {1}'.format(self.exception_msg(), support_msg_api()))
Exemple #18
0
class TravisLastBuildLog(CLI):
    def __init__(self):
        # Python 2.x
        super(TravisLastBuildLog, self).__init__()
        # Python 3.x
        # super().__init__()
        self.timeout_default = 600
        self.verbose_default = 1
        self.travis_token = None
        self.repo = None
        self.job_id = None
        self.num = None
        self.completed = False
        self.failed = False
        self.plaintext = False
        self.color = False
        self.headers = {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'Travis-API-Version': '3',
            'User-Agent': prog
        }
        self.request_handler = RequestHandler()

    def add_options(self):
        self.add_opt(
            '-R',
            '--repo',
            default=os.getenv('TRAVIS_REPO'),
            help='Travis CI repo to find last failed build ($TRAVIS_REPO)')
        self.add_opt(
            '-J',
            '--job-id',
            default=os.getenv('TRAVIS_JOB_ID'),
            help='Job ID to download log for a specific job ($TRAVIS_JOB_ID)')
        self.add_opt(
            '-T',
            '--travis-token',
            default=os.getenv('TRAVIS_TOKEN'),
            help=
            'Travis token required to authenticate to the API ($TRAVIS_TOKEN)')
        self.add_opt('-n',
                     '--num',
                     default=1,
                     help='Number of builds to pull logs from (default: 1)')
        self.add_opt('-c',
                     '--completed',
                     action='store_true',
                     default=False,
                     help='Only completed build(s)')
        self.add_opt('-f',
                     '--failed',
                     action='store_true',
                     default=False,
                     help='Only failed build(s)')
        #self.add_opt('-A', '--plaintext', action='store_true', default=False,
        #             help='Print in plaintext without fancy shell escapes ' + \
        #                  '(will do this by default if the output is not an interactive terminal ' + \
        #                  'such as piping through more)')
        #self.add_opt('-C', '--color', action='store_true', default=False,
        #             help='Force retention of fancy colour output regardless of interactive terminal or not ' + \
        #                  '(for piping through less -R)')

    def process_options(self):
        self.travis_token = self.get_opt('travis_token')
        self.repo = self.get_opt('repo')
        self.job_id = self.get_opt('job_id')
        if self.args:
            if '/' in self.args[0] and '://' not in self.args[0]:
                if not self.repo:
                    log.info('using argument as --repo')
                    self.repo = self.args[0]
            elif not self.job_id:
                log.info('using argument as --job-id')
                self.job_id = self.args[0]
        if self.job_id:
            # convenience to be able to lazily paste a URL like the following and still have it extract the job_id
            # https://travis-ci.org/HariSekhon/nagios-plugins/jobs/283840596#L1079
            self.job_id = self.job_id.split('/')[-1].split('#')[0]
            validate_chars(self.job_id, 'job id', '0-9')
        elif self.repo:
            travis_user = os.getenv('TRAVIS_USER')
            if '/' not in self.repo:
                self.repo = '/' + self.repo
            if self.repo[0] == '/' and travis_user:
                self.repo = travis_user + self.repo
            validate_chars(self.repo, 'repo', r'\/\w\.-')
        else:
            self.usage('--job-id / --repo not specified')
        validate_alnum(self.travis_token, 'travis token')
        self.headers['Authorization'] = 'token {0}'.format(self.travis_token)
        self.num = self.get_opt('num')
        validate_int(self.num, 'num', 1)
        self.num = int(self.num)
        self.completed = self.get_opt('completed')
        self.failed = self.get_opt('failed')
        #self.plaintext = self.get_opt('plaintext')
        #self.color = self.get_opt('color')
        #if self.plaintext and self.color:
        #    self.usage('cannot specify --plaintext and --color at the same time, they are mutually exclusive!')
        # test for interactive, switch off color if piping stdout somewhere
        #if not self.color and not (sys.__stdin__.isatty() and sys.__stdout__.isatty()):
        #    self.plaintext = True

    def run(self):
        if self.job_id:
            self.print_log(job_id=self.job_id)
        else:
            builds = self.get_builds()
            for build in builds:
                self.print_log(build=build)

    @staticmethod
    def parse_travis_error(req):
        error_message = ''
        try:
            _ = json.loads(req.content)
            error_message = _['error_message']
        except ValueError:
            if isStr(req.content) and len(req.content.split('\n')) == 1:
                error_message = req.content
        return error_message

    def get_builds(self):
        builds = self.get_latest_builds()
        try:
            builds = self.parse_builds(builds)
        except (KeyError, ValueError):
            exception = traceback.format_exc().split('\n')[-2]
            # this covers up the traceback info and makes it harder to debug
            #raise UnknownError('failed to parse expected json response from Travis CI API: {0}'.format(exception))
            qquit(
                'UNKNOWN',
                'failed to parse expected json response from Travis CI API: {0}. {1}'
                .format(exception, support_msg_api()))
        return builds

    def get_latest_builds(self):
        log.info('getting latest builds')
        # gets 404 unless replacing the slash
        url = 'https://api.travis-ci.org/repo/{repo}/builds'.format(
            repo=self.repo.replace('/', '%2F'))
        # request returns blank without authorization header
        req = self.request_handler.get(url, headers=self.headers)
        if log.isEnabledFor(logging.DEBUG):
            log.debug("\n%s", jsonpp(req.content))
        if not isJson(req.content):
            raise UnknownError('non-json returned by Travis CI. {0}'.format(
                support_msg_api()))
        return req.content

    def parse_builds(self, content):
        log.debug('parsing build info')
        build = None
        collected_builds = []
        json_data = json.loads(content)
        if not json_data or \
           'builds' not in json_data or \
           not json_data['builds']:
            qquit(
                'UNKNOWN', "no Travis CI builds returned by the Travis API." +
                " Either the specified repo '{0}' doesn't exist".format(
                    self.repo) + " or no builds have happened yet?" +
                " Also remember the repo is case sensitive, for example 'harisekhon/nagios-plugins' returns this"
                +
                " blank build set whereas 'HariSekhon/nagios-plugins' succeeds"
                + " in returning latest builds information")
        builds = json_data['builds']
        # get latest finished failed build
        last_build_number = None
        found_newer_passing_build = False
        for _ in builds:
            # API returns most recent build first
            # extra check to make sure we're getting the very latest build number and API hasn't changed
            build_number = _['number']
            if not isInt(build_number):
                raise UnknownError('build number returned is not an integer!')
            build_number = int(build_number)
            if last_build_number is None:
                last_build_number = int(build_number) + 1
            if build_number >= last_build_number:
                raise UnknownError('build number returned is out of sequence, cannot be >= last build returned' + \
                                   '{0}'.format(support_msg_api()))
            last_build_number = build_number
            if self.completed:
                if len(collected_builds) < self.num and _['state'] in (
                        'passed', 'finished', 'failed', 'errored'):
                    collected_builds.append(_)
            elif self.failed:
                if _['state'] == 'passed':
                    if not collected_builds and not found_newer_passing_build:
                        log.warning("found more recent successful build #%s with state = '%s'" + \
                                    ", you may not need to debug this build any more", _['number'], _['state'])
                        found_newer_passing_build = True
                elif _['state'] in ('failed', 'errored'):
                    if len(collected_builds) < self.num:
                        collected_builds.append(_)
                        # by continuing to iterate through the rest of the builds we can check
                        # their last_build numbers are descending for extra sanity checking
                        #break
            elif len(collected_builds) < self.num:
                collected_builds.append(_)
                # by continuing to iterate through the rest of the builds we can check
                # their last_build numbers are descending for extra sanity checking
                #break
        if not collected_builds:
            qquit('UNKNOWN', 'no recent builds found')
        if log.isEnabledFor(logging.DEBUG):
            for build in collected_builds:
                log.debug("build:\n%s", jsonpp(build))
        return collected_builds

    def print_log(self, build=None, job_id=None):
        if job_id:
            self.print_job_log(job_id=job_id)
            log.info('=' * 80)
            log.info('end of log for job id %s', job_id)
            log.info('=' * 80 + '\n')
        else:
            if not build:
                code_error(
                    'no job id passed to print_log(), nor build to determine job from'
                )
            log.info('getting job id for build #%s', build['number'])
            if 'jobs' not in build:
                raise UnknownError('no jobs field found in build, {0}'.format(
                    support_msg_api))
            for _ in build['jobs']:
                _id = _['id']
                url = 'https://api.travis-ci.org/jobs/{id}'.format(id=_id)
                req = self.request_handler.get(url)
                # if this raises ValueError it'll be caught by run handler
                job_data = json.loads(req.content)
                if log.isEnabledFor(logging.DEBUG):
                    log.debug("job id %s status:\n%s", _id, jsonpp(job_data))
                if self.failed is True:
                    if job_data['state'] == 'finished' and job_data[
                            'status'] in (None, 1, '1'):
                        job = job_data
                else:
                    job = job_data
            if not job:
                raise UnknownError('no job found in build {0}'.format(
                    build['number']))
            self.print_job_log(job=job)
            log.info('=' * 80)
            log.info('end of log for build number #%s job id %s',
                     build['number'], job['id'])
            log.info('=' * 80 + '\n')

    def print_job_log(self, job=None, job_id=None):
        #if (self.color or not self.plaintext) and 'log' in job:
        if not job and not job_id:
            code_error('no job data or job id passed to print_job_log()')
        content = None
        if job is not None:
            if 'log' in job and job['log']:
                content = job['log']
            else:
                job_id = job['id']
        if not content:
            url = 'https://api.travis-ci.org/jobs/{id}/log.txt?deansi=true'.format(
                id=job_id)
            req = self.request_handler.get(url)
            content = req.content
        content = re.sub(r'\r', '', content)
        #if self.plaintext:
        # leaves a few characters behind which are printable
        #content = re.sub('[^{0}]'.format(string.printable), '', content)
        # mandatory stripping ANSI control sequences for now as although color coding is nice
        # Travis has too many other control sequences that mess up my terminal
        # strip all control sequences
        content = strip_ansi_escape_codes(content)
        print(content)
Exemple #19
0
class TravisDebugSession(CLI):
    def __init__(self):
        # Python 2.x
        super(TravisDebugSession, self).__init__()
        # Python 3.x
        # super().__init__()
        self.timeout_default = 600
        self.verbose_default = 2
        self.job_id = None
        self.travis_token = None
        self.repo = None
        self.headers = {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'Travis-API-Version': '3',
            'User-Agent': prog
        }
        self.request_handler = RequestHandler()

    def check_job_launch_response_code(self, req):
        if req.status_code == 409:
            error_message = self.parse_travis_error(req)
            error_message += " (if you've just retriggered this you can avoid this error " + \
                             "using the --ignore-running switch)"
            if self.get_opt('ignore_running'):
                log.info('job already running (ignoring)')
            else:
                log.info('job already running')
                raise CriticalError('{0} {1}: {2}'.format(
                    req.status_code, req.reason, error_message))
        elif req.status_code != 202:
            error_message = self.parse_travis_error(req)
            raise CriticalError("{0} {1}: {2}".format(req.status_code,
                                                      req.reason,
                                                      error_message))

    def add_options(self):
        self.add_opt(
            '-J',
            '--job-id',
            default=os.getenv('JOB_ID'),
            help='Travis Job ID to initiate the debug session ($JOB_ID)')
        self.add_opt(
            '-T',
            '--travis-token',
            default=os.getenv('TRAVIS_TOKEN'),
            help=
            'Travis token required to authenticate to the API ($TRAVIS_TOKEN)')
        self.add_opt('-R', '--repo', default=os.getenv('TRAVIS_REPO'),
                     help='Travis CI repo to find last failed build and re-execute a job from it ($TRAVIS_REPO)' + \
                          ', easier alternative to specifying a specific --job-id' + \
                          ', convenient if working with the same repo over and over and don\'t want to copy the ' + \
                          'job id each time (--job-id takes priority if given as it\'s more specific). ' + \
                          'Be aware if running this quickly in succession you will get older and older failed ' + \
                          'builds as the last one will still be running, will only re-trigger finished failed builds')
        self.add_opt(
            '-i',
            '--ignore-running',
            action='store_true',
            help=
            'Ignore job already running error (avoids 409 error if you try to restart debug job)'
        )

    def process_options(self):
        self.job_id = self.get_opt('job_id')
        self.travis_token = self.get_opt('travis_token')
        self.repo = self.get_opt('repo')
        #if travis_token is None:
        #    self.usage('--travis-token option or ' +
        #               '$TRAVIS_TOKEN environment variable required to authenticate to the API')
        if self.args:
            # assume arg is a repo in form of HariSekhon/nagios-plugins but do not use url which we are more likely to
            # have pasted a travis-ci url to a job, see a few lines further down
            if '/' in self.args[0] and '://' not in self.args[0]:
                if not self.repo:
                    log.info('using argument as --repo')
                    self.repo = self.args[0]
            elif not self.job_id:
                log.info('using argument as --job-id')
                self.job_id = self.args[0]
        if self.job_id:
            # convenience to be able to lazily paste a URL like the following and still have it extract the job_id
            # https://travis-ci.org/HariSekhon/nagios-plugins/jobs/283840596#L1079
            self.job_id = self.job_id.split('/')[-1].split('#')[0]
            validate_chars(self.job_id, 'job id', '0-9')
        elif self.repo:
            validate_chars(self.repo, 'repo', r'\/\w\.-')
        else:
            self.usage('--job-id / --repo not specified')
        validate_alnum(self.travis_token, 'travis token')
        self.headers['Authorization'] = 'token {0}'.format(self.travis_token)

    def run(self):
        if not self.job_id:
            if self.repo:
                latest_failed_build = self.get_latest_failed_build()
                self.job_id = self.get_failing_job_id_from_build(
                    latest_failed_build)
            else:
                code_error('--job-id / --repo not specified, caught late')

        if self.job_id is None:
            raise UnknownError(
                'no job id was found, aborting getting SSH address')
        self.launch_job()
        ssh_address = self.get_ssh_address(job_id=self.job_id)
        log.info('Executing: ssh -- {0}'.format(ssh_address))
        sys.stdout.flush()
        sys.stderr.flush()
        self.disable_timeout()
        os.execvp('ssh', ['--', ssh_address])

    def launch_job(self):
        log.info('triggering debug job {job_id}'.format(job_id=self.job_id))
        url = 'https://api.travis-ci.org/job/{job_id}/debug'.format(
            job_id=self.job_id)
        self.request_handler.check_response_code = self.check_job_launch_response_code
        self.request_handler.post(url, headers=self.headers)

    @staticmethod
    def parse_travis_error(req):
        error_message = ''
        try:
            _ = json.loads(req.content)
            error_message = _['error_message']
        except ValueError:
            if isStr(req.content) and len(req.content.split('\n')) == 1:
                error_message = req.content
        return error_message

    def get_latest_failed_build(self):
        log.info('getting latest failed build')
        # gets 404 unless replacing the slash
        url = 'https://api.travis-ci.org/repo/{repo}/builds'.format(
            repo=self.repo.replace('/', '%2F'))
        # request returns blank without authorization header
        req = self.request_handler.get(url, headers=self.headers)
        if log.isEnabledFor(logging.DEBUG):
            log.debug("\n%s", jsonpp(req.content))
        try:
            latest_build = self.parse_latest_failed_build(req.content)
        except (KeyError, ValueError):
            exception = traceback.format_exc().split('\n')[-2]
            # this covers up the traceback info and makes it harder to debug
            #raise UnknownError('failed to parse expected json response from Travis CI API: {0}'.format(exception))
            qquit(
                'UNKNOWN',
                'failed to parse expected json response from Travis CI API: {0}. {1}'
                .format(exception, support_msg_api()))
        return latest_build

    def parse_latest_failed_build(self, content):
        log.debug('parsing latest failed build info')
        build = None
        json_data = json.loads(content)
        if not json_data or \
           'builds' not in json_data or \
           not json_data['builds']:
            qquit(
                'UNKNOWN', "no Travis CI builds returned by the Travis API." +
                " Either the specified repo '{0}' doesn't exist".format(
                    self.repo) + " or no builds have happened yet?" +
                " Also remember the repo is case sensitive, for example 'harisekhon/nagios-plugins' returns this"
                +
                " blank build set whereas 'HariSekhon/nagios-plugins' succeeds"
                + " in returning latest builds information")
        builds = json_data['builds']
        # get latest finished failed build
        last_build_number = None
        found_newer_passing_build = False
        for _ in builds:
            # API returns most recent build first so just take the first one that is completed
            # extra check to make sure we're getting the very latest build number and API hasn't changed
            build_number = _['number']
            if not isInt(build_number):
                raise UnknownError('build number returned is not an integer!')
            build_number = int(build_number)
            if last_build_number is None:
                last_build_number = int(build_number) + 1
            if build_number >= last_build_number:
                raise UnknownError('build number returned is out of sequence, cannot be >= last build returned' + \
                                   '{0}'.format(support_msg_api()))
            last_build_number = build_number
            if _['state'] == 'passed':
                if build is None and not found_newer_passing_build:
                    log.warning("found more recent successful build #%s with state = '%s'" + \
                                ", you may not need to debug this build any more", _['number'], _['state'])
                    found_newer_passing_build = True
            elif _['state'] in ('failed', 'errored'):
                if build is None:
                    build = _
                    # by continuing to iterate through the rest of the builds we can check
                    # their last_build numbers are descending for extra sanity checking
                    #break
        if build is None:
            qquit('UNKNOWN', 'no recent failed builds found' + \
                             ', you may need to specify the --job-id explicitly as shown in the Travis CI UI')
        if log.isEnabledFor(logging.DEBUG):
            log.debug("latest failed build:\n%s", jsonpp(build))
        return build

    def get_failing_job_id_from_build(self, build):
        log.info('getting failed job id for build #%s', build['number'])
        if 'jobs' not in build:
            raise UnknownError(
                'no jobs field found in build, {0}'.format(support_msg_api))
        for _ in build['jobs']:
            _id = _['id']
            url = 'https://api.travis-ci.org/jobs/{id}'.format(id=_id)
            req = self.request_handler.get(url)
            # if this raises ValueError it'll be caught by run handler
            job = json.loads(req.content)
            if log.isEnabledFor(logging.DEBUG):
                log.debug("job id %s status:\n%s", _id, jsonpp(job))
            if job['state'] == 'finished' and job['status'] in (None, 1, '1'):
                return _id
        raise UnknownError('no failed job found in build {0}'.format(
            build['number']))

    def get_ssh_address(self, job_id):
        log.info('getting SSH address from triggered debug build')
        max_tries = int(self.timeout / 4)
        for i in range(1, max_tries + 1):
            log.info('try {0}/{1}: checking job log for ssh address...'.format(
                i, max_tries))
            ssh_address = self.get_ssh_address_attempt(job_id=job_id)
            if ssh_address:
                return ssh_address
            time.sleep(3)
        if ssh_address is None:
            raise CriticalError(
                'ssh address not found in output from Travis API. {0}'.format(
                    support_msg_api()))

    def get_ssh_address_attempt(self, job_id):
        #url = 'https://travis-ci.org/{repo}/jobs/{job_id}'.format(repo=repo, job_id=job_id)
        url = 'https://api.travis-ci.org/jobs/{job_id}/log.txt?deansi=true'.format(
            job_id=job_id)
        log.debug('GET %s' % url)
        try:
            req = requests.get(url)
        except requests.exceptions.RequestException as _:
            raise CriticalError(_)
        log.debug("response: %s %s", req.status_code, req.reason)
        log.debug("content:\n%s\n%s\n%s", '=' * 80, req.content.strip(),
                  '=' * 80)
        # Travis CI behaviour has changed from 200 with no content indicating build log empty, not started yet
        # to now returning "500 Internal Server Error", content: "Sorry, we experienced an error."
        if req.status_code == 500:
            # don't output 500 it will confuse users in to thinking there is a real error which 500 usually indicates
            #log.info('500 internal server error, build not started yet')
            log.info('build not started yet')
            return None
        if req.status_code != 200:
            error_message = self.parse_travis_error(req)
            raise CriticalError('{0} {1}: {2}'.format(req.status_code,
                                                      req.reason,
                                                      error_message))
        content = req.content
        if not content:
            log.info('build log empty, build not started yet')
            return None
        # find last non-blank line - do this after checking for no content otherwise will hit StopIteration
        last_line = next(_ for _ in reversed(content.split('\n')) if _)
        #log.debug('last line: %s', last_line)
        # 'Done: Job Cancelled'
        if 'Job Cancelled' in last_line:
            raise CriticalError(last_line)
        elif 'Your build has been stopped' in last_line:
            raise CriticalError(last_line)
        # Done. Your build exited with 0
        elif 'build exited with' in last_line:
            raise CriticalError(last_line)
        # The build has been terminated
        elif 'build has been terminated' in last_line:
            raise CriticalError(last_line)

        ssh_address = None
        regex_ssh = re.compile(
            r'^\s*ssh\s+(\w+\@{host_regex})\s*$'.format(host_regex=host_regex),
            re.I)
        for line in content.split('\n'):
            match = regex_ssh.match(line)
            if match:
                ssh_address = match.group(1)
                break
        return ssh_address
 def __init__(self):
     super(CheckConsulKey, self).__init__()
     self.name = 'Consul'
     self.default_port = 8500
     self.request_handler = RequestHandler()
Exemple #21
0
class CheckDockerhubRepoBuildStatus(NagiosPlugin):

    def __init__(self):
        # Python 2.x
        super(CheckDockerhubRepoBuildStatus, self).__init__()
        # Python 3.x
        # super().__init__()
        self.request = RequestHandler()
        self.statuses = {
            '0': 'Queued',
            '3': 'Building',
            '10': 'Success',
            '-1': 'Error',
            '-4': 'Cancelled',
        }
        self.msg = 'DockerHub repo '
        self.repo = None
        self.tag = None
        self.max_pages = None
        self.start_time = None
        self.ok()

    def add_options(self):
        self.add_opt('-r', '--repo',
                     help="DockerHub repository to check, in form of '<user>/<repo>' eg. harisekhon/pytools")
        self.add_opt('-T', '--tag', help='Check status of only this tag eg. latest or 2.7')
        self.add_opt('-p', '--pages', default=1, metavar='num',
                     help='Max number of API pages to iterate on to find the latest build (default: 1)' + \
                          '. If increasing this you will probably also need to increase --timeout')

    def process_options(self):
        self.repo = self.get_opt('repo')
        validate_chars(self.repo, 'repo', 'A-Za-z0-9/_-')
        # official repos don't have slashes in them but then you can't check their build statuses either
        if '/' not in self.repo:
            self.usage('--repo must contain a slash (/) in it - ' + \
                       'official repos are not supported as DockerHub doesn\'t expose their build info')
        (namespace, repo) = self.repo.split('/', 1)
        validate_chars(namespace, 'namespace', 'A-Za-z0-9_-')
        validate_chars(repo, 'repo', 'A-Za-z0-9_-')
        self.repo = '{0}/{1}'.format(namespace, repo)

        # not needed as dashes and underscores are all that validation above permits through and they
        # are returned as is and processed successfully by DockerHub API
        #(user, repo) = self.repo.split('/', 1)
        #repo = urllib.quote_plus(repo)
        #self.repo = '{0}/{1}'.format(user, repo)

        self.tag = self.get_opt('tag')
        if self.tag is not None:
            # if you have a tag which characters other than these then please raise a ticket for extension at:
            #
            #   https://github.com/harisekhon/nagios-plugins/issues
            #
            self.tag = self.tag.lstrip(':')
            validate_chars(self.tag, 'tag', r'A-Za-z0-9/\._-')
            #if not self.tag:
            #    self.usage('--tag cannot be blank if given')
        self.max_pages = self.get_opt('pages')
        # if you have to iterate more than 20 pages you have problems, and this check will take ages
        validate_int(self.max_pages, 'max pages', 1, 20)
        self.max_pages = int(self.max_pages)

    def run(self):
        start_time = time.time()
        for page in range(1, self.max_pages + 1):
            url = 'https://registry.hub.docker.com/v2/repositories/{repo}/buildhistory?page={page}'\
                  .format(repo=self.repo, page=page)
            req = self.request.get(url)
            if log.isEnabledFor(logging.DEBUG):
                log.debug(jsonpp(req.content))
            json_data = json.loads(req.content)
            log.debug('%s out of %s results returned for page %s', len(json_data['results']), json_data['count'], page)
            if self.process_results(json_data):
                # not quite as accurate as before as it now includes processing time but close enough
                query_time = time.time() - start_time
                if '|' not in self.msg:
                    self.msg += ' |'
                self.msg += ' query_time={0:.2f}s'.format(query_time)
                return True
        extra_info = ''
        if self.verbose:
            extra_info = ' ({0} page{1} of API output)'\
                         .format(self.max_pages, plural(self.max_pages))
        raise UnknownError('no completed builds found in last {0} builds{1}'.format(self.max_pages * 10, extra_info))

    def process_results(self, json_data):
        for result in json_data['results']:
            tag = result['dockertag_name']
            build_code = result['build_code']
            _id = result['id']
            # Skip Queued / Building as we're only interested in latest completed build status
            if int(result['status']) in (0, 3):
                if log.isEnabledFor(logging.DEBUG):
                    log.debug("skipping queued/in progress build tag '%s', id: %s, build_code: %s",
                              tag, _id, build_code)
                continue
            if self.tag and self.tag != tag:
                if log.isEnabledFor(logging.DEBUG):
                    log.debug("skipping build tag '%s', id: %s, build_code: %s, does not match given --tag %s",
                              tag, _id, build_code, self.tag)
                continue
            self.process_result(result)
            return True
        return False

    def process_result(self, result):
        _id = result['id']
        log.info('latest build id: %s', _id)

        status = result['status']
        log.info('status: %s', status)
        if not isInt(status, allow_negative=True):
            raise UnknownError('non-integer status returned by DockerHub API. {0}'.format(support_msg_api()))

        tag = result['dockertag_name']
        log.info('tag: %s', tag)

        trigger = result['cause']
        log.info('trigger: %s', trigger)

        created_date = result['created_date']
        log.info('created date: %s', created_date)

        last_updated = result['last_updated']
        log.info('last updated: %s', last_updated)

        created_datetime = datetime.datetime.strptime(created_date.split('.')[0], '%Y-%m-%dT%H:%M:%S')
        updated_datetime = datetime.datetime.strptime(last_updated.split('.')[0], '%Y-%m-%dT%H:%M:%S')
        build_latency_timedelta = updated_datetime - created_datetime
        build_latency = build_latency_timedelta.total_seconds()
        log.info('build latency (creation to last updated): %s', build_latency)
        # results in .0 floats anyway
        build_latency = int(build_latency)

        build_code = result['build_code']
        build_url = 'https://hub.docker.com/r/{0}/builds/{1}'.format(self.repo, build_code)
        log.info('latest build URL: %s', build_url)

        if str(status) in self.statuses:
            status = self.statuses[str(status)]
        else:
            log.warning("status code '%s' not recognized! %s", status, support_msg_api())
            log.warning('defaulting to assume status is an Error')
            status = 'Error'
        if status != 'Success':
            self.critical()
        self.msg += "'{repo}' last completed build status: '{status}', tag: '{tag}', build code: {build_code}"\
                    .format(repo=self.repo, status=status, tag=tag, build_code=build_code)
        if self.verbose:
            self.msg += ', id: {0}'.format(_id)
            self.msg += ', trigger: {0}'.format(trigger)
            self.msg += ', created date: {0}'.format(created_date)
            self.msg += ', last updated: {0}'.format(last_updated)
            self.msg += ', build_latency: {0}'.format(sec2human(build_latency))
            self.msg += ', build URL: {0}'.format(build_url)
        self.msg += ' | build_latency={0:d}s'.format(build_latency)
Exemple #22
0
class RestNagiosPlugin(NagiosPlugin):

    __version__ = __version__
    # abstract class
    __metaclass__ = ABCMeta

    def __init__(self):
        # Python 2.x
        super(RestNagiosPlugin, self).__init__()
        # Python 3.x
        # super().__init__()
        self.name = None
        self.default_host = 'localhost'
        self.default_port = 80
        self.default_user = None
        self.default_password = None
        self.host = None
        self.port = None
        self.user = None
        self.password = None
        self.protocol = 'http'
        self.msg = 'rest msg not defined yet'
        self.request = RequestHandler()
        self.req = None
        self.json_data = None
        self.path = None
        self.json = False
        self.ok()

    def add_options(self):
        self.add_hostoption(name=self.name,
                            default_host=self.default_host,
                            default_port=self.default_port)
        self.add_useroption(name=self.name,
                            default_user=self.default_user,
                            default_password=self.default_password)
        self.add_ssl_option()

    def add_ssl_option(self):
        self.add_opt('-S', '--use-ssl', action='store_true', default=False, help='Use SSL')

    def process_options(self):
        self.no_args()
        self.host = self.get_opt('host')
        self.port = self.get_opt('port')
        self.user = self.get_opt('user')
        self.password = self.get_opt('password')
        validate_host(self.host)
        validate_user(self.user)
        validate_password(self.password)
        validate_port(self.port)
        use_ssl = self.get_opt('use_ssl')
        log_option('ssl', use_ssl)
        if use_ssl and self.protocol == 'http':
            self.protocol = 'https'

    def run(self):
        self.req = self.query()
        if self.json:
            self.process_json(self.req.content)
        else:
            self.parse(self.req)

    def query(self):
        url = '{proto}://{host}:{port}/'.format(proto=self.protocol,
                                                host=self.host,
                                                port=self.port)
        if self.path:
            url += self.path.lstrip('/')
        auth = None
        if self.user and self.password:
            log.info('authenicating to rest API')
            auth = (self.user, self.password)
        req = self.request.get(url, auth=auth)
        return req

    #@abstractmethod
    def parse(self, req):
        pass

    #@abstractmethod
    def parse_json(self, json_data):
        pass

    def process_json(self, content):
        try:
            self.json_data = json.loads(content)
            if log.isEnabledFor(logging.DEBUG):
                log.debug('JSON prettified:\n\n%s\n%s', jsonpp(self.json_data), '='*80)
            return self.parse_json(self.json_data)
        except (KeyError, ValueError) as _:
            #raise UnknownError('{0}: {1}. {2}'.format(type(_).__name__, _, support_msg_api()))
            raise UnknownError('{0}. {1}'.format(self.exception_msg(), support_msg_api()))
Exemple #23
0
class RestNagiosPlugin(NagiosPlugin):

    __version__ = __version__
    # abstract class
    __metaclass__ = ABCMeta

    def __init__(self):
        # Python 2.x
        super(RestNagiosPlugin, self).__init__()
        # Python 3.x
        # super().__init__()
        self.name = None
        self.default_host = 'localhost'
        self.default_port = 80
        self.default_user = None
        self.default_password = None
        self.host = None
        self.port = None
        self.user = None
        self.password = None
        self.protocol = 'http'
        self.msg = 'rest msg not defined yet'
        self.request = RequestHandler()
        self.request_method = 'get'
        self.req = None
        self.json_data = None
        self.path = None
        self.json = False
        self.headers = {}
        self.auth = True
        self.ok()

    def add_options(self):
        self.add_hostoption(name=self.name,
                            default_host=self.default_host,
                            default_port=self.default_port)
        if self.auth:
            self.add_useroption(name=self.name,
                                default_user=self.default_user,
                                default_password=self.default_password)
            self.add_opt('--kerberos', action='store_true',
                         help='Kerberos SpNego authentication, uses TGT cache from $KRB5CCNAME or keytab ' + \
                              'from $KRB5_CLIENT_KEYTAB environment variable if defined ' + \
                              '(overrides --user/--password)')
        self.add_ssl_option()

    def process_options(self):
        self.no_args()
        self.host = self.get_opt('host')
        self.port = self.get_opt('port')
        validate_host(self.host)
        validate_port(self.port)
        if self.auth and self.get_opt('kerberos'):
            self.auth = 'kerberos'
        if self.auth:
            self.user = self.get_opt('user')
            self.password = self.get_opt('password')
            if self.auth == 'optional':
                if self.user and self.password:
                    validate_user(self.user)
                    validate_password(self.password)
            elif self.auth == 'kerberos':
                if os.getenv('KRB5_CLIENT_KTNAME'):
                    log.debug('kerberos enabled, will try to use keytab at %s',
                              os.getenv('KRB5_CLIENT_KTNAME'))
                    # if using KRB5_CLIENT_KTNAME to kinit avoid clobbering the same TGT cache /tmp/krb5cc_{uid}
                    # as that may be used by different programs kinit'd different keytabs
                    os.environ[
                        'KRB5CCNAME'] = '/tmp/krb5cc_{euid}_{basename}'.format(
                            euid=os.geteuid(), basename=prog)
            else:
                validate_user(self.user)
                validate_password(self.password)
        ssl_noverify = self.get_opt('ssl_noverify')
        if ssl_noverify:
            log_option('ssl no verify', 'true')
            ssl = 1
            os.environ['SSL_NO_VERIFY'] = '1'
            # doesn't work, probably too late after instantiation
            #if not os.getenv('PYTHONWARNINGS'):
            #    os.environ['PYTHONWARNINGS'] = 'ignore:Unverified HTTPS request'
            urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
        else:
            ssl = self.get_opt('ssl')
            log_option('ssl', ssl)
        if ssl and self.protocol == 'http':
            self.protocol = 'https'
        if self.json:
            # recommended for many systems like CouchDB
            # but breaks Ambari API calls
            #self.headers['Accept'] = 'application/json'
            self.headers['Content-Type'] = 'application/json'

    def run(self):
        start_time = time.time()
        self.req = self.query()
        query_time = time.time() - start_time
        if self.json:
            log.info('parsing json response')
            self.process_json(self.req.content)
        else:
            log.info('parsing response')
            self.parse(self.req)
        if '|' not in self.msg:
            self.msg += ' |'
        if ' query_time=' not in self.msg:
            self.msg += ' query_time={0:.4f}s'.format(query_time)

    def query(self):
        url = '{proto}://{host}:{port}/'.format(proto=self.protocol,
                                                host=self.host,
                                                port=self.port)
        if self.path:
            url += self.path.lstrip('/')
        auth = None
        if self.auth == 'kerberos':
            log.info('authenticating to Rest API with Kerberos')
            auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL)
        elif self.user and self.password:
            log.info('authenticating to Rest API with username and password')
            auth = (self.user, self.password)  # pylint: disable=redefined-variable-type
        kwargs = {}
        if os.getenv('SSL_NO_VERIFY'):
            kwargs['verify'] = False
        req = self.request.req(self.request_method,
                               url,
                               auth=auth,
                               headers=self.headers,
                               **kwargs)
        return req

    #@abstractmethod
    def parse(self, req):
        pass

    #@abstractmethod
    def parse_json(self, json_data):
        pass

    def process_json(self, content):
        try:
            self.json_data = json.loads(content)
            if log.isEnabledFor(logging.DEBUG):
                log.debug('JSON prettified:\n\n%s\n%s', jsonpp(self.json_data),
                          '=' * 80)
            return self.parse_json(self.json_data)
        #except (KeyError, ValueError) as _:
        #raise UnknownError('{0}: {1}. {2}'.format(type(_).__name__, _, support_msg_api()))
        except (KeyError, ValueError):
            raise UnknownError('{0}. {1}'.format(self.exception_msg(),
                                                 support_msg_api()))
 def test_request_handler(self):
     req = RequestHandler().get('www.google.com')
     self.assertTrue(isinstance, requests.Response)
     RequestHandler(req)
 def __init__(self):
     super(CheckConsulKey, self).__init__()
     self.name = 'Consul'
     self.default_port = 8500
     self.request_handler = RequestHandler()
class CheckDockerhubRepoBuildStatus(NagiosPlugin):

    def __init__(self):
        # Python 2.x
        super(CheckDockerhubRepoBuildStatus, self).__init__()
        # Python 3.x
        # super().__init__()
        self.request = RequestHandler()
        self.statuses = {
            '0': 'Queued',
            '3': 'Building',
            '10': 'Success',
            '-1': 'Error',
            '-4': 'Cancelled',
        }
        self.msg = 'DockerHub repo '
        self.repo = None
        self.tag = None
        self.max_pages = None
        self.start_time = None
        self.ok()

    def add_options(self):
        self.add_opt('-r', '--repo',
                     help="DockerHub repository to check, in form of '<user>/<repo>' eg. harisekhon/pytools")
        self.add_opt('-T', '--tag', help='Check status of only this tag eg. latest or 2.7')
        self.add_opt('-p', '--pages', default=1, metavar='num',
                     help='Max number of API pages to iterate on to find the latest build (default: 1)' + \
                          '. If increasing this you will probably also need to increase --timeout')

    def process_options(self):
        self.repo = self.get_opt('repo')
        #validate_chars(self.repo, 'repo', 'A-Za-z0-9/_-')
        # official repos don't have slashes in them but then you can't check their build statuses either
        #if '/' not in self.repo:
        #    self.usage('--repo must contain a slash (/) in it - ' + \
        #               'official repos are not supported as DockerHub doesn\'t expose their build info')
        (namespace, repo) = self.repo.split('/', 1)
        validate_chars(namespace, 'namespace', 'A-Za-z0-9_-')
        validate_chars(repo, 'repo', 'A-Za-z0-9_-')
        self.repo = '{0}/{1}'.format(namespace, repo)

        # not needed as dashes and underscores are all that validation above permits through and they
        # are returned as is and processed successfully by DockerHub API
        #(user, repo) = self.repo.split('/', 1)
        #repo = urllib.quote_plus(repo)
        #self.repo = '{0}/{1}'.format(user, repo)

        self.tag = self.get_opt('tag')
        if self.tag is not None:
            # if you have a tag which characters other than these then please raise a ticket for extension at:
            #
            #   https://github.com/harisekhon/nagios-plugins/issues
            #
            self.tag = self.tag.lstrip(':')
            validate_chars(self.tag, 'tag', r'A-Za-z0-9/\._-')
            #if not self.tag:
            #    self.usage('--tag cannot be blank if given')
        self.max_pages = self.get_opt('pages')
        # if you have to iterate more than 20 pages you have problems, and this check will take ages
        validate_int(self.max_pages, 'max pages', 1, 20)
        self.max_pages = int(self.max_pages)

    def run(self):
        start_time = time.time()
        for page in range(1, self.max_pages + 1):
            url = 'https://registry.hub.docker.com/v2/repositories/{repo}/buildhistory?page={page}'\
                  .format(repo=self.repo, page=page)
            req = self.request.get(url)
            if log.isEnabledFor(logging.DEBUG):
                log.debug(jsonpp(req.content))
            json_data = json.loads(req.content)
            log.debug('%s out of %s results returned for page %s', len(json_data['results']), json_data['count'], page)
            if self.process_results(json_data):
                # not quite as accurate as before as it now includes processing time but close enough
                query_time = time.time() - start_time
                if '|' not in self.msg:
                    self.msg += ' |'
                self.msg += ' query_time={0:.2f}s'.format(query_time)
                return True
        extra_info = ''
        if self.verbose:
            extra_info = ' ({0} page{1} of API output)'\
                         .format(self.max_pages, plural(self.max_pages))
        raise UnknownError('no completed builds found in last {0} builds{1}'.format(self.max_pages * 10, extra_info))

    def process_results(self, json_data):
        for result in json_data['results']:
            tag = result['dockertag_name']
            build_code = result['build_code']
            _id = result['id']
            # Skip Queued / Building as we're only interested in latest completed build status
            if int(result['status']) in (0, 3):
                if log.isEnabledFor(logging.DEBUG):
                    log.debug("skipping queued/in progress build tag '%s', id: %s, build_code: %s",
                              tag, _id, build_code)
                continue
            if self.tag and self.tag != tag:
                if log.isEnabledFor(logging.DEBUG):
                    log.debug("skipping build tag '%s', id: %s, build_code: %s, does not match given --tag %s",
                              tag, _id, build_code, self.tag)
                continue
            self.process_result(result)
            return True
        return False

    def process_result(self, result):
        _id = result['id']
        log.info('latest build id: %s', _id)

        status = result['status']
        log.info('status: %s', status)
        if not isInt(status, allow_negative=True):
            raise UnknownError('non-integer status returned by DockerHub API. {0}'.format(support_msg_api()))

        tag = result['dockertag_name']
        log.info('tag: %s', tag)

        trigger = result['cause']
        log.info('trigger: %s', trigger)

        created_date = result['created_date']
        log.info('created date: %s', created_date)

        last_updated = result['last_updated']
        log.info('last updated: %s', last_updated)

        created_datetime = datetime.datetime.strptime(created_date.split('.')[0], '%Y-%m-%dT%H:%M:%S')
        updated_datetime = datetime.datetime.strptime(last_updated.split('.')[0], '%Y-%m-%dT%H:%M:%S')
        build_latency_timedelta = updated_datetime - created_datetime
        build_latency = build_latency_timedelta.total_seconds()
        log.info('build latency (creation to last updated): %s', build_latency)
        # results in .0 floats anyway
        build_latency = int(build_latency)

        build_code = result['build_code']
        build_url = 'https://hub.docker.com/r/{0}/builds/{1}'.format(self.repo, build_code)
        log.info('latest build URL: %s', build_url)

        if str(status) in self.statuses:
            status = self.statuses[str(status)]
        else:
            log.warning("status code '%s' not recognized! %s", status, support_msg_api())
            log.warning('defaulting to assume status is an Error')
            status = 'Error'
        if status != 'Success':
            self.critical()
        self.msg += "'{repo}' last completed build status: '{status}', tag: '{tag}', build code: {build_code}"\
                    .format(repo=self.repo, status=status, tag=tag, build_code=build_code)
        if self.verbose:
            self.msg += ', id: {0}'.format(_id)
            self.msg += ', trigger: {0}'.format(trigger)
            self.msg += ', created date: {0}'.format(created_date)
            self.msg += ', last updated: {0}'.format(last_updated)
            self.msg += ', build_latency: {0}'.format(sec2human(build_latency))
            self.msg += ', build URL: {0}'.format(build_url)
        self.msg += ' | build_latency={0:d}s'.format(build_latency)
class CheckConsulPeerCount(NagiosPlugin):

    def __init__(self):
        super(CheckConsulPeerCount, self).__init__()
        self.name = 'Consul'
        self.default_port = 8500
        self.host = None
        self.port = None
        self.request_handler = RequestHandler()
        self.msg = 'NO MESSAGE DEFINED'

    def add_options(self):
        self.add_hostoption(name=self.name, default_host='localhost', default_port=self.default_port)
        self.add_thresholds(default_warning=1, default_critical=1)

    @staticmethod
    def get_peers(content):
        json_data = None
        try:
            json_data = json.loads(content)
        except ValueError:
            raise UnknownError("non-json data returned by consul: '%s'. %s" % (content, support_msg_api()))
        if not json_data:
            raise CriticalError('no peers found, recently started?')
        #if not json_data:
        #    raise UnknownError("blank list returned by consul! '%s'. %s" % (content, support_msg_api()))
        if not isList(json_data):
            raise UnknownError("non-list returned by consul: '%s'. %s" % (content, support_msg_api()))
        for peer in json_data:
            log.debug('peer: {0}'.format(peer))
        peers = uniq_list(json_data)
        return peers

    # closure factory
    @staticmethod
    def check_response_code(msg):
        def tmp(req):
            if req.status_code != 200:
                err = ''
                if req.content and isStr(req.content) and len(req.content.split('\n')) < 2:
                    err += ': ' + req.content
                raise CriticalError("{0}: '{1}' {2}{3}".format(msg, req.status_code, req.reason, err))
        return tmp

    def run(self):
        self.host = self.get_opt('host')
        self.port = self.get_opt('port')
        validate_host(self.host)
        validate_port(self.port)
        self.validate_thresholds(optional=True, simple='lower')
        url = 'http://%(host)s:%(port)s/v1/status/peers' % self.__dict__
        req = self.request_handler.get(url)
        self.request_handler.check_response_code = \
            self.check_response_code('failed to retrieve Consul peers')
        peers = self.get_peers(req.content)
        peer_count = len(peers)
        self.ok()
        self.msg = 'Consul peer count = {0}'.format(peer_count)
        self.check_thresholds(peer_count)
        self.msg += ' | consul_peer_count={0}'.format(peer_count)
        #self.msg += self.get_perf_thresholds(boundary='lower')
        self.msg += self.get_perf_thresholds(boundary='lower')
 def __init__(self):
     super(ConsulKeyCheck, self).__init__()
     self.name = "Consul"
     self.default_port = 8500
     self.request_handler = RequestHandler()
 def get_status(self):
     url = 'http://%(host)s:%(port)s/oozie/v1/admin/status' % self.__dict__
     req = RequestHandler().get(url)
     return self.parse(req)
Exemple #30
0
class ConsulPeerCount(NagiosPlugin):

    def __init__(self):
        super(ConsulPeerCount, self).__init__()
        self.name = 'Consul'
        self.default_port = 8500
        self.host = None
        self.port = None
        self.request_handler = RequestHandler()
        self.msg = 'NO MESSAGE DEFINED'

    def add_options(self):
        self.add_hostoption(name=self.name, default_host='localhost', default_port=self.default_port)
        self.add_thresholds(default_warning=1, default_critical=1)

    @staticmethod
    def get_peers(content):
        json_data = None
        try:
            json_data = json.loads(content)
        except ValueError:
            raise UnknownError("non-json data returned by consul: '%s'. %s" % (content, support_msg_api()))
        if not json_data:
            raise CriticalError('no peers found, recently started?')
        #if not json_data:
        #    raise UnknownError("blank list returned by consul! '%s'. %s" % (content, support_msg_api()))
        if not isList(json_data):
            raise UnknownError("non-list returned by consul: '%s'. %s" % (content, support_msg_api()))
        for peer in json_data:
            log.debug('peer: {0}'.format(peer))
        peers = uniq_list(json_data)
        return peers

    # closure factory
    @staticmethod
    def check_response_code(msg):
        def tmp(req):
            if req.status_code != 200:
                err = ''
                if req.content and isStr(req.content) and len(req.content.split('\n')) < 2:
                    err += ': ' + req.content
                raise CriticalError("{0}: '{1}' {2}{3}".format(msg, req.status_code, req.reason, err))
        return tmp

    def run(self):
        self.host = self.get_opt('host')
        self.port = self.get_opt('port')
        validate_host(self.host)
        validate_port(self.port)
        self.validate_thresholds(optional=True, simple='lower')
        url = 'http://%(host)s:%(port)s/v1/status/peers' % self.__dict__
        req = self.request_handler.get(url)
        self.request_handler.check_response_code = \
            self.check_response_code('failed to retrieve Consul peers')
        peers = self.get_peers(req.content)
        peer_count = len(peers)
        self.ok()
        self.msg = 'Consul peer count = {0}'.format(peer_count)
        self.check_thresholds(peer_count)
        self.msg += ' | consul_peer_count={0}'.format(peer_count)
        #self.msg += self.get_perf_thresholds(boundary='lower')
        self.msg += self.get_perf_thresholds(boundary='lower')