diff --git a/AWS-Maor-Labs/101-lab/main.tf b/AWS-Maor-Labs/101-lab/main.tf new file mode 100644 index 000000000..9247f60ba --- /dev/null +++ b/AWS-Maor-Labs/101-lab/main.tf @@ -0,0 +1,55 @@ + +# Define Provider Configuration +provider "aws" { + region = var.region +} + +variable "region" { + default = "us-east-1" +} + + +# Define a security group to allow SSH access to the VM +resource "aws_security_group" "sg-Maor" { + +ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + + +# create an EC2 instance +resource "aws_instance" "vm" { + ami = "ami-0c02fb55956c7d316" # Amazon Linux 2 AMI in us-east-1 + instance_type = "t2.micro" + + vpc_security_group_ids = [aws_security_group.sg-Maor.id] + + tags = { + Name = "Maor-vm" + } +} + + +# To retrieve the public IP of the virtual machine, use the following output configuration: +output "vm_public_ip" { + value = aws_instance.vm.public_ip + description = "Public IP address of the VM" +} + + + + + + + diff --git a/AWS-Maor-Labs/102-lab/main.tf b/AWS-Maor-Labs/102-lab/main.tf new file mode 100644 index 000000000..70a54652f --- /dev/null +++ b/AWS-Maor-Labs/102-lab/main.tf @@ -0,0 +1,75 @@ + +# Define Provider Configuration +provider "aws" { + region = var.region +} + +variable "region" { + default = "us-east-1" +} + + + +# Define a security group to allow SSH access to the VM +resource "aws_security_group" "sg-Maor" { + +ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + + +# create an EC2 instance +resource "aws_instance" "vm" { + ami = "ami-0c02fb55956c7d316" # Amazon Linux 2 AMI in us-east-1 + instance_type = "t2.micro" + subnet_id = "subnet-06acd0b316280afeb" + vpc_security_group_ids = [aws_security_group.sg-Maor.id] + + tags = { + Name = "Maor-vm" + } +} + + +resource "time_sleep" "wait_for_ip" { + create_duration = "10s" # Wait for 10 seconds + depends_on = [ aws_instance.vm ] +} + + + +# This command checks if the public IP address (${aws_instance.vm.public_ip}) is empty (-z). +resource "null_resource" "check_public_ip" { + + # If it is empty, it outputs an error message and terminates with an exit code status 1, causing Terraform to stop with an error. + provisioner "local-exec" { + command = <&2 + exit 1 + fi + EOT + } + + depends_on = [aws_instance.vm] +} + + + + + + + + + diff --git a/AWS-Maor-Labs/102-lab/output.tf b/AWS-Maor-Labs/102-lab/output.tf new file mode 100644 index 000000000..8bde3d0d4 --- /dev/null +++ b/AWS-Maor-Labs/102-lab/output.tf @@ -0,0 +1,7 @@ +# To retrieve the public IP of the virtual machine, use the following output configuration: +output "vm_public_ip" { + value = aws_instance.vm.public_ip + depends_on = [null_resource.check_public_ip] + description = "Public IP address of the VM" +} + diff --git a/AWS-Maor-Labs/102-lab/provider.tf b/AWS-Maor-Labs/102-lab/provider.tf new file mode 100644 index 000000000..ad42cd7fe --- /dev/null +++ b/AWS-Maor-Labs/102-lab/provider.tf @@ -0,0 +1,4 @@ +module "iam" { + source = "terraform-aws-modules/iam/aws" + version = "5.59.0" +} diff --git a/AWS-Maor-Labs/103-lab/apache_install.tf b/AWS-Maor-Labs/103-lab/apache_install.tf new file mode 100644 index 000000000..1c0dc03b2 --- /dev/null +++ b/AWS-Maor-Labs/103-lab/apache_install.tf @@ -0,0 +1,38 @@ + + + +# Null Resource for Apache Installation +resource "null_resource" "provision_apache" { + depends_on = [aws_instance.vm] + + # Trigger to force rerun whenever timestamp changes + # This will force terraform to rerun the provisioner and update the welcome.html file if changed + triggers = { + always_run = timestamp() + } + + provisioner "remote-exec" { + inline = [ + "sudo apt update", + "sudo apt install -y apache2", + "echo '

Welcome to the Web Server!

' | sudo tee /var/www/html/welcome.html", + "sudo systemctl start apache2", + "sudo systemctl enable apache2" + ] + + connection { + type = "ssh" + user = "ubuntu" + password = var.admin_password + host = aws_instance.vm.public_ip + timeout = "1m" + } + } +} + + +# Updated Output for Server Information to use data source +output "server_info" { + value = "Please browse: http://${aws_instance.vm.public_ip}/welcome.html" + description = "Instructions to access the server, note that port 80 is currently blocked." +} \ No newline at end of file diff --git a/AWS-Maor-Labs/103-lab/global.tf b/AWS-Maor-Labs/103-lab/global.tf new file mode 100644 index 000000000..830b5c0c0 --- /dev/null +++ b/AWS-Maor-Labs/103-lab/global.tf @@ -0,0 +1,30 @@ +# This file defines the provider (AWS) and the global variables for your configuration. + + +provider "aws" { + region = var.region +} + +variable "region" { + default = "us-west-2" +} + + +variable "ami" { + default = "ami-04feae287ec8b0244" + } +variable "vm_name" { + default = "vm-Maor" +} + +variable "admin_username" { + default = "admin-user" +} + +variable "admin_password" { + default = "Password123!" +} + +variable "vm_size" { + default = "t2.micro" +} diff --git a/AWS-Maor-Labs/103-lab/network.tf b/AWS-Maor-Labs/103-lab/network.tf new file mode 100644 index 000000000..97c867bf1 --- /dev/null +++ b/AWS-Maor-Labs/103-lab/network.tf @@ -0,0 +1,54 @@ + + +resource "aws_security_group" "sg" { + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + + + + + + + + +# # Network Configuration File + + +# resource "aws_security_group" "sg" { +# # allow port 22 to be open +# ingress { +# from_port = 22 +# to_port = 22 +# protocol = "tcp" +# cidr_blocks = ["0.0.0.0/0"] +# } + +# # allow port 80 to be open +# ingress { +# from_port = 80 +# to_port = 80 +# protocol = "tcp" +# cidr_blocks = ["0.0.0.0/0"] +# } + +# # All outbound traffic (egress) to the internet — no restriction. +# egress { +# from_port = 0 +# to_port = 0 +# protocol = "-1" +# cidr_blocks = ["0.0.0.0/0"] +# } +# } \ No newline at end of file diff --git a/AWS-Maor-Labs/103-lab/validate_ip.tf b/AWS-Maor-Labs/103-lab/validate_ip.tf new file mode 100644 index 000000000..17144b32c --- /dev/null +++ b/AWS-Maor-Labs/103-lab/validate_ip.tf @@ -0,0 +1,28 @@ +resource "time_sleep" "wait_for_ip" { + create_duration = "1m" # Wait for 1 minute to allow AWS to allocate the IP +} + + +# The null_resource runs a local script to validate the IP allocation, retrying a few times if needed. +# The data source fetches the latest IP once the validation completes successfully. + +resource "null_resource" "validate_ip" { + provisioner "local-exec" { + command = <&2 + exit 1 + EOT + } + depends_on = [time_sleep.wait_for_ip] +} diff --git a/AWS-Maor-Labs/103-lab/vm.tf b/AWS-Maor-Labs/103-lab/vm.tf new file mode 100644 index 000000000..2a63aa53f --- /dev/null +++ b/AWS-Maor-Labs/103-lab/vm.tf @@ -0,0 +1,50 @@ +resource "aws_instance" "vm" { + ami = var.ami + instance_type = var.vm_size + vpc_security_group_ids = [aws_security_group.sg.id] + + tags = { + Name = var.vm_name + } + + user_data = <<-EOF + #cloud-config + users: + - name: ${var.admin_username} + groups: sudo + shell: /bin/bash + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + lock_passwd: false + passwd: $(echo ${var.admin_password} | openssl passwd -6 -stdin) + EOF + + } + +output "vm_public_ip" { + value = aws_instance.vm.public_ip +} + + +# ----------------------------------------------------------- +# Notes regarding the user login configuration: +# +# Explanation: +# 1. Cloud-Init: +# - user_data allows you to pass initialization scripts to the EC2 instance during boot. +# - The #cloud-config syntax is used to create users and set passwords. +# +# 2. Password Encryption: +# - The `passwd` field requires a hashed password. +# - Use `openssl passwd -6` to generate a secure hash for the password. +# - Replace the hash generation dynamically if needed (e.g., in CI/CD pipelines). +# +# 3. Locking SSH: +# - By not specifying an SSH key and relying on user_data, you enable user/password login. +# - Ensure the AWS security group allows SSH (port 22) if required for initial configuration. +# +# 4. Security Considerations (TBD): +# - Avoid hardcoding sensitive credentials in your Terraform code. +# - Use secure methods to pass secrets, such as: +# - Terraform variables stored in encrypted state files +# - A secrets management solution (e.g., AWS Secrets Manager) +# ----------------------------------------------------------- diff --git a/AWS-Maor-Labs/104-lab/mock_outputs.tf b/AWS-Maor-Labs/104-lab/mock_outputs.tf new file mode 100644 index 000000000..8feec75ca --- /dev/null +++ b/AWS-Maor-Labs/104-lab/mock_outputs.tf @@ -0,0 +1,35 @@ + + +# Mock the number of virtual machines needed +output "vm_count" { + value = var.high_availability ? 3 : 1 + description = "Number of VMs required for the environment. If high availability is true, 3 VMs are needed; otherwise, 1." +} + +# Mocking network requirements based on environment +output "network_configuration" { + value = var.environment == "prod" ? "Production Network - Full Scale" : "Development/Staging Network - Limited Scale" + description = "Provides the network configuration type based on the environment." +} + +# Example of conditional logic using a ternary operator +output "ha_status_message" { + value = var.high_availability ? "High availability is enabled - multiple VMs are needed." : "High availability is disabled - a single VM is sufficient." + description = "A message indicating if high availability is enabled or disabled." +} + +# Mocking subnet creation using for_each +locals { + subnets = var.high_availability ? ["subnet-a", "subnet-b", "subnet-c"] : ["subnet-a"] +} + +output "mock_subnet_list" { + value = [for subnet in local.subnets : "Configured ${subnet}"] + description = "A mocked list of subnets that would be created based on high availability." +} + + + + + + diff --git a/AWS-Maor-Labs/104-lab/studentExtension/mock_outputs.tf b/AWS-Maor-Labs/104-lab/studentExtension/mock_outputs.tf new file mode 100644 index 000000000..e0c5e48c3 --- /dev/null +++ b/AWS-Maor-Labs/104-lab/studentExtension/mock_outputs.tf @@ -0,0 +1,23 @@ +# Output to show how many VMs would be created +output "vm_count" { + value = var.high_availability ? 3 : 1 + description = "Number of VMs required for the environment. If high availability is true, 3 VMs are needed; otherwise, 1." +} + +# Output to show the environment network configuration +output "network_configuration" { + value = var.environment == "prod" ? "Production Network - Full Scale" : "Development/Staging Network - Limited Scale" + description = "Provides the network configuration type based on the environment." +} + +# Output to indicate high availability status +output "ha_status_message" { + value = var.high_availability ? "High availability is enabled - multiple VMs are needed." : "High availability is disabled - a single VM is sufficient." + description = "A message indicating if high availability is enabled or disabled." +} + +# Output to mock database creation +output "mock_database_creation" { + value = var.create_database ? "A mock database will be created for this environment." : "No database needed for this environment." + description = "Indicates whether a mock database should be created." +} \ No newline at end of file diff --git a/AWS-Maor-Labs/104-lab/studentExtension/mock_services.tf b/AWS-Maor-Labs/104-lab/studentExtension/mock_services.tf new file mode 100644 index 000000000..a0642c433 --- /dev/null +++ b/AWS-Maor-Labs/104-lab/studentExtension/mock_services.tf @@ -0,0 +1,10 @@ +# Local variable to manage the list of services based on `create_database` +locals { + services = var.create_database ? ["web", "api", "database"] : ["web", "api"] +} + +# Output the mock list of services +output "mock_services_list" { + value = [for service in local.services : "Configured ${service} service"] + description = "A mocked list of services that would be created based on the create_database variable." +} \ No newline at end of file diff --git a/AWS-Maor-Labs/104-lab/studentExtension/optional_services.tf b/AWS-Maor-Labs/104-lab/studentExtension/optional_services.tf new file mode 100644 index 000000000..7a21fd8db --- /dev/null +++ b/AWS-Maor-Labs/104-lab/studentExtension/optional_services.tf @@ -0,0 +1,11 @@ +# Local variable to manage the list of services including cache if applicable +locals { + # Adding "cache" to the list if `environment` is "prod" and `create_database` is true + extended_services = var.environment == "prod" && var.create_database ? concat(local.services, ["cache"]) : local.services +} + +# Output the extended list of services +output "extended_services_list" { + value = [for service in local.extended_services : "Configured ${service} service"] + description = "A mocked list of services that would be created, optionally including cache for production." +} \ No newline at end of file diff --git a/AWS-Maor-Labs/104-lab/studentExtension/variables.tf b/AWS-Maor-Labs/104-lab/studentExtension/variables.tf new file mode 100644 index 000000000..f2772138e --- /dev/null +++ b/AWS-Maor-Labs/104-lab/studentExtension/variables.tf @@ -0,0 +1,23 @@ + + + +# Variable to control environment type +variable "environment" { + description = "Define the environment type: dev, staging, or prod" + type = string + default = "dev" +} + +# Variable to control high availability +variable "high_availability" { + description = "Whether to enable high availability (true or false)" + type = bool + default = false +} + +# Variable to control mock database creation +variable "create_database" { + description = "Whether to create a mock database (true or false)" + type = bool + default = true +} \ No newline at end of file diff --git a/AWS-Maor-Labs/104-lab/variables.tf b/AWS-Maor-Labs/104-lab/variables.tf new file mode 100644 index 000000000..87e9193c2 --- /dev/null +++ b/AWS-Maor-Labs/104-lab/variables.tf @@ -0,0 +1,16 @@ + + +variable "environment" { + description = "Define the environment type: dev, staging, or prod" + type = string + default = "dev" +} + +variable "high_availability" { + description = "Whether to enable high availability (true or false)" + type = bool + default = false +} + + + diff --git a/AWS-Maor-Labs/105-lab/ec2.tf b/AWS-Maor-Labs/105-lab/ec2.tf new file mode 100644 index 000000000..ba80f0793 --- /dev/null +++ b/AWS-Maor-Labs/105-lab/ec2.tf @@ -0,0 +1,13 @@ +# ec2.tf +resource "aws_instance" "web_server" { + ami = "ami-0c02fb55956c7d316" + instance_type = "t2.micro" + subnet_id = module.vpc.public_subnet_id + # subnet_id = aws_subnet.public_subnet.id + vpc_security_group_ids = [aws_security_group.ec2_sg.id] + associate_public_ip_address = true + + tags = { + Name = "Web-Server" + } +} diff --git a/AWS-Maor-Labs/105-lab/main.tf b/AWS-Maor-Labs/105-lab/main.tf new file mode 100644 index 000000000..88224aea8 --- /dev/null +++ b/AWS-Maor-Labs/105-lab/main.tf @@ -0,0 +1,52 @@ +# resource "aws_vpc" "lab_vpc" { +# cidr_block = "10.0.0.0/16" +# enable_dns_support = true +# enable_dns_hostnames = true + +# tags = { +# Name = "Lab-VPC" +# } +# } + +# resource "aws_subnet" "public_subnet" { +# vpc_id = aws_vpc.lab_vpc.id +# cidr_block = "10.0.1.0/24" +# map_public_ip_on_launch = true +# availability_zone = "us-east-1a" + +# tags = { +# Name = "Public-Subnet" +# } +# } + +# resource "aws_subnet" "private_subnet" { +# vpc_id = aws_vpc.lab_vpc.id +# cidr_block = "10.0.2.0/24" +# availability_zone = "us-east-1a" + +# tags = { +# Name = "Private-Subnet" +# } +# } + +# resource "aws_internet_gateway" "igw" { +# vpc_id = aws_vpc.lab_vpc.id + +# tags = { +# Name = "Lab-IGW" +# } +# } + + +module "vpc" { + source = "./modules/vpc" + vpc_cidr = "10.0.0.0/16" + public_subnet_cidr = "10.0.1.0/24" +} + +module "ec2" { + source = "./modules/ec2" + ami = "ami-055e3d4f0bbeb5878" + instance_type = "t2.micro" + subnet_id = module.vpc.public_subnet_id +} diff --git a/AWS-Maor-Labs/105-lab/modules/ec2/main.tf b/AWS-Maor-Labs/105-lab/modules/ec2/main.tf new file mode 100644 index 000000000..894dd27a7 --- /dev/null +++ b/AWS-Maor-Labs/105-lab/modules/ec2/main.tf @@ -0,0 +1,14 @@ +# resource "aws_instance" "instance" { +# ami = var.ami +# instance_type = var.instance_type +# subnet_id = var.subnet_id +# } + + + +resource "aws_instance" "instance" { + ami = var.ami + instance_type = var.instance_type + subnet_id = var.subnet_id +} + diff --git a/AWS-Maor-Labs/105-lab/modules/ec2/variables.tf b/AWS-Maor-Labs/105-lab/modules/ec2/variables.tf new file mode 100644 index 000000000..65a023fa8 --- /dev/null +++ b/AWS-Maor-Labs/105-lab/modules/ec2/variables.tf @@ -0,0 +1,8 @@ +# variable "ami" {} +# variable "instance_type" {} +# variable "subnet_id" {} + + +variable "ami" {} +variable "instance_type" {} +variable "subnet_id" {} diff --git a/AWS-Maor-Labs/105-lab/modules/vpc/main.tf b/AWS-Maor-Labs/105-lab/modules/vpc/main.tf new file mode 100644 index 000000000..c7b93d858 --- /dev/null +++ b/AWS-Maor-Labs/105-lab/modules/vpc/main.tf @@ -0,0 +1,48 @@ +resource "aws_vpc" "lab_vpc" { + cidr_block = var.cidr_block + enable_dns_support = true + enable_dns_hostnames = true + + tags = { + Name = var.name + } +} + +resource "aws_internet_gateway" "igw" { + vpc_id = aws_vpc.lab_vpc.id + + tags = { + Name = "${var.name}-igw" + } +} + +resource "aws_subnet" "public_subnet" { + vpc_id = aws_vpc.lab_vpc.id + cidr_block = "10.0.1.0/24" + availability_zone = var.az1 + map_public_ip_on_launch = true + + tags = { + Name = "${var.name}-public" + } +} + +resource "aws_subnet" "private_subnet" { + vpc_id = aws_vpc.lab_vpc.id + cidr_block = "10.0.2.0/24" + availability_zone = var.az2 + + tags = { + Name = "${var.name}-private" + } +} + + +resource "aws_vpc" "main" { + cidr_block = var.vpc_cidr +} + +resource "aws_subnet" "public" { + vpc_id = aws_vpc.main.id + cidr_block = var.public_subnet_cidr +} \ No newline at end of file diff --git a/AWS-Maor-Labs/105-lab/modules/vpc/outputs.tf b/AWS-Maor-Labs/105-lab/modules/vpc/outputs.tf new file mode 100644 index 000000000..03d19b3e9 --- /dev/null +++ b/AWS-Maor-Labs/105-lab/modules/vpc/outputs.tf @@ -0,0 +1,27 @@ +# # outputs.tf +# output "ec2_instance_id" { +# value = aws_instance.web_server.id +# } + +# output "ec2_public_ip" { +# value = aws_instance.web_server.public_ip +# } + +# output "vpc_id" { +# value = aws_vpc.lab_vpc.id +# } + + +output "vpc_id" { + value = aws_vpc.main.id +} + + +output "public_subnet_id" { + value = aws_internet_gateway.igw.id +} + +output "igw_id" { + value = aws_internet_gateway.igw.id +} + diff --git a/AWS-Maor-Labs/105-lab/modules/vpc/variables.tf b/AWS-Maor-Labs/105-lab/modules/vpc/variables.tf new file mode 100644 index 000000000..2abf8b124 --- /dev/null +++ b/AWS-Maor-Labs/105-lab/modules/vpc/variables.tf @@ -0,0 +1,19 @@ +variable "cidr_block" { + type = string +} + +variable "name" { + type = string +} + +variable "az1" { + type = string +} + +variable "az2" { + type = string +} + + +variable "vpc_cidr" {} +variable "public_subnet_cidr" {} \ No newline at end of file diff --git a/AWS-Maor-Labs/105-lab/outputs.tf b/AWS-Maor-Labs/105-lab/outputs.tf new file mode 100644 index 000000000..6ad046fa1 --- /dev/null +++ b/AWS-Maor-Labs/105-lab/outputs.tf @@ -0,0 +1,13 @@ +# outputs.tf +output "ec2_instance_id" { + value = aws_instance.web_server.id +} + +output "ec2_public_ip" { + value = aws_instance.web_server.public_ip +} + +output "vpc_id" { + value = module.vpc.public_subnet_id + # value = aws_vpc.lab_vpc.id +} diff --git a/AWS-Maor-Labs/105-lab/provider.tf b/AWS-Maor-Labs/105-lab/provider.tf new file mode 100644 index 000000000..2a4e09908 --- /dev/null +++ b/AWS-Maor-Labs/105-lab/provider.tf @@ -0,0 +1,4 @@ +# provider.tf +provider "aws" { + region = "us-east-1" +} \ No newline at end of file diff --git a/AWS-Maor-Labs/105-lab/routes.tf b/AWS-Maor-Labs/105-lab/routes.tf new file mode 100644 index 000000000..7d2905410 --- /dev/null +++ b/AWS-Maor-Labs/105-lab/routes.tf @@ -0,0 +1,15 @@ +# routes.tf +resource "aws_route_table" "public_rt" { + vpc_id = module.vpc.vpc_id + # vpc_id = aws_vpc.lab_vpc.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = module.vpc.vpc_cidr + } +} + +resource "aws_route_table_association" "public_assoc" { + subnet_id = module.vpc.public_subnet.id + route_table_id = aws_route_table.public_rt.id +} diff --git a/AWS-Maor-Labs/105-lab/security_groups.tf b/AWS-Maor-Labs/105-lab/security_groups.tf new file mode 100644 index 000000000..ee2408af6 --- /dev/null +++ b/AWS-Maor-Labs/105-lab/security_groups.tf @@ -0,0 +1,27 @@ +resource "aws_security_group" "ec2_sg" { + vpc_id = module.vpc.vpc_id + name = "ec2-security-group" + + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + + diff --git a/Maor-Labs/103-lab/apache_install.tf b/Maor-Labs/103-lab/apache_install.tf new file mode 100644 index 000000000..96d26e5d7 --- /dev/null +++ b/Maor-Labs/103-lab/apache_install.tf @@ -0,0 +1,35 @@ + +# Null Resource for Apache Installation +resource "null_resource" "provision_apache" { + depends_on = [azurerm_linux_virtual_machine.vm] + + # Trigger to force rerun whenever timestamp changes + triggers = { + always_run = timestamp() + } + + provisioner "remote-exec" { + inline = [ + "sudo apt update", + "sudo apt install -y apache2", + "echo '

Welcome to \"${azurerm_linux_virtual_machine.vm.computer_name}\" Web Server!

' | sudo tee /var/www/html/welcome.html", + "sudo systemctl start apache2", + "sudo systemctl enable apache2" + ] + + connection { + type = "ssh" + user = var.admin_username + password = var.admin_password + host = data.azurerm_public_ip.example.ip_address + timeout = "1m" + } + } +} + +# Updated Output for Server Information to use data source +output "server_info" { + value = "Please browse: http://${data.azurerm_public_ip.example.ip_address}/welcome.html" + description = "Browse the above link" +} + diff --git a/Maor-Labs/103-lab/firewall.tf b/Maor-Labs/103-lab/firewall.tf new file mode 100644 index 000000000..ca983e3ea --- /dev/null +++ b/Maor-Labs/103-lab/firewall.tf @@ -0,0 +1,37 @@ + +# Network Security Rule to Allow SSH (Port 22) But Block HTTP (Port 80) +resource "azurerm_network_security_rule" "block_http" { + name = "allow-ssh-block-http-${var.vm_name}" + priority = 100 + direction = "Inbound" + access = "Allow" # "Deny" + protocol = "Tcp" + source_port_range = "*" + destination_port_ranges = ["80"] + source_address_prefix = "*" + destination_address_prefix = "*" + resource_group_name = azurerm_resource_group.rg.name + network_security_group_name = azurerm_network_security_group.nsg.name +} + +resource "azurerm_network_security_rule" "allow_ssh" { + name = "allow-ssh-${var.vm_name}" + priority = 200 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_ranges = ["22"] + source_address_prefix = "*" + destination_address_prefix = "*" + resource_group_name = azurerm_resource_group.rg.name + network_security_group_name = azurerm_network_security_group.nsg.name +} + + +# Associate NSG with the Network Interface +resource "azurerm_network_interface_security_group_association" "nic_nsg_association" { + network_interface_id = azurerm_network_interface.nic.id + network_security_group_id = azurerm_network_security_group.nsg.id +} + diff --git a/Maor-Labs/103-lab/global.tf b/Maor-Labs/103-lab/global.tf new file mode 100644 index 000000000..f0d75973d --- /dev/null +++ b/Maor-Labs/103-lab/global.tf @@ -0,0 +1,42 @@ +# Define the provider and global variables + + +provider "azurerm" { + features {} +} + +variable "yourname" { + default = "Maor" + description = "Change it to your first name and the first letter of your family name: ex. yanivc - for yaniv cohen" +} + +variable "vm_name" { + default = "vm-Maor" + description = "Change it to your first name and the first letter of your family name: ex. yanivc - for yaniv cohen" +} + +variable "admin_username" { + default = "adminuser" + description = "Username for the admin user on the VM" +} + +variable "admin_password" { + default = "Password123!" + description = "Password for the admin user on the VM" +} + +variable "location" { + default = "East US" + description = "Azure region where resources will be deployed" +} + +variable "vm_size" { + default = "Standard_B1ms" + description = "Size of the virtual machine" +} + +# Resource Group +resource "azurerm_resource_group" "rg" { + name = "rg-${var.yourname}" + location = var.location +} diff --git a/Maor-Labs/103-lab/network.tf b/Maor-Labs/103-lab/network.tf new file mode 100644 index 000000000..9e258c72a --- /dev/null +++ b/Maor-Labs/103-lab/network.tf @@ -0,0 +1,48 @@ + + +# Network Security Group +resource "azurerm_network_security_group" "nsg" { + name = "nsg-${var.yourname}" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name +} + +# Public IP for the VM +resource "azurerm_public_ip" "pip" { + name = "pip-${var.yourname}" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + allocation_method = "Dynamic" + sku = "Basic" +} + +# Network Interface for the VM +resource "azurerm_network_interface" "nic" { + name = "nic-${var.yourname}" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + + ip_configuration { + name = "internal-${var.yourname}" + subnet_id = azurerm_subnet.subnet.id + private_ip_address_allocation = "Dynamic" + public_ip_address_id = azurerm_public_ip.pip.id + } +} + +# Virtual Network for the subnet +resource "azurerm_virtual_network" "vnet" { + name = "vnet-${var.yourname}" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + address_space = ["10.0.0.0/16"] +} + +# Subnet within the Virtual Network +resource "azurerm_subnet" "subnet" { + name = "subnet-${var.yourname}" + resource_group_name = azurerm_resource_group.rg.name + virtual_network_name = azurerm_virtual_network.vnet.name + address_prefixes = ["10.0.1.0/24"] +} + diff --git a/Maor-Labs/103-lab/validate_ip.tf b/Maor-Labs/103-lab/validate_ip.tf new file mode 100644 index 000000000..e288809fa --- /dev/null +++ b/Maor-Labs/103-lab/validate_ip.tf @@ -0,0 +1,35 @@ + +# Wait for IP Allocation +resource "time_sleep" "wait_for_ip" { + create_duration = "1m" # Wait for 1 minute to allow Azure to allocate the IP +} + +# Null Resource to Validate IP Allocation +resource "null_resource" "validate_ip" { + provisioner "local-exec" { + command = <&2 + exit 1 + EOT + } + depends_on = [time_sleep.wait_for_ip] +} + +# Data Source to Reference Public IP after Validation +data "azurerm_public_ip" "example" { + name = azurerm_public_ip.pip.name + resource_group_name = azurerm_resource_group.rg.name + depends_on = [null_resource.validate_ip] +} + diff --git a/Maor-Labs/103-lab/vm.tf b/Maor-Labs/103-lab/vm.tf new file mode 100644 index 000000000..b68ac0fe3 --- /dev/null +++ b/Maor-Labs/103-lab/vm.tf @@ -0,0 +1,36 @@ + +# Linux Virtual Machine configuration +resource "azurerm_linux_virtual_machine" "vm" { + name = var.vm_name + location = var.location + resource_group_name = azurerm_resource_group.rg.name + network_interface_ids = [azurerm_network_interface.nic.id] + size = var.vm_size + + os_disk { + name = "os-disk-${var.yourname}" + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + } + + admin_username = var.admin_username + admin_password = var.admin_password + + disable_password_authentication = false + computer_name = var.vm_name + + source_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "18.04-LTS" + version = "latest" + } + + # Ignore changes to the network interface to avoid unnecessary recreation of the VM + lifecycle { + ignore_changes = [network_interface_ids] + } + + depends_on = [azurerm_network_interface.nic, azurerm_public_ip.pip] +} + diff --git a/Maor-Labs/104-lab/mock_outputs.tf b/Maor-Labs/104-lab/mock_outputs.tf new file mode 100644 index 000000000..3ee9e995c --- /dev/null +++ b/Maor-Labs/104-lab/mock_outputs.tf @@ -0,0 +1,42 @@ + +# Mock the number of virtual machines needed +output "vm_count" { + value = var.high_availability ? 3 : 1 + description = "Number of VMs required for the environment. If high availability is true, 3 VMs are needed; otherwise, 1." +} + +# Mocking network requirements based on environment +output "network_configuration" { + value = var.environment == "prod" ? "Production Network - Full Scale" : "Development/Staging Network - Limited Scale" + description = "Provides the network configuration type based on the environment." +} + +# Example of conditional logic using a ternary operator +output "ha_status_message" { + value = var.high_availability ? "High availability is enabled - multiple VMs are needed." : "High availability is disabled - a single VM is sufficient." + description = "A message indicating if high availability is enabled or disabled." +} + +# Mocking subnet creation using for_each +locals { + subnets = var.high_availability ? ["subnet-a", "subnet-b", "subnet-c"] : ["subnet-a"] +} + +output "mock_subnet_list" { + value = [for subnet in local.subnets : "Configured ${subnet}"] + description = "A mocked list of subnets that would be created based on high availability." +} + +output "Mock_Database_Output" { + description = "Use an output block to print a message based on the create_database variable" + value = var.create_database ? "A mock database will be created for this environment." : "No database needed for this environment." +} + +locals { + database_value = var.create_database ? ["web", "api", "database"] : ["web", "api"] +} + +output "Add_Another_Output_Using_For_each" { + description = "Use for_each to create a list of mock services based on the create_database value" + value = [ for db_value in local.database_value : "Create: ${db_value}"] +} \ No newline at end of file diff --git a/Maor-Labs/104-lab/variables.tf b/Maor-Labs/104-lab/variables.tf new file mode 100644 index 000000000..cde9a8349 --- /dev/null +++ b/Maor-Labs/104-lab/variables.tf @@ -0,0 +1,18 @@ +variable "environment" { + description = "Define the environment type: dev, staging, or prod" + type = string + default = "prod" # dev +} + +variable "high_availability" { + description = "Whether to enable high availability (true or false)" + type = bool + default = true # false +} + +variable "create_database" { + description = "This should be a boolean (true or false) that decides if a mock database should be created" + type = bool + default = true # false +} + diff --git a/Maor-Labs/Load_Balancing_lab/main.tf b/Maor-Labs/Load_Balancing_lab/main.tf new file mode 100644 index 000000000..2fe0956a3 --- /dev/null +++ b/Maor-Labs/Load_Balancing_lab/main.tf @@ -0,0 +1,66 @@ + +resource "azurerm_resource_group" "rg-Maor" { + name = "${var.yourname}-resources" + location = var.location +} + + +# Provides a public IP address for the Load Balancer, allowing clients from the internet to access the resources behind it. +resource "azurerm_public_ip" "lb_pip" { + name = "lb-pip-${var.yourname}" # The name of the public IP resource, uniquely generated using the user's name. + location = azurerm_resource_group.rg-Maor.location # location and resource_group_name: The location and resource group where the public IP is deployed, ensuring consistency with other resources. + resource_group_name = azurerm_resource_group.rg-Maor.name + allocation_method = "Static" # allocation_method: Set to Static, meaning the IP will remain constant, which is useful for reliable access. + sku = "Standard" # Set to Standard, which is necessary to work with a Standard Load Balancer. +} + + +# Creates the actual Load Balancer, which distributes incoming traffic among multiple virtual machines (VMs). +resource "azurerm_lb" "lb" { + name = "lb-${var.yourname}" + location = azurerm_resource_group.rg-Maor.location + resource_group_name = azurerm_resource_group.rg-Maor.name + sku = "Standard" + + # Defines the entry point for incoming traffic. + frontend_ip_configuration { + name = "LoadBalancerFrontEnd" + public_ip_address_id = azurerm_public_ip.lb_pip.id + } +} + +# Defines the Backend Address Pool, which contains the VMs that will receive the traffic distributed by the load balancer. +resource "azurerm_lb_backend_address_pool" "lb_pool" { + loadbalancer_id = azurerm_lb.lb.id # Associates the backend pool with the previously created Load Balancer. + name = "backend-pool-${var.yourname}" +} + + +# A Health Probe is used to check the health of the VMs in the backend pool. It helps decide if a VM can handle traffic or should be temporarily removed from the pool. +resource "azurerm_lb_probe" "lb_probe" { + loadbalancer_id = azurerm_lb.lb.id # Links the probe to the Load Balancer. + name = "http-probe-${var.yourname}" + protocol = "Http" + port = 80 + request_path = "/welcome.html" + interval_in_seconds = 15 + number_of_probes = 3 +} + + +# Defines how incoming traffic is handled by the load balancer. The Load Balancer Rule determines how requests to a specific port are routed to the backend pool. +resource "azurerm_lb_rule" "lb_rule" { + loadbalancer_id = azurerm_lb.lb.id + name = "http-rule-${var.yourname}" + protocol = "Tcp" + frontend_port = 80 + backend_port = 80 + frontend_ip_configuration_name = "LoadBalancerFrontEnd" # References the Frontend IP configuration created earlier. + backend_address_pool_ids = [azurerm_lb_backend_address_pool.lb_pool.id] # Points to the Backend Address Pool (azurerm_lb_backend_address_pool.lb_pool.id), defining which VMs will receive the incoming traffic. + probe_id = azurerm_lb_probe.lb_probe.id # References the Health Probe to determine if a backend VM is healthy and can serve traffic. +} + + + + + diff --git a/Maor-Labs/Load_Balancing_lab/varuables.tf b/Maor-Labs/Load_Balancing_lab/varuables.tf new file mode 100644 index 000000000..bae0e2d8a --- /dev/null +++ b/Maor-Labs/Load_Balancing_lab/varuables.tf @@ -0,0 +1,12 @@ +provider "azurerm" { + features {} +} + +variable "yourname" { + default = "Maor" + description = "Change it to your first name and the first letter of your family name: ex. yanivc - for yaniv cohen" +} + +variable "location" { + default = "East US" +} \ No newline at end of file diff --git a/Maor-Labs/main.tf b/Maor-Labs/main.tf new file mode 100644 index 000000000..459e3aaff --- /dev/null +++ b/Maor-Labs/main.tf @@ -0,0 +1,48 @@ +provider "azurerm" { + features {} +} + + + +resource "azurerm_resource_group" "rg-Maor" { + name = "Maor-resources" + location = var.location +} + + +resource "azurerm_virtual_network" "vnet-Maor" { + name = "Maor-vnet" + address_space = ["10.0.0.0/16"] + location = var.location + resource_group_name = azurerm_resource_group.rg-Maor.name + +} + + +resource "azurerm_subnet" "subnet-Maor" { + name = "Maor-subnet" + resource_group_name = azurerm_resource_group.rg-Maor.name + virtual_network_name = azurerm_virtual_network.vnet-Maor.name + address_prefixes = ["10.0.1.0/24"] +} + + + +module "Maor_VM_1" { + source = "./modules/vm" + name = "Maor_VM_1" + location = azurerm_resource_group.rg-Maor.location + resource_group_name = azurerm_resource_group.rg-Maor.name + subnet_id = azurerm_subnet.subnet-Maor.id + admin_username = "testadmin" + admin_password = "Password1234!" # In real use, move to secrets! + vm_size = "Standard_B1ms" +} + + + + + + + + diff --git a/Maor-Labs/modules/vm/main.tf b/Maor-Labs/modules/vm/main.tf new file mode 100644 index 000000000..a1af8ff97 --- /dev/null +++ b/Maor-Labs/modules/vm/main.tf @@ -0,0 +1,57 @@ + + +resource "azurerm_public_ip" "pip-Maor" { + name = "Maor-pip" + location = var.location + resource_group_name = var.name + allocation_method = "Dynamic" # Dynamic IP allocation for Basic SKU + sku = "Basic" +} + +resource "azurerm_network_interface" "nic-Maor" { + name = "Maor-nic" + location = var.location + resource_group_name = var.name + + ip_configuration { + name = "Maor-ipconfig" + subnet_id = var.subnet_id + private_ip_address_allocation = "Dynamic" + public_ip_address_id = "azurerm_public_ip.pip-Maor" + } +} + + +resource "azurerm_linux_virtual_machine" "vm-Maor" { + name = "Maor-vm" + location = var.location + resource_group_name = var.name + network_interface_ids = [azurerm_network_interface.nic-Maor.id] + size = var.vm_size + + os_disk { + name = "Maor-os-disk" + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + } + + admin_username = var.admin_username + admin_password = var.admin_password + + disable_password_authentication = false + + source_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "18.04-LTS" + version = "latest" + } + + computer_name = "Maor-vm" +} + + +resource "time_sleep" "wait_for_ip" { + create_duration = "30s" # Wait for 30 seconds +} + diff --git a/Maor-Labs/modules/vm/output.tf b/Maor-Labs/modules/vm/output.tf new file mode 100644 index 000000000..4b8f811b7 --- /dev/null +++ b/Maor-Labs/modules/vm/output.tf @@ -0,0 +1,5 @@ +output "vm_public_ip" { + value = azurerm_public_ip.pip-Maor.ip_address + depends_on = [time_sleep.wait_for_ip] # Wait for the time_sleep resource to complete + description = "Public IP address of the VM" +} diff --git a/Maor-Labs/modules/vm/variables.tf b/Maor-Labs/modules/vm/variables.tf new file mode 100644 index 000000000..c3f048feb --- /dev/null +++ b/Maor-Labs/modules/vm/variables.tf @@ -0,0 +1,24 @@ +variable "name" { + description = "this is the name default" + default = "Maor_VM" + validation { + condition = contains(["Maor_VM_1", "Maor_VM_2"], var.name) + error_message = "Environment must be either Maor_VM_1 or Maor_VM_2." + } + +} +variable "location" { + description = "this is the location default" + default = "West Europe" +} +variable "resource_group_name" { + description = "this is the location default" + default = "test" +} +variable "subnet_id" {} +variable "admin_username" {} +variable "admin_password" {} + +variable "vm_size" { + default = "Standard_B1ms" +} diff --git a/Maor-Labs/providers.tf b/Maor-Labs/providers.tf new file mode 100644 index 000000000..9fe5338c6 --- /dev/null +++ b/Maor-Labs/providers.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "4.34.0" + } + } +} \ No newline at end of file diff --git a/Maor-Labs/variables.tf b/Maor-Labs/variables.tf new file mode 100644 index 000000000..6ea4189fa --- /dev/null +++ b/Maor-Labs/variables.tf @@ -0,0 +1,19 @@ + +variable "location" { + default = "East US" +} + +variable "vm_size" { + default = "Standard_B1ms" +} + + + +variable "admin_username" { + default = "adminuser-Maor" +} + +variable "admin_password" { + default = "Password123!" +} + diff --git a/Terraform-Graded-Class-Exercise/main.py b/Terraform-Graded-Class-Exercise/main.py new file mode 100755 index 000000000..eee1b1966 --- /dev/null +++ b/Terraform-Graded-Class-Exercise/main.py @@ -0,0 +1,105 @@ +import re +import sys +import jinja2 +from terraform_template import render_template +from terraform_executor import execute_terraform + + + +def Get_User_Variables(): + # --- AMI selection --- + ami_choice = input("Choose between Ubuntu or Amazon Linux: ").strip().lower() + if ami_choice == "ubuntu": + ami_choice = "ubuntu" + elif ami_choice in ["amazon linux", "linux"]: + ami_choice = "amazon linux" + else: + sys.exit("❌ You must choose either 'Ubuntu' or 'Amazon Linux'!") + + + # --- Instance type selection --- + instance_type_choice = input("Choose instance type (t3.small / t3.medium): ").strip() + if instance_type_choice == "t3.small": + instance_type_choice = "t3.small" + elif instance_type_choice == "t3.medium": + instance_type_choice = "t3.medium" + else: + sys.exit("❌ You must choose either 't3.small' or 't3.medium'!") + + + # --- Region selection --- + region = input("Select region (only 'us-east-2' is allowed, others will be defaulted): ").strip() + if region != "us-east-2": + print(f"⚠️ Region '{region}' is not allowed. Defaulting to 'us-east-2'.") + region = "us-east-2" + + # --- Load Balancer name --- + alb_name = input("Enter a name for your Load Balancer (ALB): ").strip() + + if not re.match(r'^[a-zA-Z0-9\-]+$', alb_name): + sys.exit("❌ Invalid ALB name. Use only letters, numbers, and hyphens (-).") + + print("\n✅ Summary of your configuration:") + print(f" AMI: {ami_choice}") + print(f" Instance Type: {instance_type_choice}") + print(f" Region: {region}") + print(f" ALB Name: {alb_name}") + + # --- Store all values in a dictionary --- + context = { + "ami": ami_choice, + "instance_type": instance_type_choice, + "region": region, + "availability_zone": "us-east-2", + "lb_name": alb_name + } + + print("\n✅ Configuration collected successfully!") + print("Context to pass into Jinja2 template:") + print(context) + + # --- Jinja2 template usage --- + template_str = """ + resource "aws_instance" "example" { + ami = "{{ ami }}" + instance_type = "{{ instance_type }}" + region = "{{ region }}" + } + + resource "aws_lb" "example" { + name = "{{ lb_name }}" + internal = false + load_balancer_type = "application" + subnets = ["subnet-xyz"] # Replace with real subnet IDs + } + """ + + # Render the template + try: + template = jinja2.Template(template_str) + rendered_output = template.render(context) + + print("\n📄 Rendered Terraform Configuration:") + print(rendered_output) + return context + + except jinja2.exceptions.TemplateError as e: + print("\n❌ Jinja2 template rendering failed!") + print(f"Error: {e}") + + + + +if __name__ == '__main__': + print("Build a Python-based AWS Infrastructure as Code (IaC) tool!") + print("Please enter the following details to create your infrastructure.\n") + + context = Get_User_Variables() + + rendered_tf = render_template(context) + + + execute_terraform(rendered_tf, "./terraform/main.tf") + validate_resources("./terraform") + + diff --git a/Terraform-Graded-Class-Exercise/terraform/main.tf b/Terraform-Graded-Class-Exercise/terraform/main.tf new file mode 100644 index 000000000..136c791db --- /dev/null +++ b/Terraform-Graded-Class-Exercise/terraform/main.tf @@ -0,0 +1,77 @@ + +provider "aws" { + region = "us-east-2" +} + +variable "vpc_id" { + default = "vpc-0a691b1cda1dea4be" +} + +variable "subnet_ids" { + default = ["subnet-09a9b4fe4e74051b3", "subnet-05860172a9327d826"] +} + +resource "aws_instance" "web_server" { + ami = "ami-0c995fbcf99222492" + instance_type = "t3.small" + availability_zone = "us-east-2" + subnet_id = var.subnet_ids[0] + vpc_security_group_ids = [aws_security_group.lb_sg.id] + + tags = { + Name = "WebServer" + } +} + +resource "aws_security_group" "lb_sg" { + name = "lb_security_group_Maor-lb" + description = "Allow HTTP inbound traffic" + vpc_id = var.vpc_id + + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_lb" "application_lb" { + name = "Maor-lb" + internal = false + load_balancer_type = "application" + security_groups = [aws_security_group.lb_sg.id] + subnets = var.subnet_ids +} + +resource "aws_lb_listener" "http_listener" { + load_balancer_arn = aws_lb.application_lb.arn + port = 80 + protocol = "HTTP" + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.web_target_group.arn + } +} + +resource "aws_lb_target_group" "web_target_group" { + name = "web-target-group-Maor-lb" + port = 80 + protocol = "HTTP" + vpc_id = var.vpc_id +} + +resource "aws_lb_target_group_attachment" "web_instance_attachment" { + target_group_arn = aws_lb_target_group.web_target_group.arn + target_id = aws_instance.web_server.id +} + + +output "instance_id" { + value = aws_instance.web_server.id +} + +output "load_balancer_dns" { + value = aws_lb.application_lb.dns_name +} \ No newline at end of file diff --git a/Terraform-Graded-Class-Exercise/terraform_executor.py b/Terraform-Graded-Class-Exercise/terraform_executor.py new file mode 100755 index 000000000..76ed64abe --- /dev/null +++ b/Terraform-Graded-Class-Exercise/terraform_executor.py @@ -0,0 +1,64 @@ + +import os +import sys +from python_terraform import Terraform + + + +def execute_terraform(tf_content, output_path): + os.makedirs(os.path.dirname(output_path), exist_ok=True) + + with open(output_path, "w") as f: + f.write(tf_content) + print(f"✅ Terraform config written to {output_path}") + + tf = Terraform(working_dir=os.path.dirname(output_path)) + + + # ---- terraform init ---- + print("\n🔧 Running: terraform init") + code, _, init_err = tf.init(capture_output=False) + if code != 0: + print("❌ Init failed:\n", init_err) + sys.exit(1) + + + # ---- terraform plan ---- + print("\n📝 Running: terraform plan") + code, plan_out, plan_err = tf.plan(capture_output=True) + + if code != 0 and "No changes" not in plan_out and "+" not in plan_out: + print("❌ Plan failed!") + print("STDOUT:\n", plan_out) + print("STDERR:\n", plan_err) + sys.exit(1) + else: + print("✅ Plan succeeded.\n", plan_out) + + + + # ---- terraform apply ---- + print("\n🚀 Running: terraform apply") + code, apply_out, apply_err = tf.apply(skip_plan=True, capture_output=True) + if code != 0: + print("❌ Apply failed!") + print("STDOUT:\n", apply_out) + print("STDERR:\n", apply_err) + sys.exit(1) + print("✅ Apply succeeded.\n", apply_out) + + + # ---- terraform output ---- + print("\n📤 Fetching Terraform outputs") + code, tf_outputs, output_err = tf.output() + if code == 0: + for key, val in tf_outputs.items(): + print(f"{key}: {val['value']}") + else: + print("⚠️ Failed to fetch outputs:\n", output_err) + + + + + + diff --git a/Terraform-Graded-Class-Exercise/terraform_template.py b/Terraform-Graded-Class-Exercise/terraform_template.py new file mode 100755 index 000000000..3c9fef784 --- /dev/null +++ b/Terraform-Graded-Class-Exercise/terraform_template.py @@ -0,0 +1,143 @@ +import jinja2 + +# Extract user inputs from the context dictionary +ami_options = { + "ubuntu": "ami-0c995fbcf99222492", + "amazon linux": "ami-0915e09cc7ceee3ab" +} + +instance_types = { + "t3.small": "t3.small", + "t3.medium": "t3.medium" +} + + + +AVAILABILITY_ZONES = ["us-east-2a", "us-east-2b"] +ALLOWED_REGION = "us-east-2" + + + +terraform_template = """ +provider "aws" { + region = "{{ region }}" +} + +variable "vpc_id" { + default = "vpc-0a691b1cda1dea4be" +} + +variable "subnet_ids" { + default = ["subnet-09a9b4fe4e74051b3", "subnet-05860172a9327d826"] +} + +resource "aws_instance" "web_server" { + ami = "{{ ami }}" + instance_type = "{{ instance_type }}" + availability_zone = "{{ availability_zone }}" + subnet_id = var.subnet_ids[0] + vpc_security_group_ids = [aws_security_group.lb_sg.id] + + tags = { + Name = "WebServer" + } +} + +resource "aws_security_group" "lb_sg" { + name = "lb_security_group_{{ load_balancer_name }}" + description = "Allow HTTP inbound traffic" + vpc_id = var.vpc_id + + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_lb" "application_lb" { + name = "{{ load_balancer_name }}" + internal = false + load_balancer_type = "application" + security_groups = [aws_security_group.lb_sg.id] + subnets = var.subnet_ids +} + +resource "aws_lb_listener" "http_listener" { + load_balancer_arn = aws_lb.application_lb.arn + port = 80 + protocol = "HTTP" + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.web_target_group.arn + } +} + +resource "aws_lb_target_group" "web_target_group" { + name = "web-target-group-{{ load_balancer_name }}" + port = 80 + protocol = "HTTP" + vpc_id = var.vpc_id +} + +resource "aws_lb_target_group_attachment" "web_instance_attachment" { + target_group_arn = aws_lb_target_group.web_target_group.arn + target_id = aws_instance.web_server.id +} + + +output "instance_id" { + value = aws_instance.web_server.id +} + +output "load_balancer_dns" { + value = aws_lb.application_lb.dns_name +} +""" + + + + + + + +def render_template(context): + + # print(context) + + ami_key = context["ami"].lower() + ami_id = ami_options.get(ami_key) + + if not ami_id: + sys.exit("❌ No valid AMI ID found for your choice.") + + + ami_key = context["ami"].lower() + ami_id = ami_options.get(ami_key) + + if not ami_id: + raise ValueError(f"❌ Invalid AMI name '{ami_key}'. Must be one of: {list(ami_options.keys())}") + + # print(f"✅ AMI ID for '{ami_key}': {ami_id}") + + + instance_type = instance_types.get(context["instance_type"].lower(), "t3.small") + + template_context = { + "ami": ami_id, + "instance_type": instance_type, + "region": context["region"], + "availability_zone": context["availability_zone"], + "load_balancer_name": context["lb_name"] + # Do NOT pass vpc_id or subnet_ids here, since they're defined as Terraform variables +} + + # print(template_context) + + template = jinja2.Template(terraform_template) + + rendered = template.render(template_context) + + return rendered \ No newline at end of file diff --git a/Terraform-Graded-Class-Exercise/validature.py b/Terraform-Graded-Class-Exercise/validature.py new file mode 100644 index 000000000..ce703b8c8 --- /dev/null +++ b/Terraform-Graded-Class-Exercise/validature.py @@ -0,0 +1,53 @@ + + +import boto3 +import json + +def validate_with_boto3(instance_id: str, alb_dns: str, region: str = "us-east-2"): + try: + ec2 = boto3.client("ec2", region_name=region) + elbv2 = boto3.client("elbv2", region_name=region) + + # get instance details + ec2_response = ec2.describe_instances(InstanceIds=[instance_id]) + reservations = ec2_response.get("Reservations", []) + if not reservations or not reservations[0]["Instances"]: + raise Exception("EC2 instance not found.") + + instance = reservations[0]["Instances"][0] + instance_state = instance["State"]["Name"] + public_ip = instance.get("PublicIpAddress", "N/A") + + # get ALB details + lb_response = elbv2.describe_load_balancers() + lb_dns_name = None + for lb in lb_response["LoadBalancers"]: + if alb_dns in lb["DNSName"]: + lb_dns_name = lb["DNSName"] + break + + if not lb_dns_name: + raise Exception("ALB not found.") + + # Store to JSON + validation_data = { + "instance_id": instance_id, + "instance_state": instance_state, + "public_ip": public_ip, + "load_balancer_dns": lb_dns_name + } + + with open("aws_validation.json", "w") as f: + json.dump(validation_data, f, indent=4) + + print("AWS resource validation complete. Output written to aws_validation.json") + + except Exception as e: + print(f"Boto3 validation failed: {str(e)}") + + +validate_with_boto3( + instance_id="i-014c78c8124d22438", + alb_dns="tomer-lb-1577578029.us-east-2.elb.amazonaws.com" +) + diff --git a/test.txt b/test.txt index 460a86046..abad81bf1 100644 --- a/test.txt +++ b/test.txt @@ -1 +1 @@ -testing commit 07:02:17 +testing commit 14:49:47 diff --git a/user_info.txt b/user_info.txt index a960c0097..ecc4325bb 100644 --- a/user_info.txt +++ b/user_info.txt @@ -1,3 +1,3 @@ GITBRANCH=workshop/terraform -GITURL=github.com/yanivomc/devopshift-welcome.git -GITUSERNAME=yanivomc +GITURL=github.com/MaorShtern/devopshift-welcome-Terraform-labs.git +GITUSERNAME=MaorShtern