diff --git a/README.md b/README.md
index bce391f..d6004f8 100644
--- a/README.md
+++ b/README.md
@@ -50,6 +50,7 @@ This collection includes the following roles for managing OpenShift Virtualizati
* [create_mf_aap_token](roles/create_mf_aap_token/README.md) - create_mf_aap_token
* [mtv_management](roles/mtv_management/README.md) - Management of the Migration Toolkit for Virtualization (MTV).
* [mtv_migrate](roles/mtv_migrate/README.md) - Migration of Virtual Machines from Source to Destination.
+* [mtv_warm_cutover](roles/mtv_warm_cutover/README.md) - Trigger cutover for warm migrations to finish the migration process.
* [network_mgmt](roles/network_mgmt/README.md) - Management of network related components.
* [operator_management](roles/operator_management/README.md) - Management of OpenShift Operators.
* [validate_migration](roles/validate_migration/README.md) - Verification of an Ansible for OpenShift Virtualization Migration environment.
diff --git a/playbooks/mtv_warm_cutover.yml b/playbooks/mtv_warm_cutover.yml
new file mode 100644
index 0000000..f38da45
--- /dev/null
+++ b/playbooks/mtv_warm_cutover.yml
@@ -0,0 +1,11 @@
+---
+
+- name: Finish Warm Migration Cutover
+ hosts: localhost
+ connection: local
+ gather_facts: false
+ tasks:
+ - name: Invoke Warm Migration Cutover
+ ansible.builtin.include_role:
+ name: infra.openshift_virtualization_migration.mtv_warm_cutover
+...
diff --git a/roles/mtv_warm_cutover/README.md b/roles/mtv_warm_cutover/README.md
new file mode 100644
index 0000000..9358a7a
--- /dev/null
+++ b/roles/mtv_warm_cutover/README.md
@@ -0,0 +1,238 @@
+# mtv_warm_cutover
+
+## mtv_warm_cutover
+
+```
+Role belongs to infra/openshift_virtualization_migration
+Namespace - infra
+Collection - openshift_virtualization_migration
+Version - 1.25.0
+Repository - https://github.com/redhat-cop/openshift_virtualization_migration
+```
+
+Description: Trigger cutover for warm migrations to finish the migration process.
+
+### Argument Specifications
+
+
+🧩 Argument Specifications in `meta/argument_specs`
+
+#### Key: main
+
+* **Description**: MTV Warm Cutover - Trigger cutover for warm migrations
+* **Options**:
+ * **mtv_warm_cutover_default_namespace**:
+ * **Required**: false
+ * **Type**: str
+ * **Default**: openshift-mtv
+ * **Description**: Default namespace for MTV resources
+ * **mtv_warm_cutover_force_cutover**:
+ * **Required**: false
+ * **Type**: bool
+ * **Default**: False
+ * **Description**: Allow overriding an existing cutover timestamp on a Migration. Can be overridden per migration request via the force_cutover key.
+ * **mtv_warm_cutover_request**:
+ * **Required**: True
+ * **Type**: list
+ * **Default**: none
+ * **Description**: List of warm migration cutover requests
+ * **mtv_warm_cutover_verify_complete**:
+ * **Required**: false
+ * **Type**: bool
+ * **Default**: True
+ * **Description**: Wait until cutover migrations complete. Can be overridden per migration request via the verify_complete key.
+ * **mtv_warm_cutover_verify_delay**:
+ * **Required**: false
+ * **Type**: int
+ * **Default**: 20
+ * **Description**: Seconds to wait between retries
+ * **mtv_warm_cutover_verify_retries**:
+ * **Required**: false
+ * **Type**: int
+ * **Default**: 360
+ * **Description**: Number of retries when waiting for cutover completion
+
+
+
+### Defaults
+
+**These are static variables with lower priority**
+
+#### File: defaults/main.yml
+
+| Var | Type | Value |Choices |Required | Title |
+|--------------|--------------|-------------|-------------|-------------|-------------|
+| [`mtv_warm_cutover_default_namespace`](defaults/main.yml#L23) | str | `openshift-mtv` | None | False | MTV Namespace |
+| [`mtv_warm_cutover_force_cutover`](defaults/main.yml#L29) | bool | `False` | None | False | Force Cutover Override |
+| [`mtv_warm_cutover_request`](defaults/main.yml#L7) | list | `[]` | None | True | Warm Cutover Request |
+| [`mtv_warm_cutover_verify_complete`](defaults/main.yml#L35) | bool | `True` | None | False | Verify Cutover Complete |
+| [`mtv_warm_cutover_verify_delay`](defaults/main.yml#L43) | int | `20` | None | False | Verify Complete Delay |
+| [`mtv_warm_cutover_verify_retries`](defaults/main.yml#L39) | int | `360` | None | False | Verify Complete Retries |
+
+🖇️ Full descriptions for vars in defaults/main.yml
+
+`mtv_warm_cutover_default_namespace`: Default namespace for MTV resources
+
+`mtv_warm_cutover_force_cutover`: >-
+
+`mtv_warm_cutover_request`: List of warm migration cutover requests
+
+`mtv_warm_cutover_verify_complete`: >-
+
+`mtv_warm_cutover_verify_delay`: Seconds to wait between retries
+
+`mtv_warm_cutover_verify_retries`: Number of retries when waiting for cutover completion
+
+
+
+### Tasks
+
+#### File: tasks/main.yml
+
+| Name | Module | Has Conditions |
+| ---- | ------ | --------- |
+| Verify mtv_warm_cutover_request Variable Provided | `ansible.builtin.assert` | False |
+| Process Warm Cutover Requests | `ansible.builtin.include_tasks` | False |
+
+#### File: tasks/_apply_cutover.yml
+
+| Name | Module | Has Conditions |
+| ---- | ------ | --------- |
+| _apply_cutover ¦ Check if Cutover Already Defined | `ansible.builtin.set_fact` | False |
+| _apply_cutover ¦ Fail When Cutover Already Set (No Force) | `ansible.builtin.fail` | True |
+| _apply_cutover ¦ Override Existing Cutover Timestamp | `kubernetes.core.k8s_json_patch` | True |
+| _apply_cutover ¦ Report Cutover Overridden | `ansible.builtin.debug` | True |
+| _apply_cutover ¦ Patch Migration with Cutover Timestamp | `kubernetes.core.k8s_json_patch` | True |
+| _apply_cutover ¦ Report Cutover Triggered | `ansible.builtin.debug` | True |
+| _apply_cutover ¦ Wait for Migration to Complete | `kubernetes.core.k8s_info` | True |
+| _apply_cutover ¦ Verify Migration Succeeded | `ansible.builtin.assert` | True |
+
+#### File: tasks/_process_cutover.yml
+
+| Name | Module | Has Conditions |
+| ---- | ------ | --------- |
+| _process_cutover ¦ Set Working Namespace | `ansible.builtin.set_fact` | False |
+| _process_cutover ¦ Query Migrations by Name | `kubernetes.core.k8s_info` | True |
+| _process_cutover ¦ Query Migrations by Label Selector | `kubernetes.core.k8s_info` | True |
+| _process_cutover ¦ Query Migrations by Plan Name | `kubernetes.core.k8s_info` | True |
+| _process_cutover ¦ Filter Migrations for Plan | `ansible.builtin.set_fact` | True |
+| _process_cutover ¦ Build Migration List from Named Queries | `ansible.builtin.set_fact` | True |
+| _process_cutover ¦ Build Migration List from Selector | `ansible.builtin.set_fact` | True |
+| _process_cutover ¦ Build Migration List from Plan | `ansible.builtin.set_fact` | True |
+| _process_cutover ¦ Verify Migrations Found | `ansible.builtin.assert` | False |
+| _process_cutover ¦ Determine Cutover Timestamp | `ansible.builtin.set_fact` | False |
+| _process_cutover ¦ Resolve Per-Request Overrides | `ansible.builtin.set_fact` | False |
+| _process_cutover ¦ Trigger Cutover on Migrations | `ansible.builtin.include_tasks` | False |
+
+## Task Flow Graphs
+
+### Graph for _apply_cutover.yml
+
+```mermaid
+flowchart TD
+Start
+classDef block stroke:#3498db,stroke-width:2px;
+classDef task stroke:#4b76bb,stroke-width:2px;
+classDef includeTasks stroke:#16a085,stroke-width:2px;
+classDef importTasks stroke:#34495e,stroke-width:2px;
+classDef includeRole stroke:#2980b9,stroke-width:2px;
+classDef importRole stroke:#699ba7,stroke-width:2px;
+classDef includeVars stroke:#8e44ad,stroke-width:2px;
+classDef rescue stroke:#665352,stroke-width:2px;
+
+ Start-->|Task| _apply_cutover___Check_if_Cutover_Already_Defined0[ apply cutover check if cutover already defined]:::task
+ _apply_cutover___Check_if_Cutover_Already_Defined0-->|Task| _apply_cutover___Fail_When_Cutover_Already_Set__No_Force_1[ apply cutover fail when cutover already set no
force
When: **mtv warm cutover already set bool and not mtv
warm cutover effective force bool**]:::task
+ _apply_cutover___Fail_When_Cutover_Already_Set__No_Force_1-->|Task| _apply_cutover___Override_Existing_Cutover_Timestamp2[ apply cutover override existing cutover
timestamp
When: **mtv warm cutover already set bool and mtv warm
cutover effective force bool**]:::task
+ _apply_cutover___Override_Existing_Cutover_Timestamp2-->|Task| _apply_cutover___Report_Cutover_Overridden3[ apply cutover report cutover overridden
When: **mtv warm cutover already set bool and mtv warm
cutover effective force bool**]:::task
+ _apply_cutover___Report_Cutover_Overridden3-->|Task| _apply_cutover___Patch_Migration_with_Cutover_Timestamp4[ apply cutover patch migration with cutover
timestamp
When: **not mtv warm cutover already set bool**]:::task
+ _apply_cutover___Patch_Migration_with_Cutover_Timestamp4-->|Task| _apply_cutover___Report_Cutover_Triggered5[ apply cutover report cutover triggered
When: **not mtv warm cutover already set bool**]:::task
+ _apply_cutover___Report_Cutover_Triggered5-->|Task| _apply_cutover___Wait_for_Migration_to_Complete6[ apply cutover wait for migration to complete
When: **mtv warm cutover effective verify bool**]:::task
+ _apply_cutover___Wait_for_Migration_to_Complete6-->|Task| _apply_cutover___Verify_Migration_Succeeded7[ apply cutover verify migration succeeded
When: **mtv warm cutover effective verify bool**]:::task
+ _apply_cutover___Verify_Migration_Succeeded7-->End
+```
+
+### Graph for _process_cutover.yml
+
+```mermaid
+flowchart TD
+Start
+classDef block stroke:#3498db,stroke-width:2px;
+classDef task stroke:#4b76bb,stroke-width:2px;
+classDef includeTasks stroke:#16a085,stroke-width:2px;
+classDef importTasks stroke:#34495e,stroke-width:2px;
+classDef includeRole stroke:#2980b9,stroke-width:2px;
+classDef importRole stroke:#699ba7,stroke-width:2px;
+classDef includeVars stroke:#8e44ad,stroke-width:2px;
+classDef rescue stroke:#665352,stroke-width:2px;
+
+ Start-->|Task| _process_cutover___Set_Working_Namespace0[ process cutover set working namespace]:::task
+ _process_cutover___Set_Working_Namespace0-->|Task| _process_cutover___Query_Migrations_by_Name1[ process cutover query migrations by name
When: **mtv warm cutover item names default true
length 0**]:::task
+ _process_cutover___Query_Migrations_by_Name1-->|Task| _process_cutover___Query_Migrations_by_Label_Selector2[ process cutover query migrations by label
selector
When: **mtv warm cutover item names default true
length 0 and mtv warm cutover item label
selectors default true length 0**]:::task
+ _process_cutover___Query_Migrations_by_Label_Selector2-->|Task| _process_cutover___Query_Migrations_by_Plan_Name3[ process cutover query migrations by plan name
When: **mtv warm cutover item names default true
length 0 and mtv warm cutover item label
selectors default true length 0 and
mtv warm cutover item plan name default true
length 0**]:::task
+ _process_cutover___Query_Migrations_by_Plan_Name3-->|Task| _process_cutover___Filter_Migrations_for_Plan4[ process cutover filter migrations for plan
When: **mtv warm cutover by plan query is not skipped**]:::task
+ _process_cutover___Filter_Migrations_for_Plan4-->|Task| _process_cutover___Build_Migration_List_from_Named_Queries5[ process cutover build migration list from named
queries
When: **mtv warm cutover by name is not skipped**]:::task
+ _process_cutover___Build_Migration_List_from_Named_Queries5-->|Task| _process_cutover___Build_Migration_List_from_Selector6[ process cutover build migration list from
selector
When: **mtv warm cutover by selector is not skipped**]:::task
+ _process_cutover___Build_Migration_List_from_Selector6-->|Task| _process_cutover___Build_Migration_List_from_Plan7[ process cutover build migration list from plan
When: **mtv warm cutover by plan is defined and mtv warm
cutover by plan is not skipped**]:::task
+ _process_cutover___Build_Migration_List_from_Plan7-->|Task| _process_cutover___Verify_Migrations_Found8[ process cutover verify migrations found]:::task
+ _process_cutover___Verify_Migrations_Found8-->|Task| _process_cutover___Determine_Cutover_Timestamp9[ process cutover determine cutover timestamp]:::task
+ _process_cutover___Determine_Cutover_Timestamp9-->|Task| _process_cutover___Resolve_Per_Request_Overrides10[ process cutover resolve per request overrides]:::task
+ _process_cutover___Resolve_Per_Request_Overrides10-->|Include task| _process_cutover___Trigger_Cutover_on_Migrations__apply_cutover_yml_11[ process cutover trigger cutover on migrations
include_task: apply cutover yml]:::includeTasks
+ _process_cutover___Trigger_Cutover_on_Migrations__apply_cutover_yml_11-->End
+```
+
+### Graph for main.yml
+
+```mermaid
+flowchart TD
+Start
+classDef block stroke:#3498db,stroke-width:2px;
+classDef task stroke:#4b76bb,stroke-width:2px;
+classDef includeTasks stroke:#16a085,stroke-width:2px;
+classDef importTasks stroke:#34495e,stroke-width:2px;
+classDef includeRole stroke:#2980b9,stroke-width:2px;
+classDef importRole stroke:#699ba7,stroke-width:2px;
+classDef includeVars stroke:#8e44ad,stroke-width:2px;
+classDef rescue stroke:#665352,stroke-width:2px;
+
+ Start-->|Task| Verify_mtv_warm_cutover_request_Variable_Provided0[verify mtv warm cutover request variable provided]:::task
+ Verify_mtv_warm_cutover_request_Variable_Provided0-->|Include task| Process_Warm_Cutover_Requests__process_cutover_yml_1[process warm cutover requests
include_task: process cutover yml]:::includeTasks
+ Process_Warm_Cutover_Requests__process_cutover_yml_1-->End
+```
+
+## Playbook
+
+```yml
+---
+- name: Test
+ hosts: localhost
+ remote_user: root
+ roles:
+ - mtv_warm_cutover
+...
+
+```
+
+## Playbook graph
+
+```mermaid
+flowchart TD
+ hosts[localhost]-->|Role| mtv_warm_cutover[mtv warm cutover]
+```
+
+## Author Information
+
+OpenShift Virtualization Migration Contributors
+
+## License
+
+GPL-3.0-only
+
+## Minimum Ansible Version
+
+2.16.0
+
+## Platforms
+
+No platforms specified.
+
+
\ No newline at end of file
diff --git a/roles/mtv_warm_cutover/defaults/main.yml b/roles/mtv_warm_cutover/defaults/main.yml
new file mode 100644
index 0000000..56c5fa0
--- /dev/null
+++ b/roles/mtv_warm_cutover/defaults/main.yml
@@ -0,0 +1,45 @@
+---
+# defaults file for mtv_warm_cutover
+
+# title: Warm Cutover Request
+# required: True
+# description: List of warm migration cutover requests
+mtv_warm_cutover_request: []
+# - namespace: # Namespace containing the Migration resources.
+# names: # List of Migration resource names to trigger cutover for. \
+# Optional when using label_selectors or plan_name.
+# -
+# label_selectors: # Label selectors to match Migration resources.
+# - =
+# plan_name: # Name of the Plan to find associated Migrations for cutover.
+# cutover: # ISO 8601 timestamp for scheduled cutover \
+# (e.g., '2026-04-07T02:00:00Z'). If empty, uses current time.
+# force_cutover: # Override an existing cutover timestamp (default: false)
+# verify_complete: # Wait until cutover migration completes (overrides global default)
+
+# title: MTV Namespace
+# required: False
+# description: Default namespace for MTV resources
+mtv_warm_cutover_default_namespace: openshift-mtv
+# title: Force Cutover Override
+# required: False
+# description: >-
+# Allow overriding an existing cutover timestamp on a Migration.
+# Can be overridden per migration request via the force_cutover key.
+mtv_warm_cutover_force_cutover: false
+# title: Verify Cutover Complete
+# required: False
+# description: >-
+# Wait until cutover migrations complete.
+# Can be overridden per migration request via the verify_complete key.
+mtv_warm_cutover_verify_complete: true
+# title: Verify Complete Retries
+# required: False
+# description: Number of retries when waiting for cutover completion
+mtv_warm_cutover_verify_retries: 360
+# title: Verify Complete Delay
+# required: False
+# description: Seconds to wait between retries
+mtv_warm_cutover_verify_delay: 20
+
+...
diff --git a/roles/mtv_warm_cutover/meta/argument_specs.yml b/roles/mtv_warm_cutover/meta/argument_specs.yml
new file mode 100644
index 0000000..71facdb
--- /dev/null
+++ b/roles/mtv_warm_cutover/meta/argument_specs.yml
@@ -0,0 +1,67 @@
+---
+argument_specs:
+ main:
+ short_description: MTV Warm Cutover - Trigger cutover for warm migrations
+ options:
+ mtv_warm_cutover_request:
+ description: List of warm migration cutover requests
+ type: list
+ required: true
+ elements: dict
+ options:
+ namespace:
+ description: Namespace containing the Migration resources
+ type: str
+ names:
+ description: >-
+ List of Migration resource names to trigger cutover for.
+ Optional when using label_selectors or plan_name.
+ type: list
+ elements: str
+ label_selectors:
+ description: Label selectors to match Migration resources
+ type: list
+ elements: str
+ plan_name:
+ description: Name of the Plan to find associated Migrations for cutover
+ type: str
+ cutover:
+ description: >-
+ ISO 8601 timestamp for scheduled cutover
+ (e.g., '2026-04-07T02:00:00Z'). If omitted, uses current time.
+ type: str
+ force_cutover:
+ description: >-
+ Override an existing cutover timestamp on a Migration.
+ Overrides the global mtv_warm_cutover_force_cutover default.
+ type: bool
+ verify_complete:
+ description: >-
+ Wait until the cutover migration completes.
+ Overrides the global mtv_warm_cutover_verify_complete default.
+ type: bool
+ mtv_warm_cutover_default_namespace:
+ description: Default namespace for MTV resources
+ type: str
+ default: openshift-mtv
+ mtv_warm_cutover_force_cutover:
+ description: >-
+ Allow overriding an existing cutover timestamp on a Migration.
+ Can be overridden per migration request via the force_cutover key.
+ type: bool
+ default: false
+ mtv_warm_cutover_verify_complete:
+ description: >-
+ Wait until cutover migrations complete.
+ Can be overridden per migration request via the verify_complete key.
+ type: bool
+ default: true
+ mtv_warm_cutover_verify_retries:
+ description: Number of retries when waiting for cutover completion
+ type: int
+ default: 360
+ mtv_warm_cutover_verify_delay:
+ description: Seconds to wait between retries
+ type: int
+ default: 20
+...
diff --git a/roles/mtv_warm_cutover/meta/main.yml b/roles/mtv_warm_cutover/meta/main.yml
new file mode 100644
index 0000000..1829f4b
--- /dev/null
+++ b/roles/mtv_warm_cutover/meta/main.yml
@@ -0,0 +1,10 @@
+---
+galaxy_info:
+ author: ""
+ description: Trigger cutover for warm migrations to finish the migration process.
+ company: Red Hat
+ license: GPL-3.0-only
+ min_ansible_version: 2.16.0
+ galaxy_tags: []
+dependencies: []
+...
diff --git a/roles/mtv_warm_cutover/tasks/_apply_cutover.yml b/roles/mtv_warm_cutover/tasks/_apply_cutover.yml
new file mode 100644
index 0000000..1d79b9f
--- /dev/null
+++ b/roles/mtv_warm_cutover/tasks/_apply_cutover.yml
@@ -0,0 +1,102 @@
+---
+
+- name: _apply_cutover | Check if Cutover Already Defined
+ ansible.builtin.set_fact:
+ mtv_warm_cutover_already_set: >-
+ {{ mtv_warm_cutover_migration.spec.cutover is defined and
+ mtv_warm_cutover_migration.spec.cutover | default("", true) | length > 0 }}
+
+- name: _apply_cutover | Fail When Cutover Already Set (No Force)
+ when:
+ - mtv_warm_cutover_already_set | bool
+ - not (mtv_warm_cutover_effective_force | bool)
+ ansible.builtin.fail:
+ msg: >-
+ Migration '{{ mtv_warm_cutover_migration.metadata.name }}'
+ already has cutover set to
+ '{{ mtv_warm_cutover_migration.spec.cutover }}'.
+ Set force_cutover: true to override.
+
+- name: _apply_cutover | Override Existing Cutover Timestamp
+ when:
+ - mtv_warm_cutover_already_set | bool
+ - mtv_warm_cutover_effective_force | bool
+ kubernetes.core.k8s_json_patch:
+ api_version: forklift.konveyor.io/v1beta1
+ kind: Migration
+ namespace: "{{ mtv_warm_cutover_migration.metadata.namespace }}"
+ name: "{{ mtv_warm_cutover_migration.metadata.name }}"
+ patch:
+ - op: replace
+ path: /spec/cutover
+ value: "{{ mtv_warm_cutover_timestamp }}"
+ register: mtv_warm_cutover_patch_result
+
+- name: _apply_cutover | Report Cutover Overridden
+ when:
+ - mtv_warm_cutover_already_set | bool
+ - mtv_warm_cutover_effective_force | bool
+ ansible.builtin.debug:
+ msg: >-
+ Cutover overridden for Migration
+ '{{ mtv_warm_cutover_migration.metadata.name }}'
+ from '{{ mtv_warm_cutover_migration.spec.cutover }}'
+ to {{ mtv_warm_cutover_timestamp }}
+
+- name: _apply_cutover | Patch Migration with Cutover Timestamp
+ when: not (mtv_warm_cutover_already_set | bool)
+ kubernetes.core.k8s_json_patch:
+ api_version: forklift.konveyor.io/v1beta1
+ kind: Migration
+ namespace: "{{ mtv_warm_cutover_migration.metadata.namespace }}"
+ name: "{{ mtv_warm_cutover_migration.metadata.name }}"
+ patch:
+ - op: add
+ path: /spec/cutover
+ value: "{{ mtv_warm_cutover_timestamp }}"
+ register: mtv_warm_cutover_patch_result
+
+- name: _apply_cutover | Report Cutover Triggered
+ when: not (mtv_warm_cutover_already_set | bool)
+ ansible.builtin.debug:
+ msg: >-
+ Cutover triggered for Migration
+ '{{ mtv_warm_cutover_migration.metadata.name }}'
+ at {{ mtv_warm_cutover_timestamp }}
+
+- name: _apply_cutover | Wait for Migration to Complete
+ when: mtv_warm_cutover_effective_verify | bool
+ kubernetes.core.k8s_info:
+ api_version: forklift.konveyor.io/v1beta1
+ kind: Migration
+ namespace: "{{ mtv_warm_cutover_migration.metadata.namespace }}"
+ name: "{{ mtv_warm_cutover_migration.metadata.name }}"
+ register: mtv_warm_cutover_status
+ until: >
+ mtv_warm_cutover_status is defined and
+ 'resources' in mtv_warm_cutover_status and
+ mtv_warm_cutover_status.resources | length == 1 and
+ 'status' in mtv_warm_cutover_status.resources | first and
+ 'completed' in (mtv_warm_cutover_status.resources | first).status and
+ (mtv_warm_cutover_status.resources | first).status.completed
+ | trim | length > 0
+ retries: "{{ mtv_warm_cutover_verify_retries }}"
+ delay: "{{ mtv_warm_cutover_verify_delay }}"
+
+- name: _apply_cutover | Verify Migration Succeeded
+ when: mtv_warm_cutover_effective_verify | bool
+ ansible.builtin.assert:
+ that:
+ - >-
+ (mtv_warm_cutover_status.resources | first).status.conditions
+ | selectattr('type', 'defined')
+ | selectattr('status', 'defined')
+ | selectattr('type', 'equalto', 'Succeeded')
+ | selectattr('status', 'equalto', 'True')
+ | list | length == 1
+ fail_msg: >-
+ Migration '{{ mtv_warm_cutover_migration.metadata.name }}'
+ cutover did not succeed
+ quiet: true
+
+...
diff --git a/roles/mtv_warm_cutover/tasks/_process_cutover.yml b/roles/mtv_warm_cutover/tasks/_process_cutover.yml
new file mode 100644
index 0000000..8d1dc65
--- /dev/null
+++ b/roles/mtv_warm_cutover/tasks/_process_cutover.yml
@@ -0,0 +1,109 @@
+---
+
+- name: _process_cutover | Set Working Namespace
+ ansible.builtin.set_fact:
+ mtv_warm_cutover_namespace: >-
+ {{ mtv_warm_cutover_item.namespace
+ | default(mtv_warm_cutover_default_namespace) }}
+
+- name: _process_cutover | Query Migrations by Name
+ when: mtv_warm_cutover_item.names | default([], true) | length > 0
+ kubernetes.core.k8s_info:
+ api_version: forklift.konveyor.io/v1beta1
+ kind: Migration
+ namespace: "{{ mtv_warm_cutover_namespace }}"
+ name: "{{ mtv_warm_cutover_migration_name }}"
+ register: mtv_warm_cutover_by_name
+ loop: "{{ mtv_warm_cutover_item.names }}"
+ loop_control:
+ loop_var: mtv_warm_cutover_migration_name
+ label: "{{ mtv_warm_cutover_migration_name }}"
+
+- name: _process_cutover | Query Migrations by Label Selector
+ when:
+ - mtv_warm_cutover_item.names | default([], true) | length == 0
+ - mtv_warm_cutover_item.label_selectors | default([], true) | length > 0
+ kubernetes.core.k8s_info:
+ api_version: forklift.konveyor.io/v1beta1
+ kind: Migration
+ namespace: "{{ mtv_warm_cutover_namespace }}"
+ label_selectors: "{{ mtv_warm_cutover_item.label_selectors }}"
+ register: mtv_warm_cutover_by_selector
+
+- name: _process_cutover | Query Migrations by Plan Name
+ when:
+ - mtv_warm_cutover_item.names | default([], true) | length == 0
+ - mtv_warm_cutover_item.label_selectors | default([], true) | length == 0
+ - mtv_warm_cutover_item.plan_name | default("", true) | length > 0
+ kubernetes.core.k8s_info:
+ api_version: forklift.konveyor.io/v1beta1
+ kind: Migration
+ namespace: "{{ mtv_warm_cutover_namespace }}"
+ register: mtv_warm_cutover_by_plan_query
+
+- name: _process_cutover | Filter Migrations for Plan
+ when: mtv_warm_cutover_by_plan_query is not skipped
+ ansible.builtin.set_fact:
+ mtv_warm_cutover_by_plan:
+ resources: >-
+ {{ mtv_warm_cutover_by_plan_query.resources
+ | selectattr('spec.plan.name', 'equalto', mtv_warm_cutover_item.plan_name)
+ | list }}
+
+- name: _process_cutover | Build Migration List from Named Queries
+ when: mtv_warm_cutover_by_name is not skipped
+ ansible.builtin.set_fact:
+ mtv_warm_cutover_migrations: >-
+ {{ mtv_warm_cutover_by_name.results
+ | selectattr('resources', 'defined')
+ | map(attribute='resources')
+ | flatten }}
+
+- name: _process_cutover | Build Migration List from Selector
+ when: mtv_warm_cutover_by_selector is not skipped
+ ansible.builtin.set_fact:
+ mtv_warm_cutover_migrations: >-
+ {{ mtv_warm_cutover_by_selector.resources | default([]) }}
+
+- name: _process_cutover | Build Migration List from Plan
+ when:
+ - mtv_warm_cutover_by_plan is defined
+ - mtv_warm_cutover_by_plan is not skipped
+ ansible.builtin.set_fact:
+ mtv_warm_cutover_migrations: >-
+ {{ mtv_warm_cutover_by_plan.resources | default([]) }}
+
+- name: _process_cutover | Verify Migrations Found
+ ansible.builtin.assert:
+ that:
+ - mtv_warm_cutover_migrations | default([], true) | length > 0
+ fail_msg: >-
+ No Migration resources found in namespace
+ '{{ mtv_warm_cutover_namespace }}'
+ matching the provided criteria
+ quiet: true
+
+- name: _process_cutover | Determine Cutover Timestamp
+ ansible.builtin.set_fact:
+ mtv_warm_cutover_timestamp: >-
+ {{ mtv_warm_cutover_item.cutover
+ | default(now(utc=true, fmt='%Y-%m-%dT%H:%M:%SZ')) }}
+
+- name: _process_cutover | Resolve Per-Request Overrides
+ ansible.builtin.set_fact:
+ mtv_warm_cutover_effective_force: >-
+ {{ mtv_warm_cutover_item.force_cutover
+ | default(mtv_warm_cutover_force_cutover) | bool }}
+ mtv_warm_cutover_effective_verify: >-
+ {{ mtv_warm_cutover_item.verify_complete
+ | default(mtv_warm_cutover_verify_complete) | bool }}
+
+- name: _process_cutover | Trigger Cutover on Migrations
+ ansible.builtin.include_tasks:
+ file: _apply_cutover.yml
+ loop: "{{ mtv_warm_cutover_migrations }}"
+ loop_control:
+ loop_var: mtv_warm_cutover_migration
+ label: "{{ mtv_warm_cutover_migration.metadata.name }}"
+
+...
diff --git a/roles/mtv_warm_cutover/tasks/main.yml b/roles/mtv_warm_cutover/tasks/main.yml
new file mode 100644
index 0000000..7134ffc
--- /dev/null
+++ b/roles/mtv_warm_cutover/tasks/main.yml
@@ -0,0 +1,20 @@
+---
+
+- name: Verify mtv_warm_cutover_request Variable Provided
+ ansible.builtin.assert:
+ that:
+ - mtv_warm_cutover_request | default("", true) | length > 0
+ fail_msg: "'mtv_warm_cutover_request' Variable Not Provided"
+ quiet: true
+
+- name: Process Warm Cutover Requests
+ ansible.builtin.include_tasks:
+ file: _process_cutover.yml
+ loop: "{{ mtv_warm_cutover_request }}"
+ loop_control:
+ loop_var: mtv_warm_cutover_item
+ label: >-
+ Namespace: {{ mtv_warm_cutover_item.namespace
+ | default(mtv_warm_cutover_default_namespace) }}
+
+...
diff --git a/roles/mtv_warm_cutover/tests/inventory b/roles/mtv_warm_cutover/tests/inventory
new file mode 100644
index 0000000..2fbb50c
--- /dev/null
+++ b/roles/mtv_warm_cutover/tests/inventory
@@ -0,0 +1 @@
+localhost
diff --git a/roles/mtv_warm_cutover/tests/test.yml b/roles/mtv_warm_cutover/tests/test.yml
new file mode 100644
index 0000000..fa741e2
--- /dev/null
+++ b/roles/mtv_warm_cutover/tests/test.yml
@@ -0,0 +1,7 @@
+---
+- name: Test
+ hosts: localhost
+ remote_user: root
+ roles:
+ - mtv_warm_cutover
+...