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
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.terraform/

*.tfstate
*.tfstate.*

*.tfvars

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*.tfvars will ignore the required terraform.tfvars file which the task requires in the repository (Main Configuration and Variables). This will prevent graders / CI from seeing the enforced variable values. Remove this line or add an explicit allow rule such as !terraform.tfvars so the root terraform.tfvars can be committed.

*.tfvars.json

.terraformrc
terraform.rc

*.tfplan
*.log

.DS_Store

id_rsa*
42 changes: 42 additions & 0 deletions .terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file modified app/start.sh
100644 → 100755
Empty file.
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" {
resource_group_name = "mate-azure-task-12"
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.

storage_account_name = "yourstorageaccount" is correct per the task, but the storage account and the tfstate container must exist before running terraform init — otherwise terraform init will fail (see checklist items about backend pre-creation). Create them manually or ensure your storage module creates them and that they exist prior to init.

container_name = "tfstate"
key = "terraform.tfstate"
Comment thread
salabam marked this conversation as resolved.
use_oidc = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

use_oidc = true enables OIDC auth for the backend which requires compatible Terraform/CI and provider support (Terraform v1.6+ and Azure provider configuration). If your environment/CI runner does not support OIDC, remove or adjust this setting.

}
}
7 changes: 4 additions & 3 deletions install-app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
# when running the script as VM extention.
apt-get update -yq
apt-get install python3-pip -yq
apt-get install -yq git
Comment thread
salabam marked this conversation as resolved.

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 installs git but has no error handling here. Prefer combining package installs (e.g. apt-get install -yq python3-pip git) and/or add set -e at the top so the script fails fast if installation fails.


# Create a directory for the app and download the files.
mkdir /app
# Create a directory for the app and download the files.
Comment thread
salabam marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Creating /app is fine, but the script does not check for errors. Add set -euo pipefail at the top and consider verifying the directory was created and is writable before continuing.

mkdir -p /app
Comment thread
salabam marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

mkdir -p /app is fine, but you should ensure the script fails on error and consider cleaning or preparing the directory idempotently (e.g. remove stale files if appropriate).

# 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/salabam/devops_todolist_terraform_task.git
Comment thread
salabam marked this conversation as resolved.

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 hard-coded git clone will break when the VM extension is expected to download a raw install-app.sh. Make the repo path configurable or (better) have the extension supply a raw script URL. Also check git clone exit status and verify the expected devops_todolist_terraform_task/app path exists before cp-ing.

cp -r devops_todolist_terraform_task/app/* /app

# create a service for the app via systemctl and start the app
Expand Down
58 changes: 58 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
terraform {
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
azurerm = {
source = "hashicorp/azurerm"
version = "3.105.0"
}
}
}

provider "azurerm" {
features {}
use_oidc = true
}

resource "azurerm_resource_group" "rg" {
name = var.resource_group_name
Comment thread
salabam marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

resource_group_name is taken from a variable here — ensure the root variable (or terraform.tfvars) sets it to the required value mate-azure-task-12. The backend and grader expect that specific resource group name. If you rely on a different value, automated checks will fail.

location = var.location
Comment thread
salabam marked this conversation as resolved.
}

module "storage_module" {
source = "./modules/storage"
storage_account_name = var.storage_account_name
storage_container_name = var.storage_container_name
Comment thread
salabam marked this conversation as resolved.

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 storage module is being passed storage_container_name here. The task requires the storage container name to be task-artifacts — ensure this value is set in your committed root variables/terraform.tfvars (or set a default) so the grader can verify it.

resource_group_name = azurerm_resource_group.rg.name
location = var.location
}

module "network_module" {
source = "./modules/network"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.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
net_security_group_name = var.net_security_group_name
public_ip_name = var.public_ip_name
dns_prefix = var.dns_prefix
Comment thread
salabam marked this conversation as resolved.
Comment on lines +36 to +42

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 pass many network-related inputs here. The task requires specific names: virtual network vnet, subnet default, network security group defaultnsg, public IP name linuxboxpip, and a DNS label variable named dns_label (prefix matetask + random). Currently your module uses net_security_group_name, public_ip_name and dns_prefix — align these names/values with the spec or document why they differ so automated checks can validate them.

}

module "compute_module" {
source = "./modules/compute"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
subnet_id = module.network_module.subnet_id
public_ip_id = module.network_module.public_ip_id
network_interface_name_sufix = var.network_interface_name_sufix
Comment thread
salabam marked this conversation as resolved.

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 receives network_interface_name_sufix and composes the NIC name as ${var.vm_name}${var.network_interface_name_sufix} in the module. The spec requires the NIC name be exactly ${var.vm_name}-nic. Either set network_interface_name_sufix = "-nic" in terraform.tfvars or change the module to build the name explicitly as ${var.vm_name}-nic to enforce the required pattern.

vm_name = var.vm_name
vm_size = var.vm_size
Comment thread
salabam marked this conversation as resolved.
admin_username = var.admin_username
ssh_public_key = var.ssh_public_key
Comment thread
salabam marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ssh_public_key is passed to the compute module here. The task expects the root variable ssh_key_public / SSH key content to be provided and the VM to use the key named linuxboxsshkey per the spec. Make sure your root variable name and value match the spec (or change module variable names accordingly) so grading can verify the SSH configuration.

install_app_sh_git_path = var.install_app_sh_git_path
Comment thread
salabam marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

install_app_sh_git_path is being passed to the compute module and used as the VM extension fileUris. The CustomScript extension must fetch a raw install-app.sh script (not a repo root URL). Commit a reachable raw install-app.sh (or use a storage blob URL) and pass its raw URL here; also make the script robust (set -e) so provisioning fails loudly if something goes wrong. Without a raw script URL the extension will fail to run the installer.

depends_on = [module.network_module]
}
56 changes: 56 additions & 0 deletions modules/compute/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
resource "azurerm_network_interface" "ni" {
name = "${var.vm_name}${var.network_interface_name_sufix}"
Comment thread
salabam marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

NIC name is constructed as ${var.vm_name}${var.network_interface_name_sufix}. The task requires the NIC name be exactly ${var.vm_name}-nic. Either change this line to name = "${var.vm_name}-nic" or ensure the root network_interface_name_sufix variable is set to -nic in a committed terraform.tfvars so the required pattern is enforced. This is required for automated checks to pass.

resource_group_name = var.resource_group_name
location = var.location

ip_configuration {
name = "ip-cfg"
private_ip_address_allocation = "Dynamic"
subnet_id = var.subnet_id
public_ip_address_id = var.public_ip_id
}
}

resource "azurerm_linux_virtual_machine" "vm" {
name = var.vm_name
location = var.location
resource_group_name = var.resource_group_name
network_interface_ids = [azurerm_network_interface.ni.id]
size = var.vm_size
admin_username = var.admin_username
disable_password_authentication = true

admin_ssh_key {
username = var.admin_username
public_key = var.ssh_public_key
Comment thread
salabam marked this conversation as resolved.
Comment on lines +23 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.

You use var.ssh_public_key for admin_ssh_key.public_key. The task expects the SSH key variable/name and content to follow the spec (SSH key linuxboxsshkey and root variable named ssh_key_public). Align variable names and values between root and module (or rename the module variable) so the grader can verify the SSH configuration.

}

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"
Comment on lines +33 to +37

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 image and SKU here use Ubuntu 22.04 (Canonical jammy, sku 22_04-lts) which matches the requirement for Ubuntu2204 — keep this but ensure var.vm_size and var.vm_name provided at root match the required Standard_B1s and matebox values. The module relies on root inputs rather than enforcing defaults here.

}

}

resource "azurerm_virtual_machine_extension" "install_script" {
name = "install-app-extension"
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_app_sh_git_path
Comment thread
salabam marked this conversation as resolved.
Comment on lines +50 to +51

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 extension's fileUris uses var.install_app_sh_git_path. The CustomScript extension needs a raw, directly-downloadable install-app.sh URL (or a blob URL in the storage container) — not a repo root link. Commit/host a raw install-app.sh and pass its raw URL here (or rename var to install_app_sh_url), otherwise the extension will fail to fetch/execute the script.

],
commandToExecute = "bash install-app.sh"
Comment thread
salabam marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

commandToExecute runs bash install-app.sh. Make sure the referenced install-app.sh exists at the provided URL and that the script is hardened (e.g. set -euo pipefail or at minimum set -e) and idempotent so provisioning fails loudly on error and does not leave the VM in a partially-configured state.

})
}

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.vm.id
}
52 changes: 52 additions & 0 deletions modules/compute/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
### I made sure that all variables are in terraform.tfvars
### I don't need you to write "check it!" to every variable.

variable "resource_group_name" {
description = "The name of the resource group"
type = string
Comment on lines +4 to +6

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 resource group to be mate-azure-task-12 (must be enforced in code or via committed tfvars). Either set a default = "mate-azure-task-12" here or add a validation to ensure the value matches the required name so automated checks can verify it.

}

variable "location" {
description = "The location for all resources."
type = string
Comment on lines +9 to +11

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 location to be uksouth. Add a default = "uksouth" or a validation block so the module cannot be instantiated with a different location (automated grader expects this exact value).

}

variable "vm_name" {
description = "The name of the virtual machine"
type = string
Comment on lines +14 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 VM must be named matebox per the specification. Set default = "matebox" or add a validation to enforce this value (automated checks require the exact name).

Comment on lines +14 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.

This module does not expose or default the VM image. The task requires the VM image be Ubuntu2204. Add a variable (or hardcode in the module) to ensure the image is set to Canonical:0001-com-ubuntu-server-jammy:22_04-lts:latest (or an equivalent Ubuntu2204 reference), or document that the module will always use Ubuntu2204.

}
Comment thread
salabam marked this conversation as resolved.

variable "vm_size" {
description = "The size of the virtual machine"
type = string
Comment on lines +19 to +21

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 must be Standard_B1s according to the task. Provide default = "Standard_B1s" or validation here to enforce the required size.

}
Comment thread
salabam marked this conversation as resolved.

variable "network_interface_name_sufix" {
description = "The sufix of the network interface, will be concatenated with vm_name"
type = string
Comment on lines +24 to +26

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

network_interface_name_sufix is misspelled (sufix) and relying on a free-form suffix can break the requirement that the NIC name is exactly ${var.vm_name}-nic. Remove this variable or default it to -nic, and build the NIC name in the module as "${var.vm_name}-nic" to satisfy the checklist.

}
Comment thread
salabam marked this conversation as resolved.

variable "subnet_id" {
description = "The id of the sub network"
type = string
}

variable "public_ip_id" {
description = "The id of the public ip address"
type = string
}

variable "admin_username" {
description = "The user that will be created when the virtual machine is created"
type = string
}

variable "ssh_public_key" {
description = "The path to the ssh key for sign in to system"
type = string
Comment on lines +44 to +46

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 variable ssh_public_key name and description are problematic: the task expects a public key content variable (root variable named ssh_key_public / required linuxboxsshkey). Rename or map this to the expected name and update the description to clarify it should contain the public key content (not a filesystem path).

}
Comment thread
salabam marked this conversation as resolved.

variable "install_app_sh_git_path" {
description = "The path to the git repo with install script"
type = string
Comment on lines +49 to +51

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

install_app_sh_git_path is described as a git repo path. The CustomScript extension must download a raw install-app.sh file (raw URL) and execute it as bash ./install-app.sh. Rename this variable to reflect a raw script URL (e.g. install_app_sh_url) and ensure the module uses that raw URL in the VM extension fileUris and executes bash ./install-app.sh.

}
Comment thread
salabam marked this conversation as resolved.
37 changes: 37 additions & 0 deletions modules/network/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
locals {
security_rules = [
Comment thread
salabam marked this conversation as resolved.
{
name = "AllowSSH"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
Comment thread
salabam marked this conversation as resolved.
},
{
name = "AllowHTTP"
priority = 110
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "*"
destination_address_prefix = "*"
Comment thread
salabam marked this conversation as resolved.
},
{
name = "AllowHTTP8080"
priority = 120
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "8080"
source_address_prefix = "*"
destination_address_prefix = "*"
Comment thread
salabam marked this conversation as resolved.
}
]
}
53 changes: 53 additions & 0 deletions modules/network/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
resource "random_integer" "suffix" {
min = 1000
max = 9999
}

resource "azurerm_virtual_network" "vnet" {
name = var.virtual_network_name
address_space = var.vnet_address_prefix
resource_group_name = var.resource_group_name
location = var.location
Comment on lines +6 to +10

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

address_space typically expects a list of CIDR strings. If var.vnet_address_prefix is a single string, wrap it as a list here (e.g. address_space = [var.vnet_address_prefix]) or change the variable to be a list. Also the task requires the vnet be named vnet — enforce this via a default or validation on the variable so automated checks can verify it.

}

resource "azurerm_subnet" "subnet" {
name = var.subnet_name
address_prefixes = var.subnet_address_prefix
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.vnet.name
Comment on lines +13 to +17

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

address_prefixes on azurerm_subnet expects a list. If var.subnet_address_prefix is a string, use address_prefixes = [var.subnet_address_prefix] or change the variable type accordingly. Also the subnet name must be default per the task — ensure the variable enforces that value (default/validation) so graders can detect it.

}

resource "azurerm_public_ip" "pip" {
name = var.public_ip_name
location = var.location
resource_group_name = var.resource_group_name
allocation_method = "Dynamic"
sku = "Standard"
Comment on lines +24 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.

You set allocation_method = "Dynamic" (correct per task) but sku = "Standard" on line 25. In Azure, Standard public IPs are typically static; combining Standard SKU with Dynamic can be invalid. Use sku = "Basic" (or omit sku) if you need Dynamic allocation. Also ensure the public IP name is linuxboxpip (enforce via variable default/validation).

domain_name_label = "${var.dns_prefix}${random_integer.suffix.result}"
Comment thread
salabam marked this conversation as resolved.

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 domain name label is constructed with var.dns_prefix but the task expects a dns_label/matetask variable. Ensure the variable name matches the root variable (or map it) and that the value is matetask (you may append the random suffix). Otherwise the grader will not find the expected DNS prefix.

}

resource "azurerm_network_security_group" "nsg" {
name = var.net_security_group_name
Comment thread
salabam marked this conversation as resolved.
location = var.location
resource_group_name = var.resource_group_name
Comment on lines +29 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

azurerm_network_security_group.nsg must have the name defaultnsg per the requirements. Ensure var.net_security_group_name has a default or validation to enforce defaultnsg so automated checks pass.


dynamic "security_rule" {
for_each = local.security_rules
Comment thread
salabam marked this conversation as resolved.
Comment on lines +34 to +35

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 dynamic block uses for_each = local.security_rules. Make sure local.security_rules is defined in this module (locals.tf) or passed in; if it's undefined Terraform will fail. If you don't need custom rules, set local.security_rules = [] to avoid errors.

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
}
}
}

resource "azurerm_subnet_network_security_group_association" "assoc" {
subnet_id = azurerm_subnet.subnet.id
network_security_group_id = azurerm_network_security_group.nsg.id
}
15 changes: 15 additions & 0 deletions modules/network/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
output "public_ip_fqdn" {
value = azurerm_public_ip.pip.fqdn
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.

This output uses azurerm_public_ip.pip.fqdn. Ensure the public IP resource in this module is actually declared as azurerm_public_ip "pip" { ... }. Also note fqdn will only be populated when a dns_label is set on the public IP (the task requires a matetask-prefixed DNS label).

}

output "subnet_id" {
value = azurerm_subnet.subnet.id
Comment on lines +5 to +6

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 output references azurerm_subnet.subnet.id — confirm the subnet resource is named subnet in the module (resource name must match). The task requires the subnet be named default at the Azure level; make sure the resource uses the correct Azure name argument while the Terraform resource name here can remain subnet.

}

output "public_ip_id" {
value = azurerm_public_ip.pip.id
Comment on lines +9 to +10

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 references azurerm_public_ip.pip.id. As with the first comment, confirm the public IP resource in the module is declared with the local name pip, otherwise Terraform will fail during plan/apply.

}

output "public_ip_address" {
value = azurerm_public_ip.pip.ip_address
Comment on lines +13 to +14

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

azurerm_public_ip.pip.ip_address will only be available after the public IP is created. That's expected, but keep in mind if you rely on this output in other resources during the same apply you may need to manage dependencies. Consider adding description fields to outputs to improve clarity.

}
Loading