{"id":22057,"date":"2024-04-16T00:27:54","date_gmt":"2024-04-15T21:27:54","guid":{"rendered":"https:\/\/kifarunix.com\/?p=22057"},"modified":"2024-04-16T00:27:58","modified_gmt":"2024-04-15T21:27:58","slug":"automate-virtual-machine-creation-on-kvm-with-terraform","status":"publish","type":"post","link":"https:\/\/kifarunix.com\/automate-virtual-machine-creation-on-kvm-with-terraform\/","title":{"rendered":"Automate Virtual Machine Creation on KVM with Terraform"},"content":{"rendered":"\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1047\" height=\"588\" src=\"https:\/\/kifarunix.com\/wp-content\/uploads\/2024\/04\/provision-vms-with-terraform-on-kvm-libvirt.png\" alt=\"kvm and terraform\" class=\"wp-image-22171\" title=\"\" srcset=\"https:\/\/kifarunix.com\/wp-content\/uploads\/2024\/04\/provision-vms-with-terraform-on-kvm-libvirt.png?v=1713216194 1047w, https:\/\/kifarunix.com\/wp-content\/uploads\/2024\/04\/provision-vms-with-terraform-on-kvm-libvirt-768x431.png?v=1713216194 768w\" sizes=\"(max-width: 1047px) 100vw, 1047px\" \/><\/figure>\n\n\n\n<p>In this tutorial, you will learn how to automate Virtual Machine creation on KVM with <a href=\"https:\/\/developer.hashicorp.com\/terraform\/docs\" target=\"_blank\" rel=\"noreferrer noopener\">Terraform<\/a>. If you are creating multiple virtual machines on KVM more frequently, then you might have realized that the process is time consuming and not easy to manage especially where consistency is key. This is where Terraform, an Infrastructure as Code tool,  comes in; to help you automate the process and ensure consistent, efficient and error-free infrastructure provisioning. It can be used to provision infrastructure both on cloud and on-premise environments, this guide is suitable for those who are new to Terraform and want to experiment it by creating virtual machines on their KVM server. Terraform provides <a href=\"https:\/\/registry.terraform.io\/providers\/dmacvicar\/libvirt\/latest\/docs\" target=\"_blank\" rel=\"noreferrer noopener\">libvirt providers<\/a> for interacting with KVM hypervisor.<\/p>\n\n\n\n<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Table of Contents<\/h2><nav><ul><li><a href=\"#automating-virtual-machine-creation-on-kvm-with-terraform\">Automating Virtual Machine Creation on KVM with Terraform<\/a><ul><li><a href=\"#introduction-to-terraform-and-basic-architecture\">Introduction to Terraform and Basic Architecture<\/a><\/li><li><a href=\"#installing-terraform-on-linux\">Installing Terraform on Linux<\/a><\/li><li><a href=\"#install-kvm-on-linux\">Install KVM on Linux<\/a><\/li><li><a href=\"#provision-single-basic-vm-using-terraform-on-kvm\">Provision Single Basic VM using Terraform on KVM<\/a><ul><li><a href=\"#create-terraform-configuration-file\">Create Terraform Configuration File<\/a><\/li><li><a href=\"#initialize-terraform-working-directory\">Initialize Terraform working directory<\/a><\/li><li><a href=\"#validate-your-configurations\">Validate your Configurations<\/a><\/li><li><a href=\"#plan-your-infrastructure\">Plan Your Infrastructure<\/a><\/li><li><a href=\"#apply-the-configuration\">Apply the Configuration<\/a><\/li><\/ul><\/li><li><a href=\"#more-advance-provision-multiple-v-ms-using-terraform\">More Advance: Provision Multiple VMs using Terraform<\/a><\/li><li><a href=\"#conclusion\">Conclusion<\/a><\/li><\/ul><\/li><\/ul><\/nav><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"automating-virtual-machine-creation-on-kvm-with-terraform\">Automating Virtual Machine Creation on KVM with Terraform<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"introduction-to-terraform-and-basic-architecture\">Introduction to Terraform and Basic Architecture<\/h3>\n\n\n\n<p>To begin with, you can have a look at the guide below to learn one or two things on the core functionality of Terraform.<\/p>\n\n\n\n<p><a href=\"https:\/\/kifarunix.com\/introduction-to-terraform-understanding-basic-architecture\/\" target=\"_blank\" rel=\"noreferrer noopener\">Introduction to Terraform: Understanding Basic Architecture<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"installing-terraform-on-linux\">Installing Terraform on Linux<\/h3>\n\n\n\n<p>Similarly, learn how to install Terraform on Linux.<\/p>\n\n\n\n<p><a href=\"https:\/\/kifarunix.com\/install-terraform-on-ubuntu-24-04\/\" target=\"_blank\" rel=\"noreferrer noopener\">Install Terraform on Ubuntu 24.04<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"install-kvm-on-linux\">Install KVM on Linux<\/h3>\n\n\n\n<p>And of course, you need to have KVM already installed on your machine. Check the guides below on how to install KVM.<\/p>\n\n\n\n<p><a href=\"https:\/\/kifarunix.com\/?s=install+kvm\" target=\"_blank\" rel=\"noreferrer noopener\">How to Install KVM on Linux<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"provision-single-basic-vm-using-terraform-on-kvm\">Provision Single Basic VM using Terraform on KVM<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"create-terraform-configuration-file\">Create Terraform Configuration File<\/h4>\n\n\n\n<p>As you already know, Terraform uses declarative configuration language to define a desired state of the infrastructure resource to be provisioned. Thus, create a directory for your project.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir ~\/terraform-kvm<\/code><\/pre>\n\n\n\n<p>The directory name can be anything that suits you.<\/p>\n\n\n\n<p>Next, navigate into the directory and create Terraform main configuration file.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd ~\/terraform-kvm<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>vim main.tf<\/code><\/pre>\n\n\n\n<p>In the configuration file, define your infrastructure provider\/plugin, resources you need to provision, outputs, variables, provisioners, if necessary.<\/p>\n\n\n\n<p>We are using Terraform Libvirt provider\/plugin to provision KVM virtual machines. Refer to <a href=\"https:\/\/registry.terraform.io\/providers\/dmacvicar\/libvirt\/latest\/docs\" target=\"_blank\" rel=\"noreferrer noopener\">documentation page<\/a> for more information.<\/p>\n\n\n\n<p>Below is our configuration file;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat main.tf<\/code><\/pre>\n\n\n\n<pre class=\"scroll-box\"><code>terraform {\n  required_providers {\n    libvirt = {\n      source  = \"dmacvicar\/libvirt\"\n    }\n  }\n}\n\nprovider \"libvirt\" {\n  uri = \"qemu:\/\/\/system\"\n}\n\nresource \"libvirt_volume\" \"volumes\" {\n  name    = \"tf-jammy.${var.img_format}\"\n  pool    = \"default\"\n  source  = var.img_source\n  format  = var.img_format\n}\n\nresource \"libvirt_domain\" \"guest\" {\n  name   = \"tf-jammy\"\n  memory = var.mem_size\n  vcpu   = var.cpu_cores\n\n  disk {\n    volume_id = libvirt_volume.volumes.id\n  }\n\n  network_interface {\n    network_name = \"default\"\n    #wait_for_lease = true\n  }\n\n  console {\n    type        = \"pty\"\n    target_type = \"serial\"\n    target_port = \"0\"\n  }\n\n  graphics {\n    type      = \"spice\"\n    autoport  = true\n    listen_type = \"address\"\n  }\n}\n<\/code><\/pre>\n\n\n\n<p>Where:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>terraform<\/code> Block<\/strong> specifies the required provider for this configuration. The required provider is <strong>libvirt<\/strong> from dmacvicar\/libvirt in the registry.<\/li>\n\n\n\n<li><strong><code>provider \"libvirt\"<\/code> Block<\/strong> defines the specific provider configurations, including how to connect to the libvirt host.<\/li>\n\n\n\n<li><strong>resource &#8220;libvirt_volume&#8221;<\/strong> defines resource storage configurations. Image format and source are defined using variables stored in the <strong>variables.tf<\/strong> file.<\/li>\n\n\n\n<li><strong>resource &#8220;libvirt_domain&#8221;<\/strong> defines the virtual machine parameters such as RAM size, CPU cores, disk configuration, network configuration, console configuration, and graphics configuration. RAM and CPU details are defined in the variables configuration file.<\/li>\n<\/ul>\n\n\n\n<p>Here is our variables configuration file.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat variables.tf<\/code><\/pre>\n\n\n\n<pre class=\"scroll-box\"><code>variable \"img_source\" {\n  description = \"Ubuntu 22.04 LTS Cloud Image\"\n  default = \"https:\/\/cloud-images.ubuntu.com\/jammy\/current\/jammy-server-cloudimg-amd64.img\"\n}\n\nvariable \"img_format\" {\n  description = \"QCow2 UEFI\/GPT Bootable disk image\"\n  default = \"qcow2\"\n  type = string\n}\n\nvariable \"mem_size\" {\n  description = \"Amount of RAM (in MiB) for the virtual machine\"\n  type        = string\n  default     = \"2048\"\n}\n\nvariable \"cpu_cores\" {\n  description = \"Number of CPU cores for the virtual machine\"\n  type        = number\n  default     = 2\n}\n<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"initialize-terraform-working-directory\">Initialize Terraform working directory<\/h4>\n\n\n\n<p>Once you have the configuration ready, you can run the initialization command to install the required Terraform plugins.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init<\/code><\/pre>\n\n\n\n<p>Note that we are executing the command in the same directory where we have main configuration file (main.tf).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ls -1<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>main.tf\nvariables.tf<\/code><\/pre>\n\n\n\n<p>Sample initialization output.<\/p>\n\n\n\n<pre class=\"scroll-box\"><code>\nInitializing the backend...\n\nInitializing provider plugins...\n- Finding latest version of dmacvicar\/libvirt...\n- Installing dmacvicar\/libvirt v0.7.6...\n- Installed dmacvicar\/libvirt v0.7.6 (self-signed, key ID 0833E38C51E74D26)\n\nPartner and community providers are signed by their developers.\nIf you'd like to know more about provider signing, you can read about it here:\nhttps:\/\/www.terraform.io\/docs\/cli\/plugins\/signing.html\n\nTerraform has created a lock file .terraform.lock.hcl to record the provider\nselections it made above. Include this file in your version control repository\nso that Terraform can guarantee to make the same selections by default when\nyou run \"terraform init\" in the future.\n\nTerraform has been successfully initialized!\n\nYou may now begin working with Terraform. Try running \"terraform plan\" to see\nany changes that are required for your infrastructure. All Terraform commands\nshould now work.\n\nIf you ever set or change modules or backend configuration for Terraform,\nrerun this command to reinitialize your working directory. If you forget, other\ncommands will detect it and remind you to do so if necessary.\n<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"validate-your-configurations\">Validate your Configurations<\/h4>\n\n\n\n<p>As much as Terraform will validate the configuration when you run plan\/apply commands, you can as well be able to check if your Terraform files are syntactically correct before running any commands.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform validate<\/code><\/pre>\n\n\n\n<p>If the configuration files are syntactically correct, you should get such an output;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>Success! The configuration is valid.<\/strong><\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"plan-your-infrastructure\">Plan Your Infrastructure<\/h4>\n\n\n\n<p>Run the <code>terraform plan<\/code> command to create an execution plan. This command analyzes your configuration and shows you what Terraform will do when you apply it.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan<\/code><\/pre>\n\n\n\n<p>Here is our sample plan. Review the plan to ensure that it matches your expectations.<\/p>\n\n\n\n<pre class=\"scroll-box\"><code>\nTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:\n  + create\n\nTerraform will perform the following actions:\n\n  # libvirt_domain.guest will be created\n  + resource \"libvirt_domain\" \"guest\" {\n      + arch        = (known after apply)\n      + autostart   = (known after apply)\n      + emulator    = (known after apply)\n      + fw_cfg_name = \"opt\/com.coreos\/config\"\n      + id          = (known after apply)\n      + machine     = (known after apply)\n      + memory      = 2048\n      + name        = \"tf-jammy\"\n      + qemu_agent  = false\n      + running     = true\n      + type        = \"kvm\"\n      + vcpu        = 2\n\n      + console {\n          + source_host    = \"127.0.0.1\"\n          + source_service = \"0\"\n          + target_port    = \"0\"\n          + target_type    = \"serial\"\n          + type           = \"pty\"\n        }\n\n      + disk {\n          + scsi      = false\n          + volume_id = (known after apply)\n        }\n\n      + graphics {\n          + autoport       = true\n          + listen_address = \"127.0.0.1\"\n          + listen_type    = \"address\"\n          + type           = \"spice\"\n        }\n\n      + network_interface {\n          + addresses      = (known after apply)\n          + hostname       = (known after apply)\n          + mac            = (known after apply)\n          + network_id     = (known after apply)\n          + network_name   = \"default\"\n          + wait_for_lease = true\n        }\n    }\n\n  # libvirt_volume.volumes will be created\n  + resource \"libvirt_volume\" \"volumes\" {\n      + format = \"qcow2\"\n      + id     = (known after apply)\n      + name   = \"tf-jammy.qcow2\"\n      + pool   = \"default\"\n      + size   = (known after apply)\n      + source = \"https:\/\/cloud-images.ubuntu.com\/jammy\/current\/jammy-server-cloudimg-amd64.img\"\n    }\n\nPlan: 2 to add, 0 to change, 0 to destroy.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nNote: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run \"terraform apply\"\nnow.\n<\/code><\/pre>\n\n\n\n<p>To save the plan to a file, use the option, <strong>-out=&lt;filename&gt;<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -out=kvm-plan<\/code><\/pre>\n\n\n\n<p>You will see an output like;<\/p>\n\n\n\n<pre class=\"scroll-box\"><code>...\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nSaved the plan to: kvm-plan\n\nTo perform exactly these actions, run the following command to apply:\n    terraform apply \"kvm-plan\"\n\n<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"apply-the-configuration\">Apply the Configuration<\/h4>\n\n\n\n<p>If the plan is meeting your expectations, proceed to apply the changes and provision the infrastructure.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform apply<\/code><\/pre>\n\n\n\n<p>If you had save the plan to a file, you can specify the plan file. For example;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform apply \"kvm-plan\"<\/code><\/pre>\n\n\n\n<p>When you apply the plan, Terraform will ask you to confirm that you really need to perform the actions outlined in the plan. Answer <strong>yes<\/strong> and proceed.<\/p>\n\n\n\n<pre class=\"scroll-box\"><code>\nTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:\n  + create\n\nTerraform will perform the following actions:\n\n  # libvirt_domain.guest will be created\n  + resource \"libvirt_domain\" \"guest\" {\n      + arch        = (known after apply)\n      + autostart   = (known after apply)\n      + emulator    = (known after apply)\n      + fw_cfg_name = \"opt\/com.coreos\/config\"\n      + id          = (known after apply)\n      + machine     = (known after apply)\n      + memory      = 2048\n      + name        = \"tf-jammy\"\n      + qemu_agent  = false\n      + running     = true\n      + type        = \"kvm\"\n      + vcpu        = 2\n\n      + console {\n          + source_host    = \"127.0.0.1\"\n          + source_service = \"0\"\n          + target_port    = \"0\"\n          + target_type    = \"serial\"\n          + type           = \"pty\"\n        }\n\n      + disk {\n          + scsi      = false\n          + volume_id = (known after apply)\n        }\n\n      + graphics {\n          + autoport       = true\n          + listen_address = \"127.0.0.1\"\n          + listen_type    = \"address\"\n          + type           = \"spice\"\n        }\n\n      + network_interface {\n          + addresses    = (known after apply)\n          + hostname     = (known after apply)\n          + mac          = (known after apply)\n          + network_id   = (known after apply)\n          + network_name = \"default\"\n        }\n    }\n\n  # libvirt_volume.volumes will be created\n  + resource \"libvirt_volume\" \"volumes\" {\n      + format = \"qcow2\"\n      + id     = (known after apply)\n      + name   = \"tf-jammy.qcow2\"\n      + pool   = \"default\"\n      + size   = (known after apply)\n      + source = \"https:\/\/cloud-images.ubuntu.com\/jammy\/current\/jammy-server-cloudimg-amd64.img\"\n    }\n\nPlan: 2 to add, 0 to change, 0 to destroy.\n\nDo you want to perform these actions?\n  Terraform will perform the actions described above.\n  Only 'yes' will be accepted to approve.\n\n  Enter a value: yes\n\nlibvirt_volume.volumes: Creating...\nlibvirt_volume.volumes: Creation complete after 6s [id=\/var\/lib\/libvirt\/images\/tf-jammy.qcow2]\nlibvirt_domain.guest: Creating...\nlibvirt_domain.guest: Creation complete after 1s [id=dbf39c27-c0bc-4052-a32c-b5c3ee2f3895]\n\nApply complete! Resources: 2 added, 0 changed, 0 destroyed.\n<\/code><\/pre>\n\n\n\n<p>As you can see, our virtual machine is now created!<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo virsh list<\/code><\/pre>\n\n\n\n<p>And there you go.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> 19   tf-jammy    running<\/code><\/pre>\n\n\n\n<p>Confirm from Virt-manager;<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1434\" height=\"841\" src=\"https:\/\/kifarunix.com\/wp-content\/uploads\/2024\/04\/terraform-libvirt-vm-kvm.png?v=1713096882\" alt=\"kvm virtual machine provisioned using terraform\" class=\"wp-image-22164\" title=\"\" srcset=\"https:\/\/kifarunix.com\/wp-content\/uploads\/2024\/04\/terraform-libvirt-vm-kvm.png?v=1713096882 1434w, https:\/\/kifarunix.com\/wp-content\/uploads\/2024\/04\/terraform-libvirt-vm-kvm-768x450.png?v=1713096882 768w\" sizes=\"(max-width: 1434px) 100vw, 1434px\" \/><\/figure>\n\n\n\n<p>So, you have just created a virtual machine, with no defined usernames for login and that is using the default network.<\/p>\n\n\n\n<p>If you want to delete the virtual machine, simply execute;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform destroy<\/code><\/pre>\n\n\n\n<pre class=\"scroll-box\"><code>libvirt_volume.volumes: Refreshing state... [id=\/var\/lib\/libvirt\/images\/tf-jammy.qcow2]\nlibvirt_domain.guest: Refreshing state... [id=dbf39c27-c0bc-4052-a32c-b5c3ee2f3895]\n\nTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:\n  - destroy\n\nTerraform will perform the following actions:\n\n  # libvirt_domain.guest will be destroyed\n  - resource \"libvirt_domain\" \"guest\" {\n      - arch        = \"x86_64\" -> null\n      - autostart   = false -> null\n      - cmdline     = [] -> null\n      - emulator    = \"\/usr\/bin\/qemu-system-x86_64\" -> null\n      - fw_cfg_name = \"opt\/com.coreos\/config\" -> null\n      - id          = \"dbf39c27-c0bc-4052-a32c-b5c3ee2f3895\" -> null\n      - machine     = \"pc\" -> null\n      - memory      = 2048 -> null\n      - name        = \"tf-jammy\" -> null\n      - qemu_agent  = false -> null\n      - running     = true -> null\n      - type        = \"kvm\" -> null\n      - vcpu        = 2 -> null\n        # (3 unchanged attributes hidden)\n\n      - console {\n          - source_host    = \"127.0.0.1\" -> null\n          - source_service = \"0\" -> null\n          - target_port    = \"0\" -> null\n          - target_type    = \"serial\" -> null\n          - type           = \"pty\" -> null\n            # (1 unchanged attribute hidden)\n        }\n\n      - cpu {\n          - mode = \"custom\" -> null\n        }\n\n      - disk {\n          - scsi         = false -> null\n          - volume_id    = \"\/var\/lib\/libvirt\/images\/tf-jammy.qcow2\" -> null\n            # (4 unchanged attributes hidden)\n        }\n\n      - graphics {\n          - autoport       = true -> null\n          - listen_address = \"127.0.0.1\" -> null\n          - listen_type    = \"address\" -> null\n          - type           = \"spice\" -> null\n          - websocket      = 0 -> null\n        }\n\n      - network_interface {\n          - addresses      = [] -> null\n          - mac            = \"52:54:00:02:E0:E4\" -> null\n          - network_id     = \"8a9db7a7-3495-49c3-8137-c43396524bfa\" -> null\n          - network_name   = \"default\" -> null\n          - wait_for_lease = false -> null\n            # (5 unchanged attributes hidden)\n        }\n    }\n\n  # libvirt_volume.volumes will be destroyed\n  - resource \"libvirt_volume\" \"volumes\" {\n      - format = \"qcow2\" -> null\n      - id     = \"\/var\/lib\/libvirt\/images\/tf-jammy.qcow2\" -> null\n      - name   = \"tf-jammy.qcow2\" -> null\n      - pool   = \"default\" -> null\n      - size   = 2361393152 -> null\n      - source = \"https:\/\/cloud-images.ubuntu.com\/jammy\/current\/jammy-server-cloudimg-amd64.img\" -> null\n    }\n\nPlan: 0 to add, 0 to change, 2 to destroy.\n\nDo you really want to destroy all resources?\n  Terraform will destroy all your managed infrastructure, as shown above.\n  There is no undo. Only 'yes' will be accepted to confirm.\n\n  Enter a value: yes\n\nlibvirt_domain.guest: Destroying... [id=dbf39c27-c0bc-4052-a32c-b5c3ee2f3895]\nlibvirt_domain.guest: Destruction complete after 1s\nlibvirt_volume.volumes: Destroying... [id=\/var\/lib\/libvirt\/images\/tf-jammy.qcow2]\nlibvirt_volume.volumes: Destruction complete after 0s\n\nDestroy complete! Resources: 2 destroyed.\n\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"more-advance-provision-multiple-v-ms-using-terraform\">More Advance: Provision Multiple VMs using Terraform<\/h3>\n\n\n\n<p>You can customize your Terraform configuration further to allow you create multiple vms at once, run them on specific network and create user accounts on them.<\/p>\n\n\n\n<p>For example, I want to create three virtual machines at once, vm-01,02,03, attach the machines to a network called tf-network (192.168.133.0\/24), assign the virtual machines IP addresses dynamically and create a user on each of them that uses my password for authentication.<\/p>\n\n\n\n<p>This is how I can update my Terraform modules.<\/p>\n\n\n\n<p>See updated variables file.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat variables.tf<\/code><\/pre>\n\n\n\n<pre class=\"scroll-box\"><code>variable \"img_source\" {\n  description = \"Ubuntu 22.04 LTS Cloud Image\"\n  default = \"https:\/\/cloud-images.ubuntu.com\/jammy\/current\/jammy-server-cloudimg-amd64.img\"\n}\n\nvariable \"img_format\" {\n  description = \"QCow2 UEFI\/GPT Bootable disk image\"\n  default = \"qcow2\"\n  type = string\n}\n\nvariable \"mem_size\" {\n  description = \"Amount of RAM (in MiB) for the virtual machine\"\n  type        = string\n  default     = \"2048\"\n}\n\nvariable \"cpu_cores\" {\n  description = \"Number of CPU cores for the virtual machine\"\n  type        = number\n  default     = 2\n}\nvariable \"vm_count\" {\n  description = \"Number of virtual machines to create\"\n  type        = number\n  default     = 3\n}\n\nvariable \"vm_network\" {\n  description = \"Custom Network for my virtual machine names\"\n  default     = \"192.168.133.0\/24\"\n}\n\nvariable user_account {\n  type = string\n  default = \"kifarunix\"\n}\n<\/code><\/pre>\n\n\n\n<p>Updating the Terraform main configuration file to create a network resource as as well the cloud init for creating the user accounts in the virtual machines. We have also updated the configuration to provision 3 virtual machines as per the count number defined in the variables configuration.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat main.tf<\/code><\/pre>\n\n\n\n<pre class=\"scroll-box\"><code>terraform {\n  required_providers {\n    libvirt = {\n      source  = \"dmacvicar\/libvirt\"\n    }\n  }\n}\n\nprovider \"libvirt\" {\n  uri = \"qemu:\/\/\/system\"\n}\n\nresource \"libvirt_volume\" \"volumes\" {\n  for_each = toset([for i in range(var.vm_count) : format(\"vm-%02d\", i + 1)])\n  name    = \"${each.key}.${var.img_format}\"\n  pool    = \"default\"\n  source  = var.img_source\n  format  = var.img_format\n}\n\nresource \"libvirt_network\" \"tf_network\" {\n  name      = \"tf-network\"\n  mode      = \"nat\"\n  autostart = \"true\"\n  addresses = [var.vm_network]\n  dhcp {\n    enabled = true\n  }  \n  dns {\n    enabled = true\n  }\n}\n\nresource \"libvirt_domain\" \"guest\" {\n  for_each = toset([for i in range(var.vm_count) : format(\"vm-%02d\", i + 1)])\n  name    = each.key\n  memory  = var.mem_size\n  vcpu    = var.cpu_cores\n\n  disk {\n    volume_id = libvirt_volume.volumes[each.key].id\n  }\n\n  network_interface {\n    network_name    = libvirt_network.tf_network.name\n#    wait_for_lease  = true\n  }\n\n  cloudinit = \"${libvirt_cloudinit_disk.commoninit.id}\"\n\n  console {\n    type        = \"pty\"\n    target_type = \"serial\"\n    target_port = \"0\"\n  }\n\n  graphics {\n    type        = \"spice\"\n    autoport    = true\n    listen_type = \"address\"\n  }\n}\n\nresource \"libvirt_cloudinit_disk\" \"commoninit\" {\n  name           = \"commoninit.iso\"\n  user_data      = data.template_file.user_data.rendered\n  pool           = \"default\"\n}\n\ndata \"template_file\" \"user_data\" {\n  template = file(\"${path.module}\/cloud_init.cfg\")\n  vars = {\n    user_account = var.user_account\n  }\n}\n\noutput \"node_info\" {\n  value = {\n    for name, vm in libvirt_domain.guest : name => {\n      name         = vm.name\n      ip_address   = vm.network_interface[0].addresses[0]\n    }\n  }\n}\n<\/code><\/pre>\n\n\n\n<p>As you can see, we introduced yet another plugin, template_file, for use with cloud init.<\/p>\n\n\n\n<p>We have also introduced outputs, just so as to get the vm names and IPs (only possible after the vms are up and running) printed to standard output.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  for_each = toset(&#91;for i in range(var.vm_count) : format(\"vm-%02d\", i + 1)])<\/code><\/pre>\n\n\n\n<p>This will generate vms names as vm-01,02,03&#8230;<\/p>\n\n\n\n<p>The cloud init user data file is created on the same directory as with main Terraform configuration.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat cloud_init.cfg<\/code><\/pre>\n\n\n\n<pre class=\"scroll-box\"><code>#cloud-config\nusers:\n  - name: ${user_account}\n    groups: sudo\n    shell: \/bin\/bash\n    passwd: $6$FzMWq3kKBNexpm0T$U1BNz7eXKSPoYVR9Y\/LClN6FquV\/MpesQu5RPI.YGA5cFRKHdh5RgiNi5MA12hUtjFtRfQ6522ymK\/wH1IZZM1\n    lock_passwd: false\nssh_pwauth: true\n<\/code><\/pre>\n\n\n\n<p>Password hash is generated using <strong>mkpasswd -m sha-512<\/strong> command. mkpasswd is provided by whois package, just in case it is missing on your system.<\/p>\n\n\n\n<p>So, this will create a user called kifarunix, as defined in the variables file, that can login via password. Read more on <a href=\"https:\/\/cloudinit.readthedocs.io\/en\/latest\/reference\/examples.html\" target=\"_blank\" rel=\"noreferrer noopener\">Cloud config reference<\/a>.<\/p>\n\n\n\n<p>Thus, re-initialize the directory to install extra required plugins;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init<\/code><\/pre>\n\n\n\n<pre class=\"scroll-box\"><code>\nInitializing the backend...\n\nInitializing provider plugins...\n- Finding latest version of hashicorp\/template...\n- Reusing previous version of dmacvicar\/libvirt from the dependency lock file\n- Installing hashicorp\/template v2.2.0...\n- Installed hashicorp\/template v2.2.0 (signed by HashiCorp)\n- Using previously-installed dmacvicar\/libvirt v0.7.6\n\nTerraform has made some changes to the provider dependency selections recorded\nin the .terraform.lock.hcl file. Review those changes and commit them to your\nversion control system if they represent changes you intended to make.\n\nTerraform has been successfully initialized!\n\nYou may now begin working with Terraform. Try running \"terraform plan\" to see\nany changes that are required for your infrastructure. All Terraform commands\nshould now work.\n\nIf you ever set or change modules or backend configuration for Terraform,\nrerun this command to reinitialize your working directory. If you forget, other\ncommands will detect it and remind you to do so if necessary.\n<\/code><\/pre>\n\n\n\n<p>Validate the configuration;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform validate<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! The configuration is valid.<\/code><\/pre>\n\n\n\n<p>Run the plan!<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan<\/code><\/pre>\n\n\n\n<pre class=\"scroll-box\"><code>data.template_file.user_data: Reading...\ndata.template_file.user_data: Read complete after 0s [id=a8dd3d6c50dce1a1fe85bff1b7c3150b97c2b092dcdf33505c864fe4cb61a964]\nlibvirt_volume.volumes: Refreshing state... [id=\/var\/lib\/libvirt\/images\/tf-jammy.qcow2]\nlibvirt_domain.guest: Refreshing state... [id=3c1446d1-cbf9-4ccc-98d4-c6f5527a6a15]\n\nNote: Objects have changed outside of Terraform\n\nTerraform detected the following changes made outside of Terraform since the last \"terraform apply\" which may have affected this plan:\n\n  # libvirt_domain.guest has changed\n  ~ resource \"libvirt_domain\" \"guest\" {\n      + cmdline     = []\n        id          = \"3c1446d1-cbf9-4ccc-98d4-c6f5527a6a15\"\n        name        = \"tf-jammy\"\n        # (13 unchanged attributes hidden)\n\n        # (5 unchanged blocks hidden)\n    }\n\n\nUnless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may\ninclude actions to undo or respond to these changes.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:\n  + create\n  - destroy\n\nTerraform will perform the following actions:\n\n  # libvirt_cloudinit_disk.commoninit will be created\n  + resource \"libvirt_cloudinit_disk\" \"commoninit\" {\n      + id        = (known after apply)\n      + name      = \"commoninit.iso\"\n      + pool      = \"default\"\n      + user_data = <<-EOT\n            #cloud-config\n            users:\n              - name: kifarunix\n                groups: sudo\n                shell: \/bin\/bash\n                passwd: $6$FzMWq3kKBNexpm0T$U1BNz7eXKSPoYVR9Y\/LClN6FquV\/MpesQu5RPI.YGA5cFRKHdh5RgiNi5MA12hUtjFtRfQ6522ymK\/wH1IZZM1\n                lock_passwd: false\n            ssh_pwauth: true\n        EOT\n    }\n\n  # libvirt_domain.guest will be destroyed\n  # (because resource uses count or for_each)\n  - resource \"libvirt_domain\" \"guest\" {\n      - arch        = \"x86_64\" -> null\n      - autostart   = false -> null\n      - cmdline     = [] -> null\n      - emulator    = \"\/usr\/bin\/qemu-system-x86_64\" -> null\n      - fw_cfg_name = \"opt\/com.coreos\/config\" -> null\n      - id          = \"3c1446d1-cbf9-4ccc-98d4-c6f5527a6a15\" -> null\n      - machine     = \"pc\" -> null\n      - memory      = 2048 -> null\n      - name        = \"tf-jammy\" -> null\n      - qemu_agent  = false -> null\n      - running     = true -> null\n      - type        = \"kvm\" -> null\n      - vcpu        = 2 -> null\n        # (3 unchanged attributes hidden)\n\n      - console {\n          - source_host    = \"127.0.0.1\" -> null\n          - source_service = \"0\" -> null\n          - target_port    = \"0\" -> null\n          - target_type    = \"serial\" -> null\n          - type           = \"pty\" -> null\n            # (1 unchanged attribute hidden)\n        }\n\n      - cpu {\n          - mode = \"custom\" -> null\n        }\n\n      - disk {\n          - scsi         = false -> null\n          - volume_id    = \"\/var\/lib\/libvirt\/images\/tf-jammy.qcow2\" -> null\n            # (4 unchanged attributes hidden)\n        }\n\n      - graphics {\n          - autoport       = true -> null\n          - listen_address = \"127.0.0.1\" -> null\n          - listen_type    = \"address\" -> null\n          - type           = \"spice\" -> null\n          - websocket      = 0 -> null\n        }\n\n      - network_interface {\n          - addresses      = [] -> null\n          - mac            = \"52:54:00:5A:A0:D2\" -> null\n          - network_id     = \"8a9db7a7-3495-49c3-8137-c43396524bfa\" -> null\n          - network_name   = \"default\" -> null\n          - wait_for_lease = false -> null\n            # (5 unchanged attributes hidden)\n        }\n    }\n\n  # libvirt_domain.guest[\"vm-01\"] will be created\n  + resource \"libvirt_domain\" \"guest\" {\n      + arch        = (known after apply)\n      + autostart   = (known after apply)\n      + cloudinit   = (known after apply)\n      + emulator    = (known after apply)\n      + fw_cfg_name = \"opt\/com.coreos\/config\"\n      + id          = (known after apply)\n      + machine     = (known after apply)\n      + memory      = 2048\n      + name        = \"vm-01\"\n      + qemu_agent  = false\n      + running     = true\n      + type        = \"kvm\"\n      + vcpu        = 2\n\n      + console {\n          + source_host    = \"127.0.0.1\"\n          + source_service = \"0\"\n          + target_port    = \"0\"\n          + target_type    = \"serial\"\n          + type           = \"pty\"\n        }\n\n      + disk {\n          + scsi      = false\n          + volume_id = (known after apply)\n        }\n\n      + graphics {\n          + autoport       = true\n          + listen_address = \"127.0.0.1\"\n          + listen_type    = \"address\"\n          + type           = \"spice\"\n        }\n\n      + network_interface {\n          + addresses    = (known after apply)\n          + hostname     = (known after apply)\n          + mac          = (known after apply)\n          + network_id   = (known after apply)\n          + network_name = \"tf-network\"\n        }\n    }\n\n  # libvirt_domain.guest[\"vm-02\"] will be created\n  + resource \"libvirt_domain\" \"guest\" {\n      + arch        = (known after apply)\n      + autostart   = (known after apply)\n      + cloudinit   = (known after apply)\n      + emulator    = (known after apply)\n      + fw_cfg_name = \"opt\/com.coreos\/config\"\n      + id          = (known after apply)\n      + machine     = (known after apply)\n      + memory      = 2048\n      + name        = \"vm-02\"\n      + qemu_agent  = false\n      + running     = true\n      + type        = \"kvm\"\n      + vcpu        = 2\n\n      + console {\n          + source_host    = \"127.0.0.1\"\n          + source_service = \"0\"\n          + target_port    = \"0\"\n          + target_type    = \"serial\"\n          + type           = \"pty\"\n        }\n\n      + disk {\n          + scsi      = false\n          + volume_id = (known after apply)\n        }\n\n      + graphics {\n          + autoport       = true\n          + listen_address = \"127.0.0.1\"\n          + listen_type    = \"address\"\n          + type           = \"spice\"\n        }\n\n      + network_interface {\n          + addresses    = (known after apply)\n          + hostname     = (known after apply)\n          + mac          = (known after apply)\n          + network_id   = (known after apply)\n          + network_name = \"tf-network\"\n        }\n    }\n\n  # libvirt_domain.guest[\"vm-03\"] will be created\n  + resource \"libvirt_domain\" \"guest\" {\n      + arch        = (known after apply)\n      + autostart   = (known after apply)\n      + cloudinit   = (known after apply)\n      + emulator    = (known after apply)\n      + fw_cfg_name = \"opt\/com.coreos\/config\"\n      + id          = (known after apply)\n      + machine     = (known after apply)\n      + memory      = 2048\n      + name        = \"vm-03\"\n      + qemu_agent  = false\n      + running     = true\n      + type        = \"kvm\"\n      + vcpu        = 2\n\n      + console {\n          + source_host    = \"127.0.0.1\"\n          + source_service = \"0\"\n          + target_port    = \"0\"\n          + target_type    = \"serial\"\n          + type           = \"pty\"\n        }\n\n      + disk {\n          + scsi      = false\n          + volume_id = (known after apply)\n        }\n\n      + graphics {\n          + autoport       = true\n          + listen_address = \"127.0.0.1\"\n          + listen_type    = \"address\"\n          + type           = \"spice\"\n        }\n\n      + network_interface {\n          + addresses    = (known after apply)\n          + hostname     = (known after apply)\n          + mac          = (known after apply)\n          + network_id   = (known after apply)\n          + network_name = \"tf-network\"\n        }\n    }\n\n  # libvirt_network.tf_network will be created\n  + resource \"libvirt_network\" \"tf_network\" {\n      + addresses = [\n          + \"192.168.133.0\/24\",\n        ]\n      + autostart = true\n      + bridge    = (known after apply)\n      + id        = (known after apply)\n      + mode      = \"nat\"\n      + name      = \"tf-network\"\n\n      + dhcp {\n          + enabled = true\n        }\n\n      + dns {\n          + enabled = true\n        }\n    }\n\n  # libvirt_volume.volumes will be destroyed\n  # (because resource uses count or for_each)\n  - resource \"libvirt_volume\" \"volumes\" {\n      - format = \"qcow2\" -> null\n      - id     = \"\/var\/lib\/libvirt\/images\/tf-jammy.qcow2\" -> null\n      - name   = \"tf-jammy.qcow2\" -> null\n      - pool   = \"default\" -> null\n      - size   = 2361393152 -> null\n      - source = \"https:\/\/cloud-images.ubuntu.com\/jammy\/current\/jammy-server-cloudimg-amd64.img\" -> null\n    }\n\n  # libvirt_volume.volumes[\"vm-01\"] will be created\n  + resource \"libvirt_volume\" \"volumes\" {\n      + format = \"qcow2\"\n      + id     = (known after apply)\n      + name   = \"vm-01.qcow2\"\n      + pool   = \"default\"\n      + size   = (known after apply)\n      + source = \"https:\/\/cloud-images.ubuntu.com\/jammy\/current\/jammy-server-cloudimg-amd64.img\"\n    }\n\n  # libvirt_volume.volumes[\"vm-02\"] will be created\n  + resource \"libvirt_volume\" \"volumes\" {\n      + format = \"qcow2\"\n      + id     = (known after apply)\n      + name   = \"vm-02.qcow2\"\n      + pool   = \"default\"\n      + size   = (known after apply)\n      + source = \"https:\/\/cloud-images.ubuntu.com\/jammy\/current\/jammy-server-cloudimg-amd64.img\"\n    }\n\n  # libvirt_volume.volumes[\"vm-03\"] will be created\n  + resource \"libvirt_volume\" \"volumes\" {\n      + format = \"qcow2\"\n      + id     = (known after apply)\n      + name   = \"vm-03.qcow2\"\n      + pool   = \"default\"\n      + size   = (known after apply)\n      + source = \"https:\/\/cloud-images.ubuntu.com\/jammy\/current\/jammy-server-cloudimg-amd64.img\"\n    }\n\nPlan: 8 to add, 0 to change, 2 to destroy.\n\nChanges to Outputs:\n  + node_info = {\n      + vm-01 = {\n          + ip_address = (known after apply)\n          + name       = \"vm-01\"\n        }\n      + vm-02 = {\n          + ip_address = (known after apply)\n          + name       = \"vm-02\"\n        }\n      + vm-03 = {\n          + ip_address = (known after apply)\n          + name       = \"vm-03\"\n        }\n    }\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nNote: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run \"terraform apply\"\nnow.\n<\/code><\/pre>\n\n\n\n<p>Provision the resources;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform apply<\/code><\/pre>\n\n\n\n<pre class=\"scroll-box\"><code>data.template_file.user_data: Reading...\ndata.template_file.user_data: Read complete after 0s [id=a8dd3d6c50dce1a1fe85bff1b7c3150b97c2b092dcdf33505c864fe4cb61a964]\nlibvirt_volume.volumes: Refreshing state... [id=\/var\/lib\/libvirt\/images\/tf-jammy.qcow2]\nlibvirt_domain.guest: Refreshing state... [id=3c1446d1-cbf9-4ccc-98d4-c6f5527a6a15]\n\nNote: Objects have changed outside of Terraform\n\nTerraform detected the following changes made outside of Terraform since the last \"terraform apply\" which may have affected this plan:\n\n  # libvirt_domain.guest has changed\n  ~ resource \"libvirt_domain\" \"guest\" {\n      + cmdline     = []\n        id          = \"3c1446d1-cbf9-4ccc-98d4-c6f5527a6a15\"\n        name        = \"tf-jammy\"\n        # (13 unchanged attributes hidden)\n\n        # (5 unchanged blocks hidden)\n    }\n\n\nUnless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may\ninclude actions to undo or respond to these changes.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:\n  + create\n  - destroy\n\nTerraform will perform the following actions:\n\n  # libvirt_cloudinit_disk.commoninit will be created\n  + resource \"libvirt_cloudinit_disk\" \"commoninit\" {\n      + id        = (known after apply)\n      + name      = \"commoninit.iso\"\n      + pool      = \"default\"\n      + user_data = <<-EOT\n            #cloud-config\n            users:\n              - name: kifarunix\n                groups: sudo\n                shell: \/bin\/bash\n                passwd: $6$FzMWq3kKBNexpm0T$U1BNz7eXKSPoYVR9Y\/LClN6FquV\/MpesQu5RPI.YGA5cFRKHdh5RgiNi5MA12hUtjFtRfQ6522ymK\/wH1IZZM1\n                lock_passwd: false\n            ssh_pwauth: true\n        EOT\n    }\n\n  # libvirt_domain.guest will be destroyed\n  # (because resource uses count or for_each)\n  - resource \"libvirt_domain\" \"guest\" {\n      - arch        = \"x86_64\" -> null\n      - autostart   = false -> null\n      - cmdline     = [] -> null\n      - emulator    = \"\/usr\/bin\/qemu-system-x86_64\" -> null\n      - fw_cfg_name = \"opt\/com.coreos\/config\" -> null\n      - id          = \"3c1446d1-cbf9-4ccc-98d4-c6f5527a6a15\" -> null\n      - machine     = \"pc\" -> null\n      - memory      = 2048 -> null\n      - name        = \"tf-jammy\" -> null\n      - qemu_agent  = false -> null\n      - running     = true -> null\n      - type        = \"kvm\" -> null\n      - vcpu        = 2 -> null\n        # (3 unchanged attributes hidden)\n\n      - console {\n          - source_host    = \"127.0.0.1\" -> null\n          - source_service = \"0\" -> null\n          - target_port    = \"0\" -> null\n          - target_type    = \"serial\" -> null\n          - type           = \"pty\" -> null\n            # (1 unchanged attribute hidden)\n        }\n\n      - cpu {\n          - mode = \"custom\" -> null\n        }\n\n      - disk {\n          - scsi         = false -> null\n          - volume_id    = \"\/var\/lib\/libvirt\/images\/tf-jammy.qcow2\" -> null\n            # (4 unchanged attributes hidden)\n        }\n\n      - graphics {\n          - autoport       = true -> null\n          - listen_address = \"127.0.0.1\" -> null\n          - listen_type    = \"address\" -> null\n          - type           = \"spice\" -> null\n          - websocket      = 0 -> null\n        }\n\n      - network_interface {\n          - addresses      = [] -> null\n          - mac            = \"52:54:00:5A:A0:D2\" -> null\n          - network_id     = \"8a9db7a7-3495-49c3-8137-c43396524bfa\" -> null\n          - network_name   = \"default\" -> null\n          - wait_for_lease = false -> null\n            # (5 unchanged attributes hidden)\n        }\n    }\n\n  # libvirt_domain.guest[\"vm-01\"] will be created\n  + resource \"libvirt_domain\" \"guest\" {\n      + arch        = (known after apply)\n      + autostart   = (known after apply)\n      + cloudinit   = (known after apply)\n      + emulator    = (known after apply)\n      + fw_cfg_name = \"opt\/com.coreos\/config\"\n      + id          = (known after apply)\n      + machine     = (known after apply)\n      + memory      = 2048\n      + name        = \"vm-01\"\n      + qemu_agent  = false\n      + running     = true\n      + type        = \"kvm\"\n      + vcpu        = 2\n\n      + console {\n          + source_host    = \"127.0.0.1\"\n          + source_service = \"0\"\n          + target_port    = \"0\"\n          + target_type    = \"serial\"\n          + type           = \"pty\"\n        }\n\n      + disk {\n          + scsi      = false\n          + volume_id = (known after apply)\n        }\n\n      + graphics {\n          + autoport       = true\n          + listen_address = \"127.0.0.1\"\n          + listen_type    = \"address\"\n          + type           = \"spice\"\n        }\n\n      + network_interface {\n          + addresses    = (known after apply)\n          + hostname     = (known after apply)\n          + mac          = (known after apply)\n          + network_id   = (known after apply)\n          + network_name = \"tf-network\"\n        }\n    }\n\n  # libvirt_domain.guest[\"vm-02\"] will be created\n  + resource \"libvirt_domain\" \"guest\" {\n      + arch        = (known after apply)\n      + autostart   = (known after apply)\n      + cloudinit   = (known after apply)\n      + emulator    = (known after apply)\n      + fw_cfg_name = \"opt\/com.coreos\/config\"\n      + id          = (known after apply)\n      + machine     = (known after apply)\n      + memory      = 2048\n      + name        = \"vm-02\"\n      + qemu_agent  = false\n      + running     = true\n      + type        = \"kvm\"\n      + vcpu        = 2\n\n      + console {\n          + source_host    = \"127.0.0.1\"\n          + source_service = \"0\"\n          + target_port    = \"0\"\n          + target_type    = \"serial\"\n          + type           = \"pty\"\n        }\n\n      + disk {\n          + scsi      = false\n          + volume_id = (known after apply)\n        }\n\n      + graphics {\n          + autoport       = true\n          + listen_address = \"127.0.0.1\"\n          + listen_type    = \"address\"\n          + type           = \"spice\"\n        }\n\n      + network_interface {\n          + addresses    = (known after apply)\n          + hostname     = (known after apply)\n          + mac          = (known after apply)\n          + network_id   = (known after apply)\n          + network_name = \"tf-network\"\n        }\n    }\n\n  # libvirt_domain.guest[\"vm-03\"] will be created\n  + resource \"libvirt_domain\" \"guest\" {\n      + arch        = (known after apply)\n      + autostart   = (known after apply)\n      + cloudinit   = (known after apply)\n      + emulator    = (known after apply)\n      + fw_cfg_name = \"opt\/com.coreos\/config\"\n      + id          = (known after apply)\n      + machine     = (known after apply)\n      + memory      = 2048\n      + name        = \"vm-03\"\n      + qemu_agent  = false\n      + running     = true\n      + type        = \"kvm\"\n      + vcpu        = 2\n\n      + console {\n          + source_host    = \"127.0.0.1\"\n          + source_service = \"0\"\n          + target_port    = \"0\"\n          + target_type    = \"serial\"\n          + type           = \"pty\"\n        }\n\n      + disk {\n          + scsi      = false\n          + volume_id = (known after apply)\n        }\n\n      + graphics {\n          + autoport       = true\n          + listen_address = \"127.0.0.1\"\n          + listen_type    = \"address\"\n          + type           = \"spice\"\n        }\n\n      + network_interface {\n          + addresses    = (known after apply)\n          + hostname     = (known after apply)\n          + mac          = (known after apply)\n          + network_id   = (known after apply)\n          + network_name = \"tf-network\"\n        }\n    }\n\n  # libvirt_network.tf_network will be created\n  + resource \"libvirt_network\" \"tf_network\" {\n      + addresses = [\n          + \"192.168.133.0\/24\",\n        ]\n      + autostart = true\n      + bridge    = (known after apply)\n      + id        = (known after apply)\n      + mode      = \"nat\"\n      + name      = \"tf-network\"\n\n      + dhcp {\n          + enabled = true\n        }\n\n      + dns {\n          + enabled = true\n        }\n    }\n\n  # libvirt_volume.volumes will be destroyed\n  # (because resource uses count or for_each)\n  - resource \"libvirt_volume\" \"volumes\" {\n      - format = \"qcow2\" -> null\n      - id     = \"\/var\/lib\/libvirt\/images\/tf-jammy.qcow2\" -> null\n      - name   = \"tf-jammy.qcow2\" -> null\n      - pool   = \"default\" -> null\n      - size   = 2361393152 -> null\n      - source = \"https:\/\/cloud-images.ubuntu.com\/jammy\/current\/jammy-server-cloudimg-amd64.img\" -> null\n    }\n\n  # libvirt_volume.volumes[\"vm-01\"] will be created\n  + resource \"libvirt_volume\" \"volumes\" {\n      + format = \"qcow2\"\n      + id     = (known after apply)\n      + name   = \"vm-01.qcow2\"\n      + pool   = \"default\"\n      + size   = (known after apply)\n      + source = \"https:\/\/cloud-images.ubuntu.com\/jammy\/current\/jammy-server-cloudimg-amd64.img\"\n    }\n\n  # libvirt_volume.volumes[\"vm-02\"] will be created\n  + resource \"libvirt_volume\" \"volumes\" {\n      + format = \"qcow2\"\n      + id     = (known after apply)\n      + name   = \"vm-02.qcow2\"\n      + pool   = \"default\"\n      + size   = (known after apply)\n      + source = \"https:\/\/cloud-images.ubuntu.com\/jammy\/current\/jammy-server-cloudimg-amd64.img\"\n    }\n\n  # libvirt_volume.volumes[\"vm-03\"] will be created\n  + resource \"libvirt_volume\" \"volumes\" {\n      + format = \"qcow2\"\n      + id     = (known after apply)\n      + name   = \"vm-03.qcow2\"\n      + pool   = \"default\"\n      + size   = (known after apply)\n      + source = \"https:\/\/cloud-images.ubuntu.com\/jammy\/current\/jammy-server-cloudimg-amd64.img\"\n    }\n\nPlan: 8 to add, 0 to change, 2 to destroy.\n\nChanges to Outputs:\n  + node_info = {\n      + vm-01 = {\n          + ip_address = (known after apply)\n          + name       = \"vm-01\"\n        }\n      + vm-02 = {\n          + ip_address = (known after apply)\n          + name       = \"vm-02\"\n        }\n      + vm-03 = {\n          + ip_address = (known after apply)\n          + name       = \"vm-03\"\n        }\n    }\n\nDo you want to perform these actions?\n  Terraform will perform the actions described above.\n  Only 'yes' will be accepted to approve.\n\n  Enter a value: yes\n\nlibvirt_cloudinit_disk.commoninit: Creating...\nlibvirt_domain.guest: Destroying... [id=3c1446d1-cbf9-4ccc-98d4-c6f5527a6a15]\nlibvirt_network.tf_network: Creating...\nlibvirt_cloudinit_disk.commoninit: Creation complete after 0s [id=\/var\/lib\/libvirt\/images\/commoninit.iso;7af70c2b-b9e6-44c5-b269-2e0d187f43ba]\nlibvirt_domain.guest: Destruction complete after 0s\nlibvirt_volume.volumes: Destroying... [id=\/var\/lib\/libvirt\/images\/tf-jammy.qcow2]\nlibvirt_volume.volumes[\"vm-03\"]: Creating...\nlibvirt_volume.volumes[\"vm-01\"]: Creating...\nlibvirt_volume.volumes[\"vm-02\"]: Creating...\nlibvirt_volume.volumes: Destruction complete after 0s\nlibvirt_network.tf_network: Creation complete after 5s [id=1709e5e9-3e68-4170-af0a-b89330e5921c]\nlibvirt_volume.volumes[\"vm-03\"]: Creation complete after 7s [id=\/var\/lib\/libvirt\/images\/vm-03.qcow2]\nlibvirt_volume.volumes[\"vm-01\"]: Still creating... [10s elapsed]\nlibvirt_volume.volumes[\"vm-02\"]: Still creating... [10s elapsed]\nlibvirt_volume.volumes[\"vm-01\"]: Creation complete after 13s [id=\/var\/lib\/libvirt\/images\/vm-01.qcow2]\nlibvirt_volume.volumes[\"vm-02\"]: Creation complete after 19s [id=\/var\/lib\/libvirt\/images\/vm-02.qcow2]\nlibvirt_domain.guest[\"vm-02\"]: Creating...\nlibvirt_domain.guest[\"vm-03\"]: Creating...\nlibvirt_domain.guest[\"vm-01\"]: Creating...\nlibvirt_domain.guest[\"vm-02\"]: Creation complete after 1s [id=b72a5cc8-ec24-4be1-9423-c023de1c3796]\nlibvirt_domain.guest[\"vm-01\"]: Creation complete after 1s [id=b911ad4d-3e6b-4dfe-bdbe-34f65d3eb0d6]\nlibvirt_domain.guest[\"vm-03\"]: Creation complete after 1s [id=6abf0652-c405-4d16-bf0b-c9098b888320]\n\u2577\n\u2502 Error: Invalid index\n\u2502 \n\u2502   on main.tf line 81, in output \"node_info\":\n\u2502   81:       ip_address   = vm.network_interface[0].addresses[0]\n\u2502     \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\u2502     \u2502 vm.network_interface[0].addresses is empty list of string\n\u2502 \n\u2502 The given key does not identify an element in this collection value: the collection has no elements.\n\u2575\n\u2577\n\u2502 Error: Invalid index\n\u2502 \n\u2502   on main.tf line 81, in output \"node_info\":\n\u2502   81:       ip_address   = vm.network_interface[0].addresses[0]\n\u2502     \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\u2502     \u2502 vm.network_interface[0].addresses is empty list of string\n\u2502 \n\u2502 The given key does not identify an element in this collection value: the collection has no elements.\n\u2575\n\u2577\n\u2502 Error: Invalid index\n\u2502 \n\u2502   on main.tf line 81, in output \"node_info\":\n\u2502   81:       ip_address   = vm.network_interface[0].addresses[0]\n\u2502     \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\u2502     \u2502 vm.network_interface[0].addresses is empty list of string\n\u2502 \n\u2502 The given key does not identify an element in this collection value: the collection has no elements.\n<\/code><\/pre>\n\n\n\n<p>You will not get IP addresses until when all vms are up and running.<\/p>\n\n\n\n<p>You can then re-run the apply command;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform apply<\/code><\/pre>\n\n\n\n<pre class=\"scroll-box\"><code>data.template_file.user_data: Reading...\ndata.template_file.user_data: Read complete after 0s [id=a8dd3d6c50dce1a1fe85bff1b7c3150b97c2b092dcdf33505c864fe4cb61a964]\nlibvirt_cloudinit_disk.commoninit: Refreshing state... [id=\/var\/lib\/libvirt\/images\/commoninit.iso;7af70c2b-b9e6-44c5-b269-2e0d187f43ba]\nlibvirt_volume.volumes[\"vm-02\"]: Refreshing state... [id=\/var\/lib\/libvirt\/images\/vm-02.qcow2]\nlibvirt_volume.volumes[\"vm-01\"]: Refreshing state... [id=\/var\/lib\/libvirt\/images\/vm-01.qcow2]\nlibvirt_volume.volumes[\"vm-03\"]: Refreshing state... [id=\/var\/lib\/libvirt\/images\/vm-03.qcow2]\nlibvirt_network.tf_network: Refreshing state... [id=1709e5e9-3e68-4170-af0a-b89330e5921c]\nlibvirt_domain.guest[\"vm-03\"]: Refreshing state... [id=6abf0652-c405-4d16-bf0b-c9098b888320]\nlibvirt_domain.guest[\"vm-01\"]: Refreshing state... [id=b911ad4d-3e6b-4dfe-bdbe-34f65d3eb0d6]\nlibvirt_domain.guest[\"vm-02\"]: Refreshing state... [id=b72a5cc8-ec24-4be1-9423-c023de1c3796]\n\nChanges to Outputs:\n  + node_info = {\n      + vm-01 = {\n          + ip_address = \"192.168.133.176\"\n          + name       = \"vm-01\"\n        }\n      + vm-02 = {\n          + ip_address = \"192.168.133.239\"\n          + name       = \"vm-02\"\n        }\n      + vm-03 = {\n          + ip_address = \"192.168.133.157\"\n          + name       = \"vm-03\"\n        }\n    }\n\nYou can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.\n\nDo you want to perform these actions?\n  Terraform will perform the actions described above.\n  Only 'yes' will be accepted to approve.\n\n  Enter a value: yes\n\n\nApply complete! Resources: 0 added, 0 changed, 0 destroyed.\n\nOutputs:\n\nnode_info = {\n  \"vm-01\" = {\n    \"ip_address\" = \"192.168.133.176\"\n    \"name\" = \"vm-01\"\n  }\n  \"vm-02\" = {\n    \"ip_address\" = \"192.168.133.239\"\n    \"name\" = \"vm-02\"\n  }\n  \"vm-03\" = {\n    \"ip_address\" = \"192.168.133.157\"\n    \"name\" = \"vm-03\"\n  }\n}\n<\/code><\/pre>\n\n\n\n<p>Or, check the DHCP lease for the network;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo virsh net-dhcp-leases tf-network<\/code><\/pre>\n\n\n\n<pre class=\"scroll-box\"><code>Expiry Time           MAC address         Protocol   IP address           Hostname   Client ID or DUID\n------------------------------------------------------------------------------------------------------------------------------------------------\n 2024-04-15 23:55:55   52:54:00:12:36:af   ipv4       192.168.133.176\/24    ubuntu     ff:b5:5e:67:ff:00:02:00:00:ab:11:3f:01:ff:e3:44:bc:34:d8\n 2024-04-15 23:55:55   52:54:00:46:4f:6b   ipv4       192.168.133.239\/24   ubuntu     ff:b5:5e:67:ff:00:02:00:00:ab:11:39:d7:61:ea:3d:3a:c1:73\n 2024-04-15 23:55:55   52:54:00:ee:d5:b1   ipv4       192.168.133.157\/24   -          ff:b5:5e:67:ff:00:02:00:00:ab:11:c4:d3:11:18:f1:77:95:02\n<\/code><\/pre>\n\n\n\n<p>Check the vms;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo virsh list --all<\/code><\/pre>\n\n\n\n<pre class=\"scroll-box\"><code> 55   vm-03                running\n 56   vm-01                running\n 57   vm-02                running\n<\/code><\/pre>\n\n\n\n<p>Login to one of them to confirm  user account creation;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo virsh console vm-01<\/code><\/pre>\n\n\n\n<pre class=\"scroll-box\"><code>Connected to domain 'vm-01'\nEscape character is ^] (Ctrl + ])\n\nubuntu login: kifarunix\nPassword: \nWelcome to Ubuntu 22.04.4 LTS (GNU\/Linux 5.15.0-101-generic x86_64)\n\n * Documentation:  https:\/\/help.ubuntu.com\n * Management:     https:\/\/landscape.canonical.com\n * Support:        https:\/\/ubuntu.com\/pro\n\n  System information as of Mon Apr 15 20:59:01 UTC 2024\n\n  System load:  0.013671875       Processes:             98\n  Usage of \/:   71.3% of 1.96GB   Users logged in:       0\n  Memory usage: 9%                IPv4 address for ens3: 192.168.133.56\n  Swap usage:   0%\n\nExpanded Security Maintenance for Applications is not enabled.\n\n0 updates can be applied immediately.\n\nEnable ESM Apps to receive additional future security updates.\nSee https:\/\/ubuntu.com\/esm or run: sudo pro status\n\n\nThe list of available updates is more than a week old.\nTo check for new updates run: sudo apt update\n\n\nThe programs included with the Ubuntu system are free software;\nthe exact distribution terms for each program are described in the\nindividual files in \/usr\/share\/doc\/*\/copyright.\n\nUbuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by\napplicable law.\n\nTo run a command as administrator (user \"root\"), use \"sudo <command>\".\nSee \"man sudo_root\" for details.\n\nkifarunix@ubuntu:~$ groups\nkifarunix sudo\nkifarunix@ubuntu:~$\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"conclusion\">Conclusion<\/h3>\n\n\n\n<p>And that is it! You have just automated the creation of virtual machines on KVM using Terraform.<\/p>\n\n\n\n<p>You can read further on the <a href=\"https:\/\/developer.hashicorp.com\/terraform\/docs\" target=\"_blank\" rel=\"noreferrer noopener\">Documentation<\/a> as well on further usage of libvirt provider!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this tutorial, you will learn how to automate Virtual Machine creation on KVM with Terraform. If you are creating multiple virtual machines on KVM<\/p>\n","protected":false},"author":10,"featured_media":22171,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rank_math_lock_modified_date":false,"footnotes":""},"categories":[121,992,1722,112,36],"tags":[7446,7447,7445],"class_list":["post-22057","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-howtos","category-automation","category-automation-2","category-kvm","category-virtualization","tag-kvm-and-terraform","tag-libvirt-and-terraform","tag-terraform","generate-columns","tablet-grid-50","mobile-grid-100","grid-parent","grid-50","resize-featured-image"],"_links":{"self":[{"href":"https:\/\/kifarunix.com\/wp-json\/wp\/v2\/posts\/22057"}],"collection":[{"href":"https:\/\/kifarunix.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kifarunix.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kifarunix.com\/wp-json\/wp\/v2\/users\/10"}],"replies":[{"embeddable":true,"href":"https:\/\/kifarunix.com\/wp-json\/wp\/v2\/comments?post=22057"}],"version-history":[{"count":14,"href":"https:\/\/kifarunix.com\/wp-json\/wp\/v2\/posts\/22057\/revisions"}],"predecessor-version":[{"id":22173,"href":"https:\/\/kifarunix.com\/wp-json\/wp\/v2\/posts\/22057\/revisions\/22173"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/kifarunix.com\/wp-json\/wp\/v2\/media\/22171"}],"wp:attachment":[{"href":"https:\/\/kifarunix.com\/wp-json\/wp\/v2\/media?parent=22057"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/kifarunix.com\/wp-json\/wp\/v2\/categories?post=22057"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kifarunix.com\/wp-json\/wp\/v2\/tags?post=22057"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}