arachnys / cabot

Self-hosted, easily-deployable monitoring and alerts service - like a lightweight PagerDuty
MIT License
5.58k stars 590 forks source link

How to add API route for a check plugin #703

Closed jakubgs closed 3 years ago

jakubgs commented 3 years ago

I've created a custom check plugin for my use case: https://github.com/status-im/cabot-check-status-go

And it works great, but the problem is that there is no API route available to programatically create the checks.

How would one go about adding the API route to a plugin? Is this even doable within a plugin?

Or would it require changes to Cabot itself?

jakubgs commented 3 years ago

I tried adding this to my model.py:

from rest_framework import routers, serializers, viewsets, mixins
from cabot import create_viewset, status_check_fields

router = routers.DefaultRouter()

router.register(r'status_go_checks', create_viewset(
    arg_model=StatusGoStatusCheck,
    arg_fields=status_check_fields + (
        'node_type',
        'enode',
    ),
))

But worker fails to start with:

Traceback (most recent call last):
  File "/usr/local/bin/gunicorn", line 8, in <module>
    sys.exit(run())
  File "/usr/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 74, in run
    WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run()
  File "/usr/local/lib/python2.7/site-packages/gunicorn/app/base.py", line 203, in run
    super(Application, self).run()
  File "/usr/local/lib/python2.7/site-packages/gunicorn/app/base.py", line 72, in run
    Arbiter(self).run()
  File "/usr/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 231, in run
    self.halt(reason=inst.reason, exit_status=inst.exit_status)
  File "/usr/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 344, in halt
    self.stop()
  File "/usr/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 393, in stop
    time.sleep(0.1)
  File "/usr/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 244, in handle_chld
    self.reap_workers()
  File "/usr/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 524, in reap_workers
    raise HaltServer(reason, self.WORKER_BOOT_ERROR)
gunicorn.errors.HaltServer: <HaltServer 'Worker failed to boot.' 3>
jakubgs commented 3 years ago

Worker shows in logs this:

Traceback (most recent call last):
  File "/usr/local/bin/celery", line 8, in <module>
    sys.exit(main())
  File "/usr/local/lib/python2.7/site-packages/celery/__main__.py", line 16, in main
    _main()
  File "/usr/local/lib/python2.7/site-packages/celery/bin/celery.py", line 322, in main
    cmd.execute_from_commandline(argv)
  File "/usr/local/lib/python2.7/site-packages/celery/bin/celery.py", line 496, in execute_from_commandline
    super(CeleryCommand, self).execute_from_commandline(argv)))
  File "/usr/local/lib/python2.7/site-packages/celery/bin/base.py", line 275, in execute_from_commandline
    return self.handle_argv(self.prog_name, argv[1:])
  File "/usr/local/lib/python2.7/site-packages/celery/bin/celery.py", line 488, in handle_argv
    return self.execute(command, argv)
  File "/usr/local/lib/python2.7/site-packages/celery/bin/celery.py", line 420, in execute
    ).run_from_argv(self.prog_name, argv[1:], command=argv[0])
  File "/usr/local/lib/python2.7/site-packages/celery/bin/worker.py", line 223, in run_from_argv
    return self(*args, **options)
  File "/usr/local/lib/python2.7/site-packages/celery/bin/base.py", line 238, in __call__
    ret = self.run(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/celery/bin/worker.py", line 257, in run
    **kwargs)
  File "/usr/local/lib/python2.7/site-packages/celery/worker/worker.py", line 96, in __init__
    self.app.loader.init_worker()
  File "/usr/local/lib/python2.7/site-packages/celery/loaders/base.py", line 114, in init_worker
    self.import_default_modules()
  File "/usr/local/lib/python2.7/site-packages/celery/loaders/base.py", line 108, in import_default_modules
    raise response
ImportError: cannot import name create_viewset
jakubgs commented 3 years ago

I think I figured it out, based on looking at this method: https://github.com/arachnys/cabot/blob/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/urls.py#L174-L187 Specifically this part:

             urlpatterns += [ 
                 url(r'^plugins/%s/' % plugin, include('%s.urls' % plugin)) 
             ] 

Which means that the contents of urls.py file in a plugin are loaded into urlpatterns under plugins/.

jakubgs commented 3 years ago

I managed to get a route working by adding this:

from django.conf.urls import include, url
from rest_framework.routers import DefaultRouter

from cabot.rest_urls import create_viewset, status_check_fields

from .models import StatusGoStatusCheck

api_router = DefaultRouter()
api_router.register(r'status_go_checks', create_viewset(
    arg_model=StatusGoStatusCheck,
    arg_fields=status_check_fields + (
        'node_type',
        'enode',
    ),
))

urlpatterns = [
    url(r'^api/', include(api_router.urls)),
    # other routes omitted
]

And by doing this I managed to find my route using the cabot shell command:

>>> from cabot.urls import urlpatterns
>>> urlpatterns[-2]
<RegexURLResolver <module 'cabot_check_status_go.urls' from '/usr/local/lib/python2.7/site-packages/cabot_check_status_go/urls.pyc'> (None:None) ^plugins/cabot_check_status_go/>
>>> urlpatterns[-2].url_patterns
[<RegexURLResolver <RegexURLPattern list> (None:None) ^api/>]

Which showed me that I can now access the API under: /plugins/cabot_check_status_go/api/status_go_checks/

jakubgs commented 3 years ago

I'm not sure if there is an easy way to put this directly under /api/ at the root, but putting it under /plugins/cabot_check_status_go/ is good enough for me.

dbuxton commented 3 years ago

@jakubgs I'm not sure you meant to post this on this repo?

jakubgs commented 3 years ago

Heh, true, that's what happens when you have too many tabs and working on too many issues at the same time.