diff --git a/.gitignore b/.gitignore index acb04e66e..34d46dd31 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,10 @@ dev.db venv/* backups/* -static/ cabot/.collectstatic/ node_modules/* .python-eggs/* cabot.egg-info -cabot/static/ .env .DS_Store celerybeat-schedule @@ -20,6 +18,7 @@ dist/ local_config.yml build/ .dockerignore +docker.env .idea Pipfile.lock .tox/ @@ -127,6 +126,7 @@ __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid +.vscode # SageMath parsed files *.sage.py @@ -166,8 +166,4 @@ cython_debug/ # End of https://www.toptal.com/developers/gitignore/api/python -Dockerfile -requirements-plugins.txt -requirements-dev.txt -setup.py -Dockerfile copy \ No newline at end of file + diff --git a/Dockerfile b/Dockerfile index d27ed3044..fcf94f261 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,7 @@ RUN apk update && apk add --no-cache \ py-pip \ postgresql-dev \ mariadb-dev \ + mysql-client \ gcc \ curl \ curl-dev \ @@ -19,7 +20,8 @@ RUN apk update && apk add --no-cache \ python3-dev \ musl-dev \ libevent-dev \ - bash + bash \ + git # create and activate virtual environment # using final folder name to avoid path issues with packages @@ -38,10 +40,14 @@ WORKDIR /code COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt +COPY requirements-plugins.txt ./ +RUN pip install --no-cache-dir -force-reinstall -r requirements-plugins.txt + ######################################################## FROM python:3.6-alpine AS runner-image -RUN apk add --no-cache libpq +RUN apk add --no-cache libpq \ + mariadb-connector-c-dev RUN adduser -S cabot COPY --from=builder-image /home/cabot/venv /home/cabot/venv @@ -52,6 +58,7 @@ WORKDIR /home/cabot/code COPY ./cabot ./cabot COPY manage.py ./manage.py +COPY docker-entrypoint.sh ./docker-entrypoint.sh EXPOSE 8000 @@ -68,4 +75,4 @@ ENV PATH="/home/cabot/venv/bin:$PATH" #CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] -ENTRYPOINT ["docker-entrypoint.sh"] \ No newline at end of file +ENTRYPOINT ["sh","docker-entrypoint.sh"] diff --git a/Dockerfile-j b/Dockerfile-j deleted file mode 100644 index 5f112fad2..000000000 --- a/Dockerfile-j +++ /dev/null @@ -1,71 +0,0 @@ -FROM python:3.6.5-slim-jessie AS builder-image - -RUN apt-get update -RUN DEBIAN_FRONTEND=noninteractive apt-get install -y \ - libmysqlclient-dev \ - build-essential \ - python3-dev \ - python2.7-dev \ - libldap2-dev \ - libsasl2-dev \ - slapd \ - ldap-utils \ - python-tox \ - lcov \ - valgrind\ - curl\ - python-dev \ - gcc \ - musl-dev \ - libffi-dev \ - ca-certificates \ - git\ - bash - - -# create and activate virtual environment -# using final folder name to avoid path issues with packages -RUN python3 -m venv /home/cabot/venv -ENV PATH="/home/cabot/venv/bin:$PATH" - - -ENV PYTHONUNBUFFERED 1 - -RUN pip install --upgrade pip - -RUN mkdir /code - -WORKDIR /code - -COPY requirements.txt ./ -RUN pip install --no-cache-dir -r requirements.txt - -######################################################## -FROM python:3.6.5-slim-jessie AS runner-image - -RUN apt-get update && apt-get install --no-install-recommends -y python3-venv && \ -apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN useradd --create-home cabot -COPY --from=builder-image /home/cabot/venv /home/cabot/venv - -USER cabot -RUN mkdir /home/cabot/code -WORKDIR /home/cabot/code -COPY . . - -EXPOSE 8000 - -# make sure all messages always reach console -ENV PYTHONUNBUFFERED=1 - -# activate virtual environment -ENV VIRTUAL_ENV=/home/cabot/venv -ENV PATH="/home/cabot/venv/bin:$PATH" - -# /dev/shm is mapped to shared memory and should be used for gunicorn heartbeat -# this will improve performance and avoid random freezes -CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] - - -#ENTRYPOINT ["./docker-entrypoint.sh"] \ No newline at end of file diff --git a/cabot/.env _example b/cabot/.env _example new file mode 100644 index 000000000..46d4b3102 --- /dev/null +++ b/cabot/.env _example @@ -0,0 +1,66 @@ +DEBUG=True +PROD=False + +#################### DATABASE CONFIG +#Check diferent options: https://github.com/kennethreitz/dj-database-url + +DATABASE_NAME=cabot +DATABASE_USER=root +DATABASE_PASSWORD=root +DATABASE_HOST=db +DATABASE_PORT=3306 + +#################### PLUGINS - DON'T LEAVE SPACE BETWEEN THEM, ONLY COMMACABOT_PLUGINS=cabot_alert_slack,cabot_check_http,cabot_check_network,cabot_alert_email +CABOT_PLUGINS=cabot_alert_slack,cabot_check_http,cabot_check_network,cabot_alert_email + +#################### GENERAL CONFIG +DJANGO_SETTINGS_MODULE=cabot.settings +LOG_FILE=/dev/null +PORT=8000 +TIME_ZONE=America/Argentina/Buenos_Aires + +#################### CELERY CONFIG +CELERY_BROKER_URL=redis://redis:6379/1 +CELERY_RESULT_BACKEND=redis://redis:6379/1 +CELERY_BEAT_SCHEDULER=django_celery_beat.schedulers:DatabaseScheduler + +#################### DJANGO MAIL CONFIG +ADMIN_EMAIL=example@example.com +CABOT_FROM_EMAIL=example@example.com + +#################### EMAIL ALERT CONFIG +EMAIL_HOST=in-v3.example.com +EMAIL_USER=example +EMAIL_PASSWORD=example + +#################### SLACK CONFIG +SLACK_ALERT_CHANNEL=cabot +SLACK_WEBHOOK_URL=https://discord.com/api/webhooks +SLACK_ICON_URL=http://lorempixel.com/40/40/ + +# Typical SMTP port 587 or 25 configuration (with no SSL/TLS) +EMAIL_PORT=587 +EMAIL_USE_TLS=0 +EMAIL_USE_SSL=0 + +# URL of calendar to synchronise rota with +CALENDAR_ICAL_URL=http://www.google.com/calendar/ical/example.ics + +# Django settings +DJANGO_SECRET_KEY=example + +# User-Agent string used for HTTP checks +HTTP_USER_AGENT=Cabot + +# Used for pointing links back in alerts etc. +WWW_HTTP_HOST=localhost +WWW_SCHEME=http + +# Twilio integration for SMS and telephone alerts +#TWILIO_ACCOUNT_SID=your_account_sid +#TWILIO_AUTH_TOKEN=your_auth_token +#TWILIO_OUTGOING_NUMBER=+14155551234 + +# Hipchat integration +#HIPCHAT_ALERT_ROOM=room_name_or_id +#HIPCHAT_API_KEY=your_hipchat_api_key diff --git a/cabot/cabotapp/migrations/0001_initial.py b/cabot/cabotapp/migrations/0001_initial.py index c584e81eb..0c80ca3fe 100644 --- a/cabot/cabotapp/migrations/0001_initial.py +++ b/cabot/cabotapp/migrations/0001_initial.py @@ -1,293 +1,227 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals +# Generated by Django 3.2.5 on 2021-08-07 15:44 -from django.db import models, migrations from django.conf import settings +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): + initial = True + dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('contenttypes', '0001_initial'), + ('contenttypes', '0002_remove_content_type_name'), ] operations = [ - migrations.CreateModel( - name='AlertAcknowledgement', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('time', models.DateTimeField()), - ('cancelled_time', models.DateTimeField(null=True, blank=True)), - ('cancelled_user', models.ForeignKey(related_name='cancelleduser_set', blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)), - ], - options={ - }, - bases=(models.Model,), - ), migrations.CreateModel( name='AlertPlugin', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('title', models.CharField(unique=True, max_length=30, editable=False)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(editable=False, max_length=30, unique=True)), ('enabled', models.BooleanField(default=True)), - ('polymorphic_ctype', models.ForeignKey(related_name='polymorphic_cabotapp.alertplugin_set', editable=False, to='contenttypes.ContentType', null=True, on_delete=models.CASCADE)), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_cabotapp.alertplugin_set+', to='contenttypes.contenttype')), ], options={ 'abstract': False, + 'base_manager_name': 'objects', }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='AlertPluginUserData', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('title', models.CharField(max_length=30, editable=False)), - ('polymorphic_ctype', models.ForeignKey(related_name='polymorphic_cabotapp.alertpluginuserdata_set', editable=False, to='contenttypes.ContentType', null=True, on_delete=models.CASCADE)), - ], - options={ - }, - bases=(models.Model,), ), migrations.CreateModel( name='Instance', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.TextField()), - ('alerts_enabled', models.BooleanField(default=True, help_text=b'Alert when this service is not healthy.')), - ('last_alert_sent', models.DateTimeField(null=True, blank=True)), + ('alerts_enabled', models.BooleanField(default=True, help_text='Alert when this service is not healthy.')), + ('last_alert_sent', models.DateTimeField(blank=True, null=True)), ('email_alert', models.BooleanField(default=False)), ('hipchat_alert', models.BooleanField(default=True)), ('sms_alert', models.BooleanField(default=False)), - ('telephone_alert', models.BooleanField(default=False, help_text=b'Must be enabled, and check importance set to Critical, to receive telephone alerts.')), - ('overall_status', models.TextField(default=b'PASSING')), - ('old_overall_status', models.TextField(default=b'PASSING')), - ('hackpad_id', models.TextField(help_text=b'Gist, Hackpad or Refheap js embed with recovery instructions e.g. https://you.hackpad.com/some_document.js', null=True, verbose_name=b'Embedded recovery instructions', blank=True)), - ('runbook_link', models.TextField(help_text=b'Link to the service runbook on your wiki.', blank=True)), - ('address', models.TextField(help_text=b'Address (IP/Hostname) of service.', blank=True)), - ('alerts', models.ManyToManyField(help_text=b'Alerts channels through which you wish to be notified', to='cabotapp.AlertPlugin', blank=True)), + ('telephone_alert', models.BooleanField(default=False, help_text='Must be enabled, and check importance set to Critical, to receive telephone alerts.')), + ('overall_status', models.TextField(default='PASSING')), + ('old_overall_status', models.TextField(default='PASSING')), + ('hackpad_id', models.TextField(blank=True, help_text='Gist, Hackpad or Refheap js embed with recovery instructions e.g. https://you.hackpad.com/some_document.js', null=True, verbose_name='Embedded recovery instructions')), + ('runbook_link', models.TextField(blank=True, help_text='Link to the service runbook on your wiki.')), + ('address', models.TextField(blank=True, help_text='Address (IP/Hostname) of service.')), + ('alerts', models.ManyToManyField(blank=True, help_text='Alerts channels through which you wish to be notified', to='cabotapp.AlertPlugin')), ], options={ 'ordering': ['name'], }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='InstanceStatusSnapshot', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('time', models.DateTimeField(db_index=True)), - ('num_checks_active', models.IntegerField(default=0)), - ('num_checks_passing', models.IntegerField(default=0)), - ('num_checks_failing', models.IntegerField(default=0)), - ('overall_status', models.TextField(default=b'PASSING')), - ('did_send_alert', models.IntegerField(default=False)), - ('instance', models.ForeignKey(related_name='snapshots', to='cabotapp.Instance', on_delete=models.CASCADE)), - ], - options={ - 'abstract': False, - }, - bases=(models.Model,), ), migrations.CreateModel( name='Service', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.TextField()), - ('alerts_enabled', models.BooleanField(default=True, help_text=b'Alert when this service is not healthy.')), - ('last_alert_sent', models.DateTimeField(null=True, blank=True)), + ('alerts_enabled', models.BooleanField(default=True, help_text='Alert when this service is not healthy.')), + ('last_alert_sent', models.DateTimeField(blank=True, null=True)), ('email_alert', models.BooleanField(default=False)), ('hipchat_alert', models.BooleanField(default=True)), ('sms_alert', models.BooleanField(default=False)), - ('telephone_alert', models.BooleanField(default=False, help_text=b'Must be enabled, and check importance set to Critical, to receive telephone alerts.')), - ('overall_status', models.TextField(default=b'PASSING')), - ('old_overall_status', models.TextField(default=b'PASSING')), - ('hackpad_id', models.TextField(help_text=b'Gist, Hackpad or Refheap js embed with recovery instructions e.g. https://you.hackpad.com/some_document.js', null=True, verbose_name=b'Embedded recovery instructions', blank=True)), - ('runbook_link', models.TextField(help_text=b'Link to the service runbook on your wiki.', blank=True)), - ('url', models.TextField(help_text=b'URL of service.', blank=True)), - ('alerts', models.ManyToManyField(help_text=b'Alerts channels through which you wish to be notified', to='cabotapp.AlertPlugin', blank=True)), - ('instances', models.ManyToManyField(help_text=b'Instances this service is running on.', to='cabotapp.Instance', blank=True)), + ('telephone_alert', models.BooleanField(default=False, help_text='Must be enabled, and check importance set to Critical, to receive telephone alerts.')), + ('overall_status', models.TextField(default='PASSING')), + ('old_overall_status', models.TextField(default='PASSING')), + ('hackpad_id', models.TextField(blank=True, help_text='Gist, Hackpad or Refheap js embed with recovery instructions e.g. https://you.hackpad.com/some_document.js', null=True, verbose_name='Embedded recovery instructions')), + ('runbook_link', models.TextField(blank=True, help_text='Link to the service runbook on your wiki.')), + ('url', models.TextField(blank=True, help_text='URL of service.')), + ('is_public', models.BooleanField(default=False, help_text='The service will be shown in the public home', verbose_name='Is Public')), + ('alerts', models.ManyToManyField(blank=True, help_text='Alerts channels through which you wish to be notified', to='cabotapp.AlertPlugin')), + ('instances', models.ManyToManyField(blank=True, help_text='Instances this service is running on.', to='cabotapp.Instance')), ], options={ 'ordering': ['name'], }, - bases=(models.Model,), ), migrations.CreateModel( - name='ServiceStatusSnapshot', + name='StatusCheck', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('time', models.DateTimeField(db_index=True)), - ('num_checks_active', models.IntegerField(default=0)), - ('num_checks_passing', models.IntegerField(default=0)), - ('num_checks_failing', models.IntegerField(default=0)), - ('overall_status', models.TextField(default=b'PASSING')), - ('did_send_alert', models.IntegerField(default=False)), - ('service', models.ForeignKey(related_name='snapshots', to='cabotapp.Service', on_delete=models.CASCADE)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.TextField()), + ('active', models.BooleanField(default=True, help_text='If not active, check will not be used to calculate service status and will not trigger alerts.')), + ('importance', models.CharField(choices=[('WARNING', 'Warning'), ('ERROR', 'Error'), ('CRITICAL', 'Critical')], default='ERROR', help_text='Severity level of a failure. Critical alerts are for failures you want to wake you up at 2am, Errors are things you can sleep through but need to fix in the morning, and warnings for less important things.', max_length=30)), + ('frequency', models.IntegerField(default=5, help_text='Minutes between each check.')), + ('debounce', models.IntegerField(default=0, help_text='Number of successive failures permitted before check will be marked as failed. Default is 0, i.e. fail on first failure.', null=True)), + ('calculated_status', models.CharField(blank=True, choices=[('passing', 'passing'), ('intermittent', 'intermittent'), ('failing', 'failing')], default='passing', max_length=50)), + ('last_run', models.DateTimeField(null=True)), + ('cached_health', models.TextField(editable=False, null=True)), + ('metric', models.TextField(help_text='fully.qualified.name of the Graphite metric you want to watch. This can be any valid Graphite expression, including wildcards, multiple hosts, etc.', null=True)), + ('check_type', models.CharField(choices=[('>', 'Greater than'), ('>=', 'Greater than or equal'), ('<', 'Less than'), ('<=', 'Less than or equal'), ('==', 'Equal to')], max_length=100, null=True)), + ('value', models.TextField(help_text='If this expression evaluates to true, the check will fail (possibly triggering an alert).', null=True)), + ('expected_num_hosts', models.IntegerField(default=0, help_text='The minimum number of data series (hosts) you expect to see.', null=True)), + ('allowed_num_failures', models.IntegerField(default=0, help_text='The maximum number of data series (metrics) you expect to fail. For example, you might be OK with 2 out of 3 webservers having OK load (1 failing), but not 1 out of 3 (2 failing).', null=True)), + ('endpoint', models.TextField(help_text='HTTP(S) endpoint to poll.', null=True, validators=[django.core.validators.URLValidator()])), + ('username', models.TextField(blank=True, help_text='Basic auth username.', null=True)), + ('password', models.TextField(blank=True, help_text='Basic auth password.', null=True)), + ('text_match', models.TextField(blank=True, help_text='Regex to match against source of page.', null=True)), + ('status_code', models.TextField(default=200, help_text='Status code expected from endpoint.', null=True)), + ('timeout', models.IntegerField(default=30, help_text='Time out after this many seconds.', null=True)), + ('verify_ssl_certificate', models.BooleanField(default=True, help_text='Set to false to allow not try to verify ssl certificates (default True)')), + ('max_queued_build_time', models.IntegerField(blank=True, help_text='Alert if build queued for more than this many minutes.', null=True)), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_cabotapp.statuscheck_set+', to='contenttypes.contenttype')), ], options={ + 'ordering': ['name'], 'abstract': False, + 'base_manager_name': 'objects', }, - bases=(models.Model,), + ), + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('mobile_number', models.CharField(blank=True, default='', max_length=20)), + ('hipchat_alias', models.CharField(blank=True, default='', max_length=50)), + ('fallback_alert_user', models.BooleanField(default=False)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), + ], ), migrations.CreateModel( name='Shift', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('start', models.DateTimeField()), ('end', models.DateTimeField()), ('uid', models.TextField()), ('last_modified', models.DateTimeField()), ('deleted', models.BooleanField(default=False)), - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='StatusCheck', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.TextField()), - ('active', models.BooleanField(default=True, help_text=b'If not active, check will not be used to calculate service status and will not trigger alerts.')), - ('importance', models.CharField(default=b'ERROR', help_text=b'Severity level of a failure. Critical alerts are for failures you want to wake you up at 2am, Errors are things you can sleep through but need to fix in the morning, and warnings for less important things.', max_length=30, choices=[(b'WARNING', b'Warning'), (b'ERROR', b'Error'), (b'CRITICAL', b'Critical')])), - ('frequency', models.IntegerField(default=5, help_text=b'Minutes between each check.')), - ('debounce', models.IntegerField(default=0, help_text=b'Number of successive failures permitted before check will be marked as failed. Default is 0, i.e. fail on first failure.', null=True)), - ('calculated_status', models.CharField(default=b'passing', max_length=50, blank=True, choices=[(b'passing', b'passing'), (b'intermittent', b'intermittent'), (b'failing', b'failing')])), - ('last_run', models.DateTimeField(null=True)), - ('cached_health', models.TextField(null=True, editable=False)), - ('metric', models.TextField(help_text=b'fully.qualified.name of the Graphite metric you want to watch. This can be any valid Graphite expression, including wildcards, multiple hosts, etc.', null=True)), - ('check_type', models.CharField(max_length=100, null=True, choices=[(b'>', b'Greater than'), (b'>=', b'Greater than or equal'), (b'<', b'Less than'), (b'<=', b'Less than or equal'), (b'==', b'Equal to')])), - ('value', models.TextField(help_text=b'If this expression evaluates to true, the check will fail (possibly triggering an alert).', null=True)), - ('expected_num_hosts', models.IntegerField(default=0, help_text=b'The minimum number of data series (hosts) you expect to see.', null=True)), - ('allowed_num_failures', models.IntegerField(default=0, help_text=b'The maximum number of data series (metrics) you expect to fail. For example, you might be OK with 2 out of 3 webservers having OK load (1 failing), but not 1 out of 3 (2 failing).', null=True)), - ('endpoint', models.TextField(help_text=b'HTTP(S) endpoint to poll.', null=True)), - ('username', models.TextField(help_text=b'Basic auth username.', null=True, blank=True)), - ('password', models.TextField(help_text=b'Basic auth password.', null=True, blank=True)), - ('text_match', models.TextField(help_text=b'Regex to match against source of page.', null=True, blank=True)), - ('status_code', models.TextField(default=200, help_text=b'Status code expected from endpoint.', null=True)), - ('timeout', models.IntegerField(default=30, help_text=b'Time out after this many seconds.', null=True)), - ('verify_ssl_certificate', models.BooleanField(default=True, help_text=b'Set to false to allow not try to verify ssl certificates (default True)')), - ('max_queued_build_time', models.IntegerField(help_text=b'Alert if build queued for more than this many minutes.', null=True, blank=True)), - ('created_by', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)), - ('polymorphic_ctype', models.ForeignKey(related_name='polymorphic_cabotapp.statuscheck_set', editable=False, to='contenttypes.ContentType', null=True, on_delete=models.CASCADE)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], - options={ - 'ordering': ['name'], - 'abstract': False, - }, - bases=(models.Model,), ), migrations.CreateModel( - name='StatusCheckResult', + name='ServiceStatusSnapshot', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('time', models.DateTimeField(db_index=True)), - ('time_complete', models.DateTimeField(null=True, db_index=True)), - ('raw_data', models.TextField(null=True)), - ('succeeded', models.BooleanField(default=False)), - ('error', models.TextField(null=True)), - ('job_number', models.PositiveIntegerField(null=True)), - ('check', models.ForeignKey(to='cabotapp.StatusCheck', on_delete=models.CASCADE)), - ], - options={ - 'ordering': ['-time_complete'], - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='UserProfile', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('mobile_number', models.CharField(default=b'', max_length=20, blank=True)), - ('hipchat_alias', models.CharField(default=b'', max_length=50, blank=True)), - ('fallback_alert_user', models.BooleanField(default=False)), - ('user', models.OneToOneField(related_name='profile', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), + ('num_checks_active', models.IntegerField(default=0)), + ('num_checks_passing', models.IntegerField(default=0)), + ('num_checks_failing', models.IntegerField(default=0)), + ('overall_status', models.TextField(default='PASSING')), + ('did_send_alert', models.IntegerField(default=False)), + ('service', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='snapshots', to='cabotapp.service')), ], options={ + 'abstract': False, }, - bases=(models.Model,), - ), - migrations.AlterIndexTogether( - name='statuscheckresult', - index_together=set([('check', 'time_complete'), ('check', 'id')]), ), migrations.AddField( model_name='service', name='status_checks', - field=models.ManyToManyField(help_text=b'Checks used to calculate service status.', to='cabotapp.StatusCheck', blank=True), - preserve_default=True, + field=models.ManyToManyField(blank=True, help_text='Checks used to calculate service status.', to='cabotapp.StatusCheck'), ), migrations.AddField( model_name='service', name='users_to_notify', - field=models.ManyToManyField(help_text=b'Users who should receive alerts.', to=settings.AUTH_USER_MODEL, blank=True), - preserve_default=True, + field=models.ManyToManyField(blank=True, help_text='Users who should receive alerts.', to=settings.AUTH_USER_MODEL), + ), + migrations.CreateModel( + name='InstanceStatusSnapshot', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('time', models.DateTimeField(db_index=True)), + ('num_checks_active', models.IntegerField(default=0)), + ('num_checks_passing', models.IntegerField(default=0)), + ('num_checks_failing', models.IntegerField(default=0)), + ('overall_status', models.TextField(default='PASSING')), + ('did_send_alert', models.IntegerField(default=False)), + ('instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='snapshots', to='cabotapp.instance')), + ], + options={ + 'abstract': False, + }, ), migrations.AddField( model_name='instance', name='status_checks', - field=models.ManyToManyField(help_text=b'Checks used to calculate service status.', to='cabotapp.StatusCheck', blank=True), - preserve_default=True, + field=models.ManyToManyField(blank=True, help_text='Checks used to calculate service status.', to='cabotapp.StatusCheck'), ), migrations.AddField( model_name='instance', name='users_to_notify', - field=models.ManyToManyField(help_text=b'Users who should receive alerts.', to=settings.AUTH_USER_MODEL, blank=True), - preserve_default=True, - ), - migrations.AddField( - model_name='alertpluginuserdata', - name='user', - field=models.ForeignKey(editable=False, to='cabotapp.UserProfile', on_delete=models.CASCADE), - preserve_default=True, - ), - migrations.AlterUniqueTogether( - name='alertpluginuserdata', - unique_together=set([('title', 'user')]), - ), - migrations.AddField( - model_name='alertacknowledgement', - name='service', - field=models.ForeignKey(to='cabotapp.Service', on_delete=models.CASCADE), - preserve_default=True, - ), - migrations.AddField( - model_name='alertacknowledgement', - name='user', - field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE), - preserve_default=True, + field=models.ManyToManyField(blank=True, help_text='Users who should receive alerts.', to=settings.AUTH_USER_MODEL), ), migrations.CreateModel( - name='GraphiteStatusCheck', + name='AlertAcknowledgement', fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('time', models.DateTimeField()), + ('cancelled_time', models.DateTimeField(blank=True, null=True)), + ('cancelled_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cancelleduser_set', to=settings.AUTH_USER_MODEL)), + ('service', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cabotapp.service')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], - options={ - 'abstract': False, - 'proxy': True, - }, - bases=('cabotapp.statuscheck',), ), - migrations.CreateModel( - name='ICMPStatusCheck', + name='StatusCheckResult', fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('time', models.DateTimeField(db_index=True)), + ('time_complete', models.DateTimeField(db_index=True, null=True)), + ('raw_data', models.TextField(null=True)), + ('succeeded', models.BooleanField(default=False)), + ('error', models.TextField(null=True)), + ('job_number', models.PositiveIntegerField(null=True)), + ('consecutive_failures', models.PositiveIntegerField(null=True)), + ('status_check', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cabotapp.statuscheck')), ], options={ - 'abstract': False, - 'proxy': True, + 'ordering': ['-time_complete'], + 'index_together': {('status_check', 'id'), ('status_check', 'time_complete')}, }, - bases=('cabotapp.statuscheck',), ), migrations.CreateModel( - name='JenkinsStatusCheck', + name='AlertPluginUserData', fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(editable=False, max_length=30)), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_cabotapp.alertpluginuserdata_set+', to='contenttypes.contenttype')), + ('user', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='cabotapp.userprofile')), ], options={ - 'abstract': False, - 'proxy': True, + 'unique_together': {('title', 'user')}, }, - bases=('cabotapp.statuscheck',), ), ] diff --git a/cabot/cabotapp/migrations/0002_auto_20170131_1537.py b/cabot/cabotapp/migrations/0002_auto_20170131_1537.py deleted file mode 100644 index d3f0b7859..000000000 --- a/cabot/cabotapp/migrations/0002_auto_20170131_1537.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('cabotapp', '0001_initial'), - ] - - operations = [ - migrations.RenameField( - model_name='statuscheckresult', - old_name='check', - new_name='status_check', - ), - migrations.AlterIndexTogether( - name='statuscheckresult', - index_together=set([('status_check', 'time_complete'), ('status_check', 'id')]), - ), - ] diff --git a/cabot/cabotapp/migrations/0003_auto_20170201_1045.py b/cabot/cabotapp/migrations/0003_auto_20170201_1045.py deleted file mode 100644 index da979a391..000000000 --- a/cabot/cabotapp/migrations/0003_auto_20170201_1045.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('cabotapp', '0002_auto_20170131_1537'), - ] - - operations = [ - migrations.AlterField( - model_name='alertplugin', - name='polymorphic_ctype', - field=models.ForeignKey(related_name='polymorphic_cabotapp.alertplugin_set+', editable=False, to='contenttypes.ContentType', null=True, on_delete=models.CASCADE), - ), - migrations.AlterField( - model_name='alertpluginuserdata', - name='polymorphic_ctype', - field=models.ForeignKey(related_name='polymorphic_cabotapp.alertpluginuserdata_set+', editable=False, to='contenttypes.ContentType', null=True, on_delete=models.CASCADE), - ), - migrations.AlterField( - model_name='statuscheck', - name='polymorphic_ctype', - field=models.ForeignKey(related_name='polymorphic_cabotapp.statuscheck_set+', editable=False, to='contenttypes.ContentType', null=True, on_delete=models.CASCADE), - ), - ] diff --git a/cabot/cabotapp/migrations/0004_auto_20170802_1327.py b/cabot/cabotapp/migrations/0004_auto_20170802_1327.py deleted file mode 100644 index 65878c441..000000000 --- a/cabot/cabotapp/migrations/0004_auto_20170802_1327.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('cabotapp', '0003_auto_20170201_1045'), - ] - - operations = [ - migrations.AddField( - model_name='Service', - name='is_public', - field=models.BooleanField(default=False, help_text=b'The service will be shown in the public home', verbose_name=b'Is Public'), - ), - ] diff --git a/cabot/cabotapp/migrations/0005_auto_20170818_1202.py b/cabot/cabotapp/migrations/0005_auto_20170818_1202.py deleted file mode 100644 index 1eca49be0..000000000 --- a/cabot/cabotapp/migrations/0005_auto_20170818_1202.py +++ /dev/null @@ -1,107 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2017-08-18 12:02 -from __future__ import unicode_literals - -import os - -import django.db.models.deletion -from django.contrib.contenttypes.models import ContentType -from django.db import migrations, models - - -def move_old_jenkins_checks(apps, schema_editor): - db_alias = schema_editor.connection.alias - - JenkinsStatusCheck = apps.get_model("cabotapp", "JenkinsStatusCheck") - JenkinsCheck = apps.get_model("cabotapp", "JenkinsCheck") - JenkinsConfig = apps.get_model("cabotapp", "JenkinsConfig") - - # Due to a polymorphic bug, JenkinsStatusCheck actually returns all status checks - # Use this to filter out the other checks. - jenkins_content_type = ContentType.objects.filter(model="jenkinsstatuscheck").first() - - if jenkins_content_type and not JenkinsStatusCheck.objects.filter(polymorphic_ctype_id=jenkins_content_type.id).exists(): - return - - if not JenkinsConfig.objects.exists(): - JenkinsConfig.objects.create( - name="Default Jenkins", - jenkins_api=os.environ.get("JENKINS_API", "http://jenkins.example.com"), - jenkins_user=os.environ.get("JENKINS_USER", ""), - jenkins_pass=os.environ.get("JENKINS_PASS", ""), - ) - - default_config = JenkinsConfig.objects.first() - - for old_check in JenkinsStatusCheck.objects.all(): - if old_check.polymorphic_ctype_id != jenkins_content_type.id: - continue - new_check = JenkinsCheck( - active=old_check.active, - allowed_num_failures=old_check.allowed_num_failures, - cached_health=old_check.cached_health, - calculated_status=old_check.calculated_status, - check_type=old_check.check_type, - created_by_id=old_check.created_by_id, - debounce=old_check.debounce, - endpoint=old_check.endpoint, - expected_num_hosts=old_check.expected_num_hosts, - frequency=old_check.frequency, - importance=old_check.importance, - last_run=old_check.last_run, - max_queued_build_time=old_check.max_queued_build_time, - metric=old_check.metric, - name=old_check.name, - password=old_check.password, - status_code=old_check.status_code, - text_match=old_check.text_match, - timeout=old_check.timeout, - username=old_check.username, - value=old_check.value, - jenkins_config=default_config, - # For some reason this isn't handled automatically... - # The model is renamed in the next migration so the ctype - # id stays consistent. - polymorphic_ctype_id=old_check.polymorphic_ctype_id - ) - new_check.save(using=db_alias) - new_check.service_set.add(*old_check.service_set.all()) - new_check.instance_set.add(*old_check.instance_set.all()) - new_check.save(using=db_alias) - old_check.delete(using=db_alias) - - -class Migration(migrations.Migration): - - dependencies = [ - ('cabotapp', '0004_auto_20170802_1327'), - ] - - operations = [ - migrations.CreateModel( - name='JenkinsCheck', - fields=[ - ('statuscheck_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cabotapp.StatusCheck')), - ], - options={ - 'abstract': False, - }, - bases=('cabotapp.statuscheck',), - ), - migrations.CreateModel( - name='JenkinsConfig', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30)), - ('jenkins_api', models.CharField(max_length=2000)), - ('jenkins_user', models.CharField(max_length=2000)), - ('jenkins_pass', models.CharField(max_length=2000)), - ], - ), - migrations.AddField( - model_name='jenkinscheck', - name='jenkins_config', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cabotapp.JenkinsConfig'), - ), - migrations.RunPython(move_old_jenkins_checks) - ] diff --git a/cabot/cabotapp/migrations/0006_auto_20170821_1000.py b/cabot/cabotapp/migrations/0006_auto_20170821_1000.py deleted file mode 100644 index 764165ff1..000000000 --- a/cabot/cabotapp/migrations/0006_auto_20170821_1000.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2017-08-21 10:00 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('cabotapp', '0005_auto_20170818_1202'), - ] - - operations = [ - migrations.DeleteModel( - name='JenkinsStatusCheck', - ), - migrations.RenameModel( - old_name='JenkinsCheck', - new_name='JenkinsStatusCheck', - ) - ] diff --git a/cabot/cabotapp/migrations/0007_statuscheckresult_consecutive_failures.py b/cabot/cabotapp/migrations/0007_statuscheckresult_consecutive_failures.py deleted file mode 100644 index d2cf8dd12..000000000 --- a/cabot/cabotapp/migrations/0007_statuscheckresult_consecutive_failures.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2017-08-24 11:19 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('cabotapp', '0006_auto_20170821_1000'), - ] - - operations = [ - migrations.AddField( - model_name='statuscheckresult', - name='consecutive_failures', - field=models.PositiveIntegerField(null=True), - ), - ] diff --git a/cabot/cabotapp/migrations/0008_auto_20210707_1645.py b/cabot/cabotapp/migrations/0008_auto_20210707_1645.py deleted file mode 100644 index df5ca84d7..000000000 --- a/cabot/cabotapp/migrations/0008_auto_20210707_1645.py +++ /dev/null @@ -1,256 +0,0 @@ -# Generated by Django 3.2.5 on 2021-07-07 16:45 - -from django.conf import settings -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('cabotapp', '0007_statuscheckresult_consecutive_failures'), - ] - - operations = [ - migrations.AlterModelOptions( - name='alertplugin', - options={'base_manager_name': 'objects'}, - ), - migrations.AlterModelOptions( - name='graphitestatuscheck', - options={'base_manager_name': 'objects'}, - ), - migrations.AlterModelOptions( - name='icmpstatuscheck', - options={'base_manager_name': 'objects'}, - ), - migrations.AlterModelOptions( - name='jenkinsstatuscheck', - options={'base_manager_name': 'objects'}, - ), - migrations.AlterModelOptions( - name='statuscheck', - options={'base_manager_name': 'objects', 'ordering': ['name']}, - ), - migrations.AlterField( - model_name='instance', - name='address', - field=models.TextField(blank=True, help_text='Address (IP/Hostname) of service.'), - ), - migrations.AlterField( - model_name='instance', - name='alerts', - field=models.ManyToManyField(blank=True, help_text='Alerts channels through which you wish to be notified', to='cabotapp.AlertPlugin'), - ), - migrations.AlterField( - model_name='instance', - name='alerts_enabled', - field=models.BooleanField(default=True, help_text='Alert when this service is not healthy.'), - ), - migrations.AlterField( - model_name='instance', - name='hackpad_id', - field=models.TextField(blank=True, help_text='Gist, Hackpad or Refheap js embed with recovery instructions e.g. https://you.hackpad.com/some_document.js', null=True, verbose_name='Embedded recovery instructions'), - ), - migrations.AlterField( - model_name='instance', - name='old_overall_status', - field=models.TextField(default='PASSING'), - ), - migrations.AlterField( - model_name='instance', - name='overall_status', - field=models.TextField(default='PASSING'), - ), - migrations.AlterField( - model_name='instance', - name='runbook_link', - field=models.TextField(blank=True, help_text='Link to the service runbook on your wiki.'), - ), - migrations.AlterField( - model_name='instance', - name='status_checks', - field=models.ManyToManyField(blank=True, help_text='Checks used to calculate service status.', to='cabotapp.StatusCheck'), - ), - migrations.AlterField( - model_name='instance', - name='telephone_alert', - field=models.BooleanField(default=False, help_text='Must be enabled, and check importance set to Critical, to receive telephone alerts.'), - ), - migrations.AlterField( - model_name='instance', - name='users_to_notify', - field=models.ManyToManyField(blank=True, help_text='Users who should receive alerts.', to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='instancestatussnapshot', - name='overall_status', - field=models.TextField(default='PASSING'), - ), - migrations.AlterField( - model_name='service', - name='alerts', - field=models.ManyToManyField(blank=True, help_text='Alerts channels through which you wish to be notified', to='cabotapp.AlertPlugin'), - ), - migrations.AlterField( - model_name='service', - name='alerts_enabled', - field=models.BooleanField(default=True, help_text='Alert when this service is not healthy.'), - ), - migrations.AlterField( - model_name='service', - name='hackpad_id', - field=models.TextField(blank=True, help_text='Gist, Hackpad or Refheap js embed with recovery instructions e.g. https://you.hackpad.com/some_document.js', null=True, verbose_name='Embedded recovery instructions'), - ), - migrations.AlterField( - model_name='service', - name='instances', - field=models.ManyToManyField(blank=True, help_text='Instances this service is running on.', to='cabotapp.Instance'), - ), - migrations.AlterField( - model_name='service', - name='is_public', - field=models.BooleanField(default=False, help_text='The service will be shown in the public home', verbose_name='Is Public'), - ), - migrations.AlterField( - model_name='service', - name='old_overall_status', - field=models.TextField(default='PASSING'), - ), - migrations.AlterField( - model_name='service', - name='overall_status', - field=models.TextField(default='PASSING'), - ), - migrations.AlterField( - model_name='service', - name='runbook_link', - field=models.TextField(blank=True, help_text='Link to the service runbook on your wiki.'), - ), - migrations.AlterField( - model_name='service', - name='status_checks', - field=models.ManyToManyField(blank=True, help_text='Checks used to calculate service status.', to='cabotapp.StatusCheck'), - ), - migrations.AlterField( - model_name='service', - name='telephone_alert', - field=models.BooleanField(default=False, help_text='Must be enabled, and check importance set to Critical, to receive telephone alerts.'), - ), - migrations.AlterField( - model_name='service', - name='url', - field=models.TextField(blank=True, help_text='URL of service.'), - ), - migrations.AlterField( - model_name='service', - name='users_to_notify', - field=models.ManyToManyField(blank=True, help_text='Users who should receive alerts.', to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='servicestatussnapshot', - name='overall_status', - field=models.TextField(default='PASSING'), - ), - migrations.AlterField( - model_name='statuscheck', - name='active', - field=models.BooleanField(default=True, help_text='If not active, check will not be used to calculate service status and will not trigger alerts.'), - ), - migrations.AlterField( - model_name='statuscheck', - name='allowed_num_failures', - field=models.IntegerField(default=0, help_text='The maximum number of data series (metrics) you expect to fail. For example, you might be OK with 2 out of 3 webservers having OK load (1 failing), but not 1 out of 3 (2 failing).', null=True), - ), - migrations.AlterField( - model_name='statuscheck', - name='calculated_status', - field=models.CharField(blank=True, choices=[('passing', 'passing'), ('intermittent', 'intermittent'), ('failing', 'failing')], default='passing', max_length=50), - ), - migrations.AlterField( - model_name='statuscheck', - name='check_type', - field=models.CharField(choices=[('>', 'Greater than'), ('>=', 'Greater than or equal'), ('<', 'Less than'), ('<=', 'Less than or equal'), ('==', 'Equal to')], max_length=100, null=True), - ), - migrations.AlterField( - model_name='statuscheck', - name='debounce', - field=models.IntegerField(default=0, help_text='Number of successive failures permitted before check will be marked as failed. Default is 0, i.e. fail on first failure.', null=True), - ), - migrations.AlterField( - model_name='statuscheck', - name='endpoint', - field=models.TextField(help_text='HTTP(S) endpoint to poll.', null=True, validators=[django.core.validators.URLValidator()]), - ), - migrations.AlterField( - model_name='statuscheck', - name='expected_num_hosts', - field=models.IntegerField(default=0, help_text='The minimum number of data series (hosts) you expect to see.', null=True), - ), - migrations.AlterField( - model_name='statuscheck', - name='frequency', - field=models.IntegerField(default=5, help_text='Minutes between each check.'), - ), - migrations.AlterField( - model_name='statuscheck', - name='importance', - field=models.CharField(choices=[('WARNING', 'Warning'), ('ERROR', 'Error'), ('CRITICAL', 'Critical')], default='ERROR', help_text='Severity level of a failure. Critical alerts are for failures you want to wake you up at 2am, Errors are things you can sleep through but need to fix in the morning, and warnings for less important things.', max_length=30), - ), - migrations.AlterField( - model_name='statuscheck', - name='max_queued_build_time', - field=models.IntegerField(blank=True, help_text='Alert if build queued for more than this many minutes.', null=True), - ), - migrations.AlterField( - model_name='statuscheck', - name='metric', - field=models.TextField(help_text='fully.qualified.name of the Graphite metric you want to watch. This can be any valid Graphite expression, including wildcards, multiple hosts, etc.', null=True), - ), - migrations.AlterField( - model_name='statuscheck', - name='password', - field=models.TextField(blank=True, help_text='Basic auth password.', null=True), - ), - migrations.AlterField( - model_name='statuscheck', - name='status_code', - field=models.TextField(default=200, help_text='Status code expected from endpoint.', null=True), - ), - migrations.AlterField( - model_name='statuscheck', - name='text_match', - field=models.TextField(blank=True, help_text='Regex to match against source of page.', null=True), - ), - migrations.AlterField( - model_name='statuscheck', - name='timeout', - field=models.IntegerField(default=30, help_text='Time out after this many seconds.', null=True), - ), - migrations.AlterField( - model_name='statuscheck', - name='username', - field=models.TextField(blank=True, help_text='Basic auth username.', null=True), - ), - migrations.AlterField( - model_name='statuscheck', - name='value', - field=models.TextField(help_text='If this expression evaluates to true, the check will fail (possibly triggering an alert).', null=True), - ), - migrations.AlterField( - model_name='statuscheck', - name='verify_ssl_certificate', - field=models.BooleanField(default=True, help_text='Set to false to allow not try to verify ssl certificates (default True)'), - ), - migrations.AlterField( - model_name='userprofile', - name='hipchat_alias', - field=models.CharField(blank=True, default='', max_length=50), - ), - migrations.AlterField( - model_name='userprofile', - name='mobile_number', - field=models.CharField(blank=True, default='', max_length=20), - ), - ] diff --git a/cabot/cabotapp/migrations/0009_auto_20210719_2116.py b/cabot/cabotapp/migrations/0009_auto_20210719_2116.py deleted file mode 100644 index 351a00123..000000000 --- a/cabot/cabotapp/migrations/0009_auto_20210719_2116.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 3.2.5 on 2021-07-20 00:16 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('cabotapp', '0008_auto_20210707_1645'), - ] - - operations = [ - migrations.AlterField( - model_name='Service', - name='status_checks', - field=models.ManyToManyField(blank=True, help_text='Checks used to calculate service status.', to='cabotapp.StatusCheck'), - ), - migrations.AlterField( - model_name='Instance', - name='status_checks', - field=models.ManyToManyField(blank=True, help_text='Checks used to calculate service status.', to='cabotapp.StatusCheck'), - ), - migrations.AlterField( - model_name='StatusCheckResult', - name='status_check', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cabotapp.statuscheck'), - ), - ] diff --git a/cabot/cabotapp/migrations/0010_auto_20210720_1338.py b/cabot/cabotapp/migrations/0010_auto_20210720_1338.py deleted file mode 100644 index 00e9cd47e..000000000 --- a/cabot/cabotapp/migrations/0010_auto_20210720_1338.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 3.2.5 on 2021-07-20 16:38 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('cabotapp', '0009_auto_20210719_2116'), - ] - - operations = [ - migrations.RemoveField( - model_name='jenkinsstatuscheck', - name='jenkins_config', - ), - migrations.RemoveField( - model_name='jenkinsstatuscheck', - name='statuscheck_ptr', - ), - migrations.DeleteModel( - name='GraphiteStatusCheck', - ), - migrations.DeleteModel( - name='ICMPStatusCheck', - ), - migrations.DeleteModel( - name='JenkinsConfig', - ), - migrations.DeleteModel( - name='JenkinsStatusCheck', - ), - ] diff --git a/cabot/settings.py b/cabot/settings.py index fab49a246..b75090941 100644 --- a/cabot/settings.py +++ b/cabot/settings.py @@ -4,18 +4,18 @@ from django.urls import reverse_lazy from cabot.settings_utils import environ_get_list, force_bool from cabot.cabot_config import * +import environ +# reading .env file +environ.Env.read_env() settings_dir = os.path.dirname(__file__) PROJECT_ROOT = os.path.abspath(settings_dir) + +DEBUG=os.environ.get('DEBUG', False) -DEBUG = True -if os.environ.get('DEBUG', True)=='True': - DEBUG=True +PROD=os.environ.get('PROD', False) -PROD = False -if os.environ.get('PROD', True)=='True': - PROD=True ADMINS = ( ('Admin', os.environ.get('ADMIN_EMAIL', 'name@example.com')), @@ -28,12 +28,12 @@ DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'ENGINE': 'django.db.backends.mysql', 'NAME': os.environ.get('DATABASE_NAME', 'cabot'), 'USER': os.environ.get('DATABASE_USER', 'root'), 'PASSWORD': os.environ.get('DATABASE_PASSWORD', 'root'), - 'HOST': os.environ.get('DATABASE_HOST', '172.26.0.2'), - 'PORT': os.environ.get('DATABASE_PORT', '5432'), + 'HOST': os.environ.get('DATABASE_HOST', '127.0.0.1'), + 'PORT': os.environ.get('DATABASE_PORT', '3306'), } } diff --git a/cabot/static/arachnys/css/base.css b/cabot/static/arachnys/css/base.css new file mode 100644 index 000000000..664adb38b --- /dev/null +++ b/cabot/static/arachnys/css/base.css @@ -0,0 +1,43 @@ +body { + padding-top: 50px; +} +.form-group ul { + list-style-type: none; + padding: 5px 0 0 0; +} +.form-group input[type="radio"] { + margin-top: -3px; +} +tr.warning a { + text-decoration: line-through; +} +#graphite_data_container { + max-height: 200px; + overflow: scroll; +} +.ui-autocomplete { + max-height: 400px; + overflow-y: scroll; + font-size: 10px; +} +.jqstooltip { + display: none; +} +div.dataTables_paginate { + float: right; +} +div.dataTables_info { + float: left; + margin: 20px 0; +} +div.dataTables_length { + float: right; +} +.messages { + margin: auto; + width: 50%; + margin-top: 10px; +} +.navbar-brand > img { + display: inline-block; +} diff --git a/docker-compose copy.yml b/docker-compose copy.yml deleted file mode 100644 index 7e03f776a..000000000 --- a/docker-compose copy.yml +++ /dev/null @@ -1,72 +0,0 @@ -version: '3' -services: - db: - container_name: cabot_pg - image: postgres:alpine - restart: always - environment: - POSTGRES_USER: root - POSTGRES_PASSWORD: root - POSTGRES_DB: test_db - ports: - - "5432:5432" - volumes: - - datavolume:/var/lib/postgresql/data - - pgadmin: - container_name: cabot_pg_admin - image: dpage/pgadmin4 - restart: always - environment: - PGADMIN_DEFAULT_EMAIL: admin@admin.com - PGADMIN_DEFAULT_PASSWORD: root - ports: - - "5050:5050" - - redis: - container_name: cabot_redis - image: redislabs/redismod - restart: always - ports: - - 6379:6379 - - redisinsight: - container_name: cabot_redis_admin - image: redislabs/redisinsight:latest - restart: always - ports: - - '8001:8001' - - web: - build: . - extends: - file: docker-compose-base.yml - service: base - env_file: - - .env - command: python manage.py migrate - command: python manage.py runserver 0.0.0.0:8000 - ports: - - "8000:8000" - links: - - redis - - db - - worker-beat: - extends: - file: docker-compose-base.yml - service: base - env_file: - - .env - command: celery -A cabot worker --beat --scheduler django --loglevel=info - environment: - - SKIP_INIT=1 - - WAIT_FOR_MIGRATIONS=1 - links: - - redis - - db - - -volumes: - datavolume: - diff --git a/docker-compose-base.yml b/docker-compose-base.yml index 9940c5eea..955ff6331 100644 --- a/docker-compose-base.yml +++ b/docker-compose-base.yml @@ -7,4 +7,4 @@ services: volumes: - .:/code env_file: - - .env + - docker.env diff --git a/docker-compose-test.yml b/docker-compose-test.yml deleted file mode 100644 index d2c00a737..000000000 --- a/docker-compose-test.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: '2' -services: - test: - extends: - file: docker-compose-base.yml - service: base - entrypoint: /usr/bin/python - command: manage.py test -v2 - env_file: - - conf/test.env - sut: - image: ubuntu - command: "true" diff --git a/docker-compose.yml b/docker-compose.yml index b54f337ff..60335d0b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,14 @@ version: '3' services: db: - container_name: cabot_pg - image: postgres:alpine - restart: always - environment: - POSTGRES_USER: root - POSTGRES_PASSWORD: root - POSTGRES_DB: test_db + image: mysql ports: - - "5432:5432" - volumes: - - datavolume:/var/lib/postgresql/data + - '3306:3306' + environment: + MYSQL_DATABASE: 'cabot' + MYSQL_ROOT_PASSWORD: 'root' + MYSQL_USER: 'cabot' + MYSQL_PASSWORD: 'cabot' redis: container_name: cabot_redis @@ -21,14 +18,10 @@ services: - 6379:6379 web: - build: . extends: file: docker-compose-base.yml service: base - env_file: - - .env - - command: "python manage.py runserver 0.0.0.0:8000" + command: sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000" ports: - "8000:8000" links: @@ -39,8 +32,6 @@ services: extends: file: docker-compose-base.yml service: base - env_file: - - .env command: celery -A cabot worker --beat --scheduler django --loglevel=info environment: - SKIP_INIT=1 diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh old mode 100755 new mode 100644 index 4731c59cc..8c63e1fd1 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,3 +1,41 @@ #!/bin/bash +set -e -python3 manage.py migrate \ No newline at end of file +function wait_for_broker { + set +e + for try in {1..60} ; do + python -c "from kombu import Connection; x=Connection('$CELERY_BROKER_URL', timeout=1); x.connect()" && break + echo "Waiting for celery broker to respond..." + sleep 1 + done +} + +function wait_for_database { + set +e + for try in {1..60} ; do + python -c "from django.db import connection; connection.connect()" && break + echo "Waiting for database to respond..." + sleep 1 + done +} + +function wait_for_migrations { + set +e + for try in {1..60} ; do + # Kind of ugly but not sure if there's another way to determine if migrations haven't run. + # showmigrations -p returns a checkbox list of migrations, empty checkboxes mean they haven't been run + python manage.py showmigrations -p | grep "\[ \]" &> /dev/null || break + echo "Waiting for database migrations to be run..." + sleep 1 + done +} + + +wait_for_broker +wait_for_database + +if [ -n "$WAIT_FOR_MIGRATIONS" ]; then + wait_for_migrations +fi + +exec "$@" diff --git a/docker.env_example b/docker.env_example new file mode 100644 index 000000000..46d4b3102 --- /dev/null +++ b/docker.env_example @@ -0,0 +1,66 @@ +DEBUG=True +PROD=False + +#################### DATABASE CONFIG +#Check diferent options: https://github.com/kennethreitz/dj-database-url + +DATABASE_NAME=cabot +DATABASE_USER=root +DATABASE_PASSWORD=root +DATABASE_HOST=db +DATABASE_PORT=3306 + +#################### PLUGINS - DON'T LEAVE SPACE BETWEEN THEM, ONLY COMMACABOT_PLUGINS=cabot_alert_slack,cabot_check_http,cabot_check_network,cabot_alert_email +CABOT_PLUGINS=cabot_alert_slack,cabot_check_http,cabot_check_network,cabot_alert_email + +#################### GENERAL CONFIG +DJANGO_SETTINGS_MODULE=cabot.settings +LOG_FILE=/dev/null +PORT=8000 +TIME_ZONE=America/Argentina/Buenos_Aires + +#################### CELERY CONFIG +CELERY_BROKER_URL=redis://redis:6379/1 +CELERY_RESULT_BACKEND=redis://redis:6379/1 +CELERY_BEAT_SCHEDULER=django_celery_beat.schedulers:DatabaseScheduler + +#################### DJANGO MAIL CONFIG +ADMIN_EMAIL=example@example.com +CABOT_FROM_EMAIL=example@example.com + +#################### EMAIL ALERT CONFIG +EMAIL_HOST=in-v3.example.com +EMAIL_USER=example +EMAIL_PASSWORD=example + +#################### SLACK CONFIG +SLACK_ALERT_CHANNEL=cabot +SLACK_WEBHOOK_URL=https://discord.com/api/webhooks +SLACK_ICON_URL=http://lorempixel.com/40/40/ + +# Typical SMTP port 587 or 25 configuration (with no SSL/TLS) +EMAIL_PORT=587 +EMAIL_USE_TLS=0 +EMAIL_USE_SSL=0 + +# URL of calendar to synchronise rota with +CALENDAR_ICAL_URL=http://www.google.com/calendar/ical/example.ics + +# Django settings +DJANGO_SECRET_KEY=example + +# User-Agent string used for HTTP checks +HTTP_USER_AGENT=Cabot + +# Used for pointing links back in alerts etc. +WWW_HTTP_HOST=localhost +WWW_SCHEME=http + +# Twilio integration for SMS and telephone alerts +#TWILIO_ACCOUNT_SID=your_account_sid +#TWILIO_AUTH_TOKEN=your_auth_token +#TWILIO_OUTGOING_NUMBER=+14155551234 + +# Hipchat integration +#HIPCHAT_ALERT_ROOM=room_name_or_id +#HIPCHAT_API_KEY=your_hipchat_api_key diff --git a/requiements.txt b/requiements.txt deleted file mode 100644 index a4e44ae1d..000000000 --- a/requiements.txt +++ /dev/null @@ -1,111 +0,0 @@ -amqp==2.6.1 -anyjson==0.3.3 -appdirs==1.4.4 -asgiref==3.4.1 -backcall==0.2.0 -billiard==3.6.4.0 -boto==2.49.0 -boto3==1.17.106 -botocore==1.20.106 -cabot-alert-email==1.4.3 -cabot-alert-hipchat==2.0.3 -cabot-alert-slack==0.8.3 -cabot-alert-twilio==1.3.3 -cached-property==1.5.2 -celery==4.4.2 -certifi==2021.5.30 -cffi==1.14.5 -chardet==4.0.0 -click==7.1.2 -click-didyoumean==0.0.3 -click-plugins==1.1.1 -click-repl==0.2.0 -coffee-compressor-compiler==0.2.0 -coreapi==2.3.3 -coreschema==0.0.4 -coverage==5.5 -cryptography==3.4.7 -decorator==5.0.9 -defusedxml==0.7.1 -dj-database-url==0.5.0 -Django==3.2.5 -django-appconf==1.0.4 -django-auth-ldap==2.4.0 -django-autocomplete-light==3.8.2 -django-bootstrap-form==3.4 -django-compressor==2.4.1 -django-coverage-plugin==2.0.0 -django-filter==2.4.0 -django-jsonify==0.3.0 -django-polymorphic==3.0.0 -djangorestframework==3.12.4 -docutils==0.17.1 -freezegun==1.1.0 -futures==3.1.1 -gevent==21.1.2 -greenlet==1.1.0 -gunicorn==20.1.0 -httplib2==0.19.1 -icalendar==4.0.7 -idna==2.10 -importlib-metadata==4.6.1 -ipdb==0.13.9 -ipython==7.16.1 -ipython-genutils==0.2.0 -isort==5.9.1 -itypes==1.2.0 -jedi==0.18.0 -Jinja2==3.0.1 -jmespath==0.10.0 -kombu==4.6.11 -Markdown==3.3.4 -MarkupSafe==2.0.1 -mock==4.0.3 -multi-key-dict==2.0.3 -oauthlib==3.1.1 -packaging==21.0 -parso==0.8.2 -pbr==5.6.0 -pexpect==4.8.0 -pickleshare==0.7.5 -prompt-toolkit==3.0.19 -psycopg2==2.7.7 -psycopg2-binary==2.9.1 -ptyprocess==0.7.0 -pyasn1==0.4.8 -pyasn1-modules==0.2.8 -pycparser==2.20 -pycurl==7.43.0.6 -PyExecJS==1.5.1 -Pygments==2.9.0 -PyJWT==2.1.0 -pyparsing==2.4.7 -PySocks==1.7.1 -python-dateutil==2.8.1 -python-jenkins==1.7.0 -python-ldap==3.3.1 -python-openid==2.2.5 -python3-openid==3.2.0 -pytz==2021.1 -rcssmin==1.0.6 -redis==3.5.3 -requests==2.25.1 -requests-oauthlib==1.3.0 -rjsmin==1.1.0 -s3transfer==0.4.2 -six==1.16.0 -social-auth-app-django==4.0.0 -social-auth-core==4.1.0 -sqlparse==0.4.1 -toml==0.10.2 -traitlets==4.3.3 -twilio==6.50.1 -typing-extensions==3.10.0.0 -uritemplate==3.0.1 -urllib3==1.26.6 -vine==1.3.0 -wcwidth==0.2.5 -whitenoise==5.2.0 -zipp==3.5.0 -zope.event==4.5.0 -zope.interface==5.4.0 diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 9b1bae445..000000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,6 +0,0 @@ -coverage==5.5 -django-coverage-plugin==2.0.0 -freezegun==1.1.0 -mock==4.0.3 -ipdb==0.13.9 -isort==5.9.1 \ No newline at end of file diff --git a/requirements-plugins.txt b/requirements-plugins.txt index e8765ad1e..2482f8b62 100644 --- a/requirements-plugins.txt +++ b/requirements-plugins.txt @@ -1,4 +1,4 @@ git+https://github.com/senzil/cabot-alert-email.git git+https://github.com/senzil/cabot-check-http.git git+https://github.com/senzil/cabot-alert-slack.git -git+https://github.com/senzil/cabot-check-network.git \ No newline at end of file +git+https://github.com/senzil/cabot-check-network.git diff --git a/requirements.txt b/requirements.txt index 80c364b03..7f8489712 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ amqp==5.0.6 asgiref==3.4.1 billiard==3.6.4.0 +cabot-alert-slack==0.8.3 cached-property==1.5.2 celery==5.1.2 certifi==2021.5.30 @@ -18,6 +19,7 @@ django-autocomplete-light==3.8.2 django-bootstrap-form==3.4 django-celery-beat==2.2.1 django-compressor==2.4.1 +django-environ==0.4.5 django-filter==2.4.0 django-jsonify==0.3.0 django-polymorphic==3.0.0 @@ -30,13 +32,15 @@ itypes==1.2.0 Jinja2==3.0.1 kombu==5.1.0 MarkupSafe==2.0.1 +mysqlclient==2.0.3 +postgres==3.0.0 prompt-toolkit==3.0.19 -mysqlclient -postgres -psycopg2 +psycopg2==2.9.1 psycopg2-binary==2.9.1 +psycopg2-pool==1.1 python-crontab==2.5.1 python-dateutil==2.8.2 +python-dotenv==0.19.0 pytz==2021.1 rcssmin==1.0.6 redis==3.5.3 @@ -49,4 +53,4 @@ uritemplate==3.0.1 urllib3==1.26.6 vine==5.0.0 wcwidth==0.2.5 -zipp==3.5.0 \ No newline at end of file +zipp==3.5.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..e69de29bb diff --git a/setup.py b/setup.py new file mode 100644 index 000000000..f73cdb53d --- /dev/null +++ b/setup.py @@ -0,0 +1,13 @@ +from distutils.core import setup +setup( + name = 'mypackage', + packages = ['mypackage'], # this must be the same as the name above + version = '0.1', + description = 'my description', + author = 'Alejandro Esquiva', + author_email = 'alejandro@geekytheory.com', + url = 'https://github.com/{user_name}/{repo}', # use the URL to the github repo + download_url = 'https://github.com/{user_name}/{repo}/tarball/0.1', + keywords = ['testing', 'logging', 'example'], + classifiers = [], +)