Tower: upload cetmix_tower_git 16.0.2.0.2 (via marketplace)
This commit is contained in:
334
addons/cetmix_tower_git/models/cx_tower_git_project.py
Normal file
334
addons/cetmix_tower_git/models/cx_tower_git_project.py
Normal file
@@ -0,0 +1,334 @@
|
||||
# Copyright (C) 2024 Cetmix OÜ
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import re
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class CxTowerGitProject(models.Model):
|
||||
"""
|
||||
Git Project.
|
||||
Implements pre-defined git configuration.
|
||||
"""
|
||||
|
||||
_name = "cx.tower.git.project"
|
||||
_description = "Cetmix Tower Git Project"
|
||||
_order = "name"
|
||||
|
||||
_inherit = [
|
||||
"cx.tower.reference.mixin",
|
||||
"cx.tower.yaml.mixin",
|
||||
"cx.tower.access.role.mixin",
|
||||
]
|
||||
|
||||
def _get_post_create_fields(self):
|
||||
res = super()._get_post_create_fields()
|
||||
return res + [
|
||||
"source_ids",
|
||||
"git_project_rel_ids",
|
||||
"git_project_file_template_rel_ids",
|
||||
]
|
||||
|
||||
active = fields.Boolean(default=True)
|
||||
# IMPORTANT: This field may contain duplicates because of the relation nature!
|
||||
server_ids = fields.Many2many(
|
||||
comodel_name="cx.tower.server",
|
||||
relation="cx_tower_git_project_rel",
|
||||
column1="git_project_id",
|
||||
column2="server_id",
|
||||
string="Servers",
|
||||
readonly=True,
|
||||
copy=False,
|
||||
help="Servers are added automatically based on the files linked to the project."
|
||||
"\nIMPORTANT: This field may contain duplicates"
|
||||
" because of the relation nature!",
|
||||
)
|
||||
source_ids = fields.One2many(
|
||||
comodel_name="cx.tower.git.source",
|
||||
inverse_name="git_project_id",
|
||||
string="Sources",
|
||||
auto_join=True,
|
||||
copy=True,
|
||||
)
|
||||
git_project_rel_ids = fields.One2many(
|
||||
comodel_name="cx.tower.git.project.rel",
|
||||
inverse_name="git_project_id",
|
||||
string="Git Project Server File Relations",
|
||||
copy=False,
|
||||
)
|
||||
# Helper field to get all files related to git project
|
||||
file_ids = fields.Many2many(
|
||||
comodel_name="cx.tower.file",
|
||||
relation="cx_tower_git_project_rel",
|
||||
column1="git_project_id",
|
||||
column2="file_id",
|
||||
string="Files",
|
||||
readonly=True,
|
||||
depends=["git_project_rel_ids"],
|
||||
copy=False,
|
||||
)
|
||||
git_project_file_template_rel_ids = fields.One2many(
|
||||
comodel_name="cx.tower.git.project.file.template.rel",
|
||||
inverse_name="git_project_id",
|
||||
string="Git Project File Template Relations",
|
||||
copy=False,
|
||||
)
|
||||
# Helper field to get all file templates related to git project
|
||||
file_template_ids = fields.Many2many(
|
||||
comodel_name="cx.tower.file.template",
|
||||
relation="cx_tower_git_project_file_template_rel",
|
||||
column1="git_project_id",
|
||||
column2="file_template_id",
|
||||
string="File Templates",
|
||||
readonly=True,
|
||||
depends=["git_project_file_template_rel_ids"],
|
||||
copy=False,
|
||||
)
|
||||
# Helper field to get all repositories used in this project
|
||||
repo_ids = fields.Many2many(
|
||||
comodel_name="cx.tower.git.repo",
|
||||
relation="cx_tower_git_repo_project_rel",
|
||||
column1="project_id",
|
||||
column2="repo_id",
|
||||
string="Repositories",
|
||||
readonly=True,
|
||||
copy=False,
|
||||
help="Repositories used in this project through its sources and remotes",
|
||||
)
|
||||
note = fields.Text()
|
||||
|
||||
# ---- Access. Add relation for mixin fields
|
||||
user_ids = fields.Many2many(
|
||||
relation="cx_tower_git_project_user_rel",
|
||||
compute="_compute_user_ids",
|
||||
readonly=False,
|
||||
store=True,
|
||||
precompute=True,
|
||||
domain=lambda self: [
|
||||
("groups_id", "in", self.env.ref("cetmix_tower_server.group_manager").ids)
|
||||
],
|
||||
)
|
||||
manager_ids = fields.Many2many(
|
||||
relation="cx_tower_git_project_manager_rel",
|
||||
compute="_compute_user_ids",
|
||||
readonly=False,
|
||||
store=True,
|
||||
precompute=True,
|
||||
)
|
||||
|
||||
# -- UI/UX fields
|
||||
has_private_remotes = fields.Boolean(
|
||||
compute="_compute_has_private_remotes",
|
||||
help="Indicates if the project has any private remotes.",
|
||||
)
|
||||
has_partially_private_remotes = fields.Boolean(
|
||||
compute="_compute_has_private_remotes",
|
||||
help="Indicates if the project has any partially private remotes.",
|
||||
)
|
||||
|
||||
# -- Git Aggregator related fields
|
||||
git_aggregator_root_dir = fields.Char(
|
||||
help="Git aggregator root directory where sources will be cloned."
|
||||
" Eg '/tmp/git-aggregator'"
|
||||
" Will use '.' if not set",
|
||||
)
|
||||
|
||||
def _selection_project_format(self):
|
||||
"""
|
||||
Possible project formats.
|
||||
Inherit and extend when adding new project formats.
|
||||
|
||||
Returns:
|
||||
List of tuples: (code, name)
|
||||
"""
|
||||
return [
|
||||
("git_aggregator", "Git Aggregator"),
|
||||
]
|
||||
|
||||
def _default_project_format(self):
|
||||
"""
|
||||
Default project format.
|
||||
"""
|
||||
return "git_aggregator"
|
||||
|
||||
@api.depends(
|
||||
"git_project_rel_ids.server_id",
|
||||
"git_project_rel_ids.server_id.user_ids",
|
||||
"git_project_rel_ids.server_id.manager_ids",
|
||||
)
|
||||
def _compute_user_ids(self):
|
||||
"""
|
||||
Users. All users who have "Manager" group and are either set in "Users"
|
||||
or in "Managers" in all related servers.
|
||||
Managers. All users who have "Manager" group and are set as "Managers"
|
||||
in all related servers.
|
||||
|
||||
This is done to avoid unpredictable consequences when some of the servers
|
||||
are not updated due to access restrictions when a project is updated.
|
||||
"""
|
||||
for project in self:
|
||||
# Do not compute if no servers are related
|
||||
server_ids = project.git_project_rel_ids.server_id
|
||||
if not server_ids:
|
||||
continue
|
||||
|
||||
# Get all user and manager ids from related servers
|
||||
all_user_ids = server_ids.user_ids.filtered(
|
||||
lambda u: u.has_group("cetmix_tower_server.group_manager")
|
||||
).ids
|
||||
all_manager_ids = server_ids.manager_ids.ids
|
||||
|
||||
# Create a final list of user and manager ids
|
||||
user_ids = []
|
||||
manager_ids = []
|
||||
# Check if user is present in all servers
|
||||
for user_id in all_user_ids:
|
||||
if all(
|
||||
user_id in server.user_ids.ids or user_id in server.manager_ids.ids
|
||||
for server in server_ids
|
||||
):
|
||||
user_ids.append(user_id)
|
||||
# Check if manager is present in all servers
|
||||
for manager_id in all_manager_ids:
|
||||
if all(manager_id in server.manager_ids.ids for server in server_ids):
|
||||
manager_ids.append(manager_id)
|
||||
|
||||
# Set the final lists
|
||||
project.update(
|
||||
{
|
||||
"user_ids": [(6, 0, user_ids)],
|
||||
"manager_ids": [(6, 0, manager_ids)],
|
||||
}
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"source_ids", "source_ids.remote_ids", "source_ids.remote_ids.is_private"
|
||||
)
|
||||
def _compute_has_private_remotes(self):
|
||||
for project in self:
|
||||
project.has_private_remotes = any(
|
||||
source.remote_count > 0
|
||||
and source.remote_count_private == source.remote_count
|
||||
for source in project.source_ids
|
||||
)
|
||||
project.has_partially_private_remotes = any(
|
||||
source.remote_count_private > 0
|
||||
and source.remote_count_private != source.remote_count
|
||||
for source in project.source_ids
|
||||
)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
res = super().create(vals_list)
|
||||
# Update related files and templates on create
|
||||
res._update_related_files_and_templates()
|
||||
return res
|
||||
|
||||
def write(self, vals):
|
||||
res = super().write(vals)
|
||||
# Update related files and templates on update
|
||||
self._update_related_files_and_templates()
|
||||
return res
|
||||
|
||||
def _update_related_files_and_templates(self):
|
||||
# Update related files and templates
|
||||
if self.git_project_rel_ids:
|
||||
self.git_project_rel_ids._save_to_file()
|
||||
if self.git_project_file_template_rel_ids:
|
||||
self.git_project_file_template_rel_ids._save_to_file_template()
|
||||
|
||||
def _extract_variables_from_text(self, text):
|
||||
"""Extract environment variables from text.
|
||||
Helper method for file content generation.
|
||||
|
||||
Returns:
|
||||
List: List of variables
|
||||
"""
|
||||
variables = re.findall(r"\$([A-Z0-9_]+)", text)
|
||||
return sorted(list(set(variables)))
|
||||
|
||||
# ------------------------------
|
||||
# YAML mixin methods
|
||||
# ------------------------------
|
||||
def _get_fields_for_yaml(self):
|
||||
res = super()._get_fields_for_yaml()
|
||||
res += [
|
||||
"name",
|
||||
"note",
|
||||
"source_ids",
|
||||
"git_aggregator_root_dir",
|
||||
]
|
||||
return res
|
||||
|
||||
# -------------------------------
|
||||
# Git Aggregator related methods
|
||||
# -------------------------------
|
||||
def _git_aggregator_prepare_record(self):
|
||||
"""Prepare json structure for git aggregator.
|
||||
|
||||
Returns:
|
||||
Dict: Json structure for git aggregator
|
||||
"""
|
||||
self.ensure_one()
|
||||
values = {}
|
||||
for source in self.source_ids:
|
||||
if source.enabled and source.remote_count:
|
||||
root_dir = self.git_aggregator_root_dir or "."
|
||||
values.update(
|
||||
{
|
||||
f"/{source.reference}"
|
||||
if root_dir == "/"
|
||||
else f"{root_dir}/{source.reference}": source._git_aggregator_prepare_record() # noqa: E501
|
||||
}
|
||||
)
|
||||
return values
|
||||
|
||||
def _git_aggregator_prepare_yaml_comment(self, yaml_code):
|
||||
"""Generate commentary for yaml file.
|
||||
It includes brief instructions for git aggregator
|
||||
and lists environment variables that are required.
|
||||
|
||||
Args:
|
||||
yaml_code (str): Yaml code
|
||||
|
||||
Returns:
|
||||
Char: comment text or None
|
||||
"""
|
||||
|
||||
comment_text = _(
|
||||
"# This file is generated with Cetmix Tower https://cetmix.com/tower\n"
|
||||
"# It's designed to be used with git-aggregator tool developed by Acsone.\n"
|
||||
"# Documentation for git-aggregator: https://github.com/acsone/git-aggregator\n"
|
||||
)
|
||||
variable_list = self._extract_variables_from_text(yaml_code)
|
||||
if variable_list:
|
||||
comment_text += _(
|
||||
"\n# You need to set the following variables in your environment:\n# %(vars)s\n" # noqa: E501
|
||||
"# and run git-aggregator with '--expand-env' parameter.\n", # noqa: E501
|
||||
vars=(", ".join(variable_list)),
|
||||
)
|
||||
return comment_text
|
||||
|
||||
def _generate_code_git_aggregator(self, record):
|
||||
"""Generate code in git-aggregator format.
|
||||
|
||||
Args:
|
||||
record (recordset()): Model record to generate code for.
|
||||
must be a single record and have git_project_id field.
|
||||
|
||||
Returns:
|
||||
Text: Yaml code
|
||||
"""
|
||||
yaml_mixin = self.env["cx.tower.yaml.mixin"]
|
||||
|
||||
# Do not generate code if record values are empty
|
||||
record_values = record.git_project_id._git_aggregator_prepare_record()
|
||||
if record_values:
|
||||
yaml_code = yaml_mixin._convert_dict_to_yaml(record_values)
|
||||
# Prepend comment to yaml code
|
||||
comment = record.git_project_id._git_aggregator_prepare_yaml_comment(
|
||||
yaml_code
|
||||
)
|
||||
return f"{comment}\n{yaml_code}"
|
||||
return ""
|
||||
Reference in New Issue
Block a user