335 lines
11 KiB
Python
335 lines
11 KiB
Python
# 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 ""
|