Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Local .terraform directories
.terraform/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend configuration here correctly uses storage_account_name = "yourstorageaccount", container_name = "tfstate", and resource_group_name = "mate-azure-task-12", which matches the task requirements. Just make sure the storage account with this exact name is actually created by your bootstrap or main configuration (see the bootstrap file).

# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log
crash.*.log

# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json

# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module call provisions a storage account named "ands9storageaccount", but the backend configuration requires the storage account to be named "yourstorageaccount". To satisfy the remote state requirements, update this value so the created storage account name matches the backend (yourstorageaccount) and the backend can actually use it.

*_override.tf
*_override.tf.json

# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info

# Include override files you do wish to add to version control using negated pattern
# !example_override.tf

# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*

# Ignore CLI configuration files
.terraformrc
terraform.rc

# Optional: ignore graph output files generated by `terraform graph`
# *.dot

# Optional: ignore plan files saved before destroying Terraform configuration
# Uncomment the line below if you want to ignore planout files.
# planout
9 changes: 9 additions & 0 deletions backend.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
terraform {
backend "azurerm" {
storage_account_name = "yourstorageaccount"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend configuration correctly uses storage_account_name = "yourstorageaccount", which matches the explicit requirement for the remote state backend.

container_name = "tfstate"
key = "terraform.tfstate"
resource_group_name = "mate-azure-task-12"
use_oidc = true
}
}
24 changes: 24 additions & 0 deletions bootstrap/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "4.80.0"
}
}
}

provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "example" {
name = var.resource_group_name
location = var.resource_group_location
}
module "backend_storage" {
source = "../modules/storage"
resource_group_name = azurerm_resource_group.example.name
resource_group_location = azurerm_resource_group.example.location
storage_account_name = "yourstorageaccount"
container_name = "tfstate"
}
12 changes: 12 additions & 0 deletions bootstrap/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# COMMON VARIABLES
variable "resource_group_name" {
description = "Name of the resource group"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The task description explicitly requires the backend storage account name to be "yourstorageaccount" (checklist items #12 and #19), but this is currently set to "ands9storageaccount". Update this to match the required value so the configuration aligns with the assignment specification.

type = string
default = "mate-azure-task-12"
}
variable "resource_group_location" {
description = "Location of the resource group"
type = string
default = "uksouth"
}
#______________________________________________________________________________|
5 changes: 3 additions & 2 deletions install-app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ apt-get install python3-pip -yq
# Create a directory for the app and download the files.
mkdir /app
# make sure to uncomment the line bellow and update the link with your GitHub username
# git clone https://github.com/<your-gh-username>/azure_task_12_deploy_app_with_vm_extention.git
git clone https://github.com/AndS9/devops_todolist_terraform_task.git

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script correctly installs dependencies, clones the Todo app repository, copies the app files into /app, sets up the systemd service, and starts/enables it. This satisfies the requirement that the VM extension deploys the ToDo List app by executing install-app.sh. Just ensure this repo URL is correct for your fork if the instructions expect using your own GitHub username.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the assignment, this hard-coded clone URL still points to your own GitHub username. The task text asks to update it with your GitHub username; if this repo is meant to be reused by others, they’ll need to change it, but for your submission, this is fine as long as the URL matches the app repo you control.

chmod +x devops_todolist_terraform_task/app/start.sh
cp -r devops_todolist_terraform_task/app/* /app

# create a service for the app via systemctl and start the app
mv /app/todoapp.service /etc/systemd/system/
systemctl daemon-reload
systemctl start todoapp
systemctl enable todoapp
systemctl enable todoapp
73 changes: 73 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "4.80.0"
Comment on lines +4 to +5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the compute module outputs, you reference azurerm_linux_virtual_machine.main.public_ip_address. The azurerm_linux_virtual_machine resource does not expose public_ip_address directly; public IP information is typically obtained from the NIC or the public IP resource, not the VM. This may cause Terraform errors or an undefined output; consider removing or adjusting this output to reference the public IP resource created in the network module.

}
random = {
source = "hashicorp/random"
version = "3.9.0"
}
}
}

provider "azurerm" {
features {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This git clone URL uses your own GitHub username. The task description suggests replacing it with the student’s username, but functionally this still satisfies the requirement to deploy the provided ToDo app; only adjust if you intend this repo to be student-agnostic.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-running this script when the devops_todolist_terraform_task directory already exists could cause git clone to fail, which might make the extension show as failed on reapply. The task doesn’t require idempotency, but adding a simple if [ ! -d ... ] guard would make the script more robust.

}
use_oidc = true
}
resource "random_string" "suffix" {
length = 4
upper = false
special = false
}
/*
I CAN"T USE IT IN MAIN, BECAUSE IT MUST BE CREATED WITH STORAGE ACC
BEFORE BACKEND for TERRAFORM TO PROPERLY START

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assignment explicitly says the VM should be named matebox, but here the name is driven purely by var.vm_name; while the root default is "matebox", the module doesn’t enforce it. Consider hardcoding "matebox" here (or constraining vm_name) to exactly satisfy the requirement.


resource "azurerm_resource_group" "example" {
name = var.resource_group_name
location = var.resource_group_location

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assignment requires the VM size to be Standard_B1s (checklist items #8 and #17). Here, the VM size is driven by var.vm_size, and while the root variables default to Standard_B1s, make sure you don’t override this with a different value in terraform.tfvars or elsewhere, otherwise you’ll violate the requirement.

}
*/

data "azurerm_resource_group" "main" {
name = var.resource_group_name
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requirement states the VM image must be Ubuntu 22.04 (Ubuntu2204). While your source_image_reference is correct for Ubuntu 22.04 LTS, double-check that this mapping is acceptable for the task; if they expect a specific shorthand or image alias, you may need to adjust, but functionally this is Ubuntu 22.04.



module "network" {
source = "./modules/network"
resource_group_name = data.azurerm_resource_group.main.name
resource_group_location = data.azurerm_resource_group.main.location
network_name = var.virtual_network_name
v_net_address_prefix = var.vnet_address_prefix
subnet_name = var.subnet_name
subnet_address_prefix = var.subnet_address_prefix
pip_name = var.public_ip_address_name
dns_label = "${var.dns_label}-${random_string.suffix.result}"
network_SG_name = var.network_security_group_name
security_rules = var.security_rules
}
module "compute" {
source = "./modules/compute"
resource_group_name = data.azurerm_resource_group.main.name
resource_group_location = data.azurerm_resource_group.main.location
vm_name = var.vm_name
vm_size = var.vm_size
subnet_id = module.network.subnet_id
public_ip_id = module.network.public_ip_id
NSG_id = module.network.NSG_id
extension_name = "CustomScript"
path_to_script = "install-app.sh"
Comment on lines +62 to +63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compute module correctly configures a CustomScript extension pointing to install-app.sh, which meets the requirement to deploy the ToDo app via VM extension. Ensure that install-app.sh is in the expected relative location when Terraform runs so the file(...) function resolves properly.

Comment on lines +62 to +63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CustomScript extension is correctly referenced, but you’re using protected_settings.script with a base64-encoded file. Azure typically expects settings with commandToExecute (and optionally fileUris); if you encounter deployment issues, consider switching to the documented pattern so install-app.sh is executed reliably.

Comment on lines +62 to +63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This CustomScript extension wiring correctly points at install-app.sh, but note that using protected_settings.script with base64-encoded file content is slightly unusual; typically commandToExecute is used. It still satisfies the requirement to execute install-app.sh, so this is acceptable, just be sure it behaves as expected in your environment.

SSH_key = "linuxboxsshkey"
ssh_public_key_content = var.ssh_key_public
}
module "storage" {
source = "./modules/storage"
resource_group_name = data.azurerm_resource_group.main.name
resource_group_location = data.azurerm_resource_group.main.location
storage_account_name = var.storage_account_name_for_artifacts
container_name = "task-artifacts"
}
66 changes: 66 additions & 0 deletions modules/compute/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
resource "azurerm_network_interface" "main" {
name = "${var.vm_name}-nic"
Comment on lines +1 to +2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NIC is correctly named ${var.vm_name}-nic and associated with the provided subnet and public IP, matching the compute module requirements.

Comment on lines +1 to +2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output exposes the VM ID, which is consistent with how the root module references module.compute.vm_id.

location = var.resource_group_location
resource_group_name = var.resource_group_name

ip_configuration {
name = "ip_config"
subnet_id = var.subnet_id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = var.public_ip_id
}
}

resource "azurerm_network_interface_security_group_association" "main" {
network_interface_id = azurerm_network_interface.main.id
network_security_group_id = var.NSG_id
Comment on lines +13 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public IP resource uses allocation_method = "Static", but the task requires a dynamic allocation method (checklist item #10/#16). Change this to "Dynamic" to conform to the assignment.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The git clone URL still points to your own GitHub username. The task description suggests updating this link with the student’s GitHub username when they reuse the template; if this repo is your own submission, this is fine, but if it’s meant as a general solution, consider parameterizing or documenting that this needs to be changed.

}

resource "azurerm_ssh_public_key" "example" {
name = var.SSH_key
resource_group_name = var.resource_group_name
location = var.resource_group_location
public_key = var.ssh_public_key_content
}

resource "azurerm_linux_virtual_machine" "main" {
name = var.vm_name
location = var.resource_group_location
resource_group_name = var.resource_group_name
network_interface_ids = [azurerm_network_interface.main.id]
size = var.vm_size

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default vm_size inside the compute module is "Standard_D2s_v3", which conflicts with the requirement that the VM size must be Standard_B1s (checklist item #8). Even though the root module passes Standard_B1s by default, it’s safer and clearer to set this module default to Standard_B1s as well.

Comment on lines +26 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The VM uses size = var.vm_size, and with the module default set to "Standard_B1s", this now aligns with the required VM size.



admin_username = "testadmin"
disable_password_authentication = true
computer_name = "todoapp-host"
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts"
Comment on lines +37 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The task requires the VM image to be Ubuntu2204 (checklist item #7). Here you use offer = "0001-com-ubuntu-server-jammy" and sku = "22_04-lts". While this is Ubuntu 22.04, it doesn’t match the literal requirement; consider updating the image selection to align with the specified Ubuntu2204 image format expected by the assignment.

version = "latest"
}
os_disk {
name = "myosdisk1"
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}

admin_ssh_key {
username = "testadmin"
public_key = azurerm_ssh_public_key.example.public_key
}
}

resource "azurerm_virtual_machine_extension" "example" {
name = var.extension_name
virtual_machine_id = azurerm_linux_virtual_machine.main.id
publisher = "Microsoft.Azure.Extensions"
type = "CustomScript"
type_handler_version = "2.0"

protected_settings = jsonencode({
script = base64encode(file("${path.module}/../../${var.path_to_script}"))
Comment on lines +55 to +63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This CustomScript extension correctly references the VM and uses the script file from the repo. While Azure docs usually show commandToExecute, using the script field here still satisfies the requirement to use CustomScript to execute install-app.sh.

})
Comment on lines +62 to +64

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the Azure CustomScript extension, the expected settings usually involve commandToExecute (and optionally fileUris), rather than a script property containing base64-encoded file contents. With the current protected_settings JSON, the extension may not actually execute your script. Review Azure documentation for Microsoft.Azure.Extensions CustomScript and adjust this block to use the supported fields so install-app.sh is definitively run.


Comment on lines +53 to +65

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The root configuration correctly wires the compute module and passes vm_name, vm_size, ssh_public_key_content, and the path to install-app.sh, satisfying the requirement to use modules and to deploy the app via a CustomScript extension.

}
3 changes: 3 additions & 0 deletions modules/compute/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "vm_id" {
value = azurerm_linux_virtual_machine.main.id
}
55 changes: 55 additions & 0 deletions modules/compute/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
variable "resource_group_name" {
description = "Name of the resource group"
type = string
}

variable "resource_group_location" {
description = "Location of the resource group"
type = string
}

variable "pip_name" {
description = "Name of public IP resource"
type = string
default = "linuxboxpip"
}

variable "vm_name" {
description = "Virtual Machine Name"
type = string

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public IP is configured with allocation_method = "Static", but the description explicitly requires a dynamic allocation method (checklist items #10 and #16). Change this to "Dynamic" to comply with the task requirements.

Comment on lines +15 to +19

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public IP resource now uses allocation_method = "Dynamic" for linuxboxpip, which satisfies the requirement that the public IP must be dynamically allocated.

default = "matebox"
}
variable "vm_size" {
description = "Size of VM"
type = string
default = "Standard_B1s"
Comment on lines +22 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The vm_size default is now "Standard_B1s", which matches the required VM size for the compute module and fixes the earlier mismatch noted in the checklist.

}

variable "subnet_id" {
type = string
}

variable "public_ip_id" {
type = string
}

variable "NSG_id" {
type = string
}

variable "extension_name" {
type = string
default = "CustomScript"
}
variable "path_to_script" {
type = string
}

variable "SSH_key" {
type = string
default = "linuxboxsshkey"
}

variable "ssh_public_key_content" {
type = string
}
43 changes: 43 additions & 0 deletions modules/network/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
resource "azurerm_virtual_network" "main" {
name = var.network_name
address_space = ["10.0.0.0/16"]
location = var.resource_group_location
resource_group_name = var.resource_group_name
}

resource "azurerm_subnet" "internal" {
name = var.subnet_name
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.0.0/24"]
Comment on lines +1 to +12

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The VNet and subnet are hardcoded to 10.0.0.0/16 and 10.0.0.0/24 here, which matches the required address spaces. Just be aware that the v_net_address_prefix and subnet_address_prefix variables are not used in these resources; if you intended them to be configurable, you could plug them in instead of literals.

}

resource "azurerm_public_ip" "mainIP" {
name = var.pip_name
resource_group_name = var.resource_group_name
location = var.resource_group_location
allocation_method = "Dynamic"
Comment on lines +15 to +19

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public IP now uses allocation_method = "Dynamic", which aligns with the task requirement for linuxboxpip and fixes the earlier static allocation issue.

sku = "Standard"
domain_name_label = var.dns_label
}

resource "azurerm_network_security_group" "main" {
name = var.network_SG_name
Comment on lines +22 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default VM size here is "Standard_D2s_v3", but the assignment requires the virtual machine size to be Standard_B1s (checklist items #8 and #17). Update this default to "Standard_B1s" so the compute module itself matches the task specification.

Comment on lines +22 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The vm_size variable default is correctly set to "Standard_B1s", resolving the previous requirement mismatch and ensuring the compute module itself enforces the required VM size.

location = var.resource_group_location
resource_group_name = var.resource_group_name

dynamic "security_rule" {
for_each = var.security_rules
content {
name = security_rule.value.name
priority = security_rule.value.priority
direction = security_rule.value.direction
access = security_rule.value.access
protocol = security_rule.value.protocol
source_port_range = security_rule.value.source_port_range
destination_port_range = security_rule.value.destination_port_range
source_address_prefix = security_rule.value.source_address_prefix
destination_address_prefix = security_rule.value.destination_address_prefix
}
}
}
16 changes: 16 additions & 0 deletions modules/network/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
output "subnet_id" {
value = azurerm_subnet.internal.id
}

output "public_ip_id" {
value = azurerm_public_ip.mainIP.id
}
output "public_ip" {
value = azurerm_public_ip.mainIP.ip_address
}
output "public_domain" {
value = azurerm_public_ip.mainIP.domain_name_label
Comment on lines +3 to +12

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You define v_net_address_prefix and subnet_address_prefix variables, but here you hard-code the address space to 10.0.0.0/16 and 10.0.0.0/24. That matches the assignment requirements, but if you intend these prefixes to be configurable, consider using the variables instead of literals.

}
output "NSG_id" {
value = azurerm_network_security_group.main.id
Comment on lines +5 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public IP and NSG outputs are correctly exposed for use by the root module, enabling you to wire the compute module and outputs as required.

}
Loading
Loading