argus-alm 0.11.7__tar.gz → 0.12.1__tar.gz

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.
Files changed (84) hide show
  1. {argus_alm-0.11.7 → argus_alm-0.12.1}/PKG-INFO +2 -1
  2. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/controller/admin_api.py +31 -1
  3. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/controller/api.py +2 -1
  4. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/controller/client_api.py +5 -1
  5. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/models/web.py +1 -0
  6. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/core.py +5 -1
  7. argus_alm-0.12.1/argus/backend/plugins/driver_matrix_tests/controller.py +24 -0
  8. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/driver_matrix_tests/model.py +1 -1
  9. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/driver_matrix_tests/plugin.py +2 -1
  10. argus_alm-0.12.1/argus/backend/plugins/driver_matrix_tests/service.py +42 -0
  11. argus_alm-0.12.1/argus/backend/plugins/generic/model.py +76 -0
  12. argus_alm-0.12.1/argus/backend/plugins/generic/plugin.py +16 -0
  13. argus_alm-0.12.1/argus/backend/plugins/generic/types.py +13 -0
  14. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/sct/service.py +1 -1
  15. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/sct/testrun.py +6 -3
  16. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/sct/udt.py +2 -0
  17. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/sirenada/model.py +1 -1
  18. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/service/client_service.py +2 -2
  19. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/service/release_manager.py +38 -0
  20. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/service/stats.py +34 -13
  21. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/service/testrun.py +1 -1
  22. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/util/enums.py +1 -0
  23. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/client/base.py +3 -1
  24. argus_alm-0.12.1/argus/client/generic/cli.py +47 -0
  25. argus_alm-0.12.1/argus/client/generic/client.py +31 -0
  26. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/client/sct/client.py +3 -1
  27. {argus_alm-0.11.7 → argus_alm-0.12.1}/pyproject.toml +5 -1
  28. argus_alm-0.11.7/argus/backend/plugins/driver_matrix_tests/controller.py +0 -0
  29. argus_alm-0.11.7/setup.py +0 -45
  30. {argus_alm-0.11.7 → argus_alm-0.12.1}/LICENSE +0 -0
  31. {argus_alm-0.11.7 → argus_alm-0.12.1}/README.md +0 -0
  32. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/__init__.py +0 -0
  33. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/.gitkeep +0 -0
  34. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/__init__.py +0 -0
  35. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/cli.py +0 -0
  36. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/controller/__init__.py +0 -0
  37. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/controller/admin.py +0 -0
  38. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/controller/auth.py +0 -0
  39. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/controller/main.py +0 -0
  40. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/controller/notification_api.py +0 -0
  41. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/controller/notifications.py +0 -0
  42. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/controller/team.py +0 -0
  43. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/controller/team_ui.py +0 -0
  44. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/controller/testrun_api.py +0 -0
  45. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/db.py +0 -0
  46. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/error_handlers.py +0 -0
  47. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/events/event_processors.py +0 -0
  48. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/models/__init__.py +0 -0
  49. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/__init__.py +0 -0
  50. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -0
  51. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/driver_matrix_tests/udt.py +0 -0
  52. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/loader.py +0 -0
  53. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/sct/controller.py +0 -0
  54. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/sct/plugin.py +0 -0
  55. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/sct/resource_setup.py +0 -0
  56. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/sct/types.py +0 -0
  57. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/sirenada/plugin.py +0 -0
  58. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/plugins/sirenada/types.py +0 -0
  59. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/service/admin.py +0 -0
  60. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/service/argus_service.py +0 -0
  61. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/service/build_system_monitor.py +0 -0
  62. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/service/event_service.py +0 -0
  63. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/service/notification_manager.py +0 -0
  64. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/service/team_manager_service.py +0 -0
  65. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/service/user.py +0 -0
  66. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/template_filters.py +0 -0
  67. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/util/common.py +0 -0
  68. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/util/config.py +0 -0
  69. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/util/encoders.py +0 -0
  70. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/util/logsetup.py +0 -0
  71. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/util/module_loaders.py +0 -0
  72. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/backend/util/send_email.py +0 -0
  73. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/client/__init__.py +0 -0
  74. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/client/driver_matrix_tests/client.py +0 -0
  75. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/client/sct/types.py +0 -0
  76. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/client/sirenada/client.py +0 -0
  77. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/db/.gitkeep +0 -0
  78. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/db/argus_json.py +0 -0
  79. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/db/cloud_types.py +0 -0
  80. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/db/config.py +0 -0
  81. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/db/db_types.py +0 -0
  82. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/db/interface.py +0 -0
  83. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/db/testrun.py +0 -0
  84. {argus_alm-0.11.7 → argus_alm-0.12.1}/argus/db/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: argus-alm
3
- Version: 0.11.7
3
+ Version: 0.12.1
4
4
  Summary: Argus
5
5
  Home-page: https://github.com/scylladb/argus
6
6
  License: Apache-2.0
@@ -11,6 +11,7 @@ Classifier: License :: OSI Approved :: Apache Software License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
+ Requires-Dist: click (>=8.1.3,<9.0.0)
14
15
  Requires-Dist: requests (>=2.26.0,<3.0.0)
15
16
  Project-URL: Repository, https://github.com/scylladb/argus
16
17
  Description-Content-Type: text/markdown
@@ -6,7 +6,7 @@ from flask import (
6
6
  Request,
7
7
  )
8
8
  from argus.backend.error_handlers import handle_api_exception
9
- from argus.backend.service.release_manager import ReleaseManagerService
9
+ from argus.backend.service.release_manager import ReleaseEditPayload, ReleaseManagerService
10
10
  from argus.backend.service.user import api_login_required, check_roles
11
11
  from argus.backend.models.web import UserRoles
12
12
 
@@ -92,6 +92,36 @@ def set_release_dormancy():
92
92
  }
93
93
 
94
94
 
95
+ @bp.route("/release/edit", methods=["POST"])
96
+ @check_roles(UserRoles.Admin)
97
+ @api_login_required
98
+ def edit_release():
99
+ payload: ReleaseEditPayload = get_payload(request)
100
+ result = ReleaseManagerService().edit_release(payload)
101
+
102
+ return {
103
+ "status": "ok",
104
+ "response": {
105
+ "updated": result
106
+ }
107
+ }
108
+
109
+
110
+ @bp.route("/release/delete", methods=["POST"])
111
+ @check_roles(UserRoles.Admin)
112
+ @api_login_required
113
+ def delete_release():
114
+ payload = get_payload(request)
115
+ result = ReleaseManagerService().delete_release(release_id=payload["releaseId"])
116
+
117
+ return {
118
+ "status": "ok",
119
+ "response": {
120
+ "deleted": result
121
+ }
122
+ }
123
+
124
+
95
125
  @bp.route("/group/create", methods=["POST"])
96
126
  @check_roles(UserRoles.Admin)
97
127
  @api_login_required
@@ -389,9 +389,10 @@ def release_stats_v2():
389
389
  release = request.args.get("release")
390
390
  limited = bool(int(request.args.get("limited")))
391
391
  version = request.args.get("productVersion", None)
392
+ include_no_version = bool(int(request.args.get("includeNoVersion", True)))
392
393
  force = bool(int(request.args.get("force")))
393
394
  stats = ReleaseStatsCollector(
394
- release_name=release, release_version=version).collect(limited=limited, force=force)
395
+ release_name=release, release_version=version).collect(limited=limited, force=force, include_no_version=include_no_version)
395
396
 
396
397
  res = jsonify({
397
398
  "status": "ok",
@@ -81,7 +81,11 @@ def run_submit_logs(run_type: str, run_id: str):
81
81
  @bp.route("/testrun/<string:run_type>/<string:run_id>/finalize", methods=["POST"])
82
82
  @api_login_required
83
83
  def run_finalize(run_type: str, run_id: str):
84
- result = ClientService().finish_run(run_type=run_type, run_id=run_id)
84
+ try:
85
+ payload = get_payload(request)
86
+ except Exception:
87
+ payload = None
88
+ result = ClientService().finish_run(run_type=run_type, run_id=run_id, payload=payload)
85
89
  return {
86
90
  "status": "ok",
87
91
  "response": result
@@ -111,6 +111,7 @@ class ArgusRelease(Model):
111
111
  pretty_name = columns.Text()
112
112
  description = columns.Text()
113
113
  github_repo_url = columns.Text()
114
+ valid_version_regex = columns.Text()
114
115
  assignee = columns.List(value_type=columns.UUID)
115
116
  picture_id = columns.UUID()
116
117
  enabled = columns.Boolean(default=lambda: True)
@@ -38,6 +38,7 @@ class PluginModelBase(Model):
38
38
  heartbeat = columns.Integer(default=lambda: int(time()))
39
39
  end_time = columns.DateTime(default=lambda: datetime.utcfromtimestamp(0))
40
40
  build_job_url = columns.Text()
41
+ product_version = columns.Text(index=True)
41
42
 
42
43
  # Test Logs Collection
43
44
  logs = columns.List(value_type=columns.Tuple(columns.Text(), columns.Text()))
@@ -180,10 +181,13 @@ class PluginModelBase(Model):
180
181
  def submit_product_version(self, version: str):
181
182
  raise NotImplementedError()
182
183
 
184
+ def set_product_version(self, version: str):
185
+ self.product_version = version
186
+
183
187
  def submit_logs(self, logs: list[dict]):
184
188
  raise NotImplementedError()
185
189
 
186
- def finish_run(self):
190
+ def finish_run(self, payload: dict = None):
187
191
  raise NotImplementedError()
188
192
 
189
193
 
@@ -0,0 +1,24 @@
1
+ from flask import Blueprint, request
2
+
3
+ from argus.backend.error_handlers import handle_api_exception
4
+ from argus.backend.service.user import api_login_required
5
+ from argus.backend.plugins.driver_matrix_tests.service import DriverMatrixService
6
+
7
+ bp = Blueprint("driver_matrix_api", __name__, url_prefix="/driver_matrix")
8
+ bp.register_error_handler(Exception, handle_api_exception)
9
+
10
+
11
+ @bp.route("/test_report", methods=["GET"])
12
+ @api_login_required
13
+ def driver_matrix_test_report():
14
+
15
+ build_id = request.args.get("buildId")
16
+ if not build_id:
17
+ raise Exception("No build id provided")
18
+
19
+
20
+ result = DriverMatrixService().tested_versions_report(build_id=build_id)
21
+ return {
22
+ "status": "ok",
23
+ "response": result
24
+ }
@@ -166,7 +166,7 @@ class DriverTestRun(PluginModelBase):
166
166
  def submit_product_version(self, version: str):
167
167
  self.scylla_version = version
168
168
 
169
- def finish_run(self):
169
+ def finish_run(self, payload: dict = None):
170
170
  self.end_time = datetime.utcnow()
171
171
 
172
172
  def submit_logs(self, logs: list[dict]):
@@ -2,6 +2,7 @@ from flask import Blueprint
2
2
 
3
3
  from argus.backend.plugins.core import PluginInfoBase, PluginModelBase
4
4
  from argus.backend.plugins.driver_matrix_tests.model import DriverTestRun
5
+ from argus.backend.plugins.driver_matrix_tests.controller import bp as driver_matrix_api_bp
5
6
  from argus.backend.plugins.driver_matrix_tests.udt import TestCollection, EnvironmentInfo, TestCase, TestSuite
6
7
 
7
8
 
@@ -9,7 +10,7 @@ class PluginInfo(PluginInfoBase):
9
10
  # pylint: disable=too-few-public-methods
10
11
  name: str = "driver-matrix-tests"
11
12
  model: PluginModelBase = DriverTestRun
12
- controller: Blueprint = None
13
+ controller: Blueprint = driver_matrix_api_bp
13
14
  all_models = [
14
15
  DriverTestRun,
15
16
  ]
@@ -0,0 +1,42 @@
1
+ from argus.backend.db import ScyllaCluster
2
+ from argus.backend.models.web import ArgusRelease, ArgusTest
3
+ from argus.backend.plugins.driver_matrix_tests.model import DriverTestRun
4
+
5
+
6
+ class DriverMatrixService:
7
+ def tested_versions_report(self, build_id: str) -> dict:
8
+ db = ScyllaCluster.get()
9
+ all_runs_for_test_query = db.prepare(f"SELECT * FROM {DriverTestRun.table_name()} WHERE build_id = ?")
10
+
11
+ rows = list(db.session.execute(all_runs_for_test_query, parameters=(build_id,)).all())
12
+
13
+ if len(rows) == 0:
14
+ raise Exception(f"No results for build_id {build_id}", build_id)
15
+
16
+ latest = rows[0]
17
+ try:
18
+ test: ArgusTest = ArgusTest.get(id=latest["test_id"])
19
+ release: ArgusRelease = ArgusRelease.get(id=latest["release_id"])
20
+ except (ArgusTest.DoesNotExist, ArgusRelease.DoesNotExist):
21
+ raise Exception(f"Unable to find release and test information for build_id {build_id} and run_id {latest['id']}", build_id, latest["id"])
22
+
23
+
24
+ version_map = {}
25
+
26
+ for row in rows:
27
+ driver_versions = [(col["name"], col["driver"]) for col in row["test_collection"]]
28
+ for version, driver_type in driver_versions:
29
+ driver_type = driver_type if driver_type else "Unknown"
30
+ versions: list = version_map.get(driver_type, [])
31
+ if version not in versions:
32
+ versions.append(version)
33
+ versions.sort()
34
+ version_map[driver_type] = versions
35
+
36
+ response = {
37
+ "release": release.name,
38
+ "test": test.name,
39
+ "build_id": build_id,
40
+ "versions": version_map,
41
+ }
42
+ return response
@@ -0,0 +1,76 @@
1
+ from datetime import datetime
2
+ from uuid import UUID
3
+ from cassandra.cqlengine import columns
4
+ from cassandra.cqlengine.models import Model
5
+ from argus.backend.db import ScyllaCluster
6
+ from argus.backend.models.web import ArgusRelease
7
+ from argus.backend.plugins.core import PluginModelBase
8
+ from argus.backend.plugins.generic.types import GenericRunFinishRequest, GenericRunSubmitRequest
9
+ from argus.backend.util.enums import TestStatus
10
+
11
+
12
+ class GenericPluginException(Exception):
13
+ pass
14
+
15
+
16
+ class GenericRun(PluginModelBase):
17
+ _plugin_name = "generic"
18
+ __table_name__ = "generic_run"
19
+ logs = columns.Map(key_type=columns.Text(), value_type=columns.Text())
20
+ started_by = columns.Text()
21
+ # TODO: Legacy field name, should be renamed to product_version and abstracted
22
+ scylla_version = columns.Text()
23
+
24
+ @classmethod
25
+ def _stats_query(cls) -> str:
26
+ return ("SELECT id, test_id, group_id, release_id, status, start_time, build_job_url, build_id, "
27
+ f"assignee, end_time, investigation_status, heartbeat, scylla_version FROM {cls.table_name()} WHERE release_id = ?")
28
+
29
+ @classmethod
30
+ def get_distinct_product_versions(cls, release: ArgusRelease, cluster: ScyllaCluster = None) -> list[str]:
31
+ if not cluster:
32
+ cluster = ScyllaCluster.get()
33
+ statement = cluster.prepare(f"SELECT scylla_version FROM {cls.table_name()} WHERE release_id = ?")
34
+ rows = cluster.session.execute(query=statement, parameters=(release.id,))
35
+ unique_versions = {r["scylla_version"] for r in rows if r["scylla_version"]}
36
+
37
+ return sorted(list(unique_versions), reverse=True)
38
+
39
+ def submit_product_version(self, version: str):
40
+ self.scylla_version = version
41
+ self.set_product_version(version)
42
+
43
+ @classmethod
44
+ def load_test_run(cls, run_id: UUID) -> 'GenericRun':
45
+ return cls.get(id=run_id)
46
+
47
+ @classmethod
48
+ def submit_run(cls, request_data: GenericRunSubmitRequest) -> 'GenericRun':
49
+ try:
50
+ run = cls.get(id=request_data["run_id"])
51
+ raise GenericPluginException(f"Run with UUID {request_data['run_id']} already exists.", request_data["run_id"])
52
+ except cls.DoesNotExist:
53
+ pass
54
+ run = cls()
55
+ run.start_time = datetime.utcnow()
56
+ run.build_id = request_data["build_id"]
57
+ run.started_by = request_data["started_by"]
58
+ run.id = request_data["run_id"]
59
+ run.build_job_url = request_data["build_url"]
60
+ run.assign_categories()
61
+ try:
62
+ run.assignee = run.get_scheduled_assignee()
63
+ except Model.DoesNotExist:
64
+ run.assignee = None
65
+ if version := request_data.get("scylla_version"):
66
+ run.submit_product_version(version)
67
+ run.status = TestStatus.RUNNING.value
68
+ run.save()
69
+
70
+ return run
71
+
72
+ def finish_run(self, payload: GenericRunFinishRequest = None):
73
+ self.end_time = datetime.utcnow()
74
+ self.status = TestStatus(payload["status"]).value
75
+ if version := payload.get("scylla_version"):
76
+ self.submit_product_version(version)
@@ -0,0 +1,16 @@
1
+ from flask import Blueprint
2
+
3
+ from argus.backend.plugins.core import PluginInfoBase, PluginModelBase
4
+ from argus.backend.plugins.generic.model import GenericRun
5
+
6
+
7
+ class PluginInfo(PluginInfoBase):
8
+ # pylint: disable=too-few-public-methods
9
+ name: str = "generic"
10
+ model: PluginModelBase = GenericRun
11
+ controller: Blueprint = None
12
+ all_models = [
13
+ GenericRun
14
+ ]
15
+ all_types = [
16
+ ]
@@ -0,0 +1,13 @@
1
+ from typing import TypedDict
2
+
3
+ class GenericRunSubmitRequest(TypedDict):
4
+ build_id: str
5
+ build_url: str
6
+ run_id: str
7
+ started_by: str
8
+ scylla_version: str | None
9
+
10
+
11
+ class GenericRunFinishRequest(TypedDict):
12
+ status: str
13
+ scylla_version: str | None
@@ -383,7 +383,7 @@ class SCTService:
383
383
  "aborted": 0,
384
384
  }
385
385
  )
386
- if run["status"] in ["passed", "failed", "aborted"]:
386
+ if run["status"] in ["passed", "failed", "aborted", "test_error"]:
387
387
  metadata[run["status"]] += 1
388
388
  kernel_metadata[kernel_package["version"]] = metadata
389
389
 
@@ -47,9 +47,9 @@ class SCTTestRunSubmissionRequest():
47
47
  job_url: str
48
48
  started_by: str
49
49
  commit_id: str
50
- origin_url: str | None
51
- branch_name: str | None
52
50
  sct_config: dict | None
51
+ origin_url: Optional[str] = field(default=None)
52
+ branch_name: Optional[str] = field(default=None)
53
53
  runner_public_ip: Optional[str] = field(default=None)
54
54
  runner_private_ip: Optional[str] = field(default=None)
55
55
 
@@ -60,6 +60,7 @@ class SCTTestRun(PluginModelBase):
60
60
 
61
61
  # Test Details
62
62
  test_name = columns.Text()
63
+ stress_duration = columns.Float()
63
64
  scm_revision_id = columns.Text()
64
65
  branch_name = columns.Text()
65
66
  origin_url = columns.Text()
@@ -181,6 +182,8 @@ class SCTTestRun(PluginModelBase):
181
182
 
182
183
  if req.sct_config:
183
184
  backend = req.sct_config.get("cluster_backend")
185
+ if duration_override := req.sct_config.get("stress_duration"):
186
+ run.stress_duration = float(duration_override)
184
187
  region_key = SCT_REGION_PROPERTY_MAP.get(backend, SCT_REGION_PROPERTY_MAP["default"])
185
188
  raw_regions = req.sct_config.get(region_key) or "undefined_region"
186
189
  regions = raw_regions.split() if isinstance(raw_regions, str) else raw_regions
@@ -212,7 +215,7 @@ class SCTTestRun(PluginModelBase):
212
215
  def submit_product_version(self, version: str):
213
216
  self.scylla_version = version
214
217
 
215
- def finish_run(self):
218
+ def finish_run(self, payload: dict = None):
216
219
  self.end_time = datetime.utcnow()
217
220
 
218
221
  def submit_logs(self, logs: list[dict]):
@@ -20,6 +20,8 @@ class CloudInstanceDetails(UserType):
20
20
  region = columns.Text()
21
21
  public_ip = columns.Text()
22
22
  private_ip = columns.Text()
23
+ dc_name = columns.Text()
24
+ rack_name = columns.Text()
23
25
  creation_time = columns.Integer(default=lambda: int(time()))
24
26
  termination_time = columns.Integer(default=lambda: 0)
25
27
  termination_reason = columns.Text(default=lambda: "")
@@ -65,7 +65,7 @@ class SirenadaRun(PluginModelBase):
65
65
  def submit_logs(self, logs: dict[str, str]):
66
66
  raise SirenadaPluginException("Log submission is not supported for Sirenada")
67
67
 
68
- def finish_run(self):
68
+ def finish_run(self, payload: dict = None):
69
69
  raise SirenadaPluginException("Sirenada runs do not need finalization")
70
70
 
71
71
  @classmethod
@@ -62,10 +62,10 @@ class ClientService:
62
62
 
63
63
  return "Submitted"
64
64
 
65
- def finish_run(self, run_type: str, run_id: str) -> str:
65
+ def finish_run(self, run_type: str, run_id: str, payload: dict | None = None) -> str:
66
66
  model = self.get_model(run_type)
67
67
  run = model.load_test_run(UUID(run_id))
68
- run.finish_run()
68
+ run.finish_run(payload)
69
69
  run.save()
70
70
 
71
71
  return "Finalized"
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from typing import TypedDict
2
3
  from uuid import UUID
3
4
  from argus.backend.db import ScyllaCluster
4
5
  from argus.backend.plugins.sct.testrun import SCTTestRun
@@ -11,6 +12,16 @@ class ReleaseManagerException(Exception):
11
12
  pass
12
13
 
13
14
 
15
+ class ReleaseEditPayload(TypedDict):
16
+ id: str
17
+ pretty_name: str
18
+ description: str
19
+ valid_version_regex: str | None
20
+ enabled: bool
21
+ perpetual: bool
22
+ dormant: bool
23
+
24
+
14
25
  class ReleaseManagerService:
15
26
  def __init__(self) -> None:
16
27
  self.session = ScyllaCluster.get_session()
@@ -156,6 +167,33 @@ class ReleaseManagerService:
156
167
 
157
168
  return True
158
169
 
170
+ def edit_release(self, payload: ReleaseEditPayload) -> bool:
171
+
172
+ release: ArgusRelease = ArgusRelease.get(id=payload["id"])
173
+ release.pretty_name = payload["pretty_name"]
174
+ release.perpetual = payload["perpetual"]
175
+ release.enabled = payload["enabled"]
176
+ release.dormant = payload["dormant"]
177
+ release.description = payload["description"]
178
+ release.valid_version_regex = payload["valid_version_regex"]
179
+
180
+
181
+ release.save()
182
+ return True
183
+
184
+ def delete_release(self, release_id: str) -> bool:
185
+
186
+ release: ArgusRelease = ArgusRelease.get(id=release_id)
187
+
188
+ release_groups = ArgusGroup.filter(release_id=release.id)
189
+ release_tests = ArgusTest.filter(release_id=release.id)
190
+
191
+ for entity in [*release_groups.all(), *release_tests.all()]:
192
+ entity.delete()
193
+
194
+ release.delete()
195
+ return True
196
+
159
197
  def batch_move_tests(self, new_group_id: str, tests: list[str]) -> bool:
160
198
  group = ArgusGroup.get(id=UUID(new_group_id))
161
199
 
@@ -33,6 +33,7 @@ class TestRunStatRow(TypedDict):
33
33
  class ComparableTestStatus:
34
34
  PRIORITY_MAP = {
35
35
  TestStatus.FAILED: 10,
36
+ TestStatus.TEST_ERROR: 10,
36
37
  TestStatus.ABORTED: 9,
37
38
  TestStatus.RUNNING: 8,
38
39
  TestStatus.CREATED: 7,
@@ -126,7 +127,7 @@ def generate_field_status_map(
126
127
  field_name = "status",
127
128
  container_class = TestStatus,
128
129
  cmp_class = ComparableTestStatus
129
- ) -> dict[int, str]:
130
+ ) -> dict[int, tuple[str, TestRunStatRow]]:
130
131
 
131
132
  status_map = {}
132
133
  for run in last_runs:
@@ -136,7 +137,7 @@ def generate_field_status_map(
136
137
  if cmp_class(container_class(status)) < cmp_class(container_class(run[field_name])):
137
138
  status_map[run_number] = run[field_name]
138
139
  case _:
139
- status_map[run_number] = run[field_name]
140
+ status_map[run_number] = (run[field_name], run)
140
141
  return status_map
141
142
 
142
143
 
@@ -279,6 +280,7 @@ class TestStats:
279
280
  self.has_comments = False
280
281
  self.schedules = schedules if schedules else tuple()
281
282
  self.is_scheduled = len(self.schedules) > 0
283
+ self.tracked_run_number = None
282
284
 
283
285
  def to_dict(self) -> dict:
284
286
  return {
@@ -288,7 +290,8 @@ class TestStats:
288
290
  "last_runs": self.last_runs,
289
291
  "start_time": self.start_time,
290
292
  "hasBugReport": self.has_bug_report,
291
- "hasComments": self.has_comments
293
+ "hasComments": self.has_comments,
294
+ "buildNumber": self.tracked_run_number,
292
295
  }
293
296
 
294
297
  def collect(self, limited=False):
@@ -305,11 +308,10 @@ class TestStats:
305
308
  self.parent_group.increment_status(status=self.status)
306
309
  return
307
310
  status_map = generate_field_status_map(last_runs)
308
- investigation_status_map = generate_field_status_map(
309
- last_runs, "investigation_status", TestInvestigationStatus, ComparableTestInvestigationStatus)
310
311
 
311
- self.status = status_map.get(get_build_number(last_run["build_job_url"]))
312
- self.investigation_status = investigation_status_map.get(get_build_number(last_run["build_job_url"]))
312
+ worst_case = status_map.get(get_build_number(last_run["build_job_url"]))
313
+ self.status = worst_case[0]
314
+ self.investigation_status = worst_case[1]["investigation_status"]
313
315
  self.start_time = last_run["start_time"]
314
316
 
315
317
  self.parent_group.increment_status(status=self.status)
@@ -318,6 +320,7 @@ class TestStats:
318
320
 
319
321
  self.last_runs = [
320
322
  {
323
+ "id": run["id"],
321
324
  "status": run["status"],
322
325
  "build_number": get_build_number(run["build_job_url"]),
323
326
  "build_job_name": run["build_id"],
@@ -327,10 +330,18 @@ class TestStats:
327
330
  "comments": [dict(i.items()) for i in self.parent_group.parent_release.comments if i.test_run_id == run["id"]],
328
331
  }
329
332
  for run in last_runs
330
- ][:5]
331
- self.has_bug_report = len(self.last_runs[0]["issues"]) > 0
333
+ ]
334
+ try:
335
+ target_run = next(run for run in self.last_runs if run["id"] == worst_case[1]["id"])
336
+ except StopIteration:
337
+ target_run = worst_case[1]
338
+ target_run["issues"] = [dict(i.items()) for i in self.parent_group.parent_release.issues if i.run_id == target_run["id"]]
339
+ target_run["comments"] = [dict(i.items()) for i in self.parent_group.parent_release.comments if i.test_run_id == target_run["id"]]
340
+ self.has_bug_report = len(target_run["issues"]) > 0
332
341
  self.parent_group.parent_release.has_bug_report = self.has_bug_report or self.parent_group.parent_release.has_bug_report
333
- self.has_comments = len(self.last_runs[0]["comments"]) > 0
342
+ self.has_comments = len(target_run["comments"]) > 0
343
+ self.last_runs = self.last_runs[:5]
344
+ self.tracked_run_number = target_run.get("build_number", get_build_number(target_run.get("build_job_url")))
334
345
 
335
346
 
336
347
  class ReleaseStatsCollector:
@@ -343,7 +354,7 @@ class ReleaseStatsCollector:
343
354
  self.release_name = release_name
344
355
  self.release_version = release_version
345
356
 
346
- def collect(self, limited=False, force=False) -> dict:
357
+ def collect(self, limited=False, force=False, include_no_version=False) -> dict:
347
358
  self.release: ArgusRelease = ArgusRelease.get(name=self.release_name)
348
359
  self.release_rows = [row for plugin in all_plugin_models()
349
360
  for row in plugin.get_stats_for_release(release=self.release)]
@@ -353,8 +364,18 @@ class ReleaseStatsCollector:
353
364
  }
354
365
 
355
366
  if self.release_version:
356
- self.release_rows = list(
357
- filter(lambda row: row["scylla_version"] == self.release_version or not row["scylla_version"], self.release_rows))
367
+ if include_no_version:
368
+ expr = lambda row: row["scylla_version"] == self.release_version or not row["scylla_version"]
369
+ elif self.release_version == "!noVersion":
370
+ expr = lambda row: not row["scylla_version"]
371
+ else:
372
+ expr = lambda row: row["scylla_version"] == self.release_version
373
+ else:
374
+ if include_no_version:
375
+ expr = lambda row: row
376
+ else:
377
+ expr = lambda row: row["scylla_version"]
378
+ self.release_rows = list(filter(expr, self.release_rows))
358
379
 
359
380
  self.release_stats = ReleaseStats(release=self.release)
360
381
  self.release_stats.collect(rows=self.release_rows, limited=limited, force=force)
@@ -341,7 +341,7 @@ class TestRunService:
341
341
  }
342
342
  )
343
343
  if issue_request.status_code != 200:
344
- raise Exception(f"Error getting issue state: Response: HTTP {issue_request.status_code}", issue_request)
344
+ raise Exception(f"Error getting issue state: Response: HTTP {issue_request.status_code}", issue_request.json())
345
345
 
346
346
  issue_state: dict[str, Any] = issue_request.json()
347
347
 
@@ -14,6 +14,7 @@ class TestStatus(str, Enum):
14
14
  CREATED = "created"
15
15
  RUNNING = "running"
16
16
  FAILED = "failed"
17
+ TEST_ERROR = "test_error"
17
18
  ERROR = "error"
18
19
  PASSED = "passed"
19
20
  ABORTED = "aborted"
@@ -170,12 +170,14 @@ class ArgusClient:
170
170
  }
171
171
  )
172
172
 
173
- def finalize_run(self, run_type: str, run_id: UUID) -> requests.Response:
173
+ def finalize_run(self, run_type: str, run_id: UUID, body: dict = None) -> requests.Response:
174
+ body = body if body else {}
174
175
  return self.post(
175
176
  endpoint=self.Routes.FINALIZE,
176
177
  location_params={"type": run_type, "id": str(run_id)},
177
178
  body={
178
179
  **self.generic_body,
180
+ **body,
179
181
  }
180
182
  )
181
183
 
@@ -0,0 +1,47 @@
1
+ import click
2
+ import logging
3
+ from argus.backend.util.enums import TestStatus
4
+
5
+ from argus.client.generic.client import ArgusGenericClient
6
+
7
+ LOGGER = logging.getLogger(__name__)
8
+
9
+
10
+ @click.group
11
+ def cli():
12
+ pass
13
+
14
+
15
+ @click.command("submit")
16
+ @click.option("--api-key", help="Argus API key for authorization", required=True)
17
+ @click.option("--base-url", default="https://argus.scylladb.com", help="Base URL for argus instance")
18
+ @click.option("--id", required=True, help="UUID (v4 or v1) unique to the job")
19
+ @click.option("--build-id", required=True, help="Unique job identifier in the build system, e.g. scylla-master/group/job for jenkins (The full path)")
20
+ @click.option("--build-url", required=True, help="Job URL in the build system")
21
+ @click.option("--started-by", required=True, help="Username of the user who started the job")
22
+ @click.option("--scylla-version", required=False, default=None, help="Version of Scylla used for this job")
23
+ def submit_run(api_key: str, base_url: str, id: str, build_id: str, build_url: str, started_by: str, scylla_version: str = None):
24
+ LOGGER.info("Submitting %s (%s) to Argus...", build_id, id)
25
+ client = ArgusGenericClient(auth_token=api_key, base_url=base_url)
26
+ client.submit_generic_run(build_id=build_id, run_id=id, started_by=started_by, build_url=build_url, scylla_version=scylla_version)
27
+ LOGGER.info("Done.")
28
+
29
+
30
+ @click.command("finish")
31
+ @click.option("--api-key", help="Argus API key for authorization", required=True)
32
+ @click.option("--base-url", default="https://argus.scylladb.com", help="Base URL for argus instance")
33
+ @click.option("--id", required=True, help="UUID (v4 or v1) unique to the job")
34
+ @click.option("--status", required=True, help="Resulting job status")
35
+ @click.option("--scylla-version", required=False, default=None, help="Version of Scylla used for this job")
36
+ def finish_run(api_key: str, base_url: str, id: str, status: str, scylla_version: str = None):
37
+ client = ArgusGenericClient(auth_token=api_key, base_url=base_url)
38
+ status = TestStatus(status)
39
+ client.finalize_generic_run(run_id=id, status=status, scylla_version=scylla_version)
40
+
41
+
42
+ cli.add_command(submit_run)
43
+ cli.add_command(finish_run)
44
+
45
+
46
+ if __name__ == "__main__":
47
+ cli()
@@ -0,0 +1,31 @@
1
+ import logging
2
+ from argus.client.base import ArgusClient
3
+
4
+
5
+ LOGGER = logging.getLogger(__name__)
6
+
7
+ class ArgusGenericClient(ArgusClient):
8
+ test_type = "generic"
9
+ schema_version: None = "v1"
10
+ def __init__(self, auth_token: str, base_url: str, api_version="v1") -> None:
11
+ super().__init__(auth_token, base_url, api_version)
12
+
13
+ def submit_generic_run(self, build_id: str, run_id: str, started_by: str, build_url: str, scylla_version: str | None = None):
14
+ request_body = {
15
+ "build_id": build_id,
16
+ "run_id": run_id,
17
+ "started_by": started_by,
18
+ "build_url": build_url,
19
+ "scylla_version": scylla_version
20
+ }
21
+
22
+ response = self.submit_run(run_type=self.test_type, run_body=request_body)
23
+ self.check_response(response)
24
+
25
+
26
+ def finalize_generic_run(self, run_id: str, status: str, scylla_version: str | None = None):
27
+ response = self.finalize_run(run_type=self.test_type, run_id=run_id, body={
28
+ "status": status,
29
+ "scylla_version": scylla_version,
30
+ })
31
+ self.check_response(response)
@@ -148,7 +148,7 @@ class ArgusSCTClient(ArgusClient):
148
148
  self.check_response(response)
149
149
 
150
150
  def create_resource(self, name: str, resource_type: str, public_ip: str, private_ip: str,
151
- region: str, provider: str, shards_amount: int, state=ResourceState.RUNNING) -> None:
151
+ region: str, provider: str, dc_name: str, rack_name: str, shards_amount: int, state=ResourceState.RUNNING) -> None:
152
152
  """
153
153
  Creates a cloud resource record in argus.
154
154
  """
@@ -164,6 +164,8 @@ class ArgusSCTClient(ArgusClient):
164
164
  "instance_details": {
165
165
  "provider": provider,
166
166
  "region": region,
167
+ "dc_name": dc_name,
168
+ "rack_name": rack_name,
167
169
  "public_ip": public_ip,
168
170
  "private_ip": private_ip,
169
171
  "shards_amount": shards_amount,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "argus-alm"
3
- version = "0.11.7"
3
+ version = "0.12.1"
4
4
  description = "Argus"
5
5
  authors = ["Alexey Kartashov <alexey.kartashov@scylladb.com>"]
6
6
  license = "Apache-2.0"
@@ -17,6 +17,10 @@ optional = true
17
17
  [tool.poetry.dependencies]
18
18
  requests = "^2.26.0"
19
19
  python = "^3.10"
20
+ click = "^8.1.3"
21
+
22
+ [tool.poetry.scripts]
23
+ argus-client-generic = 'argus.client.generic.cli:cli'
20
24
 
21
25
  [tool.poetry.group.web-backend.dependencies]
22
26
  PyYAML = "^5.4.1"
argus_alm-0.11.7/setup.py DELETED
@@ -1,45 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from setuptools import setup
3
-
4
- packages = \
5
- ['argus',
6
- 'argus.backend',
7
- 'argus.backend.controller',
8
- 'argus.backend.events',
9
- 'argus.backend.models',
10
- 'argus.backend.plugins',
11
- 'argus.backend.plugins.driver_matrix_tests',
12
- 'argus.backend.plugins.sct',
13
- 'argus.backend.plugins.sirenada',
14
- 'argus.backend.service',
15
- 'argus.backend.util',
16
- 'argus.client',
17
- 'argus.client.driver_matrix_tests',
18
- 'argus.client.sct',
19
- 'argus.client.sirenada',
20
- 'argus.db']
21
-
22
- package_data = \
23
- {'': ['*']}
24
-
25
- install_requires = \
26
- ['requests>=2.26.0,<3.0.0']
27
-
28
- setup_kwargs = {
29
- 'name': 'argus-alm',
30
- 'version': '0.11.7',
31
- 'description': 'Argus',
32
- 'long_description': '# Argus\n\n## Description\n\nArgus is a test tracking system intended to provide observability into automated test pipelines which use long-running resources. It allows observation of a test status, its events and its allocated resources. It also allows easy comparison between particular runs of a specific test.\n\n## Installation notes\n\n### Prerequisites\n\n- Python >=3.10.0 (system-wide or pyenv)\n\n- NodeJS >=16 (with npm)\n\n- Yarn (can be installed globally with `npm -g install yarn`)\n\n- nginx\n\n- poetry >=1.2.0b1\n\n### From source\n\n#### Production\n\nPerform the following steps:\n\nCreate a user that will be used by uwsgi:\n\n```bash\nuseradd -m -s /bin/bash argus\nsudo -iu argus\n```\n\n(Optional) Install pyenv and create a virtualenv for this user:\n\n```bash\npyenv install 3.10.0\npyenv virtualenv argus\npyenv activate argus\n```\n\nClone the project into a directory somewhere where user has full write permissions\n\n```bash\ngit clone https://github.com/scylladb/argus ~/app\ncd ~/app\n```\n\nInstall project dependencies:\n\n```bash\npoetry install --with default,dev,web-backend,docker-image\nyarn install\n```\n\nCompile frontend files from `/frontend` into `/public/dist`\n\n```bash\nyarn webpack\n```\n\nCreate a `argus.local.yaml` configuration file (used to configure database connection) and a `argus_web.yaml` (used for webapp secrets) in your application install directory.\n\n```bash\ncp argus_web.example.yaml argus_web.yaml\ncp argus.yaml argus.local.yaml\n```\n\nOpen `argus.local.yaml` and add the database connection information (contact_points, user, password and keyspace name).\n\nOpen `argus_web.yaml` and change the `SECRET_KEY` value to something secure, like a sha512 digest of random bytes. Fill out GITHUB_* variables with their respective values.\n\nCopy nginx configuration file from `docs/configs/argus.nginx.conf` to nginx virtual hosts directory:\n\nUbuntu:\n\n```bash\nsudo cp docs/configs/argus.nginx.conf /etc/nginx/sites-available/argus\nsudo ln -s /etc/nginx/sites-enabled/argus /etc/nginx/sites-available/argus\n```\n\nRHEL/Centos/Alma/Fedora:\n\n```bash\nsudo cp docs/configs/argus.nginx.conf /etc/nginx/conf.d/argus.conf\n```\n\nAdjust the webhost settings in that file as necessary, particularly `listen` and `server_name` directives.\n\nCopy systemd service file from `docs/config/argus.service` to `/etc/systemd/system` directory:\n\n```bash\nsudo cp docs/config/argus.service /etc/systemd/system\n```\n\nOpen it and adjust the path to the `start_argus.sh` script in the `ExecStart=` directive and the user/group, then reload systemd daemon configuration and enable (and optionally start) the service.\n\nWARNING: `start_argus.sh` assumes pyenv is installed into `~/.pyenv`\n\n```bash\nsudo systemctl daemon-reload\nsudo systemctl enable --now argus.service\n```\n\n#### Development\n\nClone the project into a directory somewhere\n\n```bash\ngit clone https://github.com/scylladb/argus\ncd argus\n```\n\nInstall project dependencies:\n\n```bash\npoetry install --with default,dev,web-backend,docker-image\nyarn install\n```\n\nCompile frontend files from `/frontend` into `/public/dist`. Add --watch to recompile files on change.\n\n```bash\nyarn webpack --watch\n```\n##### Configuration\nCreate a `argus.local.yaml` configuration file (used to configure database connection) and a `argus_web.yaml` (used for webapp secrets) in your application install directory.\n\nSee `Production` section for more details.\nTo configure Github authentication follow steps:\n1. Authorize OAuth App\n 1. go to your Account Settings (top right corner) -> Developer settings (left pane) -> OAuth Apps\n 2. Click Create New OAuth App button\n 3. Fill the fields (app name: `argus-dev`, homepage URL `http://localhost:5000`, Auth callback URL: `http://localhost:5000/profile/oauth/github`)\n 4. Confirm and get the tokens/ids required for config\n2. Create Jenkins token for your account\n 1. Go to `Configure` in top right corner\n 2. Click `Add new Token`\n 3. Get it and paste to config to `JENKINS_API_TOKEN` param\n##### Database Initialization\n\nYou can initialize a scylla cluster in any way you like, either using docker image with docker-compose or using cassandra cluster manager. You will need to create the keyspace manually before you can sync database models.\n\nCreate keyspace according to your configuration.\ne.g. (need to test if it works with RF=1 if not, make it 3)\n```\nCREATE KEYSPACE argus WITH replication = {\'class\': \'SimpleStrategy\', \'replication_factor\' : 1}\n```\n\nInitial sync can be done as follows:\n\n```py\nfrom argus.backend.db import ScyllaCluster\nfrom argus.db.testrun import TestRun\ndb = ScyllaCluster.get()\n\ndb.sync_models() # Syncronizes Object Mapper models\nTestRun.init_own_table() # Syncronizes TestRun table (separate from python-driver Object Mapper)\n\n```\n\nYou can also use `flask sync-models` afterwards during development when making small changes to models.\n\nIt is recommended to set up jenkins api key and run `flask scan-jenkins` afterwards to get basic release/group/test structure.\n\nThere are scripts in `./scripts` directory that can be used to download data from production, upload them into your dev db and fix their relations to other models in your instance of the application. Specifically, `download_runs_from_prod.py` requires additional config, `argus.local.prod.yaml` which is the config used to connect to the production cluster. The scripts are split to prevent mistakes and accidentally affecting production cluster.\n\n##### Configuration\n\nCreate a `argus.local.yaml` configuration file (used to configure database connection) and a `argus_web.yaml` (used for webapp secrets) in your application install directory.\n\n```bash\ncp argus_web.example.yaml argus_web.yaml\ncp argus.yaml argus.local.yaml\n```\n\nOpen `argus.local.yaml` and add the database connection information (contact_points, user, password and keyspace name).\n\nOpen `argus_web.yaml` and change the `SECRET_KEY` value to something secure, like a sha512 digest of random bytes. Fill out GITHUB_* and JENKINS_* variables with their respective values.\n\nRun the application from CLI using:\n\n```bash\nFLASK_ENV="development" FLASK_APP="argus_backend:start_server" FLASK_DEBUG=1 CQLENG_ALLOW_SCHEMA_MANAGEMENT=1 flask run\n```\n\nOmit `FLASK_DEBUG` if running your own debugger (pdb, pycharm, vscode)\n',
33
- 'author': 'Alexey Kartashov',
34
- 'author_email': 'alexey.kartashov@scylladb.com',
35
- 'maintainer': 'None',
36
- 'maintainer_email': 'None',
37
- 'url': 'https://github.com/scylladb/argus',
38
- 'packages': packages,
39
- 'package_data': package_data,
40
- 'install_requires': install_requires,
41
- 'python_requires': '>=3.10,<4.0',
42
- }
43
-
44
-
45
- setup(**setup_kwargs)
File without changes
File without changes
File without changes
File without changes
File without changes