odoo-addon-odoo-repository 16.0.1.3.0.13__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.
- odoo/addons/odoo_repository/README.rst +81 -0
- odoo/addons/odoo_repository/__init__.py +2 -0
- odoo/addons/odoo_repository/__manifest__.py +58 -0
- odoo/addons/odoo_repository/controllers/__init__.py +1 -0
- odoo/addons/odoo_repository/controllers/main.py +32 -0
- odoo/addons/odoo_repository/data/ir_cron.xml +38 -0
- odoo/addons/odoo_repository/data/odoo.repository.csv +216 -0
- odoo/addons/odoo_repository/data/odoo_branch.xml +82 -0
- odoo/addons/odoo_repository/data/odoo_module.xml +16 -0
- odoo/addons/odoo_repository/data/odoo_repository.xml +71 -0
- odoo/addons/odoo_repository/data/odoo_repository_addons_path.xml +59 -0
- odoo/addons/odoo_repository/data/odoo_repository_org.xml +14 -0
- odoo/addons/odoo_repository/data/queue_job.xml +56 -0
- odoo/addons/odoo_repository/lib/__init__.py +0 -0
- odoo/addons/odoo_repository/lib/scanner.py +1302 -0
- odoo/addons/odoo_repository/migrations/16.0.1.1.0/post-migration.py +26 -0
- odoo/addons/odoo_repository/migrations/16.0.1.2.0/pre-migration.py +43 -0
- odoo/addons/odoo_repository/migrations/16.0.1.3.0/post-migration.py +19 -0
- odoo/addons/odoo_repository/models/__init__.py +18 -0
- odoo/addons/odoo_repository/models/authentication_token.py +12 -0
- odoo/addons/odoo_repository/models/odoo_author.py +16 -0
- odoo/addons/odoo_repository/models/odoo_branch.py +111 -0
- odoo/addons/odoo_repository/models/odoo_license.py +16 -0
- odoo/addons/odoo_repository/models/odoo_maintainer.py +31 -0
- odoo/addons/odoo_repository/models/odoo_module.py +24 -0
- odoo/addons/odoo_repository/models/odoo_module_branch.py +873 -0
- odoo/addons/odoo_repository/models/odoo_module_branch_version.py +123 -0
- odoo/addons/odoo_repository/models/odoo_module_category.py +15 -0
- odoo/addons/odoo_repository/models/odoo_module_dev_status.py +15 -0
- odoo/addons/odoo_repository/models/odoo_python_dependency.py +16 -0
- odoo/addons/odoo_repository/models/odoo_repository.py +664 -0
- odoo/addons/odoo_repository/models/odoo_repository_addons_path.py +40 -0
- odoo/addons/odoo_repository/models/odoo_repository_branch.py +98 -0
- odoo/addons/odoo_repository/models/odoo_repository_org.py +23 -0
- odoo/addons/odoo_repository/models/res_company.py +23 -0
- odoo/addons/odoo_repository/models/res_config_settings.py +23 -0
- odoo/addons/odoo_repository/models/ssh_key.py +12 -0
- odoo/addons/odoo_repository/readme/CONTRIBUTORS.rst +2 -0
- odoo/addons/odoo_repository/readme/DESCRIPTION.rst +1 -0
- odoo/addons/odoo_repository/security/ir.model.access.csv +27 -0
- odoo/addons/odoo_repository/security/res_groups.xml +25 -0
- odoo/addons/odoo_repository/static/description/README +4 -0
- odoo/addons/odoo_repository/static/description/icon.png +0 -0
- odoo/addons/odoo_repository/static/description/index.html +430 -0
- odoo/addons/odoo_repository/tests/__init__.py +6 -0
- odoo/addons/odoo_repository/tests/common.py +162 -0
- odoo/addons/odoo_repository/tests/test_base_scanner.py +214 -0
- odoo/addons/odoo_repository/tests/test_odoo_module_branch.py +97 -0
- odoo/addons/odoo_repository/tests/test_odoo_repository_scan.py +242 -0
- odoo/addons/odoo_repository/tests/test_repository_scanner.py +215 -0
- odoo/addons/odoo_repository/tests/test_sync_node.py +55 -0
- odoo/addons/odoo_repository/tests/test_utils.py +25 -0
- odoo/addons/odoo_repository/utils/__init__.py +0 -0
- odoo/addons/odoo_repository/utils/github.py +30 -0
- odoo/addons/odoo_repository/utils/module.py +25 -0
- odoo/addons/odoo_repository/utils/scanner.py +90 -0
- odoo/addons/odoo_repository/views/authentication_token.xml +63 -0
- odoo/addons/odoo_repository/views/menu.xml +38 -0
- odoo/addons/odoo_repository/views/odoo_author.xml +54 -0
- odoo/addons/odoo_repository/views/odoo_branch.xml +84 -0
- odoo/addons/odoo_repository/views/odoo_license.xml +40 -0
- odoo/addons/odoo_repository/views/odoo_maintainer.xml +69 -0
- odoo/addons/odoo_repository/views/odoo_module.xml +90 -0
- odoo/addons/odoo_repository/views/odoo_module_branch.xml +353 -0
- odoo/addons/odoo_repository/views/odoo_module_category.xml +40 -0
- odoo/addons/odoo_repository/views/odoo_module_dev_status.xml +40 -0
- odoo/addons/odoo_repository/views/odoo_python_dependency.xml +40 -0
- odoo/addons/odoo_repository/views/odoo_repository.xml +165 -0
- odoo/addons/odoo_repository/views/odoo_repository_addons_path.xml +49 -0
- odoo/addons/odoo_repository/views/odoo_repository_branch.xml +60 -0
- odoo/addons/odoo_repository/views/odoo_repository_org.xml +54 -0
- odoo/addons/odoo_repository/views/res_config_settings.xml +123 -0
- odoo/addons/odoo_repository/views/ssh_key.xml +63 -0
- odoo_addon_odoo_repository-16.0.1.3.0.13.dist-info/METADATA +100 -0
- odoo_addon_odoo_repository-16.0.1.3.0.13.dist-info/RECORD +77 -0
- odoo_addon_odoo_repository-16.0.1.3.0.13.dist-info/WHEEL +5 -0
- odoo_addon_odoo_repository-16.0.1.3.0.13.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Copyright 2024 Camptocamp SA
|
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
3
|
+
|
|
4
|
+
from odoo.addons.odoo_repository.utils.scanner import RepositoryScannerOdooEnv
|
|
5
|
+
|
|
6
|
+
from .common import Common
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestRepositoryScanner(Common):
|
|
10
|
+
def _init_scanner(self, **params):
|
|
11
|
+
kwargs = {
|
|
12
|
+
"org": self.org.name,
|
|
13
|
+
"name": self.repo_name,
|
|
14
|
+
"clone_url": self.repo_upstream_path,
|
|
15
|
+
"version": self.branch.name,
|
|
16
|
+
"branch": self.branch.name,
|
|
17
|
+
"addons_paths_data": [
|
|
18
|
+
{
|
|
19
|
+
"relative_path": ".",
|
|
20
|
+
"is_standard": False,
|
|
21
|
+
"is_enterprise": False,
|
|
22
|
+
"is_community": True,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
"repositories_path": self.repositories_path,
|
|
26
|
+
"env": self.env,
|
|
27
|
+
}
|
|
28
|
+
if params:
|
|
29
|
+
kwargs.update(params)
|
|
30
|
+
return RepositoryScannerOdooEnv(**kwargs)
|
|
31
|
+
|
|
32
|
+
def test_init(self):
|
|
33
|
+
scanner = self._init_scanner()
|
|
34
|
+
self.assertTrue(scanner.repositories_path.exists())
|
|
35
|
+
self.assertEqual(scanner.path.parts[-1], self.repo_name)
|
|
36
|
+
self.assertEqual(scanner.path.parts[-2], self.fork_org)
|
|
37
|
+
self.assertEqual(scanner.full_name, f"{self.fork_org}/{self.repo_name}")
|
|
38
|
+
|
|
39
|
+
def test_sync(self):
|
|
40
|
+
scanner = self._init_scanner()
|
|
41
|
+
scanner.sync()
|
|
42
|
+
|
|
43
|
+
def test_get_odoo_repository_id(self):
|
|
44
|
+
scanner = self._init_scanner()
|
|
45
|
+
repo_id = scanner._get_odoo_repository_id()
|
|
46
|
+
self.assertEqual(repo_id, self.odoo_repository.id)
|
|
47
|
+
|
|
48
|
+
def test_get_odoo_branch_id(self):
|
|
49
|
+
scanner = self._init_scanner()
|
|
50
|
+
branch_id = scanner._get_odoo_branch_id(self.branch.name)
|
|
51
|
+
self.assertEqual(branch_id, self.branch.id)
|
|
52
|
+
|
|
53
|
+
def test_create_odoo_repository_branch(self):
|
|
54
|
+
scanner = self._init_scanner()
|
|
55
|
+
repo_id = scanner._get_odoo_repository_id()
|
|
56
|
+
branch_id = scanner._get_odoo_branch_id(self.branch.name)
|
|
57
|
+
# The repository branch doesn't exist yet
|
|
58
|
+
expected_repo_branch_id = scanner._get_odoo_repository_branch_id(
|
|
59
|
+
repo_id, branch_id
|
|
60
|
+
)
|
|
61
|
+
self.assertFalse(expected_repo_branch_id)
|
|
62
|
+
# Create it
|
|
63
|
+
repo_branch_id = scanner._create_odoo_repository_branch(repo_id, branch_id)
|
|
64
|
+
self.assertTrue(repo_branch_id)
|
|
65
|
+
self.assertEqual(
|
|
66
|
+
repo_branch_id, scanner._get_odoo_repository_branch_id(repo_id, branch_id)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def test_get_repo_last_scanned_commit(self):
|
|
70
|
+
scanner = self._init_scanner()
|
|
71
|
+
repo_id = scanner._get_odoo_repository_id()
|
|
72
|
+
branch_id = scanner._get_odoo_branch_id(self.branch.name)
|
|
73
|
+
repo_branch_id = scanner._create_odoo_repository_branch(repo_id, branch_id)
|
|
74
|
+
repo_branch = self.env["odoo.repository.branch"].browse(repo_branch_id)
|
|
75
|
+
# Nothing has been scanned until now
|
|
76
|
+
self.assertFalse(scanner._get_repo_last_scanned_commit(repo_branch_id))
|
|
77
|
+
# Clone/fetch the repo
|
|
78
|
+
scanner.sync()
|
|
79
|
+
with scanner.repo() as repo:
|
|
80
|
+
last_fetched_commit = scanner._get_last_fetched_commit(
|
|
81
|
+
repo, self.branch.name
|
|
82
|
+
)
|
|
83
|
+
# Simulate the end of scan
|
|
84
|
+
repo_branch.last_scanned_commit = last_fetched_commit
|
|
85
|
+
# Check again
|
|
86
|
+
last_scanned_commit = scanner._get_repo_last_scanned_commit(repo_branch_id)
|
|
87
|
+
self.assertEqual(last_fetched_commit, last_scanned_commit)
|
|
88
|
+
|
|
89
|
+
def test_detect_modules_to_scan(self):
|
|
90
|
+
scanner = self._init_scanner()
|
|
91
|
+
scanner._clone()
|
|
92
|
+
repo_id = scanner._get_odoo_repository_id()
|
|
93
|
+
with scanner.repo() as repo:
|
|
94
|
+
res = scanner._detect_modules_to_scan(repo, repo_id)
|
|
95
|
+
self.assertTrue(res)
|
|
96
|
+
self.assertIn("my_module", res["addons_paths"]["."]["modules_to_scan"])
|
|
97
|
+
|
|
98
|
+
def test_detect_modules_to_scan_in_addons_path(self):
|
|
99
|
+
scanner = self._init_scanner()
|
|
100
|
+
scanner._clone()
|
|
101
|
+
with scanner.repo() as repo:
|
|
102
|
+
scanner._checkout_branch(repo, self.branch.name)
|
|
103
|
+
repo_id = scanner._get_odoo_repository_id()
|
|
104
|
+
branch_id = scanner._get_odoo_branch_id(self.branch.name)
|
|
105
|
+
repo_branch_id = scanner._create_odoo_repository_branch(repo_id, branch_id)
|
|
106
|
+
last_fetched_commit = scanner._get_last_fetched_commit(
|
|
107
|
+
repo, self.branch.name
|
|
108
|
+
)
|
|
109
|
+
last_scanned_commit = scanner._get_repo_last_scanned_commit(repo_branch_id)
|
|
110
|
+
# Scan the addons_path (root of the repository here)
|
|
111
|
+
modules_to_scan = scanner._detect_modules_to_scan_in_addons_path(
|
|
112
|
+
repo,
|
|
113
|
+
scanner.addons_paths_data[0]["relative_path"],
|
|
114
|
+
repo_branch_id,
|
|
115
|
+
last_fetched_commit,
|
|
116
|
+
last_scanned_commit,
|
|
117
|
+
)
|
|
118
|
+
module = self.addon
|
|
119
|
+
self.assertIn(module, modules_to_scan)
|
|
120
|
+
|
|
121
|
+
def test_scan_module(self):
|
|
122
|
+
scanner = self._init_scanner()
|
|
123
|
+
scanner._clone()
|
|
124
|
+
with scanner.repo() as repo:
|
|
125
|
+
scanner._checkout_branch(repo, self.branch.name)
|
|
126
|
+
repo_id = scanner._get_odoo_repository_id()
|
|
127
|
+
branch_id = scanner._get_odoo_branch_id(self.branch.name)
|
|
128
|
+
repo_branch_id = scanner._create_odoo_repository_branch(repo_id, branch_id)
|
|
129
|
+
module_path = self.addon
|
|
130
|
+
remote_branch = f"origin/{self.branch.name}"
|
|
131
|
+
module_tree = repo.tree(remote_branch) / module_path
|
|
132
|
+
last_module_commit = scanner._get_last_commit_of_git_tree(
|
|
133
|
+
remote_branch, module_tree
|
|
134
|
+
)
|
|
135
|
+
# Scan module
|
|
136
|
+
specs = scanner.addons_paths_data[0]
|
|
137
|
+
data = scanner._scan_module(
|
|
138
|
+
repo,
|
|
139
|
+
repo_branch_id,
|
|
140
|
+
module_path,
|
|
141
|
+
last_module_commit,
|
|
142
|
+
specs,
|
|
143
|
+
)
|
|
144
|
+
self.assertTrue(data)
|
|
145
|
+
self.assertTrue(data["code"])
|
|
146
|
+
self.assertTrue(data["manifest"])
|
|
147
|
+
self.assertEqual(data["is_standard"], specs["is_standard"])
|
|
148
|
+
self.assertEqual(data["is_enterprise"], specs["is_enterprise"])
|
|
149
|
+
self.assertEqual(data["is_community"], specs["is_community"])
|
|
150
|
+
self.assertEqual(data["last_scanned_commit"], last_module_commit)
|
|
151
|
+
self.assertIn("1.0.0", data["versions"])
|
|
152
|
+
self.assertEqual(data["versions"]["1.0.0"]["commit"], last_module_commit)
|
|
153
|
+
self.assertFalse(data["versions"]["1.0.0"]["migration_script"])
|
|
154
|
+
|
|
155
|
+
def test_push_scanned_data(self):
|
|
156
|
+
scanner = self._init_scanner()
|
|
157
|
+
scanner._clone()
|
|
158
|
+
with scanner.repo() as repo:
|
|
159
|
+
scanner._checkout_branch(repo, self.branch.name)
|
|
160
|
+
repo_id = scanner._get_odoo_repository_id()
|
|
161
|
+
branch_id = scanner._get_odoo_branch_id(self.branch.name)
|
|
162
|
+
repo_branch_id = scanner._create_odoo_repository_branch(repo_id, branch_id)
|
|
163
|
+
module = self.addon
|
|
164
|
+
remote_branch = f"origin/{self.branch.name}"
|
|
165
|
+
module_tree = repo.tree(remote_branch) / module
|
|
166
|
+
last_module_commit = scanner._get_last_commit_of_git_tree(
|
|
167
|
+
remote_branch, module_tree
|
|
168
|
+
)
|
|
169
|
+
specs = scanner.addons_paths_data[0]
|
|
170
|
+
data = scanner._scan_module(
|
|
171
|
+
repo,
|
|
172
|
+
repo_branch_id,
|
|
173
|
+
module,
|
|
174
|
+
last_module_commit,
|
|
175
|
+
specs,
|
|
176
|
+
)
|
|
177
|
+
# Push scanned data
|
|
178
|
+
module_branch = scanner._push_scanned_data(repo_branch_id, module, data)
|
|
179
|
+
self.assertEqual(module_branch.module_id.name, module)
|
|
180
|
+
self.assertEqual(module_branch.repository_branch_id.id, repo_branch_id)
|
|
181
|
+
self.assertRecordValues(
|
|
182
|
+
module_branch,
|
|
183
|
+
[
|
|
184
|
+
{
|
|
185
|
+
"repository_branch_id": repo_branch_id,
|
|
186
|
+
"is_standard": specs["is_standard"],
|
|
187
|
+
"is_enterprise": specs["is_enterprise"],
|
|
188
|
+
"is_community": specs["is_community"],
|
|
189
|
+
"application": data["manifest"].get("application", False),
|
|
190
|
+
"installable": data["manifest"]["installable"],
|
|
191
|
+
"sloc_python": data["code"]["Python"],
|
|
192
|
+
"sloc_xml": data["code"]["XML"],
|
|
193
|
+
"sloc_js": data["code"]["JavaScript"],
|
|
194
|
+
"sloc_css": data["code"]["CSS"],
|
|
195
|
+
"last_scanned_commit": last_module_commit,
|
|
196
|
+
}
|
|
197
|
+
],
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def test_update_last_scanned_commit(self):
|
|
201
|
+
scanner = self._init_scanner()
|
|
202
|
+
scanner._clone()
|
|
203
|
+
repo_id = scanner._get_odoo_repository_id()
|
|
204
|
+
branch_id = scanner._get_odoo_branch_id(self.branch.name)
|
|
205
|
+
repo_branch_id = scanner._create_odoo_repository_branch(repo_id, branch_id)
|
|
206
|
+
repo_branch = self.env["odoo.repository.branch"].browse(repo_branch_id)
|
|
207
|
+
with scanner.repo() as repo:
|
|
208
|
+
last_repo_commit = scanner._get_last_fetched_commit(repo, self.branch.name)
|
|
209
|
+
self.assertFalse(repo_branch.last_scanned_commit)
|
|
210
|
+
scanner._update_last_scanned_commit(repo_branch_id, last_repo_commit)
|
|
211
|
+
self.assertEqual(repo_branch.last_scanned_commit, last_repo_commit)
|
|
212
|
+
|
|
213
|
+
def test_workaround_fs_errors(self):
|
|
214
|
+
scanner = self._init_scanner(workaround_fs_errors=True)
|
|
215
|
+
scanner.sync()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Copyright 2024 Camptocamp SA
|
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
3
|
+
|
|
4
|
+
from .common import Common
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestSyncNode(Common):
|
|
8
|
+
def test_sync_node(self):
|
|
9
|
+
# Scan a repository
|
|
10
|
+
self.odoo_repository.with_context(queue_job__no_delay=True).action_scan(
|
|
11
|
+
self.branch.ids
|
|
12
|
+
)
|
|
13
|
+
# Check data to sync
|
|
14
|
+
data = self.env["odoo.module.branch"]._get_modules_data()
|
|
15
|
+
self.assertEqual(len(data), 1) # 1 module scanned
|
|
16
|
+
module = data[0]
|
|
17
|
+
self.assertEqual(module["module"], self.module_name)
|
|
18
|
+
self.assertEqual(module["branch"], self.branch.name)
|
|
19
|
+
self.assertEqual(module["version"], "1.0.0")
|
|
20
|
+
self.assertTrue(module["versions"])
|
|
21
|
+
self.assertEqual(module["addons_path"], ".")
|
|
22
|
+
self.assertEqual(module["repository"]["name"], self.odoo_repository.name)
|
|
23
|
+
self.assertEqual(module["repository"]["org"], self.odoo_repository.org_id.name)
|
|
24
|
+
self.assertEqual(
|
|
25
|
+
module["repository"]["repo_type"], self.odoo_repository.repo_type
|
|
26
|
+
)
|
|
27
|
+
self.assertEqual(
|
|
28
|
+
module["repository"]["repo_url"], self.odoo_repository.repo_url
|
|
29
|
+
)
|
|
30
|
+
# Sync these data with a node
|
|
31
|
+
# NOTE as we are using the same node to sync with in tests, we change
|
|
32
|
+
# the content of the data to sync to create a new module
|
|
33
|
+
data[0].update(
|
|
34
|
+
module="synced",
|
|
35
|
+
version="2.0.0",
|
|
36
|
+
branch=self.branch2_name,
|
|
37
|
+
)
|
|
38
|
+
new_module = self.env["odoo.module"].search([("name", "=", "synced")])
|
|
39
|
+
self.assertFalse(new_module)
|
|
40
|
+
self.env["odoo.repository"]._import_data(data)
|
|
41
|
+
nb_modules = self.env["odoo.module"].search_count([])
|
|
42
|
+
self.assertTrue(nb_modules >= 2)
|
|
43
|
+
new_module = self.env["odoo.module.branch"].search(
|
|
44
|
+
[("module_name", "=", "synced")]
|
|
45
|
+
)
|
|
46
|
+
self.assertTrue(new_module)
|
|
47
|
+
self.assertEqual(new_module.version, "2.0.0")
|
|
48
|
+
self.assertEqual(new_module.branch_id.name, self.branch2_name)
|
|
49
|
+
# Existing module didn't changed
|
|
50
|
+
existing_module = self.env["odoo.module.branch"].search(
|
|
51
|
+
[("module_name", "=", self.module_name)]
|
|
52
|
+
)
|
|
53
|
+
self.assertTrue(existing_module)
|
|
54
|
+
self.assertEqual(existing_module.version, "1.0.0")
|
|
55
|
+
self.assertEqual(existing_module.branch_id, self.branch)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright 2024 Camptocamp SA
|
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
3
|
+
|
|
4
|
+
from odoo.tests.common import TransactionCase
|
|
5
|
+
|
|
6
|
+
from odoo.addons.odoo_repository.utils.module import adapt_version
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestUtils(TransactionCase):
|
|
10
|
+
@classmethod
|
|
11
|
+
def setUpClass(cls):
|
|
12
|
+
super().setUpClass()
|
|
13
|
+
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
|
|
14
|
+
|
|
15
|
+
def test_adapt_version(self):
|
|
16
|
+
# Module version equals major version: add prefix
|
|
17
|
+
self.assertEqual(adapt_version("14.0", "14.0"), "14.0.14.0")
|
|
18
|
+
# Basic module version: add prefix
|
|
19
|
+
self.assertEqual(adapt_version("14.0", "1.0.0"), "14.0.1.0.0")
|
|
20
|
+
# Module version already prefixed with major version
|
|
21
|
+
self.assertEqual(adapt_version("14.0", "14.0.1.0.0"), "14.0.1.0.0")
|
|
22
|
+
# Dot chars added as prefix or suffix in the provided version
|
|
23
|
+
self.assertEqual(adapt_version("14.0", ".1.0.0"), "14.0.1.0.0")
|
|
24
|
+
self.assertEqual(adapt_version("14.0", "1.0.0."), "14.0.1.0.0")
|
|
25
|
+
self.assertEqual(adapt_version("14.0", "...1.0.0..."), "14.0.1.0.0")
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Copyright 2023 Camptocamp SA
|
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
GITHUB_URL = "https://github.com"
|
|
9
|
+
GITHUB_API_URL = "https://api.github.com"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def request(env, url, method="get", params=None, json=None):
|
|
13
|
+
"""Request GitHub API."""
|
|
14
|
+
headers = {"Accept": "application/vnd.github.groot-preview+json"}
|
|
15
|
+
key = "odoo_repository_github_token"
|
|
16
|
+
token = env["ir.config_parameter"].sudo().get_param(key, "") or os.environ.get(
|
|
17
|
+
"GITHUB_TOKEN"
|
|
18
|
+
)
|
|
19
|
+
if token:
|
|
20
|
+
headers.update({"Authorization": f"token {token}"})
|
|
21
|
+
full_url = "/".join([GITHUB_API_URL, url])
|
|
22
|
+
kwargs = {"headers": headers}
|
|
23
|
+
if json:
|
|
24
|
+
kwargs.update(json=json)
|
|
25
|
+
if params:
|
|
26
|
+
kwargs.update(params=params)
|
|
27
|
+
response = getattr(requests, method)(full_url, **kwargs)
|
|
28
|
+
if not response.ok:
|
|
29
|
+
raise RuntimeError(response.text)
|
|
30
|
+
return response.json()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright 2023 Camptocamp SA
|
|
2
|
+
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def adapt_version(major_version: str, module_version: str):
|
|
6
|
+
"""Return the module version prefixed with major version.
|
|
7
|
+
|
|
8
|
+
This function has been copied and adapted from upstream Odoo source code:
|
|
9
|
+
https://github.com/odoo/odoo/blob/16.0/odoo/modules/module.py#L527-L533
|
|
10
|
+
"""
|
|
11
|
+
# Remove extra special characters (e.g. '14.0.' => '14.0')
|
|
12
|
+
# NOTE: we need to sanitize such versions in order to compute a
|
|
13
|
+
# sequence number helping the sort of module versions.
|
|
14
|
+
chars = ["."]
|
|
15
|
+
for char in chars:
|
|
16
|
+
while module_version.startswith(char):
|
|
17
|
+
module_version = module_version[1:]
|
|
18
|
+
while module_version.endswith(char):
|
|
19
|
+
module_version = module_version[:-1]
|
|
20
|
+
# Append major Odoo version as prefix if needed
|
|
21
|
+
if module_version == major_version or not module_version.startswith(
|
|
22
|
+
major_version + "."
|
|
23
|
+
):
|
|
24
|
+
module_version = "%s.%s" % (major_version, module_version)
|
|
25
|
+
return module_version
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Copyright 2023 Camptocamp SA
|
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
3
|
+
|
|
4
|
+
from odoo.tools import config
|
|
5
|
+
|
|
6
|
+
from ..lib.scanner import RepositoryScanner
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RepositoryScannerOdooEnv(RepositoryScanner):
|
|
10
|
+
"""RepositoryScanner running on the same server than Odoo.
|
|
11
|
+
|
|
12
|
+
This class takes an additional `env` parameter (`odoo.api.Environment`)
|
|
13
|
+
used to request Odoo, and implement required methods to use it.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, *args, **kwargs):
|
|
17
|
+
if kwargs.get("env"):
|
|
18
|
+
self.env = kwargs.pop("env")
|
|
19
|
+
super().__init__(*args, **kwargs)
|
|
20
|
+
|
|
21
|
+
def _get_odoo_repository_id(self):
|
|
22
|
+
return (
|
|
23
|
+
self.env["odoo.repository"]
|
|
24
|
+
.search([("name", "=", self.name), ("org_id", "=", self.org)])
|
|
25
|
+
.id
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def _get_odoo_branch_id(self, version):
|
|
29
|
+
return self.env["odoo.branch"].search([("name", "=", version)]).id
|
|
30
|
+
|
|
31
|
+
def _get_odoo_repository_branch_id(self, repo_id, branch_id):
|
|
32
|
+
args = [
|
|
33
|
+
("repository_id", "=", repo_id),
|
|
34
|
+
("branch_id", "=", branch_id),
|
|
35
|
+
]
|
|
36
|
+
repo_branch = self.env["odoo.repository.branch"].search(args, limit=1)
|
|
37
|
+
if repo_branch:
|
|
38
|
+
return repo_branch.id
|
|
39
|
+
|
|
40
|
+
def _create_odoo_repository_branch(self, repo_id, branch_id, cloned_branch=None):
|
|
41
|
+
repo_branch_id = self._get_odoo_repository_branch_id(repo_id, branch_id)
|
|
42
|
+
if not repo_branch_id:
|
|
43
|
+
values = {
|
|
44
|
+
"repository_id": repo_id,
|
|
45
|
+
"branch_id": branch_id,
|
|
46
|
+
}
|
|
47
|
+
if cloned_branch:
|
|
48
|
+
values["cloned_branch"] = cloned_branch
|
|
49
|
+
repo_branch_model = self.env["odoo.repository.branch"]
|
|
50
|
+
repo_branch_id = repo_branch_model.create(values).id
|
|
51
|
+
return repo_branch_id
|
|
52
|
+
|
|
53
|
+
def _get_repo_last_scanned_commit(self, repo_branch_id):
|
|
54
|
+
repo_branch_model = self.env["odoo.repository.branch"]
|
|
55
|
+
repo_branch = repo_branch_model.browse(repo_branch_id)
|
|
56
|
+
return repo_branch.last_scanned_commit
|
|
57
|
+
|
|
58
|
+
def _is_module_blacklisted(self, module):
|
|
59
|
+
return bool(
|
|
60
|
+
self.env["odoo.module"].search_count(
|
|
61
|
+
[("name", "=", module), ("blacklisted", "=", True)]
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def _get_module_last_scanned_commit(self, repo_branch_id, module_name):
|
|
66
|
+
module_branch_model = self.env["odoo.module.branch"]
|
|
67
|
+
args = [
|
|
68
|
+
("repository_branch_id", "=", repo_branch_id),
|
|
69
|
+
("module_name", "=", module_name),
|
|
70
|
+
]
|
|
71
|
+
module = module_branch_model.search(args)
|
|
72
|
+
return module.last_scanned_commit
|
|
73
|
+
|
|
74
|
+
def _push_scanned_data(self, repo_branch_id, module, data):
|
|
75
|
+
res = self.env["odoo.module.branch"].push_scanned_data(
|
|
76
|
+
repo_branch_id, module, data
|
|
77
|
+
)
|
|
78
|
+
# Commit after each module
|
|
79
|
+
if not config["test_enable"]:
|
|
80
|
+
self.env.cr.commit() # pylint: disable=invalid-commit
|
|
81
|
+
return res
|
|
82
|
+
|
|
83
|
+
def _update_last_scanned_commit(self, repo_branch_id, last_fetched_commit):
|
|
84
|
+
repo_branch_model = self.env["odoo.repository.branch"]
|
|
85
|
+
repo_branch = repo_branch_model.browse(repo_branch_id)
|
|
86
|
+
repo_branch.last_scanned_commit = last_fetched_commit
|
|
87
|
+
# Commit after each repository/branch
|
|
88
|
+
if not config["test_enable"]:
|
|
89
|
+
self.env.cr.commit() # pylint: disable=invalid-commit
|
|
90
|
+
return True
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8" ?>
|
|
2
|
+
<!-- Copyright 2024 Camptocamp SA
|
|
3
|
+
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
|
4
|
+
<odoo>
|
|
5
|
+
|
|
6
|
+
<record id="authentication_token_view_form" model="ir.ui.view">
|
|
7
|
+
<field name="name">authentication.token.form</field>
|
|
8
|
+
<field name="model">authentication.token</field>
|
|
9
|
+
<field
|
|
10
|
+
name="groups_id"
|
|
11
|
+
eval="[(4,ref('odoo_repository.group_odoo_repository_manager'))]"
|
|
12
|
+
/>
|
|
13
|
+
<field name="arch" type="xml">
|
|
14
|
+
<form>
|
|
15
|
+
<sheet>
|
|
16
|
+
<group>
|
|
17
|
+
<field name="name" />
|
|
18
|
+
<field name="token" password="True" />
|
|
19
|
+
</group>
|
|
20
|
+
</sheet>
|
|
21
|
+
</form>
|
|
22
|
+
</field>
|
|
23
|
+
</record>
|
|
24
|
+
|
|
25
|
+
<record id="authentication_token_view_tree" model="ir.ui.view">
|
|
26
|
+
<field name="name">authentication.token.tree</field>
|
|
27
|
+
<field name="model">authentication.token</field>
|
|
28
|
+
<field name="arch" type="xml">
|
|
29
|
+
<tree>
|
|
30
|
+
<field name="name" />
|
|
31
|
+
</tree>
|
|
32
|
+
</field>
|
|
33
|
+
</record>
|
|
34
|
+
|
|
35
|
+
<record id="authentication_token_view_search" model="ir.ui.view">
|
|
36
|
+
<field name="name">authentication.token.search</field>
|
|
37
|
+
<field name="model">authentication.token</field>
|
|
38
|
+
<field name="type">search</field>
|
|
39
|
+
<field name="arch" type="xml">
|
|
40
|
+
<search>
|
|
41
|
+
<field name="name" />
|
|
42
|
+
</search>
|
|
43
|
+
</field>
|
|
44
|
+
</record>
|
|
45
|
+
|
|
46
|
+
<record id="authentication_token_action" model="ir.actions.act_window">
|
|
47
|
+
<field name="name">Authentication Tokens</field>
|
|
48
|
+
<field name="type">ir.actions.act_window</field>
|
|
49
|
+
<field name="res_model">authentication.token</field>
|
|
50
|
+
<field name="view_id" ref="authentication_token_view_tree" />
|
|
51
|
+
<field
|
|
52
|
+
name="groups_id"
|
|
53
|
+
eval="[(4,ref('odoo_repository.group_odoo_repository_manager'))]"
|
|
54
|
+
/>
|
|
55
|
+
</record>
|
|
56
|
+
|
|
57
|
+
<menuitem
|
|
58
|
+
id="authentication_token_menu"
|
|
59
|
+
parent="config_odoo_repository_menu"
|
|
60
|
+
action="authentication_token_action"
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
</odoo>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8" ?>
|
|
2
|
+
<!-- Copyright 2023 Camptocamp SA
|
|
3
|
+
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
|
4
|
+
<odoo>
|
|
5
|
+
|
|
6
|
+
<menuitem
|
|
7
|
+
id="main_odoo_repository_menu"
|
|
8
|
+
groups="odoo_repository.group_odoo_repository_user"
|
|
9
|
+
web_icon="odoo_repository,static/description/icon.png"
|
|
10
|
+
name="Odoo Repositories"
|
|
11
|
+
/>
|
|
12
|
+
<menuitem
|
|
13
|
+
id="data_odoo_repository_menu"
|
|
14
|
+
parent="main_odoo_repository_menu"
|
|
15
|
+
name="Data"
|
|
16
|
+
sequence="20"
|
|
17
|
+
/>
|
|
18
|
+
<menuitem
|
|
19
|
+
id="report_odoo_repository_menu"
|
|
20
|
+
parent="main_odoo_repository_menu"
|
|
21
|
+
name="Report"
|
|
22
|
+
sequence="30"
|
|
23
|
+
/>
|
|
24
|
+
<menuitem
|
|
25
|
+
id="misc_odoo_repository_menu"
|
|
26
|
+
parent="main_odoo_repository_menu"
|
|
27
|
+
name="Misc."
|
|
28
|
+
sequence="40"
|
|
29
|
+
/>
|
|
30
|
+
<menuitem
|
|
31
|
+
id="config_odoo_repository_menu"
|
|
32
|
+
parent="main_odoo_repository_menu"
|
|
33
|
+
groups="odoo_repository.group_odoo_repository_manager"
|
|
34
|
+
name="Configuration"
|
|
35
|
+
sequence="50"
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
</odoo>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8" ?>
|
|
2
|
+
<!-- Copyright 2023 Camptocamp SA
|
|
3
|
+
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
|
4
|
+
<odoo>
|
|
5
|
+
|
|
6
|
+
<record id="odoo_author_view_form" model="ir.ui.view">
|
|
7
|
+
<field name="name">odoo.author.form</field>
|
|
8
|
+
<field name="model">odoo.author</field>
|
|
9
|
+
<field name="arch" type="xml">
|
|
10
|
+
<form>
|
|
11
|
+
<sheet>
|
|
12
|
+
<group>
|
|
13
|
+
<field name="name" />
|
|
14
|
+
</group>
|
|
15
|
+
</sheet>
|
|
16
|
+
</form>
|
|
17
|
+
</field>
|
|
18
|
+
</record>
|
|
19
|
+
|
|
20
|
+
<record id="odoo_author_view_tree" model="ir.ui.view">
|
|
21
|
+
<field name="name">odoo.author.tree</field>
|
|
22
|
+
<field name="model">odoo.author</field>
|
|
23
|
+
<field name="arch" type="xml">
|
|
24
|
+
<tree>
|
|
25
|
+
<field name="name" />
|
|
26
|
+
</tree>
|
|
27
|
+
</field>
|
|
28
|
+
</record>
|
|
29
|
+
|
|
30
|
+
<record id="odoo_author_view_search" model="ir.ui.view">
|
|
31
|
+
<field name="name">odoo.author.search</field>
|
|
32
|
+
<field name="model">odoo.author</field>
|
|
33
|
+
<field name="type">search</field>
|
|
34
|
+
<field name="arch" type="xml">
|
|
35
|
+
<search>
|
|
36
|
+
<field name="name" />
|
|
37
|
+
</search>
|
|
38
|
+
</field>
|
|
39
|
+
</record>
|
|
40
|
+
|
|
41
|
+
<record id="odoo_author_action" model="ir.actions.act_window">
|
|
42
|
+
<field name="name">Authors</field>
|
|
43
|
+
<field name="type">ir.actions.act_window</field>
|
|
44
|
+
<field name="res_model">odoo.author</field>
|
|
45
|
+
<field name="view_id" ref="odoo_author_view_tree" />
|
|
46
|
+
</record>
|
|
47
|
+
|
|
48
|
+
<menuitem
|
|
49
|
+
id="odoo_author_menu"
|
|
50
|
+
parent="misc_odoo_repository_menu"
|
|
51
|
+
action="odoo_author_action"
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
</odoo>
|