[18.0][IMP] cetmix_tower_server Jets#484
Conversation
Initial implementation of the Jet and Jet Template models. Task: 4700
Task 5337
Do not queue commands with actions "Jet Action" and "Create Waypoint". These commands either launch other queued commands or run instantly.
Export/import jet templates and dependencies.
WalkthroughThis pull request introduces a comprehensive "Jets" feature to the cetmix_tower_server module. Jets are managed computational entities that can be provisioned from templates onto servers. The feature includes new models for jet templates, states, actions, dependencies, and waypoints; extends existing models (servers, commands, plans, variables) to support jet context; adds security rules for role-based access control; includes data migrations for schema changes; and provides extensive test coverage for the new functionality. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes |
There was a problem hiding this comment.
Actionable comments posted: 27
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cetmix_tower_server/models/cx_tower_server.py (1)
1158-1194:⚠️ Potential issue | 🟠 MajorInitialize the rendered values before the variable-resolution branch.
If the command/path contains placeholders but
_get_variable_values_by_references()returns an empty dict, theif variable_values:block is skipped andrendered_code/rendered_pathare never assigned.run_command()then crashes withUnboundLocalError.🔧 Suggested change
# Get variables from path path = path if path else command.path + rendered_code = command.code + rendered_path = path if path: variables_extracted = command.get_variables_from_code(path) for ve in variables_extracted: if ve not in variable_references: variable_references.append(ve) @@ # Render command code and path using variables if variable_values: if command.action == "python_code": variable_values["pythonic_mode"] = True rendered_code = ( command.render_code_custom(command.code, **variable_values) if command.code else False ) rendered_path = ( command.render_code_custom(path, **variable_values) if path else False ) - - else: - rendered_code = command.code - rendered_path = path return {"rendered_code": rendered_code, "rendered_path": rendered_path}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cetmix_tower_server/models/cx_tower_server.py` around lines 1158 - 1194, Initialize rendered_code and rendered_path before the variable_references branch so they are always defined; specifically, at the start of the block that checks variable_references set rendered_code = command.code and rendered_path = path (or False if you prefer previous falsy behavior), then proceed to fetch variable_values via _get_variable_values_by_references and apply custom_variable_values and rendering (via command.render_code_custom) only to override those defaults; reference symbols: variable_references, _get_variable_values_by_references, custom_variable_values, _have_access_to_server, command.render_code_custom, rendered_code, rendered_path, run_command.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cetmix_tower_server/demo/demo_data.xml`:
- Around line 1392-1426: The three demo scheduled task records
(cx_tower_scheduled_task_odoo, cx_tower_scheduled_task_wordpress,
cx_tower_scheduled_task_woocommerce) currently set only cadence and action but
lack concrete execution targets; add explicit server_ids and/or jet_ids fields
to each record (not just jet_template_ids/server_template_ids) so the scheduler
has actual targets to run against. Update each record to include <field
name="server_ids"> and/or <field name="jet_ids"> referencing the appropriate
demo server/jet records used elsewhere in the demo data (match the live demo
server/jet resource record IDs for Odoo, WordPress and WooCommerce). Ensure the
new fields use many2many/one2many references consistent with existing scheduled
task model relationships.
In `@cetmix_tower_server/models/cx_tower_command_log.py`:
- Around line 145-154: The computed method _compute_jet_id in
cx_tower_command_log currently only sets jet_id and jet_template_id when
plan_log_id exists, leaving stale stored values when plan_log_id is cleared;
update _compute_jet_id to explicitly clear those fields when plan_log_id is
falsy (e.g., add an else branch on the for-loop that calls
command_log.update({"jet_id": False, "jet_template_id": False}) or assigns False
to command_log.jet_id and command_log.jet_template_id) so the stored computed
fields are reset when plan_log_id is removed.
In `@cetmix_tower_server/models/cx_tower_command.py`:
- Around line 401-411: The python_libraries mapping currently exposes the
module-level _logger into the eval context; remove direct exposure of _logger
and instead create and expose a constrained logging wrapper (e.g., SafeLogger)
used by cx_tower_command evaluation routines: implement SafeLogger with only a
minimal, whitelisted API (e.g., info/warn/error) that sanitizes or formats
messages and does not allow attaching handlers or reading internal state, then
replace the "_logger": {"import": _logger, ...} entry in python_libraries with
the SafeLogger instance; alternatively, if logging from eval is not required,
drop the _logger entry entirely from python_libraries to eliminate the risk.
In `@cetmix_tower_server/models/cx_tower_jet_action.py`:
- Line 63: The TODO indicates a missing data-integrity constraint: implement an
Odoo `@api.constrains` on the CxTowerJetAction model to verify that all linked
action records share the same jet template; inside the constraint method (e.g.,
_check_actions_same_jet_template) iterate the record's action relation (likely
action_ids or actions) and compare each action.jet_template_id (or the correct
field name on the related action model) against the parent record's
jet_template_id, and raise a ValidationError if any mismatch is found; add unit
tests exercising mismatched and matched cases.
In `@cetmix_tower_server/models/cx_tower_jet_request.py`:
- Around line 87-135: The _create_request method uses the server parameter as a
single record (server.id and server.name) but is decorated with `@api.model` so
server may be a multi-recordset; add a singleton check by calling
server.ensure_one() near the top of _create_request (before any use of server.id
or server.name) and, if appropriate, raise a ValidationError when server is
falsy to make the requirement explicit; update the code paths that reference
server.id and server.name to rely on the ensured single record.
In `@cetmix_tower_server/models/cx_tower_jet_template_dependency.py`:
- Around line 109-135: _build_dependency_graph currently does a full table scan
via self.search([]), which is inefficient; implement a cached dependency graph
(e.g., a model-level attribute like _dependency_graph_cache) and change
_build_dependency_graph to return the cached graph if present, otherwise build
it, store it in _dependency_graph_cache, and return it; add cache invalidation
by clearing _dependency_graph_cache in the model methods that modify
dependencies (create, write, unlink on the CxTowerJetTemplateDependency model)
so updates to template_id/template_required_id are reflected; alternatively, for
cycle detection replace the Python graph build with a PostgreSQL recursive CTE
query using template_id and template_required_id to detect cycles without
loading the full table.
In `@cetmix_tower_server/models/cx_tower_jet_template_install.py`:
- Around line 324-337: The notification and view-reload calls inside
_flight_plan_finished() (and the similar block around lines 444-459) currently
call notify_success/notify_error and reload_views on self.env.user, which
notifies the callback executor; change these to use the job requester instead by
calling notify_success/notify_error and reload_views on self.requested_by_id
(falling back to self.env.user if requested_by_id is falsy) so the user who
started the install receives completion notifications and backend refreshes;
update all occurrences (e.g., the notify_success call shown, any notify_* and
reload_views invocations) to reference self.requested_by_id accordingly.
- Around line 229-251: The code writes current_line_id
(self.write({"current_line_id": installation_task.id})) before calling
server_id.run_flight_plan, so any exception during run_flight_plan leaves the
job stuck; change the flow to only set current_line_id after run_flight_plan
succeeds (move the write to after the successful call), or wrap
server_id.run_flight_plan in a try/except and in the except clear/reset
current_line_id and set the install state appropriately (e.g., mark failed or
remove current_line_id) so _process_install won't remain stuck; reference
current_line_id, installation_task.id, server_id.run_flight_plan and
_process_install when making the change.
In `@cetmix_tower_server/models/cx_tower_jet_template.py`:
- Around line 911-925: The helper _allow_jet_creation currently always returns
True and must enforce the "template installed on server" invariant used by
create_jet() and clone(); update _allow_jet_creation(self, server) (after
self.ensure_one()) to check the template's installed-server relation (e.g.,
self.installed_on, self.installed_server_ids, or whatever field holds servers
where the template is installed) and return True only if the given server is
present there (return False otherwise); ensure callers create_jet() and clone()
continue to use this boolean result (or if you prefer stricter behavior, have
_allow_jet_creation raise a clear ValidationError/AccessError when the server is
not in the installed list).
- Around line 820-843: The current logic in the cx.tower.jet name allocation
loop treats caller-supplied names the same as auto-generated ones and will
silently replace a requested name if it already exists; modify the routine that
sets name (referencing jet_obj, existing_names, _generate_jet_name, and
MAX_JET_NAME_RETRIES) so that if name was provided by the caller and already in
existing_names you immediately raise a ValidationError (or return a clear
failure) instead of entering the retry/generation loop; only perform the retry
loop when name was not supplied (i.e., it was generated by _generate_jet_name),
keeping the existing unique-name retry behavior for autogenerated names.
In `@cetmix_tower_server/models/cx_tower_jet_waypoint_template.py`:
- Around line 17-21: Add a DB index to the Many2one field by setting index=True
on jet_template_id in the cx_tower_jet_waypoint_template model (the
fields.Many2one definition named jet_template_id); after changing the field
definition, update/upgrade the module (or run the appropriate migration) to
apply the new index to the database so filtering/grouping by jet_template_id
benefits from the performance improvement.
In `@cetmix_tower_server/models/cx_tower_jet_waypoint.py`:
- Around line 337-344: Before calling run_flight_plan you must guard against
synchronous failures: capture the current stable state, then wrap the
run_flight_plan(...) call in a try/except; on exception revert the waypoint
state via waypoint.write(...) back to the captured stable state (or a defined
safe state), ensure any associated command/log is finalized or marked failed,
then re-raise the exception so the caller sees the error. Apply this pattern
around the run_flight_plan invocation shown (referencing run_flight_plan,
waypoint.write, waypoint._get_custom_variable_values,
waypoint.waypoint_template_id.plan_delete_id) and mirror the same
try/except+revert logic in the other launch sites noted (prepare() and the
blocks at the other ranges).
- Around line 243-255: The guard that prevents changing waypoint_template_id
uses vals.get("state") so a caller can bypass it by submitting state='draft' in
the payload; update the logic in the write/update handler (the block iterating
self in cx_tower_jet_waypoint) to check the current record state (use
waypoint.state) instead of the pending vals state when deciding if a non-draft
waypoint may change its waypoint_template_id, i.e., remove reliance on
vals.get("state") in the conditional and evaluate the existing waypoint.state
for the permission check and ValidationError raising.
In `@cetmix_tower_server/models/cx_tower_jet.py`:
- Around line 1044-1052: The write that sets state_id to transit_state and
current_action_id/current_command_log_id must be rolled back if
run_flight_plan() raises synchronously: wrap the call to run_flight_plan in a
try/except, and in the except handler call sudo().write to restore the previous
state and clear current_action_id, target_state_id and current_command_log_id
(or set them to False) so the jet is not left stuck busy; apply the same pattern
for the similar block around lines 1066-1075. Ensure you reference the same
fields written earlier (state_id, current_action_id, current_command_log_id,
target_state_id), and call _flight_plan_finished semantics only on success.
- Around line 1663-1673: The current helper only returns direct neighbors by
filtering jet_requires_ids and jet_required_by_ids and should instead traverse
the full dependency graph; update the method that computes dependencies to
perform a recursive or iterative graph traversal (DFS/BFS) starting from self to
collect all transitive jet_depends_on_id and jet_id nodes (using the existing
relations jet_requires_ids -> jet_depends_on_id and jet_required_by_ids ->
jet_id) while avoiding cycles via a visited set, then return the union of all
discovered L1+ deeper jets instead of just l1_jets | l2_jets.
- Around line 987-990: Currently _trigger_action() can be re-entered and
overwrite in-flight transition metadata (target_state_id) — instead of TODO
queueing, implement an early rejection: in the _trigger_action() path (the
method on the cx_tower_jet model / Jet class where target_state_id is set, near
the action.sudo() call) check whether self.target_state_id (or equivalent
in-flight marker) is already non-null and, if so, raise a clear exception or
return an error indicating "transition in progress" (do not overwrite); ensure
the check is done under the same sudoed security context and within the same
transactional/ORM lock (e.g., use a SELECT FOR UPDATE/record lock or Odoo
browse().with_context() transaction semantics) so concurrent callers cannot race
past the guard, and leave the existing in-flight metadata intact.
- Around line 291-304: The current construction of state_available_ids exposes
all template state_to_id options regardless of whether they are reachable from
the jet's current state; to fix it, compute the reachable states from
jet.state_id using the transition edges in actions.state_transit_id (treat
transitions where t.state_from_id is a source and t.state_to_id is a target),
perform a BFS/DFS to build the reachable state id set, then set
state_available_ids to the intersection of actions.state_to_id, the reachable
set, and the access-level filter using effective_user_access_level (keep using
jet._get_user_effective_access_level(), jet.state_id, actions.state_to_id,
actions.state_transit_id), so unreachable targets are filtered out before calls
to _bring_to_state().
In `@cetmix_tower_server/models/cx_tower_metadata_mixin.py`:
- Around line 31-45: The update_metadata method does no type-checking on the
metadata arg before using dict-unpacking and will raise a cryptic TypeError for
non-mapping inputs; add an explicit validation at the start of update_metadata
(after ensure_one()) to assert metadata is a dict or collections.abc.Mapping and
raise a clear TypeError/ValueError with a descriptive message if not; then
perform the existing merge using self.metadata and self.write (unchanged) so
callers get a predictable, explanatory failure for bad input.
In `@cetmix_tower_server/models/cx_tower_server_log.py`:
- Around line 91-96: The compute method _compute_server_id assigns an integer ID
to the Many2one field server_id (record.jet_id.server_id.id); change it to
assign the recordset instead (record.jet_id.server_id) so the field gets a
proper record rather than a raw integer; update _compute_server_id to set
record.server_id = record.jet_id.server_id when jet_id is present and server_id
is empty.
In `@cetmix_tower_server/models/cx_tower_server.py`:
- Around line 2377-2393: The get_variable_value helper ignores no_fallback and
risks KeyError; update get_variable_value to pass the no_fallback flag into
cx.tower.variable._get_variable_values_by_references (call it with
variable_references=[variable_reference], server=self, no_fallback=no_fallback)
and then return the value via values.get(variable_reference) so it returns None
when no resolved value exists instead of raising.
In `@cetmix_tower_server/models/cx_tower_variable.py`:
- Around line 668-699: The recursive call to _get_variable_values_by_references
in the method (cx_tower_variable.py) drops server_template and plan_line_action
from kwargs so nested template rendering loses its scope; update the recursive
invocation in that loop to forward server_template and plan_line_action (e.g.,
pass server_template=kwargs.get("server_template") and
plan_line_action=kwargs.get("plan_line_action")) along with server,
jet_template, jet and _depth so indirect references keep the same
template/action context during rendering.
- Around line 515-552: The plan-line-action value is checked last so it never
wins when broader contexts exist, and the server_template branch uses `or` which
treats empty-string overrides as falsy; change the precedence so
`plan_line_action` is evaluated before the broader contexts (check
`plan_line_action` and return `plan_line_action_value_char` if present), and
replace the `server_template` branch's `return server_template_value_char or
global_value_char` with an explicit None check (e.g. `if
server_template_value_char is not None: return server_template_value_char else:
return global_value_char`) so empty-string overrides are preserved; update the
logic for `server_template`, `jet`, `jet_template`, and `server` branches to use
`... is not None` checks (referencing variables server_template, jet,
jet_template, server and their *_value_char counterparts).
In `@cetmix_tower_server/readme/newsfragments/4700.feature`:
- Line 1: Replace the vague single-word fragment "Jets!" in the newsfragment
4700.feature with a concise, user-facing release note sentence describing what
changed (for example: support for Jet templates, provisioning, and import/export
of Jet configurations); update the sentence to be present-tense, focused on
user-visible scope, and keep it short so it’s changelog-friendly.
In `@cetmix_tower_server/security/cx_tower_jet_action_security.xml`:
- Around line 67-72: The ir.rule record rule_cx_tower_jet_action_root_full for
model_cx_tower_jet_action currently relies on Odoo defaults and omits explicit
perm_* flags; update that record to include explicit permission fields
(perm_read, perm_write, perm_create, perm_unlink) set to True to match the style
used in cx_tower_jet_template_install_security.xml so the root rule is
consistent and unambiguous.
In `@cetmix_tower_server/security/cx_tower_jet_template_install_security.xml`:
- Around line 26-31: The ir.rule record
rule_cx_tower_jet_template_install_root_full grants root full access via a
domain but omits explicit permission flags; update the record for
model_cx_tower_jet_template_install (record id
rule_cx_tower_jet_template_install_root_full, group
cetmix_tower_server.group_root) to add explicit fields perm_read="1",
perm_write="1", perm_create="1", and perm_unlink="1" so permissions match other
security files and improve consistency and maintainability.
In `@cetmix_tower_server/tests/common_jets.py`:
- Around line 505-516: The helper reuses cls.server_test_1 and then mutates it
when server_user_ids/server_manager_ids are provided, which causes later jets to
see overwritten ACLs; change the logic in _create_jet so that if server_user_ids
or server_manager_ids is not None you first instantiate a fresh server object
(do not reuse cls.server_test_1) and assign that new instance to the local
server variable before calling server.write(...); ensure the created server is
the one stored in jet_server_* and depends_on_server_* so each jet with per-jet
ACLs has its own distinct server record.
In `@cetmix_tower_server/tests/test_jet_access.py`:
- Around line 406-441: The test test_root_full_access currently performs
Jet.create, Jet.search, write, and unlink with the default test user instead of
exercising root; update each CRUD call to use the root user by invoking
with_user(self.root) on the recordset/Model (e.g.,
self.Jet.with_user(self.root).create(...),
self.Jet.with_user(self.root).search([...]),
manager_jet.with_user(self.root).write({...}), and
manager_jet.with_user(self.root).unlink()) so every assertion runs under the
root role and validates root record rules.
---
Outside diff comments:
In `@cetmix_tower_server/models/cx_tower_server.py`:
- Around line 1158-1194: Initialize rendered_code and rendered_path before the
variable_references branch so they are always defined; specifically, at the
start of the block that checks variable_references set rendered_code =
command.code and rendered_path = path (or False if you prefer previous falsy
behavior), then proceed to fetch variable_values via
_get_variable_values_by_references and apply custom_variable_values and
rendering (via command.render_code_custom) only to override those defaults;
reference symbols: variable_references, _get_variable_values_by_references,
custom_variable_values, _have_access_to_server, command.render_code_custom,
rendered_code, rendered_path, run_command.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: f027bb15-40a0-441a-956f-b82431da92cd
⛔ Files ignored due to path filters (17)
cetmix_tower_server/security/ir.model.access.csvis excluded by!**/*.csvcetmix_tower_server/static/demo/img/backup.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/clean.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/docker.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/kubernetes.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/mariadb.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/monitoring.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/nginx.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/odoo.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/owncloud.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/postgres.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/proxmox.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/test.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/tower.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/traefik.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/woocommerce.pngis excluded by!**/*.pngcetmix_tower_server/static/demo/img/wordpress.pngis excluded by!**/*.png
📒 Files selected for processing (135)
cetmix_tower_server/__manifest__.pycetmix_tower_server/data/cx_tower_jet_state.xmlcetmix_tower_server/demo/demo_data.xmlcetmix_tower_server/demo/demo_jets.xmlcetmix_tower_server/migrations/18.0.3.0.0/pre-migration.pycetmix_tower_server/models/__init__.pycetmix_tower_server/models/cetmix_tower.pycetmix_tower_server/models/constants.pycetmix_tower_server/models/cx_tower_command.pycetmix_tower_server/models/cx_tower_command_log.pycetmix_tower_server/models/cx_tower_file.pycetmix_tower_server/models/cx_tower_file_template.pycetmix_tower_server/models/cx_tower_jet.pycetmix_tower_server/models/cx_tower_jet_action.pycetmix_tower_server/models/cx_tower_jet_dependency.pycetmix_tower_server/models/cx_tower_jet_request.pycetmix_tower_server/models/cx_tower_jet_state.pycetmix_tower_server/models/cx_tower_jet_template.pycetmix_tower_server/models/cx_tower_jet_template_dependency.pycetmix_tower_server/models/cx_tower_jet_template_install.pycetmix_tower_server/models/cx_tower_jet_template_install_line.pycetmix_tower_server/models/cx_tower_jet_waypoint.pycetmix_tower_server/models/cx_tower_jet_waypoint_template.pycetmix_tower_server/models/cx_tower_key.pycetmix_tower_server/models/cx_tower_metadata_mixin.pycetmix_tower_server/models/cx_tower_plan.pycetmix_tower_server/models/cx_tower_plan_line.pycetmix_tower_server/models/cx_tower_plan_log.pycetmix_tower_server/models/cx_tower_scheduled_task.pycetmix_tower_server/models/cx_tower_server.pycetmix_tower_server/models/cx_tower_server_log.pycetmix_tower_server/models/cx_tower_template_mixin.pycetmix_tower_server/models/cx_tower_variable.pycetmix_tower_server/models/cx_tower_variable_mixin.pycetmix_tower_server/models/cx_tower_variable_value.pycetmix_tower_server/models/res_users.pycetmix_tower_server/models/tools.pycetmix_tower_server/readme/diagrams/jets.pumlcetmix_tower_server/readme/newsfragments/4700.featurecetmix_tower_server/security/cx_tower_jet_action_security.xmlcetmix_tower_server/security/cx_tower_jet_dependency_security.xmlcetmix_tower_server/security/cx_tower_jet_security.xmlcetmix_tower_server/security/cx_tower_jet_template_dependency_security.xmlcetmix_tower_server/security/cx_tower_jet_template_install_line_security.xmlcetmix_tower_server/security/cx_tower_jet_template_install_security.xmlcetmix_tower_server/security/cx_tower_jet_template_security.xmlcetmix_tower_server/security/cx_tower_jet_waypoint_security.xmlcetmix_tower_server/security/cx_tower_jet_waypoint_template_security.xmlcetmix_tower_server/security/cx_tower_scheduled_task_cv_security.xmlcetmix_tower_server/security/cx_tower_scheduled_task_security.xmlcetmix_tower_server/security/cx_tower_server_log_security.xmlcetmix_tower_server/security/cx_tower_server_wizard_access_rules.xmlcetmix_tower_server/security/cx_tower_variable_value_security.xmlcetmix_tower_server/tests/__init__.pycetmix_tower_server/tests/common.pycetmix_tower_server/tests/common_jets.pycetmix_tower_server/tests/test_cetmix_tower.pycetmix_tower_server/tests/test_command.pycetmix_tower_server/tests/test_jet.pycetmix_tower_server/tests/test_jet_access.pycetmix_tower_server/tests/test_jet_action_access.pycetmix_tower_server/tests/test_jet_create_wizard.pycetmix_tower_server/tests/test_jet_dependency_access.pycetmix_tower_server/tests/test_jet_state.pycetmix_tower_server/tests/test_jet_template.pycetmix_tower_server/tests/test_jet_template_access.pycetmix_tower_server/tests/test_jet_template_dependency_access.pycetmix_tower_server/tests/test_jet_template_install.pycetmix_tower_server/tests/test_jet_template_install_access.pycetmix_tower_server/tests/test_jet_template_install_line_access.pycetmix_tower_server/tests/test_jet_waypoint.pycetmix_tower_server/tests/test_jet_waypoint_access.pycetmix_tower_server/tests/test_jet_waypoint_template_access.pycetmix_tower_server/tests/test_plan.pycetmix_tower_server/tests/test_scheduled_task.pycetmix_tower_server/tests/test_server_jet_action_command.pycetmix_tower_server/tests/test_server_log.pycetmix_tower_server/tests/test_variable.pycetmix_tower_server/tests/test_variable_value.pycetmix_tower_server/views/cx_tower_command_log_view.xmlcetmix_tower_server/views/cx_tower_command_view.xmlcetmix_tower_server/views/cx_tower_file_view.xmlcetmix_tower_server/views/cx_tower_jet_action_view.xmlcetmix_tower_server/views/cx_tower_jet_request_view.xmlcetmix_tower_server/views/cx_tower_jet_state_view.xmlcetmix_tower_server/views/cx_tower_jet_template_install_view.xmlcetmix_tower_server/views/cx_tower_jet_template_view.xmlcetmix_tower_server/views/cx_tower_jet_view.xmlcetmix_tower_server/views/cx_tower_jet_waypoint_template_view.xmlcetmix_tower_server/views/cx_tower_jet_waypoint_view.xmlcetmix_tower_server/views/cx_tower_plan_line_view.xmlcetmix_tower_server/views/cx_tower_plan_log_view.xmlcetmix_tower_server/views/cx_tower_scheduled_task_view.xmlcetmix_tower_server/views/cx_tower_server_log_view.xmlcetmix_tower_server/views/cx_tower_server_template_view.xmlcetmix_tower_server/views/cx_tower_server_view.xmlcetmix_tower_server/views/cx_tower_variable_value_view.xmlcetmix_tower_server/views/cx_tower_variable_view.xmlcetmix_tower_server/views/menuitems.xmlcetmix_tower_server/wizards/__init__.pycetmix_tower_server/wizards/cx_tower_command_run_wizard.pycetmix_tower_server/wizards/cx_tower_command_run_wizard_view.xmlcetmix_tower_server/wizards/cx_tower_jet_action_wizard.pycetmix_tower_server/wizards/cx_tower_jet_action_wizard_view.xmlcetmix_tower_server/wizards/cx_tower_jet_clone_wizard.pycetmix_tower_server/wizards/cx_tower_jet_clone_wizard_view.xmlcetmix_tower_server/wizards/cx_tower_jet_create_wizard.pycetmix_tower_server/wizards/cx_tower_jet_create_wizard_view.xmlcetmix_tower_server/wizards/cx_tower_jet_state_wizard.pycetmix_tower_server/wizards/cx_tower_jet_state_wizard_view.xmlcetmix_tower_server/wizards/cx_tower_jet_template_install_wizard.pycetmix_tower_server/wizards/cx_tower_jet_template_install_wizard_view.xmlcetmix_tower_server/wizards/cx_tower_plan_run_wizard.pycetmix_tower_server/wizards/cx_tower_plan_run_wizard_view.xmlcetmix_tower_server_queue/models/cx_tower_server.pycetmix_tower_server_queue/readme/newsfragments/4700.featurecetmix_tower_yaml/__manifest__.pycetmix_tower_yaml/models/__init__.pycetmix_tower_yaml/models/cx_tower_command.pycetmix_tower_yaml/models/cx_tower_jet_action.pycetmix_tower_yaml/models/cx_tower_jet_state.pycetmix_tower_yaml/models/cx_tower_jet_template.pycetmix_tower_yaml/models/cx_tower_jet_template_dependency.pycetmix_tower_yaml/models/cx_tower_jet_waypoint_template.pycetmix_tower_yaml/models/cx_tower_plan.pycetmix_tower_yaml/models/cx_tower_scheduled_task.pycetmix_tower_yaml/models/cx_tower_scheduled_task_cv.pycetmix_tower_yaml/models/cx_tower_yaml_mixin.pycetmix_tower_yaml/readme/newsfragments/4700.featurecetmix_tower_yaml/tests/test_command.pycetmix_tower_yaml/tests/test_tower_yaml_mixin.pycetmix_tower_yaml/tests/test_yaml_export_wizard.pycetmix_tower_yaml/tests/test_yaml_import_wizard.pycetmix_tower_yaml/views/cx_tower_jet_template_view.xmlcetmix_tower_yaml/wizards/cx_tower_yaml_import_wiz.py
tendil
left a comment
There was a problem hiding this comment.
Code review: LGTM
But I think it would be better to call the PR [MIG] rather than [IMP]
|
/ocabot merge major |
|
On my way to merge this fine PR! |
|
Congratulations, your PR was merged at fe2dd5a. Thanks a lot for contributing to cetmix. ❤️ |
Jets are coming to 18.0 🎉
Task 5337
Summary by CodeRabbit
Release Notes
New Features
Improvements