Tower: upload cetmix_tower_server 16.0.2.2.9 (via marketplace)
This commit is contained in:
300
addons/cetmix_tower_server/models/cetmix_tower.py
Normal file
300
addons/cetmix_tower_server/models/cetmix_tower.py
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
from odoo import _, api, models
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
from .constants import NOT_FOUND, SSH_CONNECTION_ERROR
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CetmixTower(models.AbstractModel):
|
||||||
|
"""Generic model used to simplify Odoo automation.
|
||||||
|
|
||||||
|
Used to keep main integration function in a single place.
|
||||||
|
|
||||||
|
For example when writing automated actions one can use
|
||||||
|
`env["cetmix.tower"].create_server_from_template(..)`
|
||||||
|
instead of
|
||||||
|
`env["cx.tower.server.template"].create_server_from_template(..)
|
||||||
|
"""
|
||||||
|
|
||||||
|
_name = "cetmix.tower"
|
||||||
|
_description = "Cetmix Tower Odoo Automation"
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def server_create_from_template(self, template_reference, server_name, **kwargs):
|
||||||
|
"""Shortcut for the same method of the 'cx.tower.server.template' model.
|
||||||
|
|
||||||
|
Important! Add dedicated tests for this function if modified later.
|
||||||
|
"""
|
||||||
|
return self.env["cx.tower.server.template"].create_server_from_template(
|
||||||
|
template_reference=template_reference, server_name=server_name, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def server_run_command(
|
||||||
|
self, server_reference, command_reference, get_result=True, **variable_values
|
||||||
|
):
|
||||||
|
"""Run command on selected server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_reference (Char): Server reference
|
||||||
|
command_reference (Char): Command reference
|
||||||
|
get_result (bool, optional): Get the result of the command.
|
||||||
|
If False, the result will be saved to the log.
|
||||||
|
Defaults to True.
|
||||||
|
|
||||||
|
**variable_values:
|
||||||
|
Dict: with variable values.
|
||||||
|
The keys are the variable references and the values are the variable values.
|
||||||
|
eg `{'odoo_version': '16.0'}`
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: with two keys if `get_result` is True:
|
||||||
|
- exit_code (Int): Exit code of the command
|
||||||
|
- message (Char): Message of the command
|
||||||
|
"""
|
||||||
|
|
||||||
|
server = self.env["cx.tower.server"].get_by_reference(server_reference)
|
||||||
|
if not server:
|
||||||
|
return {"exit_code": NOT_FOUND, "message": _("Server not found")}
|
||||||
|
command = self.env["cx.tower.command"].get_by_reference(command_reference)
|
||||||
|
if not command:
|
||||||
|
return {"exit_code": NOT_FOUND, "message": _("Command not found")}
|
||||||
|
|
||||||
|
# Will return command result if get_result is True
|
||||||
|
# Otherwise will save to log and return None
|
||||||
|
command_result = server.with_context(no_command_log=get_result).run_command(
|
||||||
|
command, **{"variable_values": variable_values} if variable_values else {}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return command result if get_result is True
|
||||||
|
if command_result:
|
||||||
|
status = command_result.get("status")
|
||||||
|
response = command_result.get("response", "")
|
||||||
|
error = command_result.get("error", "")
|
||||||
|
return {
|
||||||
|
"exit_code": status,
|
||||||
|
"message": response or error,
|
||||||
|
}
|
||||||
|
|
||||||
|
def server_run_flight_plan(
|
||||||
|
self, server_reference, flight_plan_reference, **variable_values
|
||||||
|
):
|
||||||
|
"""Run flight plan on selected server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_reference (Char): Server reference
|
||||||
|
flight_plan_reference (Char): Flight plan reference
|
||||||
|
|
||||||
|
**variable_values:
|
||||||
|
Dict: with variable values.
|
||||||
|
The keys are the variable references and the values are the variable values.
|
||||||
|
eg `{'odoo_version': '16.0'}`
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
cx.tower.plan.log(): flight plan log record or False if error
|
||||||
|
"""
|
||||||
|
server = self.env["cx.tower.server"].get_by_reference(server_reference)
|
||||||
|
if not server:
|
||||||
|
# This is not the best way to handle this, but it's the only way to
|
||||||
|
# avoid complex response handling
|
||||||
|
return False
|
||||||
|
flight_plan = self.env["cx.tower.plan"].get_by_reference(flight_plan_reference)
|
||||||
|
if not flight_plan:
|
||||||
|
# This is not the best way to handle this, but it's the only way to
|
||||||
|
# avoid complex response handling
|
||||||
|
return False
|
||||||
|
return server.run_flight_plan(
|
||||||
|
flight_plan,
|
||||||
|
**{"variable_values": variable_values} if variable_values else {},
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def server_set_variable_value(self, server_reference, variable_reference, value):
|
||||||
|
"""Set variable value for selected server.
|
||||||
|
Modifies existing variable value or creates a new one.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_reference (Char): Server reference
|
||||||
|
variable_reference (Char): Variable reference
|
||||||
|
value (Char): Variable value
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: with who keys:
|
||||||
|
- exit_code (Char)
|
||||||
|
- message (Char)
|
||||||
|
"""
|
||||||
|
|
||||||
|
server = self.env["cx.tower.server"].get_by_reference(server_reference)
|
||||||
|
if not server:
|
||||||
|
return {"exit_code": NOT_FOUND, "message": _("Server not found")}
|
||||||
|
variable = self.env["cx.tower.variable"].get_by_reference(variable_reference)
|
||||||
|
if not variable:
|
||||||
|
return {"exit_code": NOT_FOUND, "message": _("Variable not found")}
|
||||||
|
|
||||||
|
# Check if variable is already defined for the server
|
||||||
|
variable_value_record = variable.value_ids.filtered(
|
||||||
|
lambda v: v.server_id == server
|
||||||
|
)
|
||||||
|
if variable_value_record:
|
||||||
|
variable_value_record.value_char = value
|
||||||
|
result = {"exit_code": 0, "message": _("Variable value updated")}
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.env["cx.tower.variable.value"].create(
|
||||||
|
{
|
||||||
|
"variable_id": variable.id,
|
||||||
|
"server_id": server.id,
|
||||||
|
"value_char": value,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
result = {"exit_code": 0, "message": _("Variable value created")}
|
||||||
|
return result
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def server_get_variable_value(
|
||||||
|
self, server_reference, variable_reference, check_global=True
|
||||||
|
):
|
||||||
|
"""Get variable value for selected server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_reference (Char): Server reference
|
||||||
|
variable_reference (Char): Variable reference
|
||||||
|
check_global (bool, optional): Check for global value if variable
|
||||||
|
is not defined for selected server. Defaults to True.
|
||||||
|
Returns:
|
||||||
|
Char: variable value or None
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Get server by reference
|
||||||
|
server = self.env["cx.tower.server"].get_by_reference(server_reference)
|
||||||
|
if not server:
|
||||||
|
return None
|
||||||
|
result = self.env["cx.tower.variable.value"].get_by_variable_reference(
|
||||||
|
variable_reference=variable_reference,
|
||||||
|
server_id=server.id,
|
||||||
|
check_global=check_global,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get server defined value first
|
||||||
|
value = result.get("server")
|
||||||
|
|
||||||
|
# Get global value if value is not set
|
||||||
|
if not value and check_global:
|
||||||
|
value = result.get("global")
|
||||||
|
return value
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def server_check_ssh_connection(
|
||||||
|
self,
|
||||||
|
server_reference,
|
||||||
|
attempts=5,
|
||||||
|
wait_time=10,
|
||||||
|
try_command=True,
|
||||||
|
try_file=True,
|
||||||
|
):
|
||||||
|
"""Check if SSH connection to the server is available.
|
||||||
|
This method only checks if the connection is available,
|
||||||
|
it does not execute any commands to check if they are working.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_reference (Char): Server reference.
|
||||||
|
attempts (int): Number of attempts to try the connection.
|
||||||
|
Default is 5.
|
||||||
|
wait_time (int): Wait time in seconds between connection attempts.
|
||||||
|
Default is 10 seconds.
|
||||||
|
try_command (bool): Try to execute a command.
|
||||||
|
Default is True.
|
||||||
|
try_file (bool): Try file operations.
|
||||||
|
Default is True.
|
||||||
|
Raises:
|
||||||
|
ValidationError:
|
||||||
|
If the provided server reference is invalid or
|
||||||
|
the server cannot be found.
|
||||||
|
Returns:
|
||||||
|
dict: {
|
||||||
|
"exit_code": int,
|
||||||
|
0 for success,
|
||||||
|
error code for failure
|
||||||
|
"message": str # Description of the result
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
server = self.env["cx.tower.server"].get_by_reference(server_reference)
|
||||||
|
if not server:
|
||||||
|
raise ValidationError(_("No server found for the provided reference."))
|
||||||
|
|
||||||
|
# Try connecting multiple times
|
||||||
|
for attempt in range(1, attempts + 1):
|
||||||
|
try:
|
||||||
|
_logger.info(
|
||||||
|
"Attempt %s of %s to connect to server %s",
|
||||||
|
attempt,
|
||||||
|
attempts,
|
||||||
|
server_reference,
|
||||||
|
)
|
||||||
|
result = server.test_ssh_connection(
|
||||||
|
raise_on_error=True,
|
||||||
|
return_notification=False,
|
||||||
|
try_command=try_command,
|
||||||
|
try_file=try_file,
|
||||||
|
)
|
||||||
|
if result.get("status") == 0:
|
||||||
|
return {
|
||||||
|
"exit_code": 0,
|
||||||
|
"message": _("Connection successful."),
|
||||||
|
}
|
||||||
|
if attempt == attempts:
|
||||||
|
return {
|
||||||
|
"exit_code": SSH_CONNECTION_ERROR,
|
||||||
|
"message": _(
|
||||||
|
"Failed to connect after %(attempts)s attempts. "
|
||||||
|
"Error: %(err)s",
|
||||||
|
attempts=attempts,
|
||||||
|
err=result.get("error", ""),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
except Exception as e: # pylint: disable=broad-except
|
||||||
|
if attempt == attempts:
|
||||||
|
return {
|
||||||
|
"exit_code": SSH_CONNECTION_ERROR,
|
||||||
|
"message": _("Failed to connect. Error: %(err)s", err=e),
|
||||||
|
}
|
||||||
|
time.sleep(wait_time)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def server_validate_secret(
|
||||||
|
self, secret_value, secret_reference, server_reference=None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Validate the provided secret value against the actual secret.
|
||||||
|
|
||||||
|
Accepts either a full inline reference (e.g. #!cxtower.secret.<REFERENCE>!#)
|
||||||
|
or just a <REFERENCE>.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
secret_value (Char): Value to validate
|
||||||
|
secret_reference (Char): Reference code or inline reference
|
||||||
|
server_reference (Char, optional): Reference code of the server
|
||||||
|
Returns:
|
||||||
|
Bool: True if the value matches the secret, False otherwise
|
||||||
|
"""
|
||||||
|
server = self.env["cx.tower.server"]
|
||||||
|
if server_reference:
|
||||||
|
server = server.get_by_reference(server_reference)
|
||||||
|
|
||||||
|
# Try to extract reference from inline format using _extract_key_parts
|
||||||
|
key_parts = self.env["cx.tower.key"]._extract_key_parts(secret_reference)
|
||||||
|
if key_parts:
|
||||||
|
# _extract_key_parts returns a tuple: (key_type, reference).
|
||||||
|
# We only need the reference part here.
|
||||||
|
secret_reference = key_parts[1]
|
||||||
|
|
||||||
|
value = self.env["cx.tower.key"]._resolve_key_type_secret(
|
||||||
|
secret_reference, server_id=server.id
|
||||||
|
)
|
||||||
|
return value == secret_value
|
||||||
Reference in New Issue
Block a user