Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] cetmix_tower_server: Server creation #183

Open
wants to merge 3 commits into
base: 14.0-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 125 additions & 53 deletions cetmix_tower_server/models/cx_tower_server_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ def create_server_from_template(self, template_reference, server_name, **kwargs)
`variable_reference`: `variable_value_char`
eg:
{'branch': 'prod', 'odoo_version': '16.0'}
pick_all_template_variables (bool): This parameter ensures that the server
being created considers existing variables from the template.
If enabled, the template variables will also be included in the server
variables. The default value is True.

Returns:
cx.tower.server: newly created server record
Expand Down Expand Up @@ -199,6 +203,10 @@ def _create_new_server(self, name, **kwargs):
`variable_reference`: `variable_value_char`
eg:
{'branch': 'prod', 'odoo_version': '16.0'}
pick_all_template_variables (bool): This parameter ensures that the server
being created considers existing variables from the template.
If enabled, the template variables will also be included in the server
variables. The default value is True.

Returns:
cx.tower.server: newly created server record
Expand Down Expand Up @@ -251,7 +259,7 @@ def _get_fields_tower_server(self):
"server_log_ids",
]

def _prepare_server_values(self, **kwargs):
def _prepare_server_values(self, pick_all_template_variables=True, **kwargs):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with these changes, prepare_server_values is not a bit long, should we refactor to make it shorter and easier to understand?

"""
Prepare the server values to create a new server based on
the current template. It reads all fields from the template, copies them,
Expand All @@ -260,6 +268,10 @@ def _prepare_server_values(self, **kwargs):
data.

Args:
pick_all_template_variables (bool): This parameter ensures that the server
being created considers existing variables from the template.
If enabled, the template variables will also be included in the server
variables. The default value is True.
**kwargs: Additional values to update in the final server record.

Returns:
Expand Down Expand Up @@ -299,76 +311,136 @@ def _prepare_server_values(self, **kwargs):

values[field] = new_records

# custom specific variable values
# Handle configuration variables if provided.
configuration_variables = kwargs.pop("configuration_variables", None)
line_ids_variables = kwargs.pop("line_ids_variables", None)
configuration_variable_options = kwargs.pop(
"configuration_variable_options", {}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see this one added to the docstring similar to how configuration_variables is, is it missed?

)

if configuration_variables:
# Validate required variables
self._validate_required_variables(configuration_variables)

variable_vals_list = []
variable_obj = self.env["cx.tower.variable"]
# Search for existing variable options.
option_references = list(configuration_variable_options.values())
existing_options = option_references and self.env[
"cx.tower.variable.option"
].search([("reference", "in", option_references)])
missing_options = list(
set(option_references)
- {option.reference for option in existing_options}
)

for (
variable_reference,
variable_value,
) in configuration_variables.items():
variable = variable_obj.search(
[("reference", "=", variable_reference)]
)
if not variable:
variable = variable_obj.create(
{
"name": variable_reference,
}
if missing_options:
# Map variable references to their corresponding
# invalid option references.
missing_options_to_variables = {
var_ref: opt_ref
for var_ref, opt_ref in configuration_variable_options.items()
if opt_ref in missing_options
}
# Generate a detailed error message for invalid variable options.
detailed_message = "\n".join(
_(
"Variable reference '%(var_ref)s' has an invalid "
"option reference '%(opt_ref)s'.",
var_ref=var_ref,
opt_ref=opt_ref,
)
variable_option_id = variable_id = False

if not variable_value and line_ids_variables:
val_found = next(
(
v
for v in line_ids_variables.values()
if v.get("variable_reference") == variable_reference
),
None,
for var_ref, opt_ref in missing_options_to_variables.items()
)
raise ValidationError(
_(
"Some variable options are invalid:\n%(detailed_message)s",
detailed_message=detailed_message,
)
if val_found:
variable_value = val_found.get("value_char")
variable_option_id = val_found.get("option_id", False)
variable_id = val_found.get("variable_id", False)
)

# Map variable options to their IDs.
configuration_variable_options_dict = {
option.variable_id.id: option for option in existing_options
}

variable_obj = self.env["cx.tower.variable"]
variable_references = list(configuration_variables.keys())

# Search for existing variables or create new ones if missing.
exist_variables = variable_obj.search(
[("reference", "in", variable_references)]
)
missing_references = list(
set(variable_references)
- {variable.reference for variable in exist_variables}
)
variable_vals_list = [
{"name": reference} for reference in missing_references
]
new_variables = variable_obj.create(variable_vals_list)
all_variables = exist_variables | new_variables

# Build a dictionary {variable: variable_value}.
configuration_variable_dict = {
variable: configuration_variables[variable.reference]
for variable in all_variables
}

server_variable_vals_list = []
for variable, variable_value in configuration_variable_dict.items():
variable_option = configuration_variable_options_dict.get(
variable.id
)

variable_vals_list.append(
server_variable_vals_list.append(
(
0,
0,
{
"variable_id": variable.id or variable_id,
"value_char": variable_value or "",
"option_id": variable_option_id,
"variable_id": variable.id,
"value_char": variable_option
and variable_option.value_char
or variable_value,
"option_id": variable_option and variable_option.id,
},
)
)

# update or add variable values
existing_variable_values = values.get("variable_value_ids", [])
variable_id_to_index = {
cmd[2]["variable_id"]: idx
for idx, cmd in enumerate(existing_variable_values)
if cmd[0] == 0 and "variable_id" in cmd[2]
}
if pick_all_template_variables:
# update or add variable values
existing_variable_values = values.get("variable_value_ids", [])
variable_id_to_index = {
cmd[2]["variable_id"]: idx
for idx, cmd in enumerate(existing_variable_values)
if cmd[0] == 0 and "variable_id" in cmd[2]
}

# Update exist variable options
for exist_variable_id, index in variable_id_to_index.items():
option = configuration_variable_options_dict.get(
exist_variable_id
)
if not option:
continue
existing_variable_values[index][2].update(
{
"option_id": option.id,
"value_char": option.value_char,
}
)

for new_command in variable_vals_list:
variable_id = new_command[2]["variable_id"]
if variable_id in variable_id_to_index:
idx = variable_id_to_index[variable_id]
# update exist command
existing_variable_values[idx] = new_command
else:
# add new command
existing_variable_values.append(new_command)

values["variable_value_ids"] = existing_variable_values
# Prepare new command values for server variables
for new_command in server_variable_vals_list:
variable_id = new_command[2]["variable_id"]
if variable_id in variable_id_to_index:
idx = variable_id_to_index[variable_id]
# update exist command
existing_variable_values[idx] = new_command
else:
# add new command
existing_variable_values.append(new_command)

values["variable_value_ids"] = existing_variable_values
else:
values["variable_value_ids"] = server_variable_vals_list

# remove the `id` field to ensure a new record is created
# instead of updating the existing one
Expand Down
13 changes: 9 additions & 4 deletions cetmix_tower_server/readme/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ This function takes the following arguments:
'variable_reference': 'variable_value_char'
eg:
{'branch': 'prod', 'odoo_version': '16.0'}
- configuration_variable_options (Dict, optional): Custom configuration variable options.
Following format is used:
'variable_reference': 'option_reference'
eg:
{'language': 'language_variable_option_1'}
```

Here is a short example of an Odoo automated action that creates a new server when a Sales Order is confirmed:
Expand All @@ -33,7 +38,7 @@ Here is a short example of an Odoo automated action that creates a new server wh

```python
for record in records:

# Check confirmed orders
if record.state == "sale":
params = {
Expand All @@ -46,14 +51,14 @@ for record in records:
"odoo_version": "16.0"
},
}
# Create a new server from template with the 'demo_template' reference

# Create a new server from template with the 'demo_template' reference
env["cetmix.tower"].server_create_from_template(
template_reference="demo_template",
server_name=record.name,
**params
)

```

## Run a Command
Expand Down
82 changes: 80 additions & 2 deletions cetmix_tower_server/tests/test_server_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ def test_create_server_from_template_action(self):
"value_char": "test template url",
}
)
# add variable option
variable_url_option = self.VariableOption.create(
{
"name": "localhost",
"value_char": "localhost",
"variable_id": self.variable_url.id,
}
)

# create new server with new variable
self.ServerTemplate.create_server_from_template(
Expand All @@ -165,6 +173,9 @@ def test_create_server_from_template_action(self):
self.variable_version.reference: "test server version",
"new_variable": "new_value",
},
configuration_variable_options={
self.variable_url.reference: variable_url_option.reference,
},
)
new_server = self.Server.search([("name", "=", name)])

Expand All @@ -189,8 +200,8 @@ def test_create_server_from_template_action(self):
)
self.assertEqual(
var_url_value.value_char,
"test template url",
"Url variable values should be same as in the template",
variable_url_option.value_char,
"Url variable values should be same as option value",
)

var_new_value = new_server.variable_value_ids.filtered(
Expand Down Expand Up @@ -798,3 +809,70 @@ def test_server_template_variable_values_manager_access_rights(self):
self.server_template_sample.message_unsubscribe([self.user_bob.partner_id.id])
with self.assertRaises(AccessError):
other_own_variable_value.unlink()

def test_with_partial_removed_variables_from_wizard(self):
"""
Test that server creation only with specified
variables from wizard and option
"""
# create new variable option
option = self.VariableOption.create(
{
"name": "test",
"value_char": "test",
"variable_id": self.variable_dir.id,
}
)

# template with variables
self.server_template_sample.variable_value_ids = [
(
0,
0,
{
"variable_id": self.variable_path.id,
"value_char": "/var/log",
"required": False,
},
),
(
0,
0,
{
"variable_id": self.variable_dir.id,
"option_id": option.id,
"required": False,
},
),
]

action = self.server_template_sample.action_create_server()

# Open the wizard and fill in the data
wizard = (
self.env["cx.tower.server.template.create.wizard"]
.with_context(**action["context"])
.create(
{
"name": "Server from Template",
"ip_v4_address": "localhost",
"server_template_id": self.server_template_sample.id,
}
)
)

with Form(wizard) as wizard_form:
wizard_form.line_ids.remove(0)
wizard_form.save()

wizard.action_confirm()

server = self.server_template_sample.server_ids
self.assertEqual(
len(server.variable_value_ids), 1, "Server variable must be 1!"
)
self.assertEqual(
server.variable_value_ids.value_char,
option.value_char,
"The variable value must be equal to the value from the option",
)
Loading
Loading