Question : How to integrate terraform with azure devops
To integrate HashiCorp Terraform with
Azure DevOps, you must
configure an Azure backend for your state file, establish a secure Service Connection, and build an automated YAML pipeline. This integration allows you to fully automate your Infrastructure as Code (IaC) workflows.
Follow this complete step-by-step workflow to get started:
1. Set Up the Remote State Backend
Terraform requires a centralized, secure location to store its state (.tfstate) file so multiple team members can collaborate without conflicts.
- Create an Azure Storage Account and a Blob Container (e.g., named
tfstate). - Note down your Resource Group, Storage Account name, and Container name. [
2. Configure Your Source Code
Push your core configuration files to your Azure Repos Git repository.
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
backend "azurerm" {} # Leave blank; Azure DevOps will inject this dynamically
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "example" {
name = "rg-terraform-demo"
location = "East US"
}
3. Create an Azure Service Connection
Azure DevOps needs permission to deploy resources into your Azure subscription. [
- Navigate to your project settings in Azure DevOps.
- Select Service connections and click New service connection.
- Choose Azure Resource Manager and select the Service principal (automatic) or Workload Identity federation option.
- Name your connection (e.g.,
azure-service-connection) and click Save. [
4. Install the Terraform Extension
To use native Terraform commands inside your pipeline, add the official tooling. [
- Install the Terraform Build & Release Tasks extension published by Microsoft.
5. Build the CI/CD Pipeline
Create a new YAML file named azure-pipelines.yml in your repository root. This pipeline contains two logical stages: a Build stage to validate and dry-run code changes, and a Deploy stage to apply them. [
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
# Reference the precise Service Connection name you created in Step 3
azureServiceConnection: 'azure-service-connection'
resourceGroup: 'your-backend-rg'
storageAccount: 'yourstorageaccountname'
containerName: 'tfstate'
stateKey: 'terraform.tfstate'
stages:
- stage: Build
jobs:
- job: Terraform_Plan
steps:
- task: TerraformInstaller@1
inputs:
terraformVersion: 'latest'
- task: TerraformTaskV4@4
displayName: 'Terraform Init'
inputs:
provider: 'azurerm'
command: 'init'
backendServiceArm: '$(azureServiceConnection)'
backendAzureRmResourceGroupName: '$(resourceGroup)'
backendAzureRmStorageAccountName: '$(storageAccount)'
backendAzureRmContainerName: '$(containerName)'
backendAzureRmKey: '$(stateKey)'
- task: TerraformTaskV4@4
displayName: 'Terraform Validate'
inputs:
provider: 'azurerm'
command: 'validate'
- task: TerraformTaskV4@4
displayName: 'Terraform Plan'
inputs:
provider: 'azurerm'
command: 'plan'
environmentServiceNameAzureRM: '$(azureServiceConnection)'
- stage: Deploy
dependsOn: Build
condition: succeeded()
jobs:
- job: Terraform_Apply
steps:
- task: TerraformInstaller@1
inputs:
terraformVersion: 'latest'
- task: TerraformTaskV4@4
displayName: 'Terraform Init'
inputs:
provider: 'azurerm'
command: 'init'
backendServiceArm: '$(azureServiceConnection)'
backendAzureRmResourceGroupName: '$(resourceGroup)'
backendAzureRmStorageAccountName: '$(storageAccount)'
backendAzureRmContainerName: '$(containerName)'
backendAzureRmKey: '$(stateKey)'
- task: TerraformTaskV4@4
displayName: 'Terraform Apply'
inputs:
provider: 'azurerm'
command: 'apply'
environmentServiceNameAzureRM: '$(azureServiceConnection)'
- Go to Pipelines in your Azure DevOps side menu.
- Click New Pipeline, select your Git repository, and point it to your
azure-pipelines.yml file. - Run the pipeline. The build agent will dynamically initialize the environment, perform security validations, map your infrastructure modifications, and provision your Azure resources. [
Q1: What are the key prerequisites to set up a Terraform CI/CD pipeline in Azure DevOps?
- Azure Service Connection: Set up a Service Principal (SPN) with Owner or Contributor access to the target Azure subscription.
- Remote State Backend: Provision an Azure Storage Account and Blob Container to securely isolate and maintain the
terraform.tfstate file. - Terraform Extension: Install an extension like the official Microsoft Terraform tasks or use native CLI scripts in your pipeline.
- Code Repository: Store your
.tf configuration files inside an Azure Repos or GitHub repository.
Q2: How do you handle the Terraform backend configuration securely across environments (Dev, QA, Prod)?
terraform {
backend "azurerm" {} # Values are injected dynamically by the pipeline
}
- Dynamic Injection: Use the Azure DevOps Terraform task to inject the
storage_account_name, container_name, and key dynamically during the init stage based on the target stage environment variables.
⚙️ Pipeline Design & Workflow Questions
Q3: What does a standard Terraform CI/CD workflow look like in Azure DevOps?
A robust enterprise pipeline splits the workflow into a
Build (CI) stage and a
Deploy (CD) stage: [
1]
| Stage | Step / Task | Purpose |
|---|
| CI (Build) | terraform init | Downloads provider plugins and configures the backend. |
| terraform validate / tflint | Verifies syntax validity and checks for linting issues. |
| terraform plan | Outputs a binary plan file (tfplan) showing proposed changes. |
| Publish Artifact | Uploads the tfplan file to the pipeline run so it cannot be altered. |
| CD (Deploy) | Manual Validation | Requires human approval to review the plan before running changes. |
| terraform apply | Downloads the artifact and executes the changes using the exact tfplan. |
Q4: Write a basic Azure DevOps YAML snippet for running a Terraform plan.
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- task: TerraformInstaller@1
inputs:
terraformVersion: 'latest'
- task: TerraformTaskV4@4
displayName: 'Terraform Init'
inputs:
provider: 'azurerm'
command: 'init'
backendServiceArm: 'Azure-Service-Connection'
backendAzureRmStorageAccountName: 'tfstatestorageacct'
backendAzureRmContainerName: 'tfstate'
backendAzureRmKey: 'terraform.tfstate'
- task: TerraformTaskV4@4
displayName: 'Terraform Plan'
inputs:
provider: 'azurerm'
command: 'plan'
environmentServiceNameAzureRM: 'Azure-Service-Connection'
Security & Advanced Scenario Questions
Q5: How do you securely handle sensitive secrets (like API keys or VM passwords) in an Azure DevOps Terraform pipeline?
- Azure Key Vault: Store passwords and secrets inside an Azure Key Vault.
- Variable Groups: Link the Azure Key Vault directly to an Azure DevOps Variable Group.
- Environment Variables: Map those secrets inside the pipeline tasks as environment variables prefixed with
TF_VAR_ (e.g., TF_VAR_admin_password), which Terraform automatically absorbs as input variables without exposing them in plain text.
Q6: How do you implement "Manual Gates" or "Approvals" to prevent unauthorized infrastructure changes?
- Use Azure DevOps Environments (e.g., Production).
- Configure Approvals and Checks on that specific environment in the Azure DevOps portal.
- Target that environment within your deployment job:
jobs:
- deployment: DeployInfra
environment: 'Production' # This triggers the manual validation check
strategy:
runOnce:
deploy:
steps:
- script: terraform apply tfplan
Q7: If a resource was manually created in the Azure portal, how do you handle it in your Azure DevOps pipeline?
- Identify the Resource: Extract the Resource ID from the Azure portal.
- Write Code: Define a matching empty resource block in your Terraform configuration files.
- Run Import: Run the
terraform import command locally or via a one-time pipeline script to map the real-world infrastructure into the remote state file. [1, 2, 3, 4, 5]
Q: Why use Terraform instead of OCI Console for Exadata?- A: Terraform provides repeatability, version control, and infrastructure-as-code consistency across environments.
Q: How do you handle secrets like SSH keys for Exadata nodes?- A: Use external secret managers or variable files (
terraform.tfvars) excluded from source control
- Q1: Why use Terraform instead of manual OCI Console actions for Exadata?
- A: Terraform enforces Infrastructure as Code (IaC), ensuring repeatability, version control tracking in Git, consistency across non-prod and prod environments, and elimination of manual configuration drift.
- Q2: How do you manage sensitive DBA inputs like SYS/SYSTEM passwords in Terraform?
- A: Use
sensitive = true input variables, reference external secret stores like HashiCorp Vault or OCI Vault, and avoid hardcoding passwords directly in .tf configuration files.
- Q3: What happens if an Exadata provisioning step fails halfway through an apply?
- A: Terraform updates its local or remote
terraform.tfstate file to reflect the last known successful resource status. You can fix the configuration error and re-run terraform apply to resume or correct the failed state safely due to idempotency.
Step-by-Step Exadata Provisioning Example
Step 1: Define the Provider and Variables
Create a provider.tf and variables.tf to set up your connection to OCI and define parameters like compartment OCID and shape.
provider "oci" {
region = "us-ashburn-1"
}
variable "compartment_id" {
type = string
default = "ocid1.compartment.oc1..exampleuniqueID"
}
Step 2: Write the Exadata Infrastructure Resource
Create exadata.tf to provision an Exadata infrastructure and VM cluster.
resource "oci_database_exadata_infrastructure" "test_exadata_infra" {
compartment_id = var.compartment_id
shape = "Exadata.X9M"
display_name = "DBA_Exadata_Infra"
compute_count = 2
storage_count = 3
}
Step 3: Run Terraform Commands
Initialize the working directory, review the execution plan, and apply the configuration.
terraform init (Downloads OCI provider plugins)terraform plan (Previews the creation of the Exadata infrastructure)terraform apply (Provisions the actual cloud components)
- TC-01 (Syntax Validation): Run
terraform validate to confirm correct HCL syntax. - TC-02 (Plan Verification): Run
terraform plan to ensure zero unexpected modifications or destructions on existing resources. - TC-03 (Resource Creation Check): Verify via OCI Console/CLI that
oci_database_exadata_infrastructure reaches the AVAILABLE state. - TC-04 (Idempotency Test): Re-run
terraform apply with no code changes; verify that Terraform reports 0 added, 0 changed, 0 destroyed.
or
Step-by-Step Example: Provisioning an Exadata VM Cluster
- Initialize Directory: Run
terraform init to download the Oracle Cloud Infrastructure (OCI) provider. - Write Configuration (
main.tf):provider "oci" {
region = "us-ashburn-1"
}
resource "oci_database_cloud_vm_cluster" "test_vm_cluster" {
compartment_id = var.compartment_id
cpu_core_count = 4
cluster_name = "ExaVMCluster"
db_node_storage_size_in_gbs = 60
display_name = "ExadataVMCluster"
domain = "://oraclevcn.com"
exadata_infrastructure_id = var.exadata_infra_id
hostname = "exavm"
ssh_public_keys = [var.ssh_public_key]
subnet_id = var.subnet_id
license_model = "LICENSE_INCLUDED"
shape = "Exadata.X9M"
}
- Plan Changes: Run
terraform plan to preview the cluster setup. - Apply Configuration: Run
terraform apply to deploy the Exadata VM cluster.
- Syntax Validation: Run
terraform validate to catch configuration errors. - Dry-Run Check: Execute
terraform plan to verify resource count and parameter alignments. - Idempotency Test: Re-run
terraform apply to confirm zero changes are reported on an unchanged infrastructure state.
Question : Step-by-Step Terraform & CI/CD integration
Step-by-Step Terraform & CI/CD Example for Exadata VM Cluster
- Define the Terraform Resource (
main.tf): Configure the Oracle Cloud Infrastructure (OCI) provider and Exadata VM cluster resource block. - Set up Variables (
variables.tf): Declare variables for compartment_id, cluster_name, cpu_core_count, and ssh_public_keys. - Commit Code to Git: Push your HCL files to a repository (e.g., GitHub).
- Trigger CI/CD Pipeline (
.github/workflows/deploy.yml):- Stage 1 (Validate): Run
terraform init and terraform validate. - Stage 2 (Plan): Run
terraform plan -out=tfplan to preview infrastructure changes. - Stage 3 (Approval): Require a manual team lead sign-off before production apply.
- Stage 4 (Apply): Run
terraform apply tfplan to provision the Exadata component on OCI.
Test Cases & Validation Framework
- Syntax and Style Check: Run
terraform fmt -check and tflint to catch errors early. - Unit Testing (
*.tftest.hcl): Use native Terraform test blocks to assert properties like cpu_core_count > 0 or correct subnet ID bindings before plan execution. - Post-Apply Health Check: Integrate a remote-exec or API script in the pipeline to verify that the Exadata database node responds to basic connectivity checks.
Question : Terraform execution work flow and troubleshooting
Terraform automates Oracle Exadata infrastructure provisioning, patching, and resource allocation through the official Oracle Cloud Infrastructure (OCI) Terraform Provider. Because Exadata combines compute, storage, and database layers into a high-performance ecosystem, managing it via Infrastructure as Code (IaC) requires precise resource sequencing.
The automation flow follows a strict structural hierarchy:
[Exadata Infrastructure] ➔ [Autonomous VM Cluster] ➔ [Autonomous Container DB] ➔ [Autonomous DB]
- Initialization: Terraform initializes the OCI provider and reads the remote state.
- Infrastructure Validation: Verifies that physical Exadata infrastructure or dedicated racks are active.
- Cluster Provisioning: Creates or modifies the Exadata VM Cluster (allocating CPU, memory, and storage).
- Database Provisioning: Deploys Container Databases (CDBs) and Pluggable Databases (PDBs).
- Post-Deployment/DBA Tasks: Runs automated SQL scripts, sets up automatic backups, and configures Oracle Data Guard.
This example demonstrates how a DBA can provision an Exadata VM Cluster and an Oracle Database using Terraform.
Step 1: Define the Provider (provider.tf)
terraform {
required_version = ">= 1.5.0"
required_providers {
oci = {
source = "oracle/oci"
version = ">= 5.0.0"
}
}
backend "s3" { # Or OCI Object Storage for state locking
bucket = "my-exadata-tf-state"
key = "exadata/terraform.tfstate"
region = "us-ashburn-1"
}
}
provider "oci" {
tenancy_ocid = var.tenancy_ocid
user_ocid = var.user_ocid
fingerprint = var.fingerprint
private_key_path = var.private_key_path
region = var.region
}
Step 2: Define Infrastructure Resources (main.tf)
# 1. Fetch Existing Exadata Infrastructure
data "oci_database_exadata_infrastructure" "infra" {
exadata_infrastructure_id = var.exadata_infra_id
}
# 2. Provision Exadata VM Cluster
resource "oci_database_cloud_vm_cluster" "exadata_vm_cluster" {
compartment_id = var.compartment_id
exadata_infrastructure_id = data.oci_database_exadata_infrastructure.infra.id
display_name = "Exadata-Prod-Cluster"
cpu_core_count = 4
gi_version = "19.0.0.0"
db_node_storage_size_in_gbs = 100
ssh_public_keys = [var.ssh_public_key]
subnet_id = var.client_subnet_id
backup_subnet_id = var.backup_subnet_id
hostname = "exaprodvm"
cluster_name = "exaprodc"
}
# 3. Create Database Home
resource "oci_database_db_home" "db_home" {
vm_cluster_id = oci_database_cloud_vm_cluster.exadata_vm_cluster.id
database {
admin_password = var.db_admin_password
db_name = "PRODDB"
db_workload = "OLTP"
pdb_name = "PRODPDB1"
}
db_version = "19.0.0.0"
display_name = "DBHome_19c"
}
Step 3: Define Input Variables (variables.tf)
variable "tenancy_ocid" { type = string }
variable "user_ocid" { type = string }
variable "fingerprint" { type = string }
variable "private_key_path" { type = string }
variable "region" { type = string; default = "us-ashburn-1" }
variable "compartment_id" { type = string }
variable "exadata_infra_id" { type = string }
variable "client_subnet_id" { type = string }
variable "backup_subnet_id" { type = string }
variable "ssh_public_key" { type = string }
variable "db_admin_password" { type = string; sensitive = true }
CI/CD Integration (GitHub Actions Workflow)
This pipeline integrates security linting, plan inspection, manual DBA approval, and automated deployment.
name: Exadata Terraform CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
validate_and_plan:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
- name: Terraform Format Check
run: terraform fmt -check
- name: Security Scan (trivy)
uses: aquasecurity/trivy-action@master
with:
scan-type: 'config'
hide-progress: true
- name: Terraform Init
run: terraform init
env:
OCI_CLI_USER: ${{ secrets.OCI_USER_OCID }}
OCI_CLI_TENANCY: ${{ secrets.OCI_TENANCY_OCID }}
OCI_CLI_FINGERPRINT: ${{ secrets.OCI_FINGERPRINT }}
OCI_CLI_KEY_CONTENT: ${{ secrets.OCI_PRIVATE_KEY }}
- name: Terraform Plan
id: plan
run: terraform plan -out=tfplan -var="db_admin_password=${{ secrets.DB_PASSWORD }}"
env:
OCI_CLI_USER: ${{ secrets.OCI_USER_OCID }}
OCI_CLI_TENANCY: ${{ secrets.OCI_TENANCY_OCID }}
OCI_CLI_FINGERPRINT: ${{ secrets.OCI_FINGERPRINT }}
OCI_CLI_KEY_CONTENT: ${{ secrets.OCI_PRIVATE_KEY }}
deploy:
needs: validate_and_plan
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
environment: production # Requires manual approval in GitHub Settings
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform Init & Apply
run: |
terraform init
terraform apply -auto-approve -var="db_admin_password=${{ secrets.DB_PASSWORD }}"
env:
OCI_CLI_USER: ${{ secrets.OCI_USER_OCID }}
OCI_CLI_TENANCY: ${{ secrets.OCI_TENANCY_OCID }}
OCI_CLI_FINGERPRINT: ${{ secrets.OCI_FINGERPRINT }}
OCI_CLI_KEY_CONTENT: ${{ secrets.OCI_PRIVATE_KEY }}
Infrastructure Test Cases (Terratest)
Write test cases using Terratest (Go framework) to validate that your Exadata infrastructure meets structural and DBA standards before integration.
package test
import (
"testing"
"://github.com"
"://github.com"
)
func TestExadataClusterProvision(t *testing.T) {
t.Parallel()
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../terraform",
Vars: map[string]interface{}{
"exadata_infra_id": "ocid1.exainfra.oc1..example",
},
})
// Clean up infrastructure at the end of the test
defer terraform.Destroy(t, terraformOptions)
// Run 'terraform init' and 'terraform apply'
terraform.InitAndApply(t, terraformOptions)
// Test Case 1: Verify Cluster Display Name
clusterName := terraform.Output(t, terraformOptions, "cluster_display_name")
assert.Equal(t, "Exadata-Prod-Cluster", clusterName)
// Test Case 2: Verify Database Version is compliant
dbVersion := terraform.Output(t, terraformOptions, "database_version")
assert.Contains(t, dbVersion, "19.0.0.0")
}
Major Challenges in Setup
- Long Provisioning Windows: Exadata VM clusters and DB Homes can take 45 to 90 minutes to provision. CI/CD pipelines often timeout. You must adjust runner timeout settings to at least 2 hours.
- State Out-of-Sync (Out-of-Band Changes): DBAs frequently perform immediate triage tasks directly via Oracle Enterprise Manager (OEM) or DBCLI/SRVCTL command lines. This bypasses Git and causes state drift.
- Destructive Schema Mutations: Modifying parameters like storage distributions or specific cluster configurations can trigger a forced destruction and recreation (
ForceNew) of the VM Cluster, causing catastrophic data loss. - IP Address Depletion: Exadata requires massive blocks of IPs for the client network, backup network, and private interconnect. IP shortage inside a VCN will cause sudden allocation failures halfway through provisioning.
1. How to Handle Pipeline Timeouts
If your pipeline crashes due to a time limit while waiting for OCI to build the database, use target overrides to track the operation manually:
# Verify which resource is hanging
terraform state list
# If the state is locked due to a timeout crash, force-unlock it
terraform force-unlock <LOCK_ID>
2. Resolving Drifts caused by Manual DBA Actions
If a DBA scales CPU via OCI Console, synchronize the state safely using refresh or target updates:
# Pull real-world changes without modifying infrastructure
terraform refresh
# View exactly what changed
terraform plan
Action: Update your local variables.tf or terraform.tfvars file to match the newly adjusted values before running your next pipeline apply.
3. Investigating Exadata Internal Creation Failures
If Terraform returns a generic 500 Internal Error or ServiceError during provisioning, the root issue is typically OS/Grid Infrastructure initialization failure inside the rack.
4. Safe Resource Targeting
To prevent accidental cluster teardowns when testing a small database configuration fix, isolate the resource execution:
terraform apply -target=oci_database_db_home.db_home
Question : Managing Oracle Exadata infrastructure using Terraform
Managing Oracle Exadata infrastructure using Terraform involves core components like oci_database_cloud_exadata_infrastructure [5.5], CI/CD pipelines (GitHub Actions/GitLab CI) [5.6], and structured Excel (CD3) templates [5.2]. Specific automated tasks include Exadata infrastructure provisioning, whereas day-to-day database patching remains pending automation.
KT Excel Template Structure (CD3 / Input Mapping)
When mapping Exadata deployment data via Excel sheets for Terraform conversion, use these standard structural components [5.2]:
- Metadata Sheet: Environment name, region, compartment OCID, and tagging strategies.
- Exadata Infrastructure Sheet: Shape selection [5.5], compute count [5.7], storage server count [5.5, 5.7], and maintenance window preferences [5.10].
- VM Cluster Sheet: Grid naming, shape, OCPU count, storage allocation, SSH public keys, and license model.
- Database Home/Cdb Sheet: Oracle database version, PDB names, character sets, and backup retention parameters.
Exadata Automation Status (Automated vs. Pending)
| Category | Automated Tasks (Terraform + CI/CD) | Pending / Manual Tasks |
|---|
| Infrastructure | Cloud Exadata Infrastructure creation | Physical rack configuration (on-premises) |
| Compute & VM | VM Cluster provisioning & scaling | Custom kernel parameter tuning at OS level |
| Database | DB Home & container/pluggable database creation | Complex Data Guard cross-region switchovers |
| Lifecycle | Basic CI/CD plan, validate, and apply | Rolling Grid/GI and Database Patching (GI/RDBMS RU) |