grimoirelab-core 0.0.1rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- grimoirelab/core/__init__.py +0 -0
- grimoirelab/core/_version.py +2 -0
- grimoirelab/core/app/__init__.py +0 -0
- grimoirelab/core/app/asgi.py +9 -0
- grimoirelab/core/app/urls.py +12 -0
- grimoirelab/core/app/wsgi.py +10 -0
- grimoirelab/core/config/__init__.py +0 -0
- grimoirelab/core/config/settings.py +307 -0
- grimoirelab/core/models.py +77 -0
- grimoirelab/core/runner/__init__.py +0 -0
- grimoirelab/core/runner/cmd.py +72 -0
- grimoirelab/core/runner/commands/__init__.py +0 -0
- grimoirelab/core/runner/commands/admin.py +163 -0
- grimoirelab/core/runner/commands/fetch.py +110 -0
- grimoirelab/core/runner/commands/run.py +114 -0
- grimoirelab/core/scheduler/__init__.py +0 -0
- grimoirelab/core/scheduler/api.py +178 -0
- grimoirelab/core/scheduler/apps.py +30 -0
- grimoirelab/core/scheduler/db.py +86 -0
- grimoirelab/core/scheduler/errors.py +83 -0
- grimoirelab/core/scheduler/jobs.py +154 -0
- grimoirelab/core/scheduler/models.py +359 -0
- grimoirelab/core/scheduler/scheduler.py +306 -0
- grimoirelab/core/scheduler/tasks/__init__.py +0 -0
- grimoirelab/core/scheduler/tasks/apps.py +26 -0
- grimoirelab/core/scheduler/tasks/chronicler.py +358 -0
- grimoirelab/core/scheduler/tasks/migrations/0001_initial.py +63 -0
- grimoirelab/core/scheduler/tasks/migrations/__init__.py +0 -0
- grimoirelab/core/scheduler/tasks/models.py +164 -0
- grimoirelab/core/scheduler/urls.py +32 -0
- grimoirelab/core/scheduler/views.py +80 -0
- grimoirelab/core/scheduler/worker.py +54 -0
- grimoirelab_core-0.0.1rc2.dist-info/LICENSE +674 -0
- grimoirelab_core-0.0.1rc2.dist-info/METADATA +160 -0
- grimoirelab_core-0.0.1rc2.dist-info/RECORD +37 -0
- grimoirelab_core-0.0.1rc2.dist-info/WHEEL +4 -0
- grimoirelab_core-0.0.1rc2.dist-info/entry_points.txt +3 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""GrimoireLab URL Configuration"""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from django.urls import path, include, re_path
|
|
5
|
+
from django.views.generic import TemplateView
|
|
6
|
+
|
|
7
|
+
from grimoirelab.core.scheduler.urls import urlpatterns as sched_urlpatterns
|
|
8
|
+
|
|
9
|
+
urlpatterns = [
|
|
10
|
+
path("scheduler/", include(sched_urlpatterns)),
|
|
11
|
+
re_path(r'^(?!static|scheduler).*$', TemplateView.as_view(template_name="index.html"))
|
|
12
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#
|
|
2
|
+
# GrimoireLab core basic settings file.
|
|
3
|
+
#
|
|
4
|
+
# This file defines the required settings to run GrimoireLab.
|
|
5
|
+
# Due to GrimoireLab is a Django based app, these settings are
|
|
6
|
+
# based on the configuration file generated by Django by
|
|
7
|
+
# default.
|
|
8
|
+
#
|
|
9
|
+
# Please check the next links for details about the configuration
|
|
10
|
+
# in a production environment:
|
|
11
|
+
#
|
|
12
|
+
# https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
|
13
|
+
# https://docs.djangoproject.com/en/4.2/ref/settings/
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
20
|
+
|
|
21
|
+
SILENCED_SYSTEM_CHECKS = [
|
|
22
|
+
'django_mysql.E016'
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
#
|
|
26
|
+
# General app parameters
|
|
27
|
+
#
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# You must never enable debug in production.
|
|
31
|
+
#
|
|
32
|
+
# https://docs.djangoproject.com/en/4.2/ref/settings/#std:setting-DEBUG
|
|
33
|
+
#
|
|
34
|
+
|
|
35
|
+
DEBUG = os.environ.get('GRIMOIRELAB_DEBUG', 'False').lower() in ('true', '1')
|
|
36
|
+
|
|
37
|
+
#
|
|
38
|
+
# ALLOWED_HOST protects the site against CSRF attacks.
|
|
39
|
+
# If DEBUG is set to False, you will need to configure this parameter,
|
|
40
|
+
# with the host you are using to serve GrimoireLab.
|
|
41
|
+
#
|
|
42
|
+
# https://docs.djangoproject.com/en/4.2/ref/settings/#allowed-hosts
|
|
43
|
+
#
|
|
44
|
+
|
|
45
|
+
if 'GRIMOIRELAB_ALLOWED_HOST' in os.environ:
|
|
46
|
+
ALLOWED_HOSTS = os.environ['GRIMOIRELAB_ALLOWED_HOST'].split(',')
|
|
47
|
+
else:
|
|
48
|
+
ALLOWED_HOSTS = [
|
|
49
|
+
'127.0.0.1',
|
|
50
|
+
'localhost',
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
#
|
|
54
|
+
# Cross-Origin Resource Sharing (CORS)
|
|
55
|
+
#
|
|
56
|
+
# You'll HAVE TO configure the origins that are authorized to make
|
|
57
|
+
# cross-site HTTP requests. Check the following link to understand
|
|
58
|
+
# the possibilities and parameters you can use.
|
|
59
|
+
#
|
|
60
|
+
# https://github.com/adamchainz/django-cors-headers#configuration
|
|
61
|
+
#
|
|
62
|
+
|
|
63
|
+
if 'GRIMOIRELAB_CORS_ALLOWED_ORIGINS' in os.environ:
|
|
64
|
+
CORS_ALLOWED_ORIGINS = os.environ['GRIMOIRELAB_CORS_ALLOWED_ORIGINS'].split(',')
|
|
65
|
+
elif 'GRIMOIRELAB_CORS_ALLOWED_ORIGIN_REGEXES' in os.environ:
|
|
66
|
+
CORS_ALLOWED_ORIGIN_REGEXES = os.environ['GRIMOIRELAB_CORS_ALLOWED_ORIGIN_REGEXES'].split(',')
|
|
67
|
+
else:
|
|
68
|
+
CORS_ALLOWED_ORIGINS = [
|
|
69
|
+
'http://localhost:5173',
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
CORS_ALLOW_CREDENTIALS = True
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
#
|
|
76
|
+
# The secret key must be a large random value and it must be kept secret.
|
|
77
|
+
#
|
|
78
|
+
# https://docs.djangoproject.com/en/4.2/ref/settings/#secret-key
|
|
79
|
+
#
|
|
80
|
+
|
|
81
|
+
SECRET_KEY = os.environ.get('GRIMOIRELAB_SECRET_KEY', 'fake-key')
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
# Application definition - DO NOT MODIFY
|
|
85
|
+
#
|
|
86
|
+
|
|
87
|
+
INSTALLED_APPS = [
|
|
88
|
+
'django.contrib.admin',
|
|
89
|
+
'django.contrib.auth',
|
|
90
|
+
'django.contrib.contenttypes',
|
|
91
|
+
'django.contrib.sessions',
|
|
92
|
+
'django.contrib.messages',
|
|
93
|
+
'django.contrib.staticfiles',
|
|
94
|
+
'django_rq',
|
|
95
|
+
'corsheaders',
|
|
96
|
+
'rest_framework',
|
|
97
|
+
'grimoirelab.core.scheduler',
|
|
98
|
+
'grimoirelab.core.scheduler.tasks',
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
MIDDLEWARE = [
|
|
102
|
+
'corsheaders.middleware.CorsMiddleware',
|
|
103
|
+
'django.middleware.security.SecurityMiddleware',
|
|
104
|
+
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
105
|
+
'django.middleware.common.CommonMiddleware',
|
|
106
|
+
'django.middleware.csrf.CsrfViewMiddleware',
|
|
107
|
+
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
108
|
+
'django.contrib.messages.middleware.MessageMiddleware',
|
|
109
|
+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
ROOT_URLCONF = 'grimoirelab.core.app.urls'
|
|
113
|
+
|
|
114
|
+
TEMPLATES = [
|
|
115
|
+
{
|
|
116
|
+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
117
|
+
'DIRS': [BASE_DIR / 'templates'],
|
|
118
|
+
'APP_DIRS': True,
|
|
119
|
+
'OPTIONS': {
|
|
120
|
+
'context_processors': [
|
|
121
|
+
'django.template.context_processors.debug',
|
|
122
|
+
'django.template.context_processors.request',
|
|
123
|
+
'django.contrib.auth.context_processors.auth',
|
|
124
|
+
'django.contrib.messages.context_processors.messages',
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
WSGI_APPLICATION = 'grimoirelab.core.app.wsgi.application'
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
#
|
|
134
|
+
# Grimoirelab core database
|
|
135
|
+
#
|
|
136
|
+
# You MUST set the database parameters in order to run
|
|
137
|
+
# GrimoireLab.
|
|
138
|
+
#
|
|
139
|
+
|
|
140
|
+
DATABASES = {
|
|
141
|
+
'default': {
|
|
142
|
+
'ENGINE': 'django.db.backends.mysql',
|
|
143
|
+
'HOST': os.environ.get('GRIMOIRELAB_DB_HOST', '127.0.0.1'),
|
|
144
|
+
'PORT': os.environ.get('GRIMOIRELAB_DB_PORT', 3306),
|
|
145
|
+
'USER': os.environ.get('GRIMOIRELAB_DB_USER', 'root'),
|
|
146
|
+
'PASSWORD': os.environ.get('GRIMOIRELAB_DB_PASSWORD', ''),
|
|
147
|
+
'NAME': os.environ.get('GRIMOIRELAB_DB_DATABASE', 'grimoirelab_test'),
|
|
148
|
+
'OPTIONS': {'charset': 'utf8mb4'},
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# Password validation
|
|
154
|
+
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
|
155
|
+
|
|
156
|
+
AUTH_PASSWORD_VALIDATORS = [
|
|
157
|
+
{
|
|
158
|
+
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
168
|
+
},
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
#
|
|
173
|
+
# Internationalization
|
|
174
|
+
#
|
|
175
|
+
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
|
176
|
+
#
|
|
177
|
+
#
|
|
178
|
+
|
|
179
|
+
LANGUAGE_CODE = 'en-us'
|
|
180
|
+
USE_I18N = True
|
|
181
|
+
|
|
182
|
+
#
|
|
183
|
+
# Time Zone
|
|
184
|
+
#
|
|
185
|
+
|
|
186
|
+
USE_TZ = True
|
|
187
|
+
TIME_ZONE = 'UTC'
|
|
188
|
+
|
|
189
|
+
#
|
|
190
|
+
# GrimoireLab Logging
|
|
191
|
+
#
|
|
192
|
+
# https://docs.djangoproject.com/en/4.2/topics/logging/#configuring-logging
|
|
193
|
+
#
|
|
194
|
+
|
|
195
|
+
LOGGING = {
|
|
196
|
+
'version': 1,
|
|
197
|
+
'disable_existing_loggers': False,
|
|
198
|
+
'formatters': {
|
|
199
|
+
'simple': {
|
|
200
|
+
'format': '[{asctime}] {message}',
|
|
201
|
+
'style': '{',
|
|
202
|
+
},
|
|
203
|
+
'verbose': {
|
|
204
|
+
'format': '[{asctime} - {levelname} - {name}:{lineno}] {message}',
|
|
205
|
+
'style': '{',
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
'handlers': {
|
|
209
|
+
'console': {
|
|
210
|
+
'class': 'logging.StreamHandler',
|
|
211
|
+
'formatter': 'verbose',
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
'root': {
|
|
215
|
+
'handlers': ['console'],
|
|
216
|
+
'level': 'INFO',
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
#
|
|
222
|
+
# Static files (CSS, JavaScript, Images)
|
|
223
|
+
#
|
|
224
|
+
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
|
225
|
+
#
|
|
226
|
+
|
|
227
|
+
STATIC_URL = '/static/'
|
|
228
|
+
STATICFILES_DIRS = [
|
|
229
|
+
BASE_DIR / 'templates/static'
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
# UI static files will be copied to the next path when
|
|
233
|
+
# 'collectstatic' is run.
|
|
234
|
+
# If you are serving these files in a dedicated server, you will
|
|
235
|
+
# need to copy them to their final destination.
|
|
236
|
+
|
|
237
|
+
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
|
238
|
+
|
|
239
|
+
# MEDIA_URL is only needed when DEBUG is set to True.
|
|
240
|
+
# Modify this URL if you want to run the server in developer mode.
|
|
241
|
+
|
|
242
|
+
MEDIA_URL = 'http://media.localhost/'
|
|
243
|
+
|
|
244
|
+
#
|
|
245
|
+
# Default primary key field type
|
|
246
|
+
#
|
|
247
|
+
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
|
248
|
+
#
|
|
249
|
+
|
|
250
|
+
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
251
|
+
|
|
252
|
+
# Django REST Framework settings
|
|
253
|
+
|
|
254
|
+
REST_FRAMEWORK = {
|
|
255
|
+
'DEFAULT_RENDERER_CLASSES': (
|
|
256
|
+
'rest_framework.renderers.JSONRenderer',
|
|
257
|
+
),
|
|
258
|
+
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
|
259
|
+
'PAGE_SIZE': 100
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
#
|
|
263
|
+
# GrimoireLab uses RQ to run background and async jobs.
|
|
264
|
+
# You'll HAVE TO set the next parameters in order to run
|
|
265
|
+
# them in the background.
|
|
266
|
+
#
|
|
267
|
+
# You can define the names of the RQ queues using the following
|
|
268
|
+
# environment variables.
|
|
269
|
+
#
|
|
270
|
+
# Take into account RQ uses Redis database. You have more
|
|
271
|
+
# info about these parameters on the following link:
|
|
272
|
+
#
|
|
273
|
+
# https://github.com/rq/django-rq
|
|
274
|
+
#
|
|
275
|
+
|
|
276
|
+
Q_PERCEVAL_JOBS = os.environ.get('GRIMOIRELAB_Q_PERCEVAL_JOBS', 'default')
|
|
277
|
+
Q_STORAGE_ITEMS = os.environ.get('GRIMOIRELAB_Q_STORAGE_ITEMS', 'items')
|
|
278
|
+
Q_EVENTS = os.environ.get('GRIMOIRELAB_Q_EVENTS', 'events')
|
|
279
|
+
|
|
280
|
+
_RQ_DATABASE = {
|
|
281
|
+
'HOST': os.environ.get('GRIMOIRELAB_REDIS_HOST', '127.0.0.1'),
|
|
282
|
+
'PORT': os.environ.get('GRIMOIRELAB_REDIS_PORT', 6379),
|
|
283
|
+
'PASSWORD': os.environ.get('GRIMOIRELAB_REDIS_PASSWORD', ''),
|
|
284
|
+
'DB': os.environ.get('GRIMOIRELAB_REDIS_DB', 0),
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
RQ_QUEUES = {
|
|
288
|
+
Q_PERCEVAL_JOBS: _RQ_DATABASE,
|
|
289
|
+
Q_STORAGE_ITEMS: _RQ_DATABASE,
|
|
290
|
+
Q_EVENTS: _RQ_DATABASE,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
RQ = {
|
|
294
|
+
'JOB_CLASS': 'grimoirelab.core.scheduler.jobs.GrimoireLabJob',
|
|
295
|
+
'WORKER_CLASS': 'grimoirelab.core.scheduler.worker.GrimoireLabWorker',
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
#
|
|
299
|
+
# Task default configuration
|
|
300
|
+
#
|
|
301
|
+
|
|
302
|
+
GRIMOIRELAB_JOB_INTERVAL = int(os.environ.get('GRIMOIRELAB_JOB_INTERVAL', 60 * 60 * 2))
|
|
303
|
+
GRIMOIRELAB_JOB_MAX_RETRIES = int(os.environ.get('GRIMOIRELAB_JOB_MAX_RETRIES', 5))
|
|
304
|
+
GRIMOIRELAB_JOB_RESULT_TTL = int(os.environ.get('GRIMOIRELAB_JOB_RESULT_TTL', 300))
|
|
305
|
+
GRIMOIRELAB_JOB_TIMEOUT = int(os.environ.get('GRIMOIRELAB_JOB_TIMEOUT', -1))
|
|
306
|
+
|
|
307
|
+
GIT_STORAGE_PATH = os.environ.get('GRIMOIRELAB_GIT_PATH', '~/.perceval')
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) GrimoireLab Contributors
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import typing
|
|
22
|
+
|
|
23
|
+
from django.db.models import (
|
|
24
|
+
DateTimeField,
|
|
25
|
+
Model
|
|
26
|
+
)
|
|
27
|
+
from grimoirelab_toolkit.datetime import datetime_utcnow
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if typing.TYPE_CHECKING:
|
|
31
|
+
import datetime
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Innodb and utf8mb4 can only index 191 characters
|
|
35
|
+
# For more information regarding this topic see:
|
|
36
|
+
# https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-conversion.html
|
|
37
|
+
MAX_SIZE_CHAR_INDEX = 191
|
|
38
|
+
MAX_SIZE_CHAR_FIELD = 128
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CreationDateTimeField(DateTimeField):
|
|
42
|
+
"""Field automatically set to the current date when an object is created."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
45
|
+
kwargs.setdefault('editable', False)
|
|
46
|
+
kwargs.setdefault('default', datetime_utcnow)
|
|
47
|
+
super().__init__(*args, **kwargs)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class LastModificationDateTimeField(DateTimeField):
|
|
51
|
+
"""Field automatically set to the current date on each save() call."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
54
|
+
kwargs.setdefault('editable', False)
|
|
55
|
+
kwargs.setdefault('default', datetime_utcnow)
|
|
56
|
+
super().__init__(*args, **kwargs)
|
|
57
|
+
|
|
58
|
+
def pre_save(self, model_instance: Model, add: bool) -> datetime.datetime:
|
|
59
|
+
value = datetime_utcnow()
|
|
60
|
+
setattr(model_instance, self.attname, value)
|
|
61
|
+
return value
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class BaseModel(Model):
|
|
65
|
+
"""Base abstract model for all models.
|
|
66
|
+
|
|
67
|
+
This model provides the common fields for all GrimoireLab Core
|
|
68
|
+
models.
|
|
69
|
+
|
|
70
|
+
The fields `created_at` and `last_modified` are automatically
|
|
71
|
+
set to the current date when the object is created or updated.
|
|
72
|
+
"""
|
|
73
|
+
created_at = CreationDateTimeField()
|
|
74
|
+
last_modified = LastModificationDateTimeField()
|
|
75
|
+
|
|
76
|
+
class Meta:
|
|
77
|
+
abstract = True
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) GrimoireLab Contributors
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import typing
|
|
22
|
+
import os
|
|
23
|
+
|
|
24
|
+
import click
|
|
25
|
+
import django.core.wsgi
|
|
26
|
+
|
|
27
|
+
from .commands.admin import admin
|
|
28
|
+
from .commands.run import run
|
|
29
|
+
|
|
30
|
+
if typing.TYPE_CHECKING:
|
|
31
|
+
from click import Context
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@click.group()
|
|
35
|
+
@click.option('--config', 'cfg',
|
|
36
|
+
envvar='GRIMOIRELAB_CONFIG',
|
|
37
|
+
default='grimoirelab.core.config.settings',
|
|
38
|
+
show_default=True,
|
|
39
|
+
help="Configuration module in Python path syntax")
|
|
40
|
+
@click.pass_context
|
|
41
|
+
def grimoirelab(ctx: Context, cfg: str):
|
|
42
|
+
"""Toolset for software development analytics.
|
|
43
|
+
|
|
44
|
+
GrimoireLab is a set of tools and a platform to retrieve, analyze,
|
|
45
|
+
and provide insights about data coming from software development
|
|
46
|
+
repositories. With this command, you'll be able to configure and
|
|
47
|
+
run its different services.
|
|
48
|
+
|
|
49
|
+
It requires to pass a configuration file module using
|
|
50
|
+
the Python path syntax (e.g. grimoirelab.core.config.settings).
|
|
51
|
+
Take into account the configuration should be accessible by your
|
|
52
|
+
PYTHON_PATH. You can also use the environment variable
|
|
53
|
+
GRIMOIRELAB_CONFIG to define the config location.
|
|
54
|
+
"""
|
|
55
|
+
env = os.environ
|
|
56
|
+
|
|
57
|
+
if cfg:
|
|
58
|
+
env['DJANGO_SETTINGS_MODULE'] = cfg
|
|
59
|
+
ctx.ensure_object(dict)
|
|
60
|
+
ctx.obj["cfg"] = cfg
|
|
61
|
+
else:
|
|
62
|
+
raise click.ClickException(
|
|
63
|
+
"Configuration file not given. "
|
|
64
|
+
"Set it with '--config' option "
|
|
65
|
+
"or 'GRIMOIRELAB_CONFIG' env variable."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
_ = django.core.wsgi.get_wsgi_application()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
grimoirelab.add_command(admin)
|
|
72
|
+
grimoirelab.add_command(run)
|
|
File without changes
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) GrimoireLab Contributors
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import typing
|
|
22
|
+
|
|
23
|
+
import click
|
|
24
|
+
import django.core
|
|
25
|
+
import django_rq
|
|
26
|
+
|
|
27
|
+
if typing.TYPE_CHECKING:
|
|
28
|
+
from click import Context
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@click.group()
|
|
32
|
+
@click.pass_context
|
|
33
|
+
def admin(ctx: Context):
|
|
34
|
+
"""GrimoireLab administration tool.
|
|
35
|
+
|
|
36
|
+
This swiss army knife tool allows to run administrative tasks to
|
|
37
|
+
configure, initialize, update, or check the service.
|
|
38
|
+
"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@admin.command()
|
|
43
|
+
def setup():
|
|
44
|
+
"""Run initialization tasks to configure GrimoireLab.
|
|
45
|
+
|
|
46
|
+
This command will create the database, tables and apply the
|
|
47
|
+
defined migrations and fixtures.
|
|
48
|
+
"""
|
|
49
|
+
_setup()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _setup():
|
|
53
|
+
"""Setup GrimoireLab"""
|
|
54
|
+
|
|
55
|
+
_create_database()
|
|
56
|
+
_setup_database()
|
|
57
|
+
_install_static_files()
|
|
58
|
+
|
|
59
|
+
click.secho("\nGrimoirelab configuration completed", fg='bright_cyan')
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _create_database(database: str = 'default', db_name: str | None = None):
|
|
63
|
+
"""Create an empty database"""
|
|
64
|
+
|
|
65
|
+
import MySQLdb
|
|
66
|
+
from django.conf import settings
|
|
67
|
+
|
|
68
|
+
db_params = settings.DATABASES[database]
|
|
69
|
+
db_name = db_name if db_name else db_params['NAME']
|
|
70
|
+
|
|
71
|
+
click.secho("## GrimoireLab database creation\n", fg='bright_cyan')
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
cursor = MySQLdb.connect(
|
|
75
|
+
user=db_params['USER'],
|
|
76
|
+
password=db_params['PASSWORD'],
|
|
77
|
+
host=db_params['HOST'],
|
|
78
|
+
port=int(db_params['PORT'])
|
|
79
|
+
).cursor()
|
|
80
|
+
cursor.execute(
|
|
81
|
+
f"CREATE DATABASE IF NOT EXISTS {db_name} "
|
|
82
|
+
"CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;"
|
|
83
|
+
)
|
|
84
|
+
except MySQLdb.DatabaseError as exc:
|
|
85
|
+
msg = f"Error creating database '{db_name}' for '{database}': {exc}."
|
|
86
|
+
raise click.ClickException(msg)
|
|
87
|
+
|
|
88
|
+
click.echo(f"GrimoireLab database '{db_name}' for '{database}' created.\n")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _setup_database(database: str = 'default'):
|
|
92
|
+
"""Apply migrations and fixtures to the database"""
|
|
93
|
+
|
|
94
|
+
click.secho(f"## GrimoireLab database setup for {database}\n",
|
|
95
|
+
fg='bright_cyan')
|
|
96
|
+
|
|
97
|
+
django.core.management.call_command('migrate', database=database)
|
|
98
|
+
|
|
99
|
+
click.echo()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _install_static_files():
|
|
103
|
+
"""Collect static files and install them."""
|
|
104
|
+
|
|
105
|
+
click.secho('## GrimoireLab static files installation\n',
|
|
106
|
+
fg='bright_cyan')
|
|
107
|
+
|
|
108
|
+
django.core.management.call_command('collectstatic',
|
|
109
|
+
ignore=['admin', 'rest_framework'],
|
|
110
|
+
clear=True,
|
|
111
|
+
interactive=False)
|
|
112
|
+
|
|
113
|
+
click.echo()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@admin.group()
|
|
117
|
+
@click.pass_context
|
|
118
|
+
def queues(ctx: Context):
|
|
119
|
+
"""Manage the GrimoireLab job queues."""
|
|
120
|
+
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@queues.command(name='list')
|
|
125
|
+
def list_jobs():
|
|
126
|
+
"""List the jobs in the queues.
|
|
127
|
+
|
|
128
|
+
Print the jobs classified by their status.
|
|
129
|
+
"""
|
|
130
|
+
queue = django_rq.get_queue()
|
|
131
|
+
|
|
132
|
+
jobs = {
|
|
133
|
+
'Started': [jid for jid in queue.started_job_registry.get_job_ids()],
|
|
134
|
+
'Scheduled': [jid for jid in queue.scheduled_job_registry.get_job_ids()],
|
|
135
|
+
'Failed': [jid for jid in queue.failed_job_registry.get_job_ids()],
|
|
136
|
+
'Deferred': [jid for jid in queue.deferred_job_registry.get_job_ids()],
|
|
137
|
+
'Canceled': [jid for jid in queue.canceled_job_registry.get_job_ids()],
|
|
138
|
+
'Finished': [jid for jid in queue.finished_job_registry.get_job_ids()],
|
|
139
|
+
}
|
|
140
|
+
for key, value in jobs.items():
|
|
141
|
+
click.echo(key)
|
|
142
|
+
click.echo(value)
|
|
143
|
+
click.echo()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@queues.command(name='purge')
|
|
147
|
+
def remove_jobs():
|
|
148
|
+
"""Remove the jobs from the queues."""
|
|
149
|
+
|
|
150
|
+
queue = django_rq.get_queue()
|
|
151
|
+
|
|
152
|
+
registries = {
|
|
153
|
+
'Started': queue.failed_job_registry,
|
|
154
|
+
'Scheduled': queue.finished_job_registry,
|
|
155
|
+
'Failed': queue.scheduled_job_registry,
|
|
156
|
+
'Deferred': queue.started_job_registry,
|
|
157
|
+
'Canceled': queue.canceled_job_registry,
|
|
158
|
+
'Finished': queue.deferred_job_registry
|
|
159
|
+
}
|
|
160
|
+
for key, registry in registries.items():
|
|
161
|
+
for jid in registry.get_job_ids():
|
|
162
|
+
registry.remove(jid)
|
|
163
|
+
click.echo(f"{key} registry removed.")
|