Skip to content

Commit

Permalink
Merge pull request #95 from alinabuzachis/move_vm_from_on_prem_to_aws
Browse files Browse the repository at this point in the history
Add move_vm_from_on_prem_to_aws playbook and corresponding roles
  • Loading branch information
alinabuzachis authored Oct 25, 2023
2 parents f99f858 + 1c39b43 commit f24c6f3
Show file tree
Hide file tree
Showing 12 changed files with 549 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,16 @@ Name | Description
[cloud.aws_ops.manage_transit_gateway](https://github.com/ansible-collections/cloud.aws_ops/blob/main/roles/manage_transit_gateway/README.md)|A role to create/delete transit_gateway with vpc and vpn attachments.
[cloud.aws_ops.deploy_flask_app](https://github.com/ansible-collections/cloud.aws_ops/blob/main/roles/deploy_flask_app/README.md)|A role to deploy a flask web application on AWS.
[cloud.aws_ops.create_rds_global_cluster](https://github.com/ansible-collections/cloud.aws_ops/blob/main/roles/create_rds_global_cluster/README.md)|A role to create, delete aurora global cluster with a primary cluster and a replica cluster in different regions.
[cloud.aws_ops.clone_on_prem_vm](https://github.com/ansible-collections/cloud.aws_ops/blob/main/roles/clone_on_prem_vm/README.md)|A role to clone an existing on prem VM using the KVM hypervisor.
[cloud.aws_ops.import_image_and_run_aws_instance](https://github.com/ansible-collections/cloud.aws_ops/blob/main/roles/import_image_and_run_aws_instance/README.md)|A role that imports a local .raw image into an Amazon Machine Image (AMI) and run an AWS EC2 instance.

### Playbooks
Name | Description
--- | ---
[cloud.aws_ops.eda](https://github.com/ansible-collections/cloud.aws_ops/blob/main/playbooks/README.md)|A set of playbooks to restore AWS Cloudtrail configurations, created for use with the [cloud.aws_manage_cloudtrail_encryption rulebook](https://github.com/ansible-collections/cloud.aws_ops/blob/main/extensions/eda/rulebooks/AWS_MANAGE_CLOUDTRAIL_ENCRYPTION.md).
[cloud.aws_ops.webapp](https://github.com/ansible-collections/cloud.aws_ops/blob/main/playbooks/webapp/README.md)|A set of playbooks to create, delete, or migrate a webapp on AWS.
[cloud.aws_ops.upload_file_to_s3](https://github.com/ansible-collections/cloud.aws_ops/blob/main/playbooks/UPLOAD_FILE_TO_S3.md)|A playbook to upload a local file to S3.
[cloud.aws_ops.move_vm_from_on_prem_to_aws](https://github.com/ansible-collections/cloud.aws_ops/blob/main/playbooks/move_vm_from_on_prem_to_aws/README.md)|A playbook to migrate an existing on prem VM running on KVM hypervisor to AWS.

### Rulebooks
Name | Description
Expand Down
1 change: 1 addition & 0 deletions galaxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies:
amazon.aws: '>=5.1.0'
community.aws: '>=5.0.0'
amazon.cloud: '>=0.4.0'
community.libvirt: '>=1.2.0'
version: 1.0.3
build_ignore:
- .DS_Store
Expand Down
43 changes: 43 additions & 0 deletions playbooks/move_vm_from_on_prem_to_aws/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# cloud.aws_ops.move_vm_from_on_prem_to_aws playbooks

A playbook to migrate an existing on prem VM running on KVM hypervisor to AWS.

## Requirements

This playbook uses the ``cloud.aws_ops.clone_on_prem_vm`` role to clone an existing VM on prem using the KVM hypervisor and the ``cloud.aws_ops.import_image_and_run_aws_instance`` role to import a local .raw image into an Amazon machine image (AMI) and run an AWS EC2 instance. For a complete list of requirements, see [clone_on_prem_vm](../clone_on_prem_vm/README.md#Requirements) and [import_image_and_run_aws_instance](../roles/import_image_and_run_aws_instance/REAME.md#Requirements), respectively.


## Playbook Variables

For a full list of accepted variables see: [clone_on_prem_vm](../clone_on_prem_vm/README.md#Role-Variables) and respectively [import_image_and_run_aws_instance](../roles/import_image_and_run_aws_instance/REAME.md#Role-Variables).

## Example Usage

Create a `credentials.yml` file with the folling contents:

```yaml
aws_access_key: "xxxxxxxxxxxxxxxxxxxx"
aws_secret_key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
aws_region: "us-east-1"
```
Create an `inventory.yml` file with information about the host running the KVM hypervisor.

```yaml
---
all:
hosts:
kvm:
ansible_host: myhost
ansible_user: myuser
ansible_ssh_private_key_file: /path/to/private_key
groups: mygroup
```

All the variables defined in section ``Playbook Variables`` can be defined inside the ``vars.yml`` file.

Run the playbook:

```shell
ansible-playbook cloud.aws_ops.move_vm_from_on_prem_to_aws.move_vm_from_on_prem_to_aws -e "@credentials.yml" -e "@vars.yml" -i inventory.yml
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
- name: A playbook to migrate an existing on prem VM running on KVM hypervisor to AWS
hosts: localhost
gather_facts: false

module_defaults:
group/aws:
aws_access_key: "{{ aws_access_key | default(omit) }}"
aws_secret_key: "{{ aws_secret_key | default(omit) }}"
security_token: "{{ security_token | default(omit) }}"
region: "{{ aws_region | default('us-east-1') }}"

tasks:
- name: Import 'cloud.aws_ops.clone_on_prem_vm' role
ansible.builtin.import_role:
name: cloud.aws_ops.clone_on_prem_vm
vars:
clone_on_prem_vm_source_vm_name: "{{ clone_on_prem_vm_source_vm_name }}"
clone_on_prem_vm_image_name: "{{ clone_on_prem_vm_image_name }}"
clone_on_prem_vm_uri: "{{ clone_on_prem_vm_uri }}"
clone_on_prem_vm_local_image_path: "{{ clone_on_prem_vm_local_image_path }}"
clone_on_prem_vm_overwrite: "{{ clone_on_prem_vm_overwrite }}"
delegate_to: kvm

- name: Import 'cloud.aws_ops.import_image_and_run_aws_instance' role
ansible.builtin.import_role:
name: cloud.aws_ops.import_image_and_run_aws_instance
vars:
import_image_and_run_aws_instance_bucket_name: "{{ import_image_and_run_aws_instance_bucket_name }}"
import_image_and_run_aws_instance_image_path: "{{ clone_on_prem_vm_raw_image_path }}"
import_image_and_run_aws_instance_instance_name: "{{ import_image_and_run_aws_instance_instance_name }}"
import_image_and_run_aws_instance_instance_type: "{{ import_image_and_run_aws_instance_instance_type }}"
import_image_and_run_aws_instance_import_image_task_name: "{{ import_image_and_run_aws_instance_import_image_task_name }}"
import_image_and_run_aws_instances_keypair_name: "{{ import_image_and_run_aws_instances_keypair_name }}"
import_image_and_run_aws_instance_security_groups: "{{ import_image_and_run_aws_instance_security_groups }}"
import_image_and_run_aws_instance_vpc_subnet_id: "{{ import_image_and_run_aws_instance_vpc_subnet_id }}"
import_image_and_run_aws_instance_volumes: "{{ import_image_and_run_aws_instance_volumes }}"
77 changes: 77 additions & 0 deletions roles/clone_on_prem_vm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
clone_on_prem_vm
================

A role to clone an existing on prem VM using the KVM hypervisor. The role sets the **clone_on_prem_vm_raw_image_path** variable containing the path where the image was saved on localhost. This role requires privilege escalation because the .qcow2 file created by ``virt-clone`` is owned by root and ``qemu-img convert`` requires access to convert it to .raw.

Requirements
------------

**qemu** and **qemu-img** packages installed.

Role Variables
--------------

* **clone_on_prem_vm_source_vm_name**: (Required) The name of the on-prem VM you want to clone.
* **clone_on_prem_vm_image_name**: (Optional) The name you want to call the cloned image. If not set, the **clone_on_prem_vm_source_vm_name** will be used with a _-clone_ suffix.
* **clone_on_prem_vm_overwrite**: (Optional) Whether to overwrite or not an already existing on prem VM clone. Default: true.
* **clone_on_prem_vm_local_image_path**: (Optional) The path where you would like to save the image. If the path does not exists on localhost, the role will create it. If this parameter is not set, the role will save the image in a _~/tmp_ folder.
* **clone_on_prem_vm_uri**: (Optional) Libvirt connection uri. Default: "qemu:///system".

Dependencies
------------

N/A

Example Playbook
----------------

Create an `inventory.yml` file with information about the host running the KVM hypervisor.

```yaml
---
all:
hosts:
kvm:
ansible_host: myhost
ansible_user: myuser
ansible_ssh_private_key_file: /path/to/private_key
groups: mygroup
```
All the variables defined in section ``Playbook Variables`` can be defined inside the ``vars.yml`` file.
Create a ``playbook.yml`` file like this:
```
---
- hosts: kvm
gather_facts: true

tasks:
- name: Import 'cloud.aws_ops.clone_on_prem_vm' role
ansible.builtin.import_role:
name: cloud.aws_ops.clone_on_prem_vm
vars:
clone_on_prem_vm_source_vm_name: "{{ clone_on_prem_vm_source_vm_name }}"
clone_on_prem_vm_image_name: "{{ clone_on_prem_vm_image_name }}"
clone_on_prem_vm_local_image_path: "{{ clone_on_prem_vm_local_image_path }}"
clone_on_prem_vm_uri: "{{ clone_on_prem_vm_uri }}"
```
Run the playbook:
```shell
ansible-playbook playbook.yml -i inventory.yml -e "@vars.yml"
```

License
-------

GNU General Public License v3.0 or later

See [LICENCE](https://github.com/ansible-collections/cloud.aws_ops/blob/main/LICENSE) to see the full text.

Author Information
------------------

- Ansible Cloud Content Team
3 changes: 3 additions & 0 deletions roles/clone_on_prem_vm/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
clone_on_prem_vm_uri: "qemu:///system"
clone_on_prem_vm_overwrite: true
5 changes: 5 additions & 0 deletions roles/clone_on_prem_vm/handlers/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
- name: Delete temporary directory
ansible.builtin.file:
state: absent
path: "{{ clone_on_prem_vm__tmpdir.path }}"
144 changes: 144 additions & 0 deletions roles/clone_on_prem_vm/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
- name: Fail when 'clone_on_prem_vm_source_vm_name' is undefined
ansible.builtin.fail:
msg: The name of the VM you want to clone must be defined as clone_on_prem_vm_source_vm_name
when: clone_on_prem_vm_source_vm_name is undefined

- name: Gather package facts
ansible.builtin.package_facts:
manager: auto
register: package_facts

- name: qemu is not installed
debug:
msg: "qemu is not installed"
when: "'qemu' not in package_facts.ansible_facts.packages"

- name: qemu-img is not installed
debug:
msg: "qemu-img is not installed"
when: "'qemu-img' not in package_facts.ansible_facts.packages"

- name: Create temporary directory to create the clone in
ansible.builtin.tempfile:
state: directory
suffix: .storage
register: clone_on_prem_vm__tmpdir
notify:
- "Delete temporary directory"

- name: Get information about the on prem VM
community.libvirt.virt:
command: info
name: "{{ clone_on_prem_vm_source_vm_name }}"
uri: "{{ clone_on_prem_vm_uri }}"
register: clone_on_prem_vm__vm_info

- name: Fail when on prem VM does not exist
ansible.builtin.fail:
msg: "The on prem VM {{ clone_on_prem_vm_source_vm_name }} does not exist."
when: clone_on_prem_vm_source_vm_name not in clone_on_prem_vm__vm_info

- name: Fail when on prem VM's state is destroyed
ansible.builtin.fail:
msg: "The VM {{ clone_on_prem_vm_source_vm_name }} has been destroyed."
when: clone_on_prem_vm__vm_info[clone_on_prem_vm_source_vm_name].state == "destroyed"

- name: Set 'clone_on_prem_vm_image_name' varible
ansible.builtin.set_fact:
clone_on_prem_vm_image_name: "{{ clone_on_prem_vm_source_vm_name }}-clone"
when: clone_on_prem_vm_image_name is undefined

- name: Check if domain exists
community.libvirt.virt:
name: "{{ clone_on_prem_vm_image_name }}"
command: info
uri: "{{ clone_on_prem_vm_uri }}"
register: clone_on_prem_vm__domain_info

- name: Fail when a domain already exists
ansible.builtin.fail:
msg: "A domain {{ clone_on_prem_vm_image_name }} already exists. Please undefine it first or set clone_on_prem_vm_overwrite: true."
when: clone_on_prem_vm_image_name in clone_on_prem_vm__domain_info and clone_on_prem_vm_overwrite is false

- name: Undefine domain
community.libvirt.virt:
name: "{{ clone_on_prem_vm_image_name }}"
command: undefine
when: clone_on_prem_vm_image_name in clone_on_prem_vm__domain_info and clone_on_prem_vm_overwrite is true

- name: Ensure on prem VM is paused
community.libvirt.virt:
state: paused
name: "{{ clone_on_prem_vm_source_vm_name }}"
uri: "{{ clone_on_prem_vm_uri }}"
when: clone_on_prem_vm__vm_info[clone_on_prem_vm_source_vm_name].state == "running"

- name: Set 'clone_on_prem_vm__clone_path' and 'clone_on_prem_vm__raw_image_path'
ansible.builtin.set_fact:
clone_on_prem_vm__clone_path: "{{ clone_on_prem_vm__tmpdir.path }}/{{ clone_on_prem_vm_image_name }}.qcow2"
clone_on_prem_vm__raw_image_path: "{{ clone_on_prem_vm__tmpdir.path }}/{{ clone_on_prem_vm_image_name }}.raw"

- name: Cloning {{ clone_on_prem_vm_source_vm_name }} on prem VM
ansible.builtin.command: |
virt-clone --original {{ clone_on_prem_vm_source_vm_name }} \
--name {{ clone_on_prem_vm_image_name }} \
--file {{ clone_on_prem_vm__clone_path }}
environment:
LIBVIRT_DEFAULT_URI: "{{ clone_on_prem_vm_uri }}"

- name: Get information about the clone
ansible.builtin.stat:
path: "{{ clone_on_prem_vm__clone_path }}"
register: clone_on_prem_vm__clone_info

# Privilege escalation is needed because the .qcow2 file is owned by root
# when default hypervisor is used
- name: Convert qcow2 to raw using qemu-img with privilege escalation
ansible.builtin.command: |
qemu-img convert -f qcow2 -O raw \
{{ clone_on_prem_vm__clone_path }} \
{{ clone_on_prem_vm__raw_image_path }}
become: true
become_method: sudo
environment:
LIBVIRT_DEFAULT_URI: "{{ clone_on_prem_vm_uri }}"
when: clone_on_prem_vm__clone_info.stat.exists and clone_on_prem_vm__clone_info.stat.pw_name == "root"

- name: Convert qcow2 to raw using qemu-img
ansible.builtin.command: |
qemu-img convert -f qcow2 -O raw \
{{ clone_on_prem_vm__clone_path }} \
{{ clone_on_prem_vm__raw_image_path }}
environment:
LIBVIRT_DEFAULT_URI: "{{ clone_on_prem_vm_uri }}"
when: clone_on_prem_vm__clone_info.stat.exists and clone_on_prem_vm__clone_info.stat.pw_name != "root"

- name: Create temporary directory to localhost when clone_on_prem_vm_local_image_path is not set
ansible.builtin.tempfile:
state: directory
suffix: .storage
register: clone_on_prem_vm__dir_localhost
when: clone_on_prem_vm_local_image_path is undefined
delegate_to: localhost

- name: Create directory if it does not exist
ansible.builtin.file:
path: "{{ clone_on_prem_vm_local_image_path }}"
state: directory
mode: 0775
recurse: yes
register: clone_on_prem_vm__dir_localhost
when: clone_on_prem_vm_local_image_path is defined
delegate_to: localhost

- name: Fetch the converted RAW image to localhost
ansible.builtin.fetch:
src: "{{ clone_on_prem_vm__raw_image_path }}"
dest: "{{ clone_on_prem_vm__dir_localhost.path }}"
validate_checksum: true
register: clone_on_prem_vm_fetch_to_localhost

- name: Set 'clone_on_prem_vm_raw_image_path'
ansible.builtin.set_fact:
clone_on_prem_vm_raw_image_path: "{{ clone_on_prem_vm_fetch_to_localhost.dest }}"
Loading

0 comments on commit f24c6f3

Please sign in to comment.