Skip to content

yazsul/apim-deployment-template

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Azure API Management (APIM) Module Repository

This repository contains reusable Terraform modules for deploying APIs to existing Azure API Management services. These modules are designed to be consumed by developers in their own repositories through Azure DevOps pipelines.

πŸ—οΈ Architecture

Developer Repository:
β”œβ”€β”€ azure-pipelines.yml          # Multi-environment pipeline
β”œβ”€β”€ terraform/
β”‚   β”œβ”€β”€ main.tf                  # References our modules
β”‚   β”œβ”€β”€ variables.tf             # Variable definitions
β”‚   β”œβ”€β”€ terraform.tfvars         # Common configuration
β”‚   β”œβ”€β”€ dev.tfvars               # DEV subscription config
β”‚   └── test.tfvars              # TEST subscription config
└── README.md

Centralized Module Repository (this repo):
β”œβ”€β”€ modules/
β”‚   β”œβ”€β”€ apim_api/               # API deployment module
β”‚   └── apim_subscription/      # Subscription module
└── examples/
    └── petstore/               # Reference implementation

πŸš€ Features

  • Centralized Module Repository: Single source of truth for APIM deployments
  • Developer Self-Service: Developers reference modules in their own repositories
  • Multi-Environment Pipeline: Single pipeline with multiple stages for different environments
  • Hybrid Configuration: Common config in one file, user-specific subscriptions in separate files
  • Variable Interpolation: Azure DevOps variables properly passed to Terraform
  • API Deployment: Deploy APIs to existing APIM services
  • API Versioning: Support for multiple API versions with version sets
  • Policy Templates: Pre-built policy templates for different scenarios
  • Subscription Management: Automatic subscription key generation
  • Backend Integration: Support for external API backends
  • Product Association: Automatic product creation and API linking

πŸ“‹ Prerequisites

  • Azure CLI installed and authenticated
  • Terraform >= 1.0
  • Existing Azure API Management service
  • Azure subscription with appropriate permissions
  • Azure DevOps pipeline access

πŸ› οΈ Modules

API Module (apim_api)

Creates APIs within existing APIM services with:

  • Version sets for API versioning
  • Backend configuration
  • Product association
  • Policy application
  • OpenAPI/Swagger import

Subscription Module (apim_subscription)

Creates subscription keys for products with:

  • Automatic key generation
  • User association
  • Active state management

πŸ“ Usage for Developers

Step 1: Create Terraform Configuration

Developers create a terraform/main.tf file in their repository:

terraform {
  required_version = ">= 1.0"
  
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

# Reference the centralized API module
module "my_api" {
  source = "git::https://dev.azure.com/your-org/your-project/_git/apim-modules//modules/apim_api?ref=v1.0.0"

  api_name         = var.api_name
  api_display_name = var.api_display_name
  api_path         = var.api_path
  api_spec_url     = var.api_spec_url

  api_version           = var.api_version
  api_version_set_name  = var.api_version_set_name
  api_versioning_scheme = var.api_versioning_scheme

  backend_name = var.backend_name
  backend_url  = var.backend_url

  product_name    = var.product_name
  product_display = var.product_display

  policy_template = var.policy_template

  resource_group_name = var.resource_group_name
  service_name        = var.service_name
}

# Reference the centralized subscription module
module "my_subscription" {
  source = "git::https://dev.azure.com/your-org/your-project/_git/apim-modules//modules/apim_subscription?ref=v1.0.0"
  
  resource_group_name = var.resource_group_name
  service_name        = var.service_name
  subscription_name   = var.subscription_name
  product_id          = module.my_api.product_id
}

Step 2: Create Variables File

Create terraform/variables.tf:

# API Configuration
variable "api_name" {
  description = "Terraform resource name for the API"
  type        = string
}

variable "api_display_name" {
  description = "Display name shown in Azure Portal"
  type        = string
}

variable "api_path" {
  description = "Path under the APIM gateway"
  type        = string
}

variable "api_spec_url" {
  description = "URL to the OpenAPI/Swagger spec file"
  type        = string
}

# Versioning Configuration
variable "api_version" {
  description = "Version label for the API"
  type        = string
}

variable "api_version_set_name" {
  description = "Logical group name linking multiple API versions"
  type        = string
}

variable "api_versioning_scheme" {
  description = "How versioning is exposed to clients"
  type        = string
}

# Backend Configuration
variable "backend_name" {
  description = "Name of the backend service in APIM"
  type        = string
}

variable "backend_url" {
  description = "URL of the actual backend API/service"
  type        = string
}

# Product Configuration
variable "product_name" {
  description = "Internal product ID"
  type        = string
}

variable "product_display" {
  description = "Display name of the product"
  type        = string
}

# Policy Configuration
variable "policy_template" {
  description = "Name of the policy template to apply"
  type        = string
}

# APIM Service Configuration
variable "resource_group_name" {
  description = "Name of the existing resource group containing the APIM service"
  type        = string
}

variable "service_name" {
  description = "Name of the existing APIM service"
  type        = string
}

# Subscription Configuration
variable "subscription_name" {
  description = "Name of the subscription"
  type        = string
}

Step 3: Create Common Configuration

Create terraform/terraform.tfvars with common configuration for all environments:

# Common Configuration (identical across all environments)
api_name         = "my-api-v1"
api_display_name = "My API"
api_path         = "myapi"
api_spec_url     = "https://myapi.com/openapi.json"

# Versioning
api_version           = "v1"
api_version_set_name  = "myapi"
api_versioning_scheme = "Segment"

# Backend
backend_name = "my-backend"
backend_url  = "https://myapi.com"

# Product
product_name    = "my-product"
product_display = "My Product"

# Policy
policy_template = "external-inbound-base"

# Note: subscription_name is environment-specific and defined in:
# - dev.tfvars
# - test.tfvars

Step 4: Create Environment-Specific Subscription Configuration

Create environment-specific .tfvars files for subscription names:

terraform/dev.tfvars:

# DEV Environment - Subscription Configuration
subscription_name = "my-subscription-dev"

terraform/test.tfvars:

# TEST Environment - Subscription Configuration
subscription_name = "my-subscription-test"

Step 5: Create Multi-Environment Pipeline

Create azure-pipelines.yml:

trigger:
  - main
  - develop

pool:
  vmImage: 'ubuntu-latest'

stages:
- stage: DeployDev
  displayName: 'Deploy to DEV'
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
  variables:
    - group: apim-variables-dev
  jobs:
  - deployment: DeployToDev
    displayName: 'Deploy to DEV'
    environment: 'dev'
    strategy:
      runOnce:
        deploy:
          steps:
          - task: TerraformInstaller@0
            displayName: 'Install Terraform'
            inputs:
              terraformVersion: 'latest'

          - task: TerraformTaskV4@4
            displayName: 'Terraform Init'
            inputs:
              provider: 'azurerm'
              command: 'init'
              workingDirectory: '$(System.DefaultWorkingDirectory)/terraform'
              backendServiceArm: 'Azure-Service-Connection'
              backendAzureRmResourceGroupName: '$(TF_STATE_RG)'
              backendAzureRmStorageAccountName: '$(TF_STATE_SA)'
              backendAzureRmContainerName: '$(TF_STATE_CONTAINER)'
              backendAzureRmKey: '$(TF_STATE_KEY)'

          - task: TerraformTaskV4@4
            displayName: 'Terraform Plan'
            inputs:
              provider: 'azurerm'
              command: 'plan'
              workingDirectory: '$(System.DefaultWorkingDirectory)/terraform'
              environmentServiceNameAzureRM: 'Azure-Service-Connection'
              commandOptions: '-var-file=terraform.tfvars -var-file=dev.tfvars -var="resource_group_name=$(RESOURCE_GROUP_NAME)" -var="service_name=$(SERVICE_NAME)" -out=$(Build.ArtifactStagingDirectory)/tfplan'

          - task: TerraformTaskV4@4
            displayName: 'Terraform Apply'
            inputs:
              provider: 'azurerm'
              command: 'apply'
              workingDirectory: '$(System.DefaultWorkingDirectory)/terraform'
              environmentServiceNameAzureRM: 'Azure-Service-Connection'
              commandOptions: '$(Build.ArtifactStagingDirectory)/tfplan'

- stage: DeployTest
  displayName: 'Deploy to TEST'
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
  variables:
    - group: apim-variables-test
  jobs:
  - deployment: DeployToTest
    displayName: 'Deploy to TEST'
    environment: 'test'
    strategy:
      runOnce:
        deploy:
          steps:
          # Similar steps but with test variable group and test.tfvars
          - task: TerraformTaskV4@4
            displayName: 'Terraform Plan'
            inputs:
              provider: 'azurerm'
              command: 'plan'
              workingDirectory: '$(System.DefaultWorkingDirectory)/terraform'
              environmentServiceNameAzureRM: 'Azure-Service-Connection'
              commandOptions: '-var-file=terraform.tfvars -var-file=test.tfvars -var="resource_group_name=$(RESOURCE_GROUP_NAME)" -var="service_name=$(SERVICE_NAME)" -out=$(Build.ArtifactStagingDirectory)/tfplan'

πŸ”§ Configuration Sources

Azure DevOps Variable Groups (Environment-Specific Infrastructure)

Each environment has its own variable group with:

  • RESOURCE_GROUP_NAME - Environment-specific resource group
  • SERVICE_NAME - Environment-specific APIM service name
  • TF_STATE_RG - Terraform state resource group
  • TF_STATE_SA - Terraform state storage account
  • TF_STATE_CONTAINER - Terraform state container
  • TF_STATE_KEY - Terraform state key

Terraform terraform.tfvars (Common Application Config)

Single .tfvars file with common configuration for all environments:

  • API Configuration (name, display name, path, spec URL)
  • Versioning Configuration
  • Backend Configuration
  • Product Configuration
  • Policy Configuration

Environment-Specific .tfvars Files (User-Specific Subscriptions)

Each environment has its own .tfvars file with:

  • subscription_name - User-specific subscription name for that environment

πŸ”§ Variable Interpolation

How Azure DevOps Variables Flow to Terraform

The key mechanism is variable interpolation from Azure DevOps to Terraform:

  1. Azure DevOps Variable Groups β†’ Pipeline Variables β†’ Terraform Variables
# Pipeline stage with variable group
variables:
  - group: apim-variables-dev  # Contains RESOURCE_GROUP_NAME, SERVICE_NAME

# Terraform command with variable interpolation
commandOptions: '-var-file=terraform.tfvars -var-file=dev.tfvars -var="resource_group_name=$(RESOURCE_GROUP_NAME)" -var="service_name=$(SERVICE_NAME)"'
  1. Variable Resolution Order (highest priority first):
    • -var="resource_group_name=$(RESOURCE_GROUP_NAME)" (from Azure DevOps)
    • dev.tfvars (subscription names)
    • terraform.tfvars (common config)
    • Default values in variables.tf

Example Variable Resolution

For DEV environment:

# Pipeline command
terraform plan \
  -var-file=terraform.tfvars \
  -var-file=dev.tfvars \
  -var="resource_group_name=rg-apim-dev" \
  -var="service_name=apim-dev"

Variable Resolution:

  • resource_group_name = "rg-apim-dev" (from Azure DevOps)
  • service_name = "apim-dev" (from Azure DevOps)
  • subscription_name = "my-subscription-dev" (from dev.tfvars)
  • api_name = "my-api-v1" (from terraform.tfvars)
  • product_name = "my-product" (from terraform.tfvars)

πŸ”§ Azure DevOps Setup

Prerequisites

  • Azure DevOps project with appropriate permissions
  • Azure Service Connection configured
  • Access to create variable groups and environments

1. Create Variable Groups

Create two separate variable groups for each environment. Only resource group and service names are environment-specific and stored in variable groups:

apim-variables-dev (DEV Environment)

RESOURCE_GROUP_NAME = "rg-apim-dev"
SERVICE_NAME = "apim-dev"
TF_STATE_RG = "rg-terraform-state-dev"
TF_STATE_SA = "tfstatedev"
TF_STATE_CONTAINER = "tfstate"
TF_STATE_KEY = "dev.tfstate"

apim-variables-test (TEST Environment)

RESOURCE_GROUP_NAME = "rg-apim-test"
SERVICE_NAME = "apim-test"
TF_STATE_RG = "rg-terraform-state-test"
TF_STATE_SA = "tfstatetest"
TF_STATE_CONTAINER = "tfstate"
TF_STATE_KEY = "test.tfstate"

2. Create Environments

Create two environments in Azure DevOps:

  1. dev - For DEV deployments
  2. test - For TEST deployments

3. Create Service Connection

Create an Azure Resource Manager service connection named Azure-Service-Connection with appropriate permissions for all environments.

4. Pipeline Behavior

Branch Triggers

  • develop branch: Triggers DEV deployment only
  • main branch: Triggers TEST deployment

Stage Structure

The pipeline has two stages, each representing an environment:

  1. DeployDev Stage: Uses apim-variables-dev variable group
  2. DeployTest Stage: Uses apim-variables-test variable group

5. Example Values

DEV Environment

  • From Variable Group: rg-apim-dev, apim-dev
  • From terraform.tfvars: Common API, product configuration
  • From dev.tfvars: subscription_name = "my-subscription-dev"
  • API URL: https://apim-dev.azure-api.net/myapi

TEST Environment

  • From Variable Group: rg-apim-test, apim-test
  • From terraform.tfvars: Common API, product configuration
  • From test.tfvars: subscription_name = "my-subscription-test"
  • API URL: https://apim-test.azure-api.net/myapi

6. Configuration Separation

Azure DevOps Variable Groups (Environment-Specific Infrastructure)

Variable DEV TEST
RESOURCE_GROUP_NAME rg-apim-dev rg-apim-test
SERVICE_NAME apim-dev apim-test
TF_STATE_RG rg-terraform-state-dev rg-terraform-state-test
TF_STATE_SA tfstatedev tfstatetest
TF_STATE_CONTAINER tfstate tfstate
TF_STATE_KEY dev.tfstate test.tfstate

Terraform terraform.tfvars (Common Application Config)

  • Single file: Contains all API, product configuration
  • Version controlled: Part of the code repository
  • Environment agnostic: Same configuration deployed to all environments

Environment-Specific .tfvars Files (User-Specific Subscriptions)

File Environment Subscription Name
dev.tfvars DEV my-subscription-dev
test.tfvars TEST my-subscription-test

7. Security Considerations

  1. Environment Isolation: Each environment has separate resource groups and APIM services
  2. State Isolation: Separate Terraform state files prevent cross-environment conflicts
  3. Variable Group Isolation: Each environment has its own variable group
  4. Approval Gates: Configure approval gates on environments as needed
  5. Variable Security: Mark sensitive variables as secret in variable groups

8. Usage

  1. Development: Push to develop branch to deploy to DEV
  2. Testing: Push to main branch to deploy to TEST
  3. Monitoring: Check pipeline logs for deployment status and API URLs

9. Testing

Test the pipeline by:

  1. Creating a feature branch from develop
  2. Making changes to the terraform.tfvars file
  3. Pushing to develop to trigger DEV deployment
  4. Merging to main to trigger TEST deployment

πŸ”§ Policy Templates

Available Templates

  • external-inbound-base: Basic external API policy
  • internal-inbound-base: Internal API with rate limiting and quotas
  • external-outbound-base: External API with request forwarding

Policy Features

  • Rate limiting
  • Quota management
  • API key validation
  • Error handling
  • Response headers
  • Request tracking

πŸ“Š Module Outputs

Each module provides useful outputs for pipeline consumption:

  • API Module: API ID, Product ID, Version Set ID
  • Subscription Module: Subscription ID, Primary/Secondary Keys

πŸ”’ Security Considerations

  • Subscription keys are marked as sensitive
  • Policies include proper error handling
  • Backend SSL validation enabled
  • Secure default configurations
  • Module versioning for stability
  • Environment isolation with separate resource groups

🧹 Cleanup

To destroy all resources:

terraform destroy

πŸ“š Examples

Petstore API Example

The examples/petstore/ directory contains a complete reference implementation showing:

  • Multi-environment pipeline configuration
  • Common terraform.tfvars file with shared configuration
  • Environment-specific .tfvars files for subscription names
  • Azure DevOps setup documentation
  • Petstore API with versioning
  • Product with subscription
  • Policy application

Note: This example assumes existing APIM services in different resource groups for each environment.

🀝 Contributing

  1. Follow the modular structure
  2. Add proper variable validation
  3. Include comprehensive outputs
  4. Document new policy templates
  5. Update examples as needed
  6. Version modules for pipeline stability

πŸ“„ License

This project is licensed under the MIT License.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages