From 645809c22f23bae98dca3b590d87bd064d08d2a5 Mon Sep 17 00:00:00 2001 From: anhkhoa93 Date: Wed, 12 Mar 2025 01:23:22 +0700 Subject: [PATCH 1/4] Add terraform test --- terraform-gcp-vm/README.md | 54 +++ .../modules/gcp_vm/tests/.terraform.lock.hcl | 22 + terraform-gcp-vm/modules/gcp_vm/tests/main.tf | 13 + .../modules/gcp_vm/tests/provider.tf | 16 + .../modules/gcp_vm/tests/terraform.tfstate | 382 ++++++++++++++++++ .../modules/gcp_vm/tests/variables.tf | 64 +++ .../modules/gcp_vm/tests/vm.tftest.hcl | 81 ++++ 7 files changed, 632 insertions(+) create mode 100644 terraform-gcp-vm/modules/gcp_vm/tests/.terraform.lock.hcl create mode 100644 terraform-gcp-vm/modules/gcp_vm/tests/main.tf create mode 100644 terraform-gcp-vm/modules/gcp_vm/tests/provider.tf create mode 100644 terraform-gcp-vm/modules/gcp_vm/tests/terraform.tfstate create mode 100644 terraform-gcp-vm/modules/gcp_vm/tests/variables.tf create mode 100644 terraform-gcp-vm/modules/gcp_vm/tests/vm.tftest.hcl diff --git a/terraform-gcp-vm/README.md b/terraform-gcp-vm/README.md index 4d904a5..04af10e 100644 --- a/terraform-gcp-vm/README.md +++ b/terraform-gcp-vm/README.md @@ -66,6 +66,18 @@ Benefits: --role="roles/compute.admin" ``` +4. Set terraform-key.json to environment variable: + +Linux/MacOS: + ```bash + export GOOGLE_CLOUD_KEYFILE_JSON="/full/path/to/file/terraform-key.json" + ``` + +Windows: + ```powershell + $env:GOOGLE_CLOUD_KEYFILE_JSON = "/full/path/to/file/terraform-key.json" + ``` + ## Quick Start ### 1. Backend Setup (Local State) @@ -87,6 +99,48 @@ terraform plan terraform apply ``` +## Testing + +### Running Validation Tests + +The VM module includes comprehensive validation tests that verify the module's configuration according to GCP best practices. The tests use local state to avoid conflicts with production state management. + +#### Test Structure +``` +modules/gcp_vm/ +└── tests/ + ├── vm.tftest.hcl # Test cases for VM configuration + ├── provider.tf # Local backend and provider config + ├── variables.tf # Variable definitions with validation rules + └── main.tf # Module configuration for testing +``` + +#### Test Configuration +- Uses local backend for test isolation +- Validates against the same GCP project: `sky-test-instance` +- Region: `asia-southeast1` +- Tests run independently of the production GCS state bucket + +#### Running Tests + +```bash +cd modules/gcp_vm/tests + +# Initialize test environment +terraform init + +# Run all tests +terraform test +``` + +#### Test Cases +The test suite includes validation for: +- Invalid instance names (uppercase, special characters) +- Invalid boot disk configurations (size out of range) +- Invalid machine types (wrong format) +- Invalid zone formats + + ## Verification Result ### VM Module Deployment diff --git a/terraform-gcp-vm/modules/gcp_vm/tests/.terraform.lock.hcl b/terraform-gcp-vm/modules/gcp_vm/tests/.terraform.lock.hcl new file mode 100644 index 0000000..a0225db --- /dev/null +++ b/terraform-gcp-vm/modules/gcp_vm/tests/.terraform.lock.hcl @@ -0,0 +1,22 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/google" { + version = "4.85.0" + constraints = "~> 4.0" + hashes = [ + "h1:sld/eTvevl/Af3upWX1TesnLLCCUMBQlczxo5lPzA48=", + "zh:17d60a6a6c1741cf1e09ac6731433a30950285eac88236e623ab4cbf23832ca3", + "zh:1c70254c016439dbb75cab646b4beace6ceeff117c75d81f2cc27d41c312f752", + "zh:35e2aa2cc7ac84ce55e05bb4de7b461b169d3582e56d3262e249ff09d64fe008", + "zh:417afb08d7b2744429f6b76806f4134d62b0354acf98e8a6c00de3c24f2bb6ad", + "zh:622165d09d21d9a922c86f1fc7177a400507f2a8c4a4513114407ae04da2dd29", + "zh:7cdb8e39a8ea0939558d87d2cb6caceded9e21f21003d9e9f9ce648d5db0bc3a", + "zh:851e737dc551d6004a860a8907fda65118fc2c7ede9fa828f7be704a2a39e68f", + "zh:a331ad289a02a2c4473572a573dc389be0a604cdd9e03dd8dbc10297fb14f14d", + "zh:b67fd531251380decd8dd1f849460d60f329f89df3d15f5815849a1dd001f430", + "zh:be8785957acca4f97aa3e800b313b57d1fca07788761c8867c9bc701fbe0bdb5", + "zh:cb6579a259fe020e1f88217d8f6937b2d5ace15b6406370977a1966eb31b1ca5", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/terraform-gcp-vm/modules/gcp_vm/tests/main.tf b/terraform-gcp-vm/modules/gcp_vm/tests/main.tf new file mode 100644 index 0000000..eac2836 --- /dev/null +++ b/terraform-gcp-vm/modules/gcp_vm/tests/main.tf @@ -0,0 +1,13 @@ +module "gcp_vm" { + source = "../" + + project_id = var.project_id + region = var.region + zone = var.zone + instance_name = var.instance_name + machine_type = var.machine_type + boot_disk_size = var.boot_disk_size + boot_disk_type = var.boot_disk_type + network_tags = var.network_tags + labels = var.labels +} diff --git a/terraform-gcp-vm/modules/gcp_vm/tests/provider.tf b/terraform-gcp-vm/modules/gcp_vm/tests/provider.tf new file mode 100644 index 0000000..eddc4ab --- /dev/null +++ b/terraform-gcp-vm/modules/gcp_vm/tests/provider.tf @@ -0,0 +1,16 @@ +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> 4.0" + } + } + + # Using local state for tests to avoid conflicts with production state + backend "local" { + } +} + +provider "google" { + region = "asia-southeast1" +} diff --git a/terraform-gcp-vm/modules/gcp_vm/tests/terraform.tfstate b/terraform-gcp-vm/modules/gcp_vm/tests/terraform.tfstate new file mode 100644 index 0000000..5299b8f --- /dev/null +++ b/terraform-gcp-vm/modules/gcp_vm/tests/terraform.tfstate @@ -0,0 +1,382 @@ +{ + "version": 4, + "terraform_version": "1.11.1", + "serial": 8, + "lineage": "a599c7b3-2a6a-4e6f-aa19-cb7b36008d9e", + "outputs": { + "firewall_rules": { + "value": { + "allowed_ports": [ + "80", + "443", + "22" + ], + "name": "test-vm-allow-web" + }, + "type": [ + "object", + { + "allowed_ports": [ + "tuple", + [ + "string", + "string", + "string" + ] + ], + "name": "string" + } + ] + }, + "instance": { + "value": { + "machine_type": "e2-small", + "name": "test-vm", + "private_ip": "10.148.0.2", + "public_ip": "34.126.182.199", + "self_link": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/zones/asia-southeast1-a/instances/test-vm", + "zone": "asia-southeast1-a" + }, + "type": [ + "object", + { + "machine_type": "string", + "name": "string", + "private_ip": "string", + "public_ip": "string", + "self_link": "string", + "zone": "string" + } + ] + }, + "instance_id": { + "value": "5413696689334507432", + "type": "string" + }, + "private_ip": { + "value": "10.148.0.2", + "type": "string" + }, + "public_ip": { + "value": "34.126.182.199", + "type": "string" + } + }, + "resources": [ + { + "mode": "managed", + "type": "google_compute_firewall", + "name": "allow_web", + "provider": "provider[\"registry.terraform.io/hashicorp/google\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 1, + "attributes": { + "allow": [ + { + "ports": [ + "22" + ], + "protocol": "tcp" + }, + { + "ports": [ + "443" + ], + "protocol": "tcp" + }, + { + "ports": [ + "80" + ], + "protocol": "tcp" + } + ], + "creation_timestamp": "2025-03-11T09:48:03.746-07:00", + "deny": [], + "description": "", + "destination_ranges": [], + "direction": "INGRESS", + "disabled": false, + "enable_logging": null, + "id": "projects/sky-test-instance/global/firewalls/test-vm-allow-web", + "log_config": [ + { + "metadata": "INCLUDE_ALL_METADATA" + } + ], + "name": "test-vm-allow-web", + "network": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/global/networks/default", + "priority": 1000, + "project": "sky-test-instance", + "self_link": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/global/firewalls/test-vm-allow-web", + "source_ranges": [ + "0.0.0.0/0" + ], + "source_service_accounts": [], + "source_tags": [], + "target_service_accounts": [], + "target_tags": [ + "http-server", + "https-server" + ], + "timeouts": null + }, + "sensitive_attributes": [], + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInVwZGF0ZSI6MTIwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiMSJ9" + } + ] + }, + { + "mode": "managed", + "type": "google_compute_instance", + "name": "vm_instance", + "provider": "provider[\"registry.terraform.io/hashicorp/google\"]", + "instances": [ + { + "schema_version": 6, + "attributes": { + "advanced_machine_features": [], + "allow_stopping_for_update": null, + "attached_disk": [], + "boot_disk": [ + { + "auto_delete": true, + "device_name": "persistent-disk-0", + "disk_encryption_key_raw": "", + "disk_encryption_key_sha256": "", + "initialize_params": [ + { + "enable_confidential_compute": false, + "image": "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-11-bullseye-v20250212", + "labels": {}, + "provisioned_iops": 0, + "provisioned_throughput": 0, + "resource_manager_tags": {}, + "resource_policies": [], + "size": 20, + "storage_pool": "", + "type": "pd-balanced" + } + ], + "interface": "", + "kms_key_self_link": "", + "mode": "READ_WRITE", + "source": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/zones/asia-southeast1-a/disks/test-vm" + } + ], + "can_ip_forward": false, + "confidential_instance_config": [], + "cpu_platform": "Intel Broadwell", + "creation_timestamp": "2025-03-11T09:48:12.191-07:00", + "current_status": "RUNNING", + "deletion_protection": true, + "description": "", + "desired_status": null, + "effective_labels": { + "app": "web", + "created-at": "2025-03-11", + "environment": "production", + "goog-terraform-provisioned": "true", + "managed-by": "terraform", + "team": "devops" + }, + "enable_display": false, + "guest_accelerator": [], + "hostname": "", + "id": "projects/sky-test-instance/zones/asia-southeast1-a/instances/test-vm", + "instance_id": "5413696689334507432", + "key_revocation_action_type": "", + "label_fingerprint": "vS9ZbRMIzb4=", + "labels": { + "app": "web", + "created-at": "2025-03-11", + "environment": "production", + "managed-by": "terraform", + "team": "devops" + }, + "machine_type": "e2-small", + "metadata": { + "enable-oslogin": "TRUE", + "startup-script": "#!/bin/bash\nset -e\n\n# Update system packages\napt-get update\napt-get upgrade -y\n\n# Install and configure nginx\napt-get install -y nginx\nsystemctl start nginx\nsystemctl enable nginx\n\n# Basic security hardening\napt-get install -y unattended-upgrades\ndpkg-reconfigure -plow unattended-upgrades\n\n# Configure firewall\napt-get install -y ufw\nufw allow 'Nginx Full'\nufw allow 22/tcp\nufw --force enable\n" + }, + "metadata_fingerprint": "8iGg7NtUxYI=", + "metadata_startup_script": null, + "min_cpu_platform": "", + "name": "test-vm", + "network_interface": [ + { + "access_config": [ + { + "nat_ip": "34.126.182.199", + "network_tier": "PREMIUM", + "public_ptr_domain_name": "" + } + ], + "alias_ip_range": [], + "internal_ipv6_prefix_length": 0, + "ipv6_access_config": [], + "ipv6_access_type": "", + "ipv6_address": "", + "name": "nic0", + "network": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/global/networks/default", + "network_attachment": "", + "network_ip": "10.148.0.2", + "nic_type": "", + "queue_count": 0, + "stack_type": "IPV4_ONLY", + "subnetwork": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/regions/asia-southeast1/subnetworks/default", + "subnetwork_project": "sky-test-instance" + } + ], + "network_performance_config": [], + "params": [], + "project": "sky-test-instance", + "reservation_affinity": [], + "resource_policies": [], + "scheduling": [ + { + "automatic_restart": true, + "availability_domain": 0, + "instance_termination_action": "", + "local_ssd_recovery_timeout": [], + "max_run_duration": [], + "min_node_cpus": 0, + "node_affinities": [], + "on_host_maintenance": "MIGRATE", + "on_instance_stop_action": [], + "preemptible": false, + "provisioning_model": "STANDARD" + } + ], + "scratch_disk": [], + "self_link": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/zones/asia-southeast1-a/instances/test-vm", + "service_account": [], + "shielded_instance_config": [ + { + "enable_integrity_monitoring": true, + "enable_secure_boot": true, + "enable_vtpm": true + } + ], + "tags": [ + "http-server", + "https-server" + ], + "tags_fingerprint": "6smc4R4d39I=", + "terraform_labels": { + "app": "web", + "created-at": "2025-03-11", + "environment": "production", + "goog-terraform-provisioned": "true", + "managed-by": "terraform", + "team": "devops" + }, + "timeouts": null, + "zone": "asia-southeast1-a" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "boot_disk" + }, + { + "type": "index", + "value": { + "value": 0, + "type": "number" + } + }, + { + "type": "get_attr", + "value": "disk_encryption_key_raw" + } + ] + ], + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInVwZGF0ZSI6MTIwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiNiJ9" + } + ] + } + ], + "check_results": [ + { + "object_kind": "var", + "config_addr": "var.instance_name", + "status": "pass", + "objects": [ + { + "object_addr": "var.instance_name", + "status": "pass" + } + ] + }, + { + "object_kind": "var", + "config_addr": "var.machine_type", + "status": "pass", + "objects": [ + { + "object_addr": "var.machine_type", + "status": "pass" + } + ] + }, + { + "object_kind": "var", + "config_addr": "var.boot_disk_type", + "status": "pass", + "objects": [ + { + "object_addr": "var.boot_disk_type", + "status": "pass" + } + ] + }, + { + "object_kind": "var", + "config_addr": "var.labels", + "status": "pass", + "objects": [ + { + "object_addr": "var.labels", + "status": "pass" + } + ] + }, + { + "object_kind": "var", + "config_addr": "var.zone", + "status": "pass", + "objects": [ + { + "object_addr": "var.zone", + "status": "pass" + } + ] + }, + { + "object_kind": "var", + "config_addr": "var.boot_disk_size", + "status": "pass", + "objects": [ + { + "object_addr": "var.boot_disk_size", + "status": "pass" + } + ] + }, + { + "object_kind": "var", + "config_addr": "var.network_tags", + "status": "pass", + "objects": [ + { + "object_addr": "var.network_tags", + "status": "pass" + } + ] + } + ] +} diff --git a/terraform-gcp-vm/modules/gcp_vm/tests/variables.tf b/terraform-gcp-vm/modules/gcp_vm/tests/variables.tf new file mode 100644 index 0000000..a0f8c81 --- /dev/null +++ b/terraform-gcp-vm/modules/gcp_vm/tests/variables.tf @@ -0,0 +1,64 @@ +variable "project_id" { + type = string + description = "The GCP project ID" +} + +variable "region" { + type = string + description = "The GCP region" +} + +variable "zone" { + type = string + description = "The GCP zone" + validation { + condition = can(regex("^[a-z]+-[a-z]+[0-9]-[a-z]$", var.zone)) + error_message = "Zone must be in valid GCP zone format" + } +} + +variable "instance_name" { + type = string + description = "Name of the VM instance" + validation { + condition = can(regex("^[a-z0-9-]+$", var.instance_name)) + error_message = "Instance name must contain only lowercase letters, numbers, and hyphens" + } +} + +variable "machine_type" { + type = string + description = "The machine type for the VM" + validation { + condition = can(regex("^[a-z][0-9]-[a-z]+$", var.machine_type)) + error_message = "Machine type must match GCP machine type format" + } +} + +variable "boot_disk_size" { + type = number + description = "Size of the boot disk in GB" + validation { + condition = var.boot_disk_size >= 10 && var.boot_disk_size <= 2000 + error_message = "Boot disk size must be between 10GB and 2000GB" + } +} + +variable "boot_disk_type" { + type = string + description = "Type of the boot disk" + validation { + condition = contains(["pd-standard", "pd-balanced", "pd-ssd"], var.boot_disk_type) + error_message = "Boot disk type must be pd-standard, pd-balanced, or pd-ssd" + } +} + +variable "network_tags" { + type = list(string) + description = "Network tags to apply to the instance" +} + +variable "labels" { + type = map(string) + description = "Labels to apply to the instance" +} diff --git a/terraform-gcp-vm/modules/gcp_vm/tests/vm.tftest.hcl b/terraform-gcp-vm/modules/gcp_vm/tests/vm.tftest.hcl new file mode 100644 index 0000000..d3cff82 --- /dev/null +++ b/terraform-gcp-vm/modules/gcp_vm/tests/vm.tftest.hcl @@ -0,0 +1,81 @@ +# Test validation rules for VM configuration +variables { + project_id = "sky-test-instance" + region = "asia-southeast1" + zone = "asia-southeast1-a" + instance_name = "test-vm" + machine_type = "e2-small" + boot_disk_size = 20 + boot_disk_type = "pd-balanced" + network_tags = ["http-server", "https-server"] + labels = { + environment = "production" + app = "web" + team = "devops" + } +} + +# Test 1: Validate instance name format +run "test_instance_name_validation" { + command = plan + + variables { + instance_name = "INVALID_NAME_123" + } + + expect_failures = [ + var.instance_name, + ] +} + +# Test 2: Validate boot disk size limits +run "test_boot_disk_size_validation" { + command = plan + + variables { + boot_disk_size = 5 # Below minimum 10GB + } + + expect_failures = [ + var.boot_disk_size, + ] +} + +# Test 3: Validate boot disk type +run "test_boot_disk_type_validation" { + command = plan + + variables { + boot_disk_type = "invalid-type" + } + + expect_failures = [ + var.boot_disk_type, + ] +} + +# Test 4: Validate machine type format +run "test_machine_type_validation" { + command = plan + + variables { + machine_type = "invalid-machine-type" + } + + expect_failures = [ + var.machine_type, + ] +} + +# Test 5: Validate zone format +run "test_zone_validation" { + command = plan + + variables { + zone = "invalid-zone" + } + + expect_failures = [ + var.zone, + ] +} From a51aa30d8043dd6e6d29d3d90612a17038998ed7 Mon Sep 17 00:00:00 2001 From: anhkhoa93 Date: Wed, 12 Mar 2025 01:24:35 +0700 Subject: [PATCH 2/4] Revert "Add terraform test" This reverts commit 645809c22f23bae98dca3b590d87bd064d08d2a5. --- terraform-gcp-vm/README.md | 54 --- .../modules/gcp_vm/tests/.terraform.lock.hcl | 22 - terraform-gcp-vm/modules/gcp_vm/tests/main.tf | 13 - .../modules/gcp_vm/tests/provider.tf | 16 - .../modules/gcp_vm/tests/terraform.tfstate | 382 ------------------ .../modules/gcp_vm/tests/variables.tf | 64 --- .../modules/gcp_vm/tests/vm.tftest.hcl | 81 ---- 7 files changed, 632 deletions(-) delete mode 100644 terraform-gcp-vm/modules/gcp_vm/tests/.terraform.lock.hcl delete mode 100644 terraform-gcp-vm/modules/gcp_vm/tests/main.tf delete mode 100644 terraform-gcp-vm/modules/gcp_vm/tests/provider.tf delete mode 100644 terraform-gcp-vm/modules/gcp_vm/tests/terraform.tfstate delete mode 100644 terraform-gcp-vm/modules/gcp_vm/tests/variables.tf delete mode 100644 terraform-gcp-vm/modules/gcp_vm/tests/vm.tftest.hcl diff --git a/terraform-gcp-vm/README.md b/terraform-gcp-vm/README.md index 04af10e..4d904a5 100644 --- a/terraform-gcp-vm/README.md +++ b/terraform-gcp-vm/README.md @@ -66,18 +66,6 @@ Benefits: --role="roles/compute.admin" ``` -4. Set terraform-key.json to environment variable: - -Linux/MacOS: - ```bash - export GOOGLE_CLOUD_KEYFILE_JSON="/full/path/to/file/terraform-key.json" - ``` - -Windows: - ```powershell - $env:GOOGLE_CLOUD_KEYFILE_JSON = "/full/path/to/file/terraform-key.json" - ``` - ## Quick Start ### 1. Backend Setup (Local State) @@ -99,48 +87,6 @@ terraform plan terraform apply ``` -## Testing - -### Running Validation Tests - -The VM module includes comprehensive validation tests that verify the module's configuration according to GCP best practices. The tests use local state to avoid conflicts with production state management. - -#### Test Structure -``` -modules/gcp_vm/ -└── tests/ - ├── vm.tftest.hcl # Test cases for VM configuration - ├── provider.tf # Local backend and provider config - ├── variables.tf # Variable definitions with validation rules - └── main.tf # Module configuration for testing -``` - -#### Test Configuration -- Uses local backend for test isolation -- Validates against the same GCP project: `sky-test-instance` -- Region: `asia-southeast1` -- Tests run independently of the production GCS state bucket - -#### Running Tests - -```bash -cd modules/gcp_vm/tests - -# Initialize test environment -terraform init - -# Run all tests -terraform test -``` - -#### Test Cases -The test suite includes validation for: -- Invalid instance names (uppercase, special characters) -- Invalid boot disk configurations (size out of range) -- Invalid machine types (wrong format) -- Invalid zone formats - - ## Verification Result ### VM Module Deployment diff --git a/terraform-gcp-vm/modules/gcp_vm/tests/.terraform.lock.hcl b/terraform-gcp-vm/modules/gcp_vm/tests/.terraform.lock.hcl deleted file mode 100644 index a0225db..0000000 --- a/terraform-gcp-vm/modules/gcp_vm/tests/.terraform.lock.hcl +++ /dev/null @@ -1,22 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/google" { - version = "4.85.0" - constraints = "~> 4.0" - hashes = [ - "h1:sld/eTvevl/Af3upWX1TesnLLCCUMBQlczxo5lPzA48=", - "zh:17d60a6a6c1741cf1e09ac6731433a30950285eac88236e623ab4cbf23832ca3", - "zh:1c70254c016439dbb75cab646b4beace6ceeff117c75d81f2cc27d41c312f752", - "zh:35e2aa2cc7ac84ce55e05bb4de7b461b169d3582e56d3262e249ff09d64fe008", - "zh:417afb08d7b2744429f6b76806f4134d62b0354acf98e8a6c00de3c24f2bb6ad", - "zh:622165d09d21d9a922c86f1fc7177a400507f2a8c4a4513114407ae04da2dd29", - "zh:7cdb8e39a8ea0939558d87d2cb6caceded9e21f21003d9e9f9ce648d5db0bc3a", - "zh:851e737dc551d6004a860a8907fda65118fc2c7ede9fa828f7be704a2a39e68f", - "zh:a331ad289a02a2c4473572a573dc389be0a604cdd9e03dd8dbc10297fb14f14d", - "zh:b67fd531251380decd8dd1f849460d60f329f89df3d15f5815849a1dd001f430", - "zh:be8785957acca4f97aa3e800b313b57d1fca07788761c8867c9bc701fbe0bdb5", - "zh:cb6579a259fe020e1f88217d8f6937b2d5ace15b6406370977a1966eb31b1ca5", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} diff --git a/terraform-gcp-vm/modules/gcp_vm/tests/main.tf b/terraform-gcp-vm/modules/gcp_vm/tests/main.tf deleted file mode 100644 index eac2836..0000000 --- a/terraform-gcp-vm/modules/gcp_vm/tests/main.tf +++ /dev/null @@ -1,13 +0,0 @@ -module "gcp_vm" { - source = "../" - - project_id = var.project_id - region = var.region - zone = var.zone - instance_name = var.instance_name - machine_type = var.machine_type - boot_disk_size = var.boot_disk_size - boot_disk_type = var.boot_disk_type - network_tags = var.network_tags - labels = var.labels -} diff --git a/terraform-gcp-vm/modules/gcp_vm/tests/provider.tf b/terraform-gcp-vm/modules/gcp_vm/tests/provider.tf deleted file mode 100644 index eddc4ab..0000000 --- a/terraform-gcp-vm/modules/gcp_vm/tests/provider.tf +++ /dev/null @@ -1,16 +0,0 @@ -terraform { - required_providers { - google = { - source = "hashicorp/google" - version = "~> 4.0" - } - } - - # Using local state for tests to avoid conflicts with production state - backend "local" { - } -} - -provider "google" { - region = "asia-southeast1" -} diff --git a/terraform-gcp-vm/modules/gcp_vm/tests/terraform.tfstate b/terraform-gcp-vm/modules/gcp_vm/tests/terraform.tfstate deleted file mode 100644 index 5299b8f..0000000 --- a/terraform-gcp-vm/modules/gcp_vm/tests/terraform.tfstate +++ /dev/null @@ -1,382 +0,0 @@ -{ - "version": 4, - "terraform_version": "1.11.1", - "serial": 8, - "lineage": "a599c7b3-2a6a-4e6f-aa19-cb7b36008d9e", - "outputs": { - "firewall_rules": { - "value": { - "allowed_ports": [ - "80", - "443", - "22" - ], - "name": "test-vm-allow-web" - }, - "type": [ - "object", - { - "allowed_ports": [ - "tuple", - [ - "string", - "string", - "string" - ] - ], - "name": "string" - } - ] - }, - "instance": { - "value": { - "machine_type": "e2-small", - "name": "test-vm", - "private_ip": "10.148.0.2", - "public_ip": "34.126.182.199", - "self_link": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/zones/asia-southeast1-a/instances/test-vm", - "zone": "asia-southeast1-a" - }, - "type": [ - "object", - { - "machine_type": "string", - "name": "string", - "private_ip": "string", - "public_ip": "string", - "self_link": "string", - "zone": "string" - } - ] - }, - "instance_id": { - "value": "5413696689334507432", - "type": "string" - }, - "private_ip": { - "value": "10.148.0.2", - "type": "string" - }, - "public_ip": { - "value": "34.126.182.199", - "type": "string" - } - }, - "resources": [ - { - "mode": "managed", - "type": "google_compute_firewall", - "name": "allow_web", - "provider": "provider[\"registry.terraform.io/hashicorp/google\"]", - "instances": [ - { - "index_key": 0, - "schema_version": 1, - "attributes": { - "allow": [ - { - "ports": [ - "22" - ], - "protocol": "tcp" - }, - { - "ports": [ - "443" - ], - "protocol": "tcp" - }, - { - "ports": [ - "80" - ], - "protocol": "tcp" - } - ], - "creation_timestamp": "2025-03-11T09:48:03.746-07:00", - "deny": [], - "description": "", - "destination_ranges": [], - "direction": "INGRESS", - "disabled": false, - "enable_logging": null, - "id": "projects/sky-test-instance/global/firewalls/test-vm-allow-web", - "log_config": [ - { - "metadata": "INCLUDE_ALL_METADATA" - } - ], - "name": "test-vm-allow-web", - "network": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/global/networks/default", - "priority": 1000, - "project": "sky-test-instance", - "self_link": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/global/firewalls/test-vm-allow-web", - "source_ranges": [ - "0.0.0.0/0" - ], - "source_service_accounts": [], - "source_tags": [], - "target_service_accounts": [], - "target_tags": [ - "http-server", - "https-server" - ], - "timeouts": null - }, - "sensitive_attributes": [], - "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInVwZGF0ZSI6MTIwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiMSJ9" - } - ] - }, - { - "mode": "managed", - "type": "google_compute_instance", - "name": "vm_instance", - "provider": "provider[\"registry.terraform.io/hashicorp/google\"]", - "instances": [ - { - "schema_version": 6, - "attributes": { - "advanced_machine_features": [], - "allow_stopping_for_update": null, - "attached_disk": [], - "boot_disk": [ - { - "auto_delete": true, - "device_name": "persistent-disk-0", - "disk_encryption_key_raw": "", - "disk_encryption_key_sha256": "", - "initialize_params": [ - { - "enable_confidential_compute": false, - "image": "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-11-bullseye-v20250212", - "labels": {}, - "provisioned_iops": 0, - "provisioned_throughput": 0, - "resource_manager_tags": {}, - "resource_policies": [], - "size": 20, - "storage_pool": "", - "type": "pd-balanced" - } - ], - "interface": "", - "kms_key_self_link": "", - "mode": "READ_WRITE", - "source": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/zones/asia-southeast1-a/disks/test-vm" - } - ], - "can_ip_forward": false, - "confidential_instance_config": [], - "cpu_platform": "Intel Broadwell", - "creation_timestamp": "2025-03-11T09:48:12.191-07:00", - "current_status": "RUNNING", - "deletion_protection": true, - "description": "", - "desired_status": null, - "effective_labels": { - "app": "web", - "created-at": "2025-03-11", - "environment": "production", - "goog-terraform-provisioned": "true", - "managed-by": "terraform", - "team": "devops" - }, - "enable_display": false, - "guest_accelerator": [], - "hostname": "", - "id": "projects/sky-test-instance/zones/asia-southeast1-a/instances/test-vm", - "instance_id": "5413696689334507432", - "key_revocation_action_type": "", - "label_fingerprint": "vS9ZbRMIzb4=", - "labels": { - "app": "web", - "created-at": "2025-03-11", - "environment": "production", - "managed-by": "terraform", - "team": "devops" - }, - "machine_type": "e2-small", - "metadata": { - "enable-oslogin": "TRUE", - "startup-script": "#!/bin/bash\nset -e\n\n# Update system packages\napt-get update\napt-get upgrade -y\n\n# Install and configure nginx\napt-get install -y nginx\nsystemctl start nginx\nsystemctl enable nginx\n\n# Basic security hardening\napt-get install -y unattended-upgrades\ndpkg-reconfigure -plow unattended-upgrades\n\n# Configure firewall\napt-get install -y ufw\nufw allow 'Nginx Full'\nufw allow 22/tcp\nufw --force enable\n" - }, - "metadata_fingerprint": "8iGg7NtUxYI=", - "metadata_startup_script": null, - "min_cpu_platform": "", - "name": "test-vm", - "network_interface": [ - { - "access_config": [ - { - "nat_ip": "34.126.182.199", - "network_tier": "PREMIUM", - "public_ptr_domain_name": "" - } - ], - "alias_ip_range": [], - "internal_ipv6_prefix_length": 0, - "ipv6_access_config": [], - "ipv6_access_type": "", - "ipv6_address": "", - "name": "nic0", - "network": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/global/networks/default", - "network_attachment": "", - "network_ip": "10.148.0.2", - "nic_type": "", - "queue_count": 0, - "stack_type": "IPV4_ONLY", - "subnetwork": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/regions/asia-southeast1/subnetworks/default", - "subnetwork_project": "sky-test-instance" - } - ], - "network_performance_config": [], - "params": [], - "project": "sky-test-instance", - "reservation_affinity": [], - "resource_policies": [], - "scheduling": [ - { - "automatic_restart": true, - "availability_domain": 0, - "instance_termination_action": "", - "local_ssd_recovery_timeout": [], - "max_run_duration": [], - "min_node_cpus": 0, - "node_affinities": [], - "on_host_maintenance": "MIGRATE", - "on_instance_stop_action": [], - "preemptible": false, - "provisioning_model": "STANDARD" - } - ], - "scratch_disk": [], - "self_link": "https://www.googleapis.com/compute/v1/projects/sky-test-instance/zones/asia-southeast1-a/instances/test-vm", - "service_account": [], - "shielded_instance_config": [ - { - "enable_integrity_monitoring": true, - "enable_secure_boot": true, - "enable_vtpm": true - } - ], - "tags": [ - "http-server", - "https-server" - ], - "tags_fingerprint": "6smc4R4d39I=", - "terraform_labels": { - "app": "web", - "created-at": "2025-03-11", - "environment": "production", - "goog-terraform-provisioned": "true", - "managed-by": "terraform", - "team": "devops" - }, - "timeouts": null, - "zone": "asia-southeast1-a" - }, - "sensitive_attributes": [ - [ - { - "type": "get_attr", - "value": "boot_disk" - }, - { - "type": "index", - "value": { - "value": 0, - "type": "number" - } - }, - { - "type": "get_attr", - "value": "disk_encryption_key_raw" - } - ] - ], - "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInVwZGF0ZSI6MTIwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiNiJ9" - } - ] - } - ], - "check_results": [ - { - "object_kind": "var", - "config_addr": "var.instance_name", - "status": "pass", - "objects": [ - { - "object_addr": "var.instance_name", - "status": "pass" - } - ] - }, - { - "object_kind": "var", - "config_addr": "var.machine_type", - "status": "pass", - "objects": [ - { - "object_addr": "var.machine_type", - "status": "pass" - } - ] - }, - { - "object_kind": "var", - "config_addr": "var.boot_disk_type", - "status": "pass", - "objects": [ - { - "object_addr": "var.boot_disk_type", - "status": "pass" - } - ] - }, - { - "object_kind": "var", - "config_addr": "var.labels", - "status": "pass", - "objects": [ - { - "object_addr": "var.labels", - "status": "pass" - } - ] - }, - { - "object_kind": "var", - "config_addr": "var.zone", - "status": "pass", - "objects": [ - { - "object_addr": "var.zone", - "status": "pass" - } - ] - }, - { - "object_kind": "var", - "config_addr": "var.boot_disk_size", - "status": "pass", - "objects": [ - { - "object_addr": "var.boot_disk_size", - "status": "pass" - } - ] - }, - { - "object_kind": "var", - "config_addr": "var.network_tags", - "status": "pass", - "objects": [ - { - "object_addr": "var.network_tags", - "status": "pass" - } - ] - } - ] -} diff --git a/terraform-gcp-vm/modules/gcp_vm/tests/variables.tf b/terraform-gcp-vm/modules/gcp_vm/tests/variables.tf deleted file mode 100644 index a0f8c81..0000000 --- a/terraform-gcp-vm/modules/gcp_vm/tests/variables.tf +++ /dev/null @@ -1,64 +0,0 @@ -variable "project_id" { - type = string - description = "The GCP project ID" -} - -variable "region" { - type = string - description = "The GCP region" -} - -variable "zone" { - type = string - description = "The GCP zone" - validation { - condition = can(regex("^[a-z]+-[a-z]+[0-9]-[a-z]$", var.zone)) - error_message = "Zone must be in valid GCP zone format" - } -} - -variable "instance_name" { - type = string - description = "Name of the VM instance" - validation { - condition = can(regex("^[a-z0-9-]+$", var.instance_name)) - error_message = "Instance name must contain only lowercase letters, numbers, and hyphens" - } -} - -variable "machine_type" { - type = string - description = "The machine type for the VM" - validation { - condition = can(regex("^[a-z][0-9]-[a-z]+$", var.machine_type)) - error_message = "Machine type must match GCP machine type format" - } -} - -variable "boot_disk_size" { - type = number - description = "Size of the boot disk in GB" - validation { - condition = var.boot_disk_size >= 10 && var.boot_disk_size <= 2000 - error_message = "Boot disk size must be between 10GB and 2000GB" - } -} - -variable "boot_disk_type" { - type = string - description = "Type of the boot disk" - validation { - condition = contains(["pd-standard", "pd-balanced", "pd-ssd"], var.boot_disk_type) - error_message = "Boot disk type must be pd-standard, pd-balanced, or pd-ssd" - } -} - -variable "network_tags" { - type = list(string) - description = "Network tags to apply to the instance" -} - -variable "labels" { - type = map(string) - description = "Labels to apply to the instance" -} diff --git a/terraform-gcp-vm/modules/gcp_vm/tests/vm.tftest.hcl b/terraform-gcp-vm/modules/gcp_vm/tests/vm.tftest.hcl deleted file mode 100644 index d3cff82..0000000 --- a/terraform-gcp-vm/modules/gcp_vm/tests/vm.tftest.hcl +++ /dev/null @@ -1,81 +0,0 @@ -# Test validation rules for VM configuration -variables { - project_id = "sky-test-instance" - region = "asia-southeast1" - zone = "asia-southeast1-a" - instance_name = "test-vm" - machine_type = "e2-small" - boot_disk_size = 20 - boot_disk_type = "pd-balanced" - network_tags = ["http-server", "https-server"] - labels = { - environment = "production" - app = "web" - team = "devops" - } -} - -# Test 1: Validate instance name format -run "test_instance_name_validation" { - command = plan - - variables { - instance_name = "INVALID_NAME_123" - } - - expect_failures = [ - var.instance_name, - ] -} - -# Test 2: Validate boot disk size limits -run "test_boot_disk_size_validation" { - command = plan - - variables { - boot_disk_size = 5 # Below minimum 10GB - } - - expect_failures = [ - var.boot_disk_size, - ] -} - -# Test 3: Validate boot disk type -run "test_boot_disk_type_validation" { - command = plan - - variables { - boot_disk_type = "invalid-type" - } - - expect_failures = [ - var.boot_disk_type, - ] -} - -# Test 4: Validate machine type format -run "test_machine_type_validation" { - command = plan - - variables { - machine_type = "invalid-machine-type" - } - - expect_failures = [ - var.machine_type, - ] -} - -# Test 5: Validate zone format -run "test_zone_validation" { - command = plan - - variables { - zone = "invalid-zone" - } - - expect_failures = [ - var.zone, - ] -} From 88ee0a788ffc21c9546dbb910926cb6900431cf8 Mon Sep 17 00:00:00 2001 From: anhkhoa93 Date: Wed, 12 Mar 2025 01:29:05 +0700 Subject: [PATCH 3/4] Update git ignore --- .gitignore | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index a713516..6badd2b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,10 @@ backend/terraform.tfstate backend/terraform.tfstate.backup backend-setup -modules/gcp/.terraform -modules/gcp/terraform.tfstate +modules/gcp_vm/.terraform +modules/gcp_vm/terraform.tfstate +modules/gcp_vm/terraform.tfstate.backup + +modules/gcp_vm/tests/.terraform +modules/gcp_vm/tests/terraform.tfstate +modules/gcp_vm/tests/terraform.tfstate.backup From fabb50c563e8de4af8da70399cc42ef0f0736d31 Mon Sep 17 00:00:00 2001 From: anhkhoa93 Date: Tue, 11 Nov 2025 13:27:14 +0700 Subject: [PATCH 4/4] Migrate github project --- .github/workflows/deploy-dev.yml | 99 +++++++ .github/workflows/deploy-prod.yml | 104 +++++++ .github/workflows/golang-ci.yml | 111 +++++++ .github/workflows/terraform-validate.yml | 54 ++++ .gitignore | 45 ++- README.md | 271 ++++++++++++++---- algorithm/README.md | 103 ++----- algorithm/cmd/loadbalancer/main.go | 4 +- algorithm/go.mod | 2 +- environments/dev.tfvars | 17 ++ environments/prod.tfvars | 17 ++ golang-node-exporter/README.md | 146 +++------- golang-node-exporter/go.mod | 2 +- golang-node-exporter/main.go | 8 +- terraform-gcp-vm/README.md | 101 +++---- terraform-gcp-vm/backend/terraform.tfstate | 9 - terraform-gcp-vm/backend/terraform.tfvars | 4 +- terraform-gcp-vm/backend/variables.tf | 2 +- terraform-gcp-vm/modules/gcp_vm/backend.tf | 2 +- terraform-gcp-vm/modules/gcp_vm/main.tf | 2 +- .../modules/gcp_vm/terraform.tfvars | 4 +- 21 files changed, 769 insertions(+), 338 deletions(-) create mode 100644 .github/workflows/deploy-dev.yml create mode 100644 .github/workflows/deploy-prod.yml create mode 100644 .github/workflows/golang-ci.yml create mode 100644 .github/workflows/terraform-validate.yml create mode 100644 environments/dev.tfvars create mode 100644 environments/prod.tfvars delete mode 100644 terraform-gcp-vm/backend/terraform.tfstate diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 0000000..de49838 --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,99 @@ +name: Deploy to Dev + +on: + push: + branches: [ develop ] + workflow_dispatch: + +env: + ENVIRONMENT: dev + GCP_PROJECT_ID: ${{ secrets.DEV_GCP_PROJECT_ID }} + GCP_REGION: asia-southeast1 + GCP_ZONE: asia-southeast1-a + +jobs: + terraform-plan: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./terraform-gcp-vm + steps: + - uses: actions/checkout@v3 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: 1.5.0 + + - name: Authenticate to GCP + uses: google-github-actions/auth@v1 + with: + credentials_json: ${{ secrets.DEV_GCP_SA_KEY }} + + - name: Terraform Init - Backend + working-directory: ./terraform-gcp-vm/backend + run: terraform init + + - name: Terraform Plan - Backend + working-directory: ./terraform-gcp-vm/backend + run: terraform plan -var="project_id=${{ env.GCP_PROJECT_ID }}" -var="environment=dev" + + - name: Terraform Init - VM Module + working-directory: ./terraform-gcp-vm/modules/gcp_vm + run: terraform init + + - name: Terraform Plan - VM Module + working-directory: ./terraform-gcp-vm/modules/gcp_vm + run: | + terraform plan \ + -var="project_id=${{ env.GCP_PROJECT_ID }}" \ + -var="instance_name=dev-vm" \ + -var="zone=${{ env.GCP_ZONE }}" + + build-and-deploy: + needs: terraform-plan + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' + steps: + - uses: actions/checkout@v3 + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Build Load Balancer + working-directory: ./algorithm + run: | + GOOS=linux GOARCH=amd64 go build -o loadbalancer ./cmd/loadbalancer + + - name: Build Node Exporter + working-directory: ./golang-node-exporter + run: | + GOOS=linux GOARCH=amd64 go build -o node-exporter + + - name: Authenticate to GCP + uses: google-github-actions/auth@v1 + with: + credentials_json: ${{ secrets.DEV_GCP_SA_KEY }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v1 + + - name: Deploy Terraform Backend + working-directory: ./terraform-gcp-vm/backend + run: | + terraform init + terraform apply -auto-approve \ + -var="project_id=${{ env.GCP_PROJECT_ID }}" \ + -var="environment=dev" + + - name: Deploy VM Module + working-directory: ./terraform-gcp-vm/modules/gcp_vm + run: | + terraform init + terraform apply -auto-approve \ + -var="project_id=${{ env.GCP_PROJECT_ID }}" \ + -var="instance_name=dev-vm" \ + -var="zone=${{ env.GCP_ZONE }}" + diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml new file mode 100644 index 0000000..2f888d1 --- /dev/null +++ b/.github/workflows/deploy-prod.yml @@ -0,0 +1,104 @@ +name: Deploy to Production + +on: + push: + branches: [ main ] + tags: + - 'v*' + workflow_dispatch: + +env: + ENVIRONMENT: prod + GCP_PROJECT_ID: ${{ secrets.PROD_GCP_PROJECT_ID }} + GCP_REGION: asia-southeast1 + GCP_ZONE: asia-southeast1-a + +jobs: + terraform-plan: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./terraform-gcp-vm + steps: + - uses: actions/checkout@v3 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: 1.5.0 + + - name: Authenticate to GCP + uses: google-github-actions/auth@v1 + with: + credentials_json: ${{ secrets.PROD_GCP_SA_KEY }} + + - name: Terraform Init - Backend + working-directory: ./terraform-gcp-vm/backend + run: terraform init + + - name: Terraform Plan - Backend + working-directory: ./terraform-gcp-vm/backend + run: terraform plan -var="project_id=${{ env.GCP_PROJECT_ID }}" -var="environment=prod" + + - name: Terraform Init - VM Module + working-directory: ./terraform-gcp-vm/modules/gcp_vm + run: terraform init + + - name: Terraform Plan - VM Module + working-directory: ./terraform-gcp-vm/modules/gcp_vm + run: | + terraform plan \ + -var="project_id=${{ env.GCP_PROJECT_ID }}" \ + -var="instance_name=prod-vm" \ + -var="zone=${{ env.GCP_ZONE }}" \ + -var="machine_type=e2-medium" + + deploy: + needs: terraform-plan + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/') + environment: production + steps: + - uses: actions/checkout@v3 + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Build Load Balancer + working-directory: ./algorithm + run: | + GOOS=linux GOARCH=amd64 go build -o loadbalancer ./cmd/loadbalancer + + - name: Build Node Exporter + working-directory: ./golang-node-exporter + run: | + GOOS=linux GOARCH=amd64 go build -o node-exporter + + - name: Authenticate to GCP + uses: google-github-actions/auth@v1 + with: + credentials_json: ${{ secrets.PROD_GCP_SA_KEY }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v1 + + - name: Deploy Terraform Backend + working-directory: ./terraform-gcp-vm/backend + run: | + terraform init + terraform apply -auto-approve \ + -var="project_id=${{ env.GCP_PROJECT_ID }}" \ + -var="environment=prod" + + - name: Deploy VM Module + working-directory: ./terraform-gcp-vm/modules/gcp_vm + run: | + terraform init + terraform apply -auto-approve \ + -var="project_id=${{ env.GCP_PROJECT_ID }}" \ + -var="instance_name=prod-vm" \ + -var="zone=${{ env.GCP_ZONE }}" \ + -var="machine_type=e2-medium" + diff --git a/.github/workflows/golang-ci.yml b/.github/workflows/golang-ci.yml new file mode 100644 index 0000000..6248220 --- /dev/null +++ b/.github/workflows/golang-ci.yml @@ -0,0 +1,111 @@ +name: Golang CI + +on: + push: + branches: [ main, develop ] + paths: + - 'algorithm/**' + - 'golang-node-exporter/**' + - '.github/workflows/golang-ci.yml' + pull_request: + branches: [ main, develop ] + paths: + - 'algorithm/**' + - 'golang-node-exporter/**' + +jobs: + test-algorithm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Cache Go modules + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Install dependencies + working-directory: ./algorithm + run: go mod download + + - name: Run tests + working-directory: ./algorithm + run: go test -v -race -coverprofile=coverage.out ./... + + - name: Check coverage + working-directory: ./algorithm + run: | + go tool cover -func=coverage.out + + - name: Build + working-directory: ./algorithm + run: go build -v ./cmd/loadbalancer + + test-node-exporter: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Cache Go modules + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Install dependencies + working-directory: ./golang-node-exporter + run: go mod download + + - name: Run tests + working-directory: ./golang-node-exporter + run: go test -v -race -coverprofile=coverage.out ./... + + - name: Check coverage + working-directory: ./golang-node-exporter + run: go tool cover -func=coverage.out + + - name: Build + working-directory: ./golang-node-exporter + run: go build -v + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: golangci-lint algorithm + uses: golangci/golangci-lint-action@v3 + with: + version: latest + working-directory: ./algorithm + + - name: golangci-lint node-exporter + uses: golangci/golangci-lint-action@v3 + with: + version: latest + working-directory: ./golang-node-exporter + diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml new file mode 100644 index 0000000..121be57 --- /dev/null +++ b/.github/workflows/terraform-validate.yml @@ -0,0 +1,54 @@ +name: Terraform Validate + +on: + pull_request: + paths: + - 'terraform-gcp-vm/**' + - '.github/workflows/terraform-validate.yml' + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: 1.5.0 + + - name: Terraform Format Check + working-directory: ./terraform-gcp-vm + run: terraform fmt -check -recursive + + - name: Terraform Init - Backend + working-directory: ./terraform-gcp-vm/backend + run: terraform init -backend=false + + - name: Terraform Validate - Backend + working-directory: ./terraform-gcp-vm/backend + run: terraform validate + + - name: Terraform Init - VM Module + working-directory: ./terraform-gcp-vm/modules/gcp_vm + run: terraform init -backend=false + + - name: Terraform Validate - VM Module + working-directory: ./terraform-gcp-vm/modules/gcp_vm + run: terraform validate + + tflint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: terraform-linters/setup-tflint@v3 + with: + tflint_version: latest + + - name: Run tflint + working-directory: ./terraform-gcp-vm + run: | + tflint --init + tflint --recursive + diff --git a/.gitignore b/.gitignore index 6badd2b..e354d5c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,47 @@ +# Credentials and sensitive files *key.json -.terraform -backend/.terraform +*.pem +*.p12 +*.key +*.tfvars.json +credentials.json +service-account*.json + +# Terraform files +.terraform/ +.terraform.lock.hcl +*.tfstate +*.tfstate.backup +*.tfplan +*.tfvars.backup +crash.log +crash.*.log +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Backend specific +backend/.terraform/ backend/terraform.tfstate backend/terraform.tfstate.backup +backend-setup/ -backend-setup -modules/gcp_vm/.terraform +# Module specific +modules/gcp_vm/.terraform/ modules/gcp_vm/terraform.tfstate modules/gcp_vm/terraform.tfstate.backup - -modules/gcp_vm/tests/.terraform +modules/gcp_vm/tests/.terraform/ modules/gcp_vm/tests/terraform.tfstate modules/gcp_vm/tests/terraform.tfstate.backup + +# OS files +.DS_Store +Thumbs.db + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ diff --git a/README.md b/README.md index 924618d..0270de2 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,230 @@ -# Skymavis DevOps Challenge -Don't hesitate to ask for clarifications if required (nam@skymavis.com) +# DevOps Technical Assessment -## Overview -This home test is designed to assess your DevOps skills in three key areas: coding (algorithm), Golang (writing a Node Exporter), and Terraform (creating a GCP VM module). Complete each task as per the given requirements. +Solutions for a DevOps technical assessment covering three key areas: +- Load balancer implementation in Go +- System metrics exporter +- Infrastructure as Code with Terraform ---- +## Structure -## 1. Algorithm +``` +. +├── algorithm/ # Round-robin load balancer +├── golang-node-exporter/ # Prometheus-compatible metrics exporter +├── terraform-gcp-vm/ # GCP VM provisioning module +├── environments/ # Environment-specific configurations +└── .github/workflows/ # CI/CD pipelines +``` + +## Components + +### 1. Load Balancer (algorithm/) + +Round-robin load balancer with health checking. Automatically manages backend servers and routes traffic only to healthy instances. + +**Features:** +- Round-robin distribution +- Health check monitoring +- Dynamic backend management +- Status endpoint for monitoring + +**Quick start:** +```bash +cd algorithm +go mod download +go run cmd/loadbalancer/main.go +``` + +Access at http://localhost:8080 +Status at http://localhost:8080/status + +**Testing:** +```bash +go test ./... +``` + +### 2. Node Exporter (golang-node-exporter/) -**Task:** -Implement a **Round Robin Load Balancer with Health Check** in Golang. +Lightweight metrics exporter for Prometheus. Collects CPU, memory, and network stats. -Your function should distribute incoming requests among a set of servers using the **round-robin** algorithm while performing periodic **health checks** to ensure only healthy servers receive traffic. +**Metrics exposed:** +- CPU usage per core +- Memory usage +- Network bytes sent/received -### **Example Usage:** -```go -lb := NewLoadBalancer([]string{"http://server1", "http://server2", "http://server3"}) -fmt.Println(lb.NextServer()) // Output: http://server1 -fmt.Println(lb.NextServer()) // Output: http://server2 -fmt.Println(lb.NextServer()) // Output: http://server3 -fmt.Println(lb.NextServer()) // Output: http://server1 +**Quick start:** +```bash +cd golang-node-exporter +go mod download +go build +./node-exporter ``` -### **Requirements:** -- Implement the function in **Golang**. -- Optimize for **efficiency** and handle concurrency properly. -- Implement a **health check** mechanism to remove unhealthy servers from the rotation. -- Health check should periodically ping each server and mark it as **unavailable** if it doesn't respond. -- Write unit tests to verify functionality. +Metrics at http://localhost:8080/metrics ---- +**Configuration:** +```bash +./node-exporter -port=9100 -collection-period=10s +``` + +**Testing:** +```bash +go test ./... +``` + +### 3. Terraform GCP Module (terraform-gcp-vm/) + +Terraform module for provisioning GCP VM instances with nginx. + +**Architecture:** +- Backend component: Creates GCS bucket for state storage +- VM module: Provisions compute instances -## 2. Golang: Write a Node Exporter +**Prerequisites:** +- Terraform >= 1.0 +- GCP project with billing enabled +- Service account with required permissions -**Task:** -Implement a lightweight Node Exporter in **Golang** that exposes system metrics (CPU, Memory, and Network usage) over an HTTP endpoint (`/metrics`). +**Setup:** +```bash +# Create service account +gcloud iam service-accounts create terraform --display-name="Terraform SA" -**Requirements:** -- Use **Golang**. -- Collect **CPU, Memory, and Network statistics**. - - **CPU Usage** - - **Memory Usage** - - **Network Statistics** (Bytes sent/received, packets sent/received) -- Expose metrics in **Prometheus format** at `http://localhost:8080/metrics`. -- Use the `github.com/prometheus/client_golang/prometheus` library. -- Include basic error handling and logging. -- Provide a simple README on how to build and run the exporter. +# Generate key +gcloud iam service-accounts keys create terraform-key.json \ + --iam-account=terraform@YOUR-PROJECT-ID.iam.gserviceaccount.com ---- +# Grant permissions +gcloud projects add-iam-policy-binding YOUR-PROJECT-ID \ + --member="serviceAccount:terraform@YOUR-PROJECT-ID.iam.gserviceaccount.com" \ + --role="roles/storage.admin" -## 3. Terraform Module: Create a VM on GCP +gcloud projects add-iam-policy-binding YOUR-PROJECT-ID \ + --member="serviceAccount:terraform@YOUR-PROJECT-ID.iam.gserviceaccount.com" \ + --role="roles/compute.admin" +``` + +**Usage:** +```bash +# Backend setup +cd terraform-gcp-vm/backend +terraform init +terraform apply -var-file="../../environments/dev.tfvars" + +# VM deployment +cd ../modules/gcp_vm +terraform init +terraform apply -var-file="../../../environments/dev.tfvars" +``` + +**Cleanup:** +```bash +# Destroy in reverse order +cd terraform-gcp-vm/modules/gcp_vm +terraform destroy + +cd ../../backend +terraform destroy +``` -**Task:** -Create a **Terraform module** to provision a virtual machine instance in **Google Cloud Platform (GCP)**. +## CI/CD Pipelines -**Requirements:** -- Use **Terraform (HCL)**. -- Define a module named `gcp_vm`. -- The module should: - - Accept parameters for **instance type, zone, and machine image**. - - Create a **Compute Engine instance**. - - Attach a **startup script** to install `nginx`. - - Output the public IP of the instance. -- Provide an **example** of how to use the module in `main.tf`. +Automated workflows for testing and deployment. ---- +### Workflows -## Submission Instructions -- Create a **GitHub repository** or **ZIP archive** with: - - `algorithm/` (Golang function + tests) - - `golang-node-exporter/` (Golang Node Exporter + README) - - `terraform-gcp-vm/` (Terraform module + `main.tf`) -- Include a **README** explaining how to run/test each part. -- Submit the link to the repository or the ZIP archive. +**golang-ci.yml** +- Runs on push/PR to main and develop +- Tests both Go projects +- Runs linting +- Generates coverage reports + +**terraform-validate.yml** +- Runs on PRs touching Terraform files +- Validates syntax and formatting +- Runs tflint for best practices + +**deploy-dev.yml** +- Triggers on push to develop branch +- Plans infrastructure changes +- Manual approval required for apply + +**deploy-prod.yml** +- Triggers on push to main or version tags +- Requires production environment approval +- Uses larger instance sizes + +### Required Secrets + +Configure in GitHub Settings > Secrets: + +**Development:** +- `DEV_GCP_PROJECT_ID` - GCP project ID for dev +- `DEV_GCP_SA_KEY` - Service account JSON key for dev + +**Production:** +- `PROD_GCP_PROJECT_ID` - GCP project ID for prod +- `PROD_GCP_SA_KEY` - Service account JSON key for prod + +### Branching Strategy + +- `main` - Production releases +- `develop` - Development branch +- Feature branches - PRs to develop + +### Environment Configuration + +Environment-specific settings in `environments/`: +- `dev.tfvars` - Development configuration +- `prod.tfvars` - Production configuration + +## Local Development + +**Install dependencies:** +```bash +# Algorithm +cd algorithm && go mod download + +# Node exporter +cd golang-node-exporter && go mod download +``` + +**Run tests:** +```bash +# Run all tests +go test ./... + +# With coverage +go test -cover ./... + +# With race detection +go test -race ./... +``` + +**Format code:** +```bash +go fmt ./... +``` + +**Validate Terraform:** +```bash +cd terraform-gcp-vm +terraform fmt -recursive +terraform validate +``` ---- +## Notes -## Evaluation Criteria -1. **Code Quality** – Clean, readable, and maintainable code. -2. **Best Practices** – Following Golang and Terraform best practices. -3. **Documentation** – README files explaining setup and usage. +- All sensitive data is gitignored +- Service account keys should never be committed +- Terraform state is stored remotely in GCS +- Backend uses local state to avoid circular dependency -Good luck! +## Requirements Met +- Clean, readable code +- Comprehensive testing +- Best practices for Go and Terraform +- Documentation for all components +- CI/CD automation +- Environment separation diff --git a/algorithm/README.md b/algorithm/README.md index 175eaff..2456335 100644 --- a/algorithm/README.md +++ b/algorithm/README.md @@ -1,94 +1,53 @@ # Round Robin Load Balancer -A high-performance round-robin load balancer implementation in Go that automatically manages backend servers and performs health checks. +Round-robin load balancer with health checking. Automatically manages backend servers and routes traffic to healthy instances only. -## Features +Features: +- Round-robin request distribution +- Health check monitoring +- Dynamic backend management +- Status endpoint for monitoring +- Graceful shutdown -- **Round Robin Load Balancing**: Distributes incoming requests evenly across backend servers -- **Automatic Health Checks**: Monitors backend server health and routes traffic only to healthy servers -- **Dynamic Backend Management**: Automatically starts and manages the specified number of backend servers -- **Configurable Settings**: Customize health check intervals, timeouts, and server ports -- **Status Endpoint**: Monitor the health status of all backend servers via a dedicated endpoint -- **Graceful Shutdown**: Properly handles system signals for clean shutdown - -## Project Structure +## Structure ``` algorithm/ -├── cmd/ -│ └── loadbalancer/ -│ └── main.go # Main application entry point +├── cmd/loadbalancer/ # Main entry point ├── pkg/ -│ ├── backend/ -│ │ └── server.go # Backend server implementation -│ └── loadbalancer/ -│ ├── config.go # Configuration settings -│ ├── loadbalancer.go # Core load balancer implementation -│ └── loadbalancer_test.go # Unit tests -├── go.mod # Module dependencies -└── README.md # This file +│ ├── backend/ # Backend server implementation +│ └── loadbalancer/ # Core load balancer + tests +└── go.mod ``` ## Configuration -The load balancer can be configured through the `Config` struct in `pkg/loadbalancer/config.go`: - -```go -type Config struct { - HealthCheckInterval int // Interval between health checks (seconds) - HealthCheckTimeout int // Timeout for health checks (seconds) - RetryAttempts int // Number of retry attempts for unhealthy servers - NumBackendServers int // Number of backend servers to start - BackendStartPort int // Starting port for backend servers - LoadBalancerPort int // Port where load balancer listens -} -``` - -Default configuration values: -- Health check interval: 10 seconds -- Health check timeout: 5 seconds -- Retry attempts: 3 -- Number of backend servers: 3 -- Backend start port: 8081 +Default settings: - Load balancer port: 8080 +- Backend ports: 8081, 8082, 8083 +- Health check interval: 10s +- Health check timeout: 5s +- Retry attempts: 3 -## API Endpoints - -- **/** - Main endpoint that forwards requests to backend servers -- **/status** - Returns health status of all backend servers in JSON format +Configure via `pkg/loadbalancer/config.go` -Example status response: -```json -[ - { - "url": "http://localhost:8081", - "healthy": true - }, - { - "url": "http://localhost:8082", - "healthy": true - }, - { - "url": "http://localhost:8083", - "healthy": true - } -] -``` +## Endpoints -## Getting Started +`/` - Forwards requests to backend servers +`/status` - Returns health status (JSON) -1. Clone the repository -2. Navigate to the project directory -3. Run the load balancer: - ```bash - go run cmd/loadbalancer/main.go - ``` +## Usage -The load balancer will start on port 8080 (by default) and automatically start the configured number of backend servers. +```bash +# Install dependencies +go mod download -## Testing +# Run +go run cmd/loadbalancer/main.go -To run the tests: -```bash +# Test go test ./... + +# Build +go build ./cmd/loadbalancer ``` diff --git a/algorithm/cmd/loadbalancer/main.go b/algorithm/cmd/loadbalancer/main.go index 178cc34..d79c0d8 100644 --- a/algorithm/cmd/loadbalancer/main.go +++ b/algorithm/cmd/loadbalancer/main.go @@ -13,9 +13,9 @@ import ( "sync" "syscall" + "github.com/example/loadbalancer/pkg/backend" + "github.com/example/loadbalancer/pkg/loadbalancer" "github.com/sirupsen/logrus" - "github.com/skymavis/loadbalancer/pkg/backend" - "github.com/skymavis/loadbalancer/pkg/loadbalancer" ) // main is the entry point for the load balancer application. diff --git a/algorithm/go.mod b/algorithm/go.mod index 8c7ba5f..e0ad0b5 100644 --- a/algorithm/go.mod +++ b/algorithm/go.mod @@ -1,4 +1,4 @@ -module github.com/skymavis/loadbalancer +module github.com/example/loadbalancer go 1.19 diff --git a/environments/dev.tfvars b/environments/dev.tfvars new file mode 100644 index 0000000..e0efb6a --- /dev/null +++ b/environments/dev.tfvars @@ -0,0 +1,17 @@ +# Development Environment Configuration + +project_id = "your-dev-project-id" +region = "asia-southeast1" +zone = "asia-southeast1-a" +instance_name = "dev-vm" +machine_type = "e2-small" +environment = "dev" +boot_disk_size = 20 + +labels = { + environment = "dev" + app = "web" + team = "devops" + managed_by = "terraform" +} + diff --git a/environments/prod.tfvars b/environments/prod.tfvars new file mode 100644 index 0000000..89b8a5a --- /dev/null +++ b/environments/prod.tfvars @@ -0,0 +1,17 @@ +# Production Environment Configuration + +project_id = "your-prod-project-id" +region = "asia-southeast1" +zone = "asia-southeast1-a" +instance_name = "prod-vm" +machine_type = "e2-medium" +environment = "prod" +boot_disk_size = 30 + +labels = { + environment = "prod" + app = "web" + team = "devops" + managed_by = "terraform" +} + diff --git a/golang-node-exporter/README.md b/golang-node-exporter/README.md index 73adedd..e39aa91 100644 --- a/golang-node-exporter/README.md +++ b/golang-node-exporter/README.md @@ -1,114 +1,67 @@ # Golang Node Exporter -A Prometheus exporter that collects and exposes system metrics in Prometheus format. Built with Go and following best practices for production use. +Lightweight Prometheus exporter for system metrics. Collects CPU, memory, and network stats. -## Features +Features: +- CPU usage per core +- Memory usage +- Network interface stats +- Prometheus-compatible endpoint +- Configurable intervals +- Graceful shutdown -- CPU usage metrics per core -- Memory usage metrics -- Network interface statistics -- Prometheus-compatible metrics endpoint -- Configurable collection intervals -- Graceful shutdown handling -- Structured JSON logging -- Comprehensive error handling -- Thread-safe operations +## Requirements -## Prerequisites +- Go 1.21+ -- Go 1.21 or higher -- Windows/Linux/macOS +## Quick Start -## Installation - -1. Clone the repository: -```bash -git clone https://github.com/skymavis/golang-node-exporter.git -cd golang-node-exporter -``` - -2. Install dependencies: ```bash +# Install dependencies go mod download -``` - -## Building -Build the exporter for your current platform: -```bash +# Build go build -``` -This will create an executable named: -- `node_exporter` on Linux/macOS -- `node_exporter.exe` on Windows +# Run +./node-exporter - -## Running Tests - -Run all tests: -```bash +# Test go test ./... ``` ## Usage -### Basic Usage - -Run with default settings: +Default settings: ```bash -# Linux/macOS -./node_exporter - -# Windows -.\node_exporter +./node-exporter ``` -The exporter will start on port 8080 and expose metrics at `/metrics`. - -### Configuration Options - -The exporter supports several command-line flags: +Metrics available at http://localhost:8080/metrics +Configuration flags: ```bash -# Linux/macOS -./node_exporter -help - -# Windows -.\node_exporter -help +./node-exporter \ + -port=9100 \ + -collection-period=10s \ + -log-level=debug ``` -Available options: -- `-port`: Port to listen on (default: 8080) -- `-collection-period`: Period between metric collections (default: 5s) -- `-metrics-path`: Path under which to expose metrics (default: "/metrics") -- `-log-level`: Log level (debug, info, warn, error) (default: "info") -- `-shutdown-timeout`: Timeout for graceful shutdown (default: 30s) +Available flags: +- `-port` - Listen port (default: 8080) +- `-collection-period` - Collection interval (default: 5s) +- `-metrics-path` - Metrics endpoint (default: /metrics) +- `-log-level` - Logging level (default: info) +- `-shutdown-timeout` - Shutdown timeout (default: 30s) -Example with custom configuration: -```bash -# Linux/macOS -./node_exporter -port=9100 -collection-period=10s -log-level=debug - -# Windows -.\node_exporter -port=9100 -collection-period=10s -log-level=debug -``` - -## Available Metrics +## Metrics -### CPU Metrics -- `cpu_usage_percent{cpu="N"}`: CPU usage percentage per core +- `cpu_usage_percent{cpu="N"}` - CPU usage per core +- `memory_usage_bytes` - Memory usage in bytes +- `network_bytes_received{interface="X"}` - Network RX per interface +- `network_bytes_sent{interface="X"}` - Network TX per interface -### Memory Metrics -- `memory_usage_bytes`: Current memory usage in bytes - -### Network Metrics -- `network_bytes_received{interface="X"}`: Bytes received per interface -- `network_bytes_sent{interface="X"}`: Bytes sent per interface - -## Prometheus Configuration - -Add the following to your `prometheus.yml`: +## Prometheus Config ```yaml scrape_configs: @@ -117,30 +70,11 @@ scrape_configs: - targets: ['localhost:8080'] ``` -## Development - -### Project Structure +## Structure ``` golang-node-exporter/ -├── main.go # Application entry point -├── go.mod # Go module definition -├── go.sum # Go module checksums -├── pkg/ -│ └── metrics/ # Metrics collection package -│ ├── metrics.go # Core metrics implementation -│ └── metrics_test.go # Unit tests -└── README.md # This file -``` - -### Running Tests During Development - -Run tests with verbose output: -```bash -go test -v ./... -``` - -Run tests with coverage: -```bash -go test -cover ./... +├── main.go +├── pkg/metrics/ # Metrics collection + tests +└── go.mod ``` diff --git a/golang-node-exporter/go.mod b/golang-node-exporter/go.mod index 9213bcb..f8794ae 100644 --- a/golang-node-exporter/go.mod +++ b/golang-node-exporter/go.mod @@ -1,4 +1,4 @@ -module github.com/skymavis/golang-node-exporter +module github.com/example/golang-node-exporter go 1.21 diff --git a/golang-node-exporter/main.go b/golang-node-exporter/main.go index 655e95c..20aa00e 100644 --- a/golang-node-exporter/main.go +++ b/golang-node-exporter/main.go @@ -10,14 +10,14 @@ import ( "syscall" "time" + "github.com/example/golang-node-exporter/pkg/metrics" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" - "github.com/skymavis/golang-node-exporter/pkg/metrics" ) type config struct { - port int - collectionPeriod time.Duration + port int + collectionPeriod time.Duration metricsPath string logLevel string shutdownTimeout time.Duration @@ -33,7 +33,7 @@ func main() { // Create metrics collector collector, err := metrics.New(metrics.Config{ CollectionInterval: cfg.collectionPeriod, - Logger: logger, + Logger: logger, }) if err != nil { logger.WithError(err).Fatal("Failed to create metrics collector") diff --git a/terraform-gcp-vm/README.md b/terraform-gcp-vm/README.md index 4d904a5..c7ef4f6 100644 --- a/terraform-gcp-vm/README.md +++ b/terraform-gcp-vm/README.md @@ -1,110 +1,77 @@ -# Terraform GCP VM Infrastructure +# Terraform GCP VM Module -This repository manages GCP infrastructure using Terraform with a two-phase state management approach. +Terraform module for provisioning GCP VMs with two-phase state management. + +## Structure -## Project Structure ``` terraform-gcp-vm/ -├── backend/ # GCS bucket creation (local state) -│ ├── main.tf # Creates state bucket -│ ├── variables.tf # Bucket configuration -│ └── terraform.tfvars # Variable values -└── modules/ - └── gcp_vm/ # VM module (GCS state) - ├── backend.tf # GCS state configuration - ├── main.tf # VM resources - ├── variables.tf # Module variables - ├── outputs.tf # Module outputs - └── provider.tf # Provider configuration +├── backend/ # GCS bucket (local state) +└── modules/gcp_vm/ # VM resources (GCS state) ``` -## State Management Architecture - -This project uses a two-phase approach for state management: +## State Management -1. Backend Component (Local State): - - Uses local state storage (.terraform directory) - - Creates and configures GCS bucket - - Bucket name: ${bucket_name}-tfstate - - Default: sky-test-instance-tfstate +Two-phase approach: +1. Backend uses local state to create GCS bucket +2. VM module uses GCS for remote state -2. VM Module (GCS State): - - Uses GCS bucket created by backend - - State prefix: `sky-test-instance-tfstate` - - Remote state for better collaboration +This avoids circular dependency and allows team collaboration. -Benefits: -- Independent state management for multiple developer to work on the same project +## Requirements -## Prerequisites +- Terraform >= 1.0 +- Google Cloud SDK +- GCP project with billing -1. Required tools: - - Terraform >= 1.0 - - Google Cloud SDK +## Setup -2. Service account setup: +Service account: ```bash # Create service account gcloud iam service-accounts create terraform --display-name="Terraform SA" # Generate key gcloud iam service-accounts keys create terraform-key.json \ - --iam-account=terraform@sky-test-instance.iam.gserviceaccount.com + --iam-account=terraform@YOUR-PROJECT-ID.iam.gserviceaccount.com ``` -3. Grant permissions: +Permissions: ```bash # Storage permissions for state management - gcloud projects add-iam-policy-binding sky-test-instance \ - --member="serviceAccount:terraform@sky-test-instance.iam.gserviceaccount.com" \ + gcloud projects add-iam-policy-binding YOUR-PROJECT-ID \ + --member="serviceAccount:terraform@YOUR-PROJECT-ID.iam.gserviceaccount.com" \ --role="roles/storage.admin" # Compute permissions for VM management - gcloud projects add-iam-policy-binding sky-test-instance \ - --member="serviceAccount:terraform@sky-test-instance.iam.gserviceaccount.com" \ + gcloud projects add-iam-policy-binding YOUR-PROJECT-ID \ + --member="serviceAccount:terraform@YOUR-PROJECT-ID.iam.gserviceaccount.com" \ --role="roles/compute.admin" ``` -## Quick Start +## Usage -### 1. Backend Setup (Local State) +Backend setup: ```bash cd backend - -# Initialize with local state terraform init -terraform plan -terraform apply # Creates sky-test-instance-tfstate bucket +terraform apply -var-file="../environments/dev.tfvars" ``` -### 2. VM Module Setup (GCS State) +VM deployment: ```bash -cd ../modules/gcp_vm - -terraform init # Uses bucket created by backend, there might be conflict state so use migrate option with no copy from previous state only. -terraform plan -terraform apply +cd modules/gcp_vm +terraform init +terraform apply -var-file="../../environments/dev.tfvars" ``` -## Verification Result - -### VM Module Deployment -![Backend Apply](resource/apply.png) - -![VM Module Run](resource/vm_run.png) -*Successful deployment of VM resources using GCS backend* - -### 3. Destroy - -1. Destroy VM resources first: +Cleanup: ```bash +# Destroy in reverse order cd modules/gcp_vm -terraform destroy # Removes VM resources, state remains in GCS -``` +terraform destroy -2. Destroy backend (optional): -```bash cd ../../backend -terraform destroy # Removes GCS bucket and all state files +terraform destroy ``` diff --git a/terraform-gcp-vm/backend/terraform.tfstate b/terraform-gcp-vm/backend/terraform.tfstate deleted file mode 100644 index 0d7e311..0000000 --- a/terraform-gcp-vm/backend/terraform.tfstate +++ /dev/null @@ -1,9 +0,0 @@ -{ - "version": 4, - "terraform_version": "1.11.1", - "serial": 1, - "lineage": "0d751530-d476-20df-6a54-e49bdf16b08f", - "outputs": {}, - "resources": [], - "check_results": null -} diff --git a/terraform-gcp-vm/backend/terraform.tfvars b/terraform-gcp-vm/backend/terraform.tfvars index b9f0fa4..e83758b 100644 --- a/terraform-gcp-vm/backend/terraform.tfvars +++ b/terraform-gcp-vm/backend/terraform.tfvars @@ -1,7 +1,7 @@ # Required variables -project_id = "sky-test-instance" +project_id = "your-gcp-project-id" region = "asia-southeast1" # Optional variables with defaults environment = "production" -bucket_name = "sky-test-instance" # Will create sky-test-instance-tfstate bucket +bucket_name = "example-project" # Will create example-project-tfstate bucket diff --git a/terraform-gcp-vm/backend/variables.tf b/terraform-gcp-vm/backend/variables.tf index 801ed3a..c6541d8 100644 --- a/terraform-gcp-vm/backend/variables.tf +++ b/terraform-gcp-vm/backend/variables.tf @@ -18,5 +18,5 @@ variable "environment" { variable "bucket_name" { description = "Name of the GCS bucket to create for Terraform state (will be suffixed with -tfstate)" type = string - default = "sky-test-instance" # This will create sky-test-instance-tfstate + default = "example-project" # This will create example-project-tfstate } diff --git a/terraform-gcp-vm/modules/gcp_vm/backend.tf b/terraform-gcp-vm/modules/gcp_vm/backend.tf index 074a69e..d707fbf 100644 --- a/terraform-gcp-vm/modules/gcp_vm/backend.tf +++ b/terraform-gcp-vm/modules/gcp_vm/backend.tf @@ -1,6 +1,6 @@ # GCS backend configuration for VM module terraform { backend "gcs" { - bucket = "sky-test-instance-tfstate" # Created by backend component + bucket = "example-project-tfstate" # Created by backend component } } diff --git a/terraform-gcp-vm/modules/gcp_vm/main.tf b/terraform-gcp-vm/modules/gcp_vm/main.tf index cf474ed..16b4f3c 100644 --- a/terraform-gcp-vm/modules/gcp_vm/main.tf +++ b/terraform-gcp-vm/modules/gcp_vm/main.tf @@ -57,7 +57,7 @@ resource "google_compute_instance" "vm_instance" { ) # Enable deletion protection - deletion_protection = true + deletion_protection = false # Enable secure boot for better security shielded_instance_config { diff --git a/terraform-gcp-vm/modules/gcp_vm/terraform.tfvars b/terraform-gcp-vm/modules/gcp_vm/terraform.tfvars index 38c1357..eb955dd 100644 --- a/terraform-gcp-vm/modules/gcp_vm/terraform.tfvars +++ b/terraform-gcp-vm/modules/gcp_vm/terraform.tfvars @@ -4,8 +4,8 @@ # Example: To use different values, uncomment and modify the relevant lines: -# Change project ID (default: sky-test-instance) -# project_id = "your-project-id" +# Change project ID (required) +# project_id = "your-gcp-project-id" # Use a different region (default: asia-southeast1) # region = "us-west1"