Search code examples
azureazure-devopsterraformazure-pipelinesterraform-provider-azure

Azure DevOps Pipeline Throwing Error with Terraform While Deploying to Azure


I am trying to deploy a VM in an existing availability set and VNET/SNET in a particular existing Resource Group I am using open source Terraform and my state file is stored in a storage container

This is my pipeline file below

variables:
  - group: infra-variables
  
trigger:
  branches:
    include:
    - master
  paths:
    include:
    - Terraform-Test 
    exclude:
    - README.md
  
stages:
- stage: Validate
  displayName: Validate
  jobs:
  - job: validate
    pool:
      vmImage: ubuntu-latest
    steps:
    - checkout : self 
    # - task: AzureCLI@2
    #   displayName : 
    #   inputs:
    #     azureSubscription: 'PalTest'
    #     scriptType: 'bash'
    #     scriptLocation: 'inlineScript'
    #     inlineScript: |
    #       az account set --subscription $AZURE_SUBSCRIPTION_ID
    #       az login --service-principal -u $AZURE_CLIENT_ID -p $AZURE_CLIENT_SECRET --tenant $AZURE_TENANT_ID 
    #       STORAGE_ACCOUNT_KEY=$(az storage account keys list -g $(Terraform_Backend_RG) -n $(TF_STATE_BLOB_ACCOUNT_NAME) | jq ".[0].value" -r)
    #       echo "setting storage account key variable"
    #       echo "##vso[task.setvariable variable=ARM_ACCESS_KEY;issecret=true]$ARM_ACCESS_KEY"

    - task: ms-devlabs.custom-terraform-tasks.custom-terraform-installer-task.TerraformInstaller@0
      displayName: Install Terraform
      inputs:
        terraformVersion: 'latest'


  # Init
    - task: TerraformCLI@0
      displayName: Terraform Init
      inputs:
        backendType : 'azurerm'
        command: 'init'
        workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform-Test'
        backendServiceArm: 'Pallab-ADO-Setup'
        backendAzureRmResourceGroupName: $(Terraform_Backend_RG)
        backendAzureRmStorageAccountName: $(TF_STATE_BLOB_ACCOUNT_NAME)
        backendAzureRmContainerName: $(TF_STATE_BLOB_CONTAINER_NAME)
        backendAzureRmKey: 'infrastructure/terraform.ntfstate'
        

  # Validate
    - task: TerraformCLI@0
      displayName: Validate Config
      inputs:
        command: 'validate'
        workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform-Test'

- stage: Plan
  displayName: Plan
  jobs:
  - job: plan
    pool:
      vmImage: ubuntu-latest
    steps:
    - task: ms-devlabs.custom-terraform-tasks.custom-terraform-installer-task.TerraformInstaller@0
      displayName: Install Terraform
      inputs:
        terraformVersion: 'latest'

  # Init
    - task: TerraformCLI@0
      displayName: Terraform Init
      inputs:
        backendType : 'azurerm'
        command: 'init'
        workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform-Test'
        backendServiceArm: 'Pallab-ADO-Setup'
        backendAzureRmResourceGroupName: $(Terraform_Backend_RG)
        backendAzureRmStorageAccountName: $(TF_STATE_BLOB_ACCOUNT_NAME)
        backendAzureRmContainerName: $(TF_STATE_BLOB_CONTAINER_NAME)
        backendAzureRmKey: 'infrastructure/terraform.ntfstate'

  # Plan
    - task: TerraformCLI@0
      displayName: Plan Terraform Deployment
      inputs:
        backendType : 'azurerm'
        command: 'plan'
        commandOptions: '-input=false'
        environmentServiceName : 'Pallab-ADO-Setup'
        workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform-Test'
        # backendServiceArm: 'PalTest'
        # backendAzureRmResourceGroupName: $(Terraform_Backend_RG)
        # backendAzureRmStorageAccountName: $(TF_STATE_BLOB_ACCOUNT_NAME)
        # backendAzureRmContainerName: $(TF_STATE_BLOB_CONTAINER_NAME)
        # backendAzureRmKey: 'infrastructure/terraform.tfstate'

# Approve
- stage: Approve
  displayName: Approve
  jobs:
  - job: approve
    displayName: Wait for approval
    pool: server
    steps: 
    - task: ManualValidation@0
      timeoutInMinutes: 60
      inputs:
        notifyUsers: '[email protected]'
        instructions: 'Review the plan in the next hour'

- stage: Apply
  displayName: Apply
  jobs:
  - job: apply
    pool:
      vmImage: ubuntu-latest
    steps:
      - task: ms-devlabs.custom-terraform-tasks.custom-terraform-installer-task.TerraformInstaller@0
        displayName: Install Terraform
        inputs:
          terraformVersion: 'latest'
      
      # Init
      - task: TerraformCLI@0
        displayName: TF Init 
        inputs:
          command: 'init'
          workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform-Test'
          commandOptions: '-backend-config=storage_account_name=$(TF_STATE_BLOB_ACCOUNT_NAME) -backend-config=container_name=$(TF_STATE_BLOB_CONTAINER_NAME) -backend-config=key=$(ARM_ACCESS_KEY)'
          backendType: 'selfConfigured'


      # Apply
      - task: TerraformCLI@0
        displayName: TF Apply 
        env:
          ARM_SAS_TOKEN: $(ARM_ACCESS_KEY)
          ARM_CLIENT_ID: $(AZURE_CLIENT_ID)
          ARM_CLIENT_SECRET: $(AZURE_CLIENT_SECRET)
          ARM_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID)
          ARM_TENANT_ID: $(AZURE_TENANT_ID)
        inputs:
          command: 'apply'
          workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform-Test'
          commandOptions: '-auto-approve'

This is my Terraform Code :

terraform {
  required_version = "~> 1.0"

  backend "azurerm" {
  key = "terraform.ntfstate"
}
  
  required_providers {
    azuread = "~> 2.0"  
    azurerm = "~> 2.0"
  }
}


provider "azurerm" {
  tenant_id       = var.tenant_id
  client_id       = var.client_id
  client_secret   = var.client_secret
  subscription_id = var.subscription_id
  features {}
}

data "azurerm_resource_group" "az-rg-wu" {
  name = "Great-Learning"
}

I am getting this error in the plan stage

Planning failed. Terraform encountered an error while generating this plan.

     building account: getting authenticated object ID: listing Service Principals: autorest.DetailedError{Original:adal.tokenRefreshError{message:"adal: Failed to unmarshal the service principal token during refresh. Error = 'invalid character '<' looking for beginning of value' JSON = '\r\n\r\n<!-- Copyright (C) Microsoft Corporation. All rights reserv


rror]Terraform command 'plan' failed with exit code '1'.
##[error]╷
│ Error: building account: getting authenticated object ID: listing Service Principals: autorest.DetailedError{Original:adal.tokenRefreshError{message:"adal: Failed to unmarshal the service principal token during refresh. Error = 'invalid character '<' looking for beginning of value' JSON = '\r\n\r\n<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->\r\n<!DOCTYPE html>\r\n<html dir=\"ltr\" class=\"\" lang=\"en\">\r\n<head>\r\n    <title>Sign in to your account</title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\r\n   

If i comment out the below lines, i don't get the above error and my plan goes successfully

provider "azurerm" {
  # tenant_id       = var.tenant_id
  # client_id       = var.client_id
  # client_secret   = var.client_secret
  # subscription_id = var.subscription_id
  features {}
}

But then at the Apply Stage i encounter the below error at the 'tf init' stage :

Initializing the backend...
╷
│ Error: Error building ARM Config: obtain subscription() from Azure CLI: parsing json result from the Azure CLI: waiting for the Azure CLI: exit status 1: ERROR: Please run 'az login' to setup account.
│ 
│ 
╵
##[error]Terraform command 'init' failed with exit code '1'.
##[error]╷
│ Error: Error building ARM Config: obtain subscription() from Azure CLI: parsing json result from the Azure CLI: waiting for the Azure CLI: exit status 1: ERROR: Please run 'az login' to setup account.

Solution

  • Based on your description, you can bypass the first error by commenting the service principal block in the .tf file.

    Error: Error building ARM Config: obtain subscription() from Azure CLI: parsing json result from the Azure CLI: waiting for the Azure CLI: exit status 1: ERROR: Please run 'az login' to setup account.

    For the error in the Apply stage, the cause of the issue can be that you haven't pass the service connection credentials to the terraform init step.

    Code block:

      - task: TerraformCLI@0
        displayName: TF Init 
        inputs:
          command: 'init'
          workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform-Test'
          commandOptions: '-backend-config=storage_account_name=$(TF_STATE_BLOB_ACCOUNT_NAME) -backend-config=container_name=$(TF_STATE_BLOB_CONTAINER_NAME) -backend-config=key=$(ARM_ACCESS_KEY)'
          backendType: 'selfConfigured'
    

    To solve this issue, you need to change the selfConfigured type to azurerm type in Terraform init step. And you also need to define the service connection in terraform apply step to make sure that it can use the correct service principal.

    For example:

    stages:
    - stage: Apply
      displayName: Apply
      jobs:
      - job: apply
        pool:
          vmImage: ubuntu-latest
        steps:
          - task: ms-devlabs.custom-terraform-tasks.custom-terraform-installer-task.TerraformInstaller@0
            displayName: Install Terraform
            inputs:
              terraformVersion: 'latest'
          
          # Init
          - task: TerraformCLI@0
            displayName: TF Init 
            inputs:
              command: 'init'
              workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform-Test'
              backendType: 'azurerm'
              backendServiceArm: 'Pallab-ADO-Setup'
              backendAzureRmStorageAccountName: '$(TF_STATE_BLOB_ACCOUNT_NAME)'
              backendAzureRmContainerName: '$(TF_STATE_BLOB_CONTAINER_NAME)'
              backendAzureRmKey: '$(ARM_ACCESS_KEY)'
    
    
          # Apply
          - task: TerraformCLI@0
            displayName: TF Apply 
            env:
              ARM_SAS_TOKEN: $(ARM_ACCESS_KEY)
              ARM_CLIENT_ID: $(AZURE_CLIENT_ID)
              ARM_CLIENT_SECRET: $(AZURE_CLIENT_SECRET)
              ARM_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID)
              ARM_TENANT_ID: $(AZURE_TENANT_ID)
            inputs:
              command: 'apply'
              workingDirectory: '$(System.DefaultWorkingDirectory)/Terraform-Test'
              environmentServiceName: 'Pallab-ADO-Setup' #Add Correct Service Connection
              runAzLogin: true
              commandOptions: '-auto-approve'
      
    

    If you need to keep using the selfConfigured type in terraform init step, you can add additional tasks before terraform init step to execute the az login command with the correct service connection.

    For example:

    - task: AzureCLI@2
      displayName: 'Azure CLI '
      inputs:
        azureSubscription: Pallab-ADO-Setup
        scriptType: bash
        scriptLocation: inlineScript
        inlineScript: |
         echo "##vso[task.setvariable variable=ARM_CLIENT_ID]$servicePrincipalId" 
         
         echo "##vso[task.setvariable variable=ARM_CLIENT_SECRET]$servicePrincipalKey"
        
         echo "##vso[task.setvariable variable=ARM_TENANT_ID]$tenantId"
        addSpnToEnvironment: true
    
    - bash: |
       az login --service-principal --username $(ARM_CLIENT_ID) --password $(ARM_CLIENT_SECRET)  --tenant $(ARM_TENANT_ID)
    
      displayName: 'Az login to set account'
    
    - task: TerraformCLI@0
      displayName: TF Init