diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4431bbe --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.terraform/ +*.tfstate +*.tfstate.* +.terraform.tfstate.lock.info +crash.log +crash.*.log +*.tfplan +override.tf +override.tf.json +*_override.tf +*_override.tf.json diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl new file mode 100644 index 0000000..3d1167e --- /dev/null +++ b/.terraform.lock.hcl @@ -0,0 +1,43 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "4.79.0" + constraints = "~> 4.0" + hashes = [ + "h1:hEVrA2r7nS0jSd32CwsWNfifS8tV0xhoGuc1T9qU074=", + "zh:0129b3fa33289d54f86f150377b19127f85787116b7cc53564fed77c2e2468be", + "zh:131427cf40dd3fa4ec52af938b7ab0fd100bcacd75b7137c8e1ea5bf268c4638", + "zh:162ebd8f195ac28324d9e9f2e9b39a05d8a946abff21087a43123c83ca807a5d", + "zh:209a23777acdcfc674791a4ad37ba4e2110044857afb9db88aa305222fef706c", + "zh:21cf740e1fee936de429fa324b86af69c4fe90bbfdbb1f374e6a55118fdff841", + "zh:4ad1cde7ad4ef7187633b7fc7b0ed76ef2526b56fbb29f80a6fc54d07ac1204a", + "zh:57b942405b27af17cfad9a48d9e4ec75c0bb452f229dd132d6f1d8518ca4a067", + "zh:69a4fda56d6405027f303463f796a0d2d250403f67cecf2a25bcdfaeaf731630", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7c21524a423d95a4c84039e06a610f131c0618ee117da9c35e4b4f41d1210c64", + "zh:9045f6bb9f764709a5773d8ec65735f56c9995dc9c9ec3f85f2c6adf58654f3c", + "zh:e018581ed136640ba733ab583fd0072536b1bc096d66f2215787823cee6cfcdc", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.9.0" + constraints = "~> 3.0" + hashes = [ + "h1:OO+IuvQJSPmWdN8AyyIEvPJbLvDQpgX/zbktoa9KsJE=", + "zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1", + "zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea", + "zh:269eda8fe42daa7974d5a34d166c3ba9defe80cde86c01e4dadcfdf2e1f05e5f", + "zh:373f7c65566f8f2cc7f45d698654feb9d988996957e1266a69ca00c52d6d16d0", + "zh:5599d16804c41c83009ec621b6d6b6f74e102f5827678a4750f8809055546b61", + "zh:583be0440469a22bff70dcfa56593b01566860b29607437264adb51060cf46fc", + "zh:5f211d8ec3f2e1f414870d9584bfe26e6995560ef81c748f8447a48164767398", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b547fd16216761ef86efc3ed516ac5ac0c5c42b7c7eb24a08cef2d93f69ed5e", + "zh:7e7c0679daf2a382151d05068c8c3f0dae6b7b7dccf818827b73dd08638df2ef", + "zh:8089dec888a8038b9b4fb23b3df7e1057293dbc5b60b42cc47ff690d69d4b61b", + "zh:c51f15a031edfd6f23ce8ced3446ca7f8d8d647e2499890d7d5d10d5016d7257", + "zh:c94784f005708890dc6895afd53636ec00ec1e430b15d41e5aebfb1d4b39bd04", + ] +} diff --git a/backend.tf b/backend.tf new file mode 100644 index 0000000..1d054c3 --- /dev/null +++ b/backend.tf @@ -0,0 +1,8 @@ +terraform { + backend "azurerm" { + resource_group_name = "mate-azure-task-12" + storage_account_name = "yourstorageaccount" + container_name = "tfstate" + key = "terraform.tfstate" + } +} diff --git a/install-app.sh b/install-app.sh index 03706de..98527ea 100644 --- a/install-app.sh +++ b/install-app.sh @@ -8,13 +8,15 @@ # used to suppress any interactive prompts - we won't be able to confirm operation # when running the script as VM extention. apt-get update -yq -apt-get install python3-pip -yq +apt-get install python3-pip git -yq # Create a directory for the app and download the files. -mkdir /app +mkdir -p /app +cd /tmp # make sure to uncomment the line bellow and update the link with your GitHub username -# git clone https://github.com//azure_task_12_deploy_app_with_vm_extention.git -cp -r devops_todolist_terraform_task/app/* /app +git clone https://github.com/volodymyrlp/devops_todolist_terraform_task.git +cp -r /tmp/devops_todolist_terraform_task/app/* /app +chmod +x /app/start.sh # create a service for the app via systemctl and start the app mv /app/todoapp.service /etc/systemd/system/ diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..1f876f0 --- /dev/null +++ b/main.tf @@ -0,0 +1,58 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 4.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + } +} + +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "main" { + name = var.resource_group_name + location = var.location +} + +module "network" { + source = "./modules/network" + + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + virtual_network_name = var.virtual_network_name + vnet_address_prefix = var.vnet_address_prefix + subnet_name = var.subnet_name + subnet_address_prefix = var.subnet_address_prefix + network_security_group_name = var.network_security_group_name + public_ip_address_name = var.public_ip_address_name + dns_label = var.dns_label +} + +module "storage" { + source = "./modules/storage" + + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + storage_account_name = var.storage_account_name +} + +module "compute" { + source = "./modules/compute" + + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + vm_name = var.vm_name + vm_size = var.vm_size + admin_username = var.admin_username + ssh_key_public = var.ssh_key_public + install_script_url = var.install_script_url + + subnet_id = module.network.subnet_id + public_ip_id = module.network.public_ip_id +} diff --git a/modules/compute/main.tf b/modules/compute/main.tf new file mode 100644 index 0000000..7661e49 --- /dev/null +++ b/modules/compute/main.tf @@ -0,0 +1,51 @@ +resource "azurerm_network_interface" "nic" { + name = "${var.vm_name}-nic" + location = var.location + resource_group_name = var.resource_group_name + + ip_configuration { + name = "internal" + subnet_id = var.subnet_id + private_ip_address_allocation = "Dynamic" + public_ip_address_id = var.public_ip_id + } +} + +resource "azurerm_linux_virtual_machine" "vm" { + name = var.vm_name + resource_group_name = var.resource_group_name + location = var.location + size = var.vm_size + admin_username = var.admin_username + network_interface_ids = [azurerm_network_interface.nic.id] + + admin_ssh_key { + username = var.admin_username + public_key = var.ssh_key_public + } + + os_disk { + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + } + + source_image_reference { + publisher = "Canonical" + offer = "0001-com-ubuntu-server-jammy" + sku = "22_04-lts" + version = "latest" + } +} + +resource "azurerm_virtual_machine_extension" "app" { + name = "install-app" + virtual_machine_id = azurerm_linux_virtual_machine.vm.id + publisher = "Microsoft.Azure.Extensions" + type = "CustomScript" + type_handler_version = "2.1" + + settings = jsonencode({ + fileUris = [var.install_script_url] + commandToExecute = "bash install-app.sh" + }) +} diff --git a/modules/compute/outputs.tf b/modules/compute/outputs.tf new file mode 100644 index 0000000..fbe80dc --- /dev/null +++ b/modules/compute/outputs.tf @@ -0,0 +1,9 @@ +output "vm_name" { + description = "Name of the virtual machine" + value = azurerm_linux_virtual_machine.vm.name +} + +output "vm_id" { + description = "Resource ID of the virtual machine" + value = azurerm_linux_virtual_machine.vm.id +} diff --git a/modules/compute/variables.tf b/modules/compute/variables.tf new file mode 100644 index 0000000..5b1a8bf --- /dev/null +++ b/modules/compute/variables.tf @@ -0,0 +1,44 @@ +variable "resource_group_name" { + description = "Name of the resource group for the compute resources" + type = string +} + +variable "location" { + description = "Azure region" + type = string +} + +variable "vm_name" { + description = "Name of the virtual machine" + type = string +} + +variable "vm_size" { + description = "Size of the virtual machine" + type = string +} + +variable "admin_username" { + description = "Admin username for the virtual machine" + type = string +} + +variable "ssh_key_public" { + description = "SSH public key used for VM authentication" + type = string +} + +variable "subnet_id" { + description = "ID of the subnet the network interface attaches to" + type = string +} + +variable "public_ip_id" { + description = "ID of the public IP attached to the network interface" + type = string +} + +variable "install_script_url" { + description = "Raw GitHub URL of install-app.sh for the VM extension" + type = string +} diff --git a/modules/network/main.tf b/modules/network/main.tf new file mode 100644 index 0000000..544a4d2 --- /dev/null +++ b/modules/network/main.tf @@ -0,0 +1,61 @@ +resource "azurerm_virtual_network" "vnet" { + name = var.virtual_network_name + address_space = [var.vnet_address_prefix] + location = var.location + resource_group_name = var.resource_group_name +} + +resource "azurerm_subnet" "subnet" { + name = var.subnet_name + resource_group_name = var.resource_group_name + virtual_network_name = azurerm_virtual_network.vnet.name + address_prefixes = [var.subnet_address_prefix] +} + +resource "azurerm_network_security_group" "nsg" { + name = var.network_security_group_name + location = var.location + resource_group_name = var.resource_group_name + + security_rule { + name = "AllowSSH" + priority = 1001 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "22" + source_address_prefix = "*" + destination_address_prefix = "*" + } + + security_rule { + name = "AllowAppHttp" + priority = 1002 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "8080" + source_address_prefix = "*" + destination_address_prefix = "*" + } +} + +resource "azurerm_subnet_network_security_group_association" "subnet_nsg" { + subnet_id = azurerm_subnet.subnet.id + network_security_group_id = azurerm_network_security_group.nsg.id +} + +resource "random_integer" "dns" { + min = 1000 + max = 9999 +} + +resource "azurerm_public_ip" "pip" { + name = var.public_ip_address_name + location = var.location + resource_group_name = var.resource_group_name + allocation_method = "Dynamic" + domain_name_label = "${var.dns_label}${random_integer.dns.result}" +} diff --git a/modules/network/outputs.tf b/modules/network/outputs.tf new file mode 100644 index 0000000..ed73925 --- /dev/null +++ b/modules/network/outputs.tf @@ -0,0 +1,14 @@ +output "subnet_id" { + description = "ID of the subnet (used by the network interface in the compute module)" + value = azurerm_subnet.subnet.id +} + +output "public_ip_id" { + description = "ID of the public IP (attached to the network interface in the compute module)" + value = azurerm_public_ip.pip.id +} + +output "public_ip_fqdn" { + description = "Full DNS name of the public IP (used to open the app in a browser)" + value = azurerm_public_ip.pip.fqdn +} diff --git a/modules/network/variables.tf b/modules/network/variables.tf new file mode 100644 index 0000000..bedb697 --- /dev/null +++ b/modules/network/variables.tf @@ -0,0 +1,44 @@ +variable "resource_group_name" { + description = "Name of the resource group for the network resources" + type = string +} + +variable "location" { + description = "Azure region (e.g. westeurope)" + type = string +} + +variable "virtual_network_name" { + description = "Name of the virtual network" + type = string +} + +variable "vnet_address_prefix" { + description = "Address space of the virtual network in CIDR format (e.g. 10.0.0.0/16)" + type = string +} + +variable "subnet_name" { + description = "Name of the subnet" + type = string +} + +variable "subnet_address_prefix" { + description = "Address range of the subnet in CIDR format (e.g. 10.0.0.0/24)" + type = string +} + +variable "network_security_group_name" { + description = "Name of the network security group (NSG)" + type = string +} + +variable "public_ip_address_name" { + description = "Name of the public IP resource" + type = string +} + +variable "dns_label" { + description = "DNS label prefix for the public IP (a random number is appended)" + type = string +} diff --git a/modules/storage/main.tf b/modules/storage/main.tf new file mode 100644 index 0000000..5050c6a --- /dev/null +++ b/modules/storage/main.tf @@ -0,0 +1,19 @@ +resource "random_string" "storage" { + length = 6 + upper = false + special = false +} + +resource "azurerm_storage_account" "storage" { + name = "${var.storage_account_name}${random_string.storage.result}" + resource_group_name = var.resource_group_name + location = var.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "container" { + name = "task-artifacts" + storage_account_id = azurerm_storage_account.storage.id + container_access_type = "private" +} diff --git a/modules/storage/outputs.tf b/modules/storage/outputs.tf new file mode 100644 index 0000000..15dd9f7 --- /dev/null +++ b/modules/storage/outputs.tf @@ -0,0 +1,9 @@ +output "storage_account_name" { + description = "Generated name of the storage account" + value = azurerm_storage_account.storage.name +} + +output "storage_container_name" { + description = "Name of the storage container" + value = azurerm_storage_container.container.name +} diff --git a/modules/storage/variables.tf b/modules/storage/variables.tf new file mode 100644 index 0000000..6f1cdbb --- /dev/null +++ b/modules/storage/variables.tf @@ -0,0 +1,14 @@ +variable "resource_group_name" { + description = "Name of the resource group for the storage resources" + type = string +} + +variable "location" { + description = "Azure region (e.g. westeurope)" + type = string +} + +variable "storage_account_name" { + description = "Base name of the storage account (a random suffix is appended for global uniqueness)" + type = string +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..92d98f5 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,9 @@ +output "app_url" { + description = "URL to open the ToDo List application in a browser" + value = "http://${module.network.public_ip_fqdn}:8080" +} + +output "vm_name" { + description = "Name of the deployed virtual machine" + value = module.compute.vm_name +} diff --git a/terraform.tfvars b/terraform.tfvars new file mode 100644 index 0000000..dc88c0c --- /dev/null +++ b/terraform.tfvars @@ -0,0 +1,3 @@ +storage_account_name = "matetaskvolodymyr" +install_script_url = "https://raw.githubusercontent.com/volodymyrlp/devops_todolist_terraform_task/main/install-app.sh" +ssh_key_public = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCn9u247Nw8JycJeA/hx0l0lAh8EA5QI1Ewrmq2AOyh/UMqWNY2PhMmNrPsWKa2MhJS3vHo2u19c5QBh2e8mqYoLg43T7CgL6w/ysL79xychJTAdI4iKEHFD3/vFpdWYDPkisYv+1UKDG6YF/hkSeJUEfuO+TrYlVCiLyJPYbAcWrFPMsS6kXhQXP40Zzp910FzxkHexIepE5L+/O00mCarj45vEOiAsPQgLqa1vi1hYMfZ1cz6Ift+PkpFFkApDzJiBnzGyK/ArEqn0tYMH/AvEQJkUSPP5vLSvY5DTk6aZ8bHVxNd+qOMmx6rjWiwoovkHX0GGeDZh8wWZLxGu+KqHXBWf40HLrqri3754ErowNN7hHETt97suFT7kWgQywtez8wY5aOdsLVoHALyLW+vBOnmWAMtYXJJgOJqdoDEewMJH7BnmkPs0CJQorLUnTozoxfVECuec2DKhPDhbN6Rj2GT3hK32PjPI78pILiWeSV38PhQMTKk28K3KyOfa+POWIrDsVF1DKqNwZQnbDTWo0yQktHZYsJaVu25xgvG38kM8LtfQhtJhFS7thc5dYbBHnZVxSRyPiExTi/zSS4t/jHpPC5HhkH1tX/hTFD46uJCeMMYkQpEpSFgnvwMYPQSdLbgJa8DmqGS2g0VU1RneWzCB6P6p6BYCO9M84LeVw== macbookair@MacBookAir" diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..e57fdb1 --- /dev/null +++ b/variables.tf @@ -0,0 +1,86 @@ +variable "location" { + description = "Azure region for all resources" + type = string + default = "uksouth" +} + +variable "resource_group_name" { + description = "Name of the resource group" + type = string + default = "mate-azure-task-12" +} + +variable "virtual_network_name" { + description = "Name of the virtual network" + type = string + default = "vnet" +} + +variable "vnet_address_prefix" { + description = "Address space of the virtual network in CIDR format" + type = string + default = "10.0.0.0/16" +} + +variable "subnet_name" { + description = "Name of the subnet" + type = string + default = "default" +} + +variable "subnet_address_prefix" { + description = "Address range of the subnet in CIDR format" + type = string + default = "10.0.0.0/24" +} + +variable "network_security_group_name" { + description = "Name of the network security group" + type = string + default = "defaultnsg" +} + +variable "public_ip_address_name" { + description = "Name of the public IP address" + type = string + default = "linuxboxpip" +} + +variable "dns_label" { + description = "DNS label prefix for the public IP" + type = string + default = "matetask" +} + +variable "vm_name" { + description = "Name of the virtual machine" + type = string + default = "matebox" +} + +variable "vm_size" { + description = "Size of the virtual machine" + type = string + default = "Standard_B1s" +} + +variable "admin_username" { + description = "Admin username for the virtual machine" + type = string + default = "azureuser" +} + +variable "storage_account_name" { + description = "Base name of the storage account" + type = string +} + +variable "ssh_key_public" { + description = "Contents of the SSH public key for VM access" + type = string +} + +variable "install_script_url" { + description = "Raw GitHub URL of install-app.sh for the VM extension" + type = string +}