diff --git a/.gitignore b/.gitignore index 964613a..07f22a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,11 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# terraform +.terraform/ +.terraform.lock.hcl +terraform.tfstate +terraform.tfstate.backup + # dependencies /node_modules /.pnp @@ -35,4 +41,4 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts -.vscode \ No newline at end of file +.vscode diff --git a/Dockerfile b/Dockerfile index bebc65d..d577f59 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,6 @@ RUN npm install -g pnpm RUN pnpm install && pnpm add sharp # Copy the rest of the application code -COPY .env . COPY . . # Build the application diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..c0cbccf --- /dev/null +++ b/main.tf @@ -0,0 +1,155 @@ +terraform { + required_providers { + oci = { + source = "oracle/oci" + version = "6.25.0" + } + } +} + +provider "oci" { + tenancy_ocid = var.tenancy_ocid + user_ocid = var.user_ocid + private_key = replace(var.private_key, "\\n", "\n") + fingerprint = var.fingerprint + region = var.region +} + +# Create Virtual Cloud Network (VCN) +resource "oci_core_vcn" "devops_dynamics_vcn" { + compartment_id = var.compartment_id + cidr_blocks = var.vcn_cidr_blocks + display_name = var.vcn_display_name + dns_label = var.vcn_dns_label +} + +# Internet Gateway for Public Access +resource "oci_core_internet_gateway" "devops_igw" { + compartment_id = var.compartment_id + vcn_id = oci_core_vcn.devops_dynamics_vcn.id + display_name = "devops_internet_gateway" + enabled = true +} + +# Route Table for Internet Access +resource "oci_core_route_table" "devops_rt" { + compartment_id = var.compartment_id + vcn_id = oci_core_vcn.devops_dynamics_vcn.id + display_name = "devops_route_table" + + route_rules { + destination = "0.0.0.0/0" + destination_type = "CIDR_BLOCK" + network_entity_id = oci_core_internet_gateway.devops_igw.id + } +} + +# Security List (Allow SSH, HTTP, HTTPS) +resource "oci_core_security_list" "devops_security_list" { + compartment_id = var.compartment_id + vcn_id = oci_core_vcn.devops_dynamics_vcn.id + display_name = "devops_security_list" + + # Allow inbound SSH + ingress_security_rules { + protocol = "6" + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + + tcp_options { + min = 22 + max = 22 + } + } + + # Allow HTTP (Port 80) + ingress_security_rules { + protocol = "6" + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + + tcp_options { + min = 80 + max = 80 + } + } + + # Allow HTTPS (Port 443) + ingress_security_rules { + protocol = "6" + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + + tcp_options { + min = 443 + max = 443 + } + } + + # Allow all outbound traffic + egress_security_rules { + protocol = "all" + destination = "0.0.0.0/0" + } +} + +# Create Subnet for Compute Instances +resource "oci_core_subnet" "devops_subnet" { + compartment_id = var.compartment_id + vcn_id = oci_core_vcn.devops_dynamics_vcn.id + cidr_block = var.subnet_cidr_block + display_name = "devops_subnet" + route_table_id = oci_core_route_table.devops_rt.id + security_list_ids = [oci_core_security_list.devops_security_list.id] + dns_label = "devsubnet" + prohibit_public_ip_on_vnic = false +} + +# Fetch Availability Domains +data "oci_identity_availability_domains" "ads" { + compartment_id = var.tenancy_ocid +} + +# Create AMD Compute Instance for Production +resource "oci_core_instance" "prod_server" { + compartment_id = var.compartment_id + availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name + shape = "VM.Standard.E2.1.Micro" + display_name = "prod-server" + + create_vnic_details { + subnet_id = oci_core_subnet.devops_subnet.id + assign_public_ip = true + } + + source_details { + source_type = "image" + source_id = var.image_ocid + } + + metadata = { + ssh_authorized_keys = replace(var.ssh_public_keys, "\\n", "\n") + } +} + +# Create AMD Compute Instance for Staging +resource "oci_core_instance" "staging_server" { + compartment_id = var.compartment_id + availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name + shape = "VM.Standard.E2.1.Micro" + display_name = "staging-server" + + create_vnic_details { + subnet_id = oci_core_subnet.devops_subnet.id + assign_public_ip = true + } + + source_details { + source_type = "image" + source_id = var.image_ocid + } + + metadata = { + ssh_authorized_keys = replace(var.ssh_public_keys, "\\n", "\n") + } +} diff --git a/terraform-oci-state.sh b/terraform-oci-state.sh new file mode 100755 index 0000000..ad42e82 --- /dev/null +++ b/terraform-oci-state.sh @@ -0,0 +1,115 @@ +#!/bin/bash +# Automate Terraform state file management in OCI with "pull" and "push" commands + +BUCKET_NAME="devops-dynamics-terraform-state" +PROFILE="DEVOPS-DYNAMICS" +STATE_FILE="terraform.tfstate" +BACKUP_FILE="terraform.tfstate.backup" +REMOTE_FILE="terraform.tfstate.remote" + +# Function to get object ETag (checksum) from OCI Storage +get_etag() { + local object_name=$1 + oci os object head \ + --bucket-name "$BUCKET_NAME" \ + --name "$object_name" \ + --profile "$PROFILE" \ + --query 'etag' 2>/dev/null | tr -d '"' +} + +# Ensure a valid argument is passed +if [[ $# -ne 1 ]]; then + echo "❌ Error: No valid argument provided." + echo "Usage: $0 " + exit 1 +fi + +# đŸ“Ĩ **Pull Command: Download the latest Terraform state from OCI** +if [[ "$1" == "pull" ]]; then + echo "đŸ“Ĩ Downloading the latest Terraform state from OCI..." + oci os object get \ + --bucket-name "$BUCKET_NAME" \ + --name "$STATE_FILE" \ + --file "$STATE_FILE" \ + --profile "$PROFILE" \ + 2>/dev/null + + if [[ $? -eq 0 ]]; then + echo "✅ Successfully pulled latest Terraform state from OCI." + else + echo "âš ī¸ No existing Terraform state found in OCI." + fi + exit 0 +fi + +# 🚀 **Push Command: Upload updated Terraform state** +if [[ "$1" == "push" ]]; then + # Ensure Terraform state file exists before proceeding + if [[ ! -f "$STATE_FILE" ]]; then + echo "❌ Error: Terraform state file '$STATE_FILE' not found!" + exit 1 + fi + + # Step 1: Download the current Terraform state for comparison + echo "đŸ“Ĩ Fetching the latest Terraform state from OCI..." + oci os object get \ + --bucket-name "$BUCKET_NAME" \ + --name "$STATE_FILE" \ + --file "$REMOTE_FILE" \ + --profile "$PROFILE" \ + 2>/dev/null || echo "â„šī¸ No existing state file found in OCI." + + # Step 2: Compute local and remote checksums + LOCAL_ETAG=$(openssl dgst -sha256 -binary "$STATE_FILE" | openssl base64) + REMOTE_ETAG="" + if [[ -f "$REMOTE_FILE" ]]; then + REMOTE_ETAG=$(openssl dgst -sha256 -binary "$REMOTE_FILE" | openssl base64) + fi + + # Remove the temporary remote state file + rm -f "$REMOTE_FILE" + + # Step 3: If checksums match, skip upload + if [[ "$LOCAL_ETAG" == "$REMOTE_ETAG" ]]; then + echo "✅ No changes detected in Terraform state. Skipping upload." + exit 0 + else + echo "🔄 Changes detected in Terraform state! Updating OCI Storage..." + + # Step 4: Delete the old backup if it exists + if get_etag "$BACKUP_FILE" &>/dev/null; then + echo "đŸ—‘ī¸ Removing old backup file from OCI..." + oci os object delete \ + --bucket-name "$BUCKET_NAME" \ + --name "$BACKUP_FILE" \ + --force \ + --profile "$PROFILE" + fi + + # Step 5: Rename current `terraform.tfstate` to `terraform.tfstate.backup` + if [[ -n "$REMOTE_ETAG" ]]; then + echo "📂 Renaming existing state file to backup in OCI..." + oci os object rename \ + --bucket-name "$BUCKET_NAME" \ + --source-name "$STATE_FILE" \ + --new-name "$BACKUP_FILE" \ + --profile "$PROFILE" + fi + + # Step 6: Upload the new Terraform state file + echo "âŦ†ī¸ Uploading new Terraform state to OCI..." + oci os object put \ + --bucket-name "$BUCKET_NAME" \ + --file "$STATE_FILE" \ + --name "$STATE_FILE" \ + --profile "$PROFILE" + + echo "✅ Terraform state successfully updated in OCI!" + fi + exit 0 +fi + +# ❌ If an invalid argument is passed +echo "❌ Error: Invalid argument '$1'." +echo "Usage: $0 " +exit 1 diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..8542e7a --- /dev/null +++ b/variables.tf @@ -0,0 +1,65 @@ +variable "tenancy_ocid" { + description = "The OCID of the tenancy" + type = string +} + +variable "user_ocid" { + description = "The OCID of the user" + type = string +} + +variable "private_key" { + description = "The private key for API signing" + type = string + sensitive = true +} + +variable "fingerprint" { + description = "The fingerprint for the private key" + type = string +} + +variable "region" { + description = "The region for OCI operations" + type = string +} + +variable "compartment_id" { + description = "Compartment ID" + type = string +} + +variable "vcn_cidr_blocks" { + description = "VCN CIDR Blocks" + type = list(string) +} + +variable "vcn_display_name" { + description = "VCN Display Name" + type = string +} + +variable "vcn_dns_label" { + description = "VCN DNS Label" + type = string +} + +variable "oci_group_name" { + description = "OCI group name that will be allowed to manage VCN resources" + type = string +} + +variable "subnet_cidr_block" { + description = "Subnet CIDR Block" + type = string +} + +variable "image_ocid" { + description = "OCID of the image to be used for the Compute instance" + type = string +} + +variable "ssh_public_keys" { + description = "SSH Public Keys" + type = string +}