How-To: Configure custom Terraform Providers

Learn how to setup your Radius Environment with custom Terraform Providers and deploy Recipes.

This how-to guide will describe how to:

  • Configure a custom Terraform provider in a Radius Environment.
  • Configure credentials to authenticate into the Terraform provider.
  • Consume the Terraform modules from a custom Terraform provider and use it in a Terraform recipe.

In this example you’re going to configure a PostgreSQL Terraform provider in a Radius Environment and deploy a Recipe.

Prerequisites

Before you get started, you’ll need to make sure you have the following tools and resources:

Step 1: Define a secretStore resource for the custom provider

Configure a Radius Secret Store with any sensitive information needed as input configuration for the custom Terraform Provider. Define the namespace for the cluster that will contain your Kubernetes Secret with the resource property.

While this example shows a Radius-managed secret store where Radius creates the underlying secrets infrastructure, you can also bring your own existing secrets. Refer to the secrets documentation for more information.

Create a Bicep file env.bicep with the secretStore resource:

extension radius

@description('username for PostgreSQL db')
@secure()
param username string

@description('password for PostgreSQL db')
@secure()
param password string

resource pgsSecretStore 'Applications.Core/secretStores@2023-10-01-preview' = {
  name: 'my-secret-store'
  properties: {
    resource: 'my-secret-namespace/my-secret-store'
    type: 'generic'
    data: {
      username: {
        value: username
      }
      password: {
        value: password
      }
      host: {
        value: 'my-postgres-host'
      }
    }
  }
}

In this example, you’re creating a secret with keys username and password as sensitive data required to authenticate into the Terraform Provider cyrilgdn/postgresql.

Step 2: Configure Terraform Provider

recipeConfig/terraform/providers allows you to setup configurations for one or multiple Terraform Providers. For more information refer to the Radius Environment schema page.

In your env.bicep file add an Environment resource, along with Recipe configuration which leverages properties from the previously defined secret store. In this example you’re also passing in host and port as environment variables to highlight use cases where, depending on provider configuration requirements, users can pass environment variables as plain text and as secret values in envSecrets block to the Terraform recipes runtime.

resource env 'Applications.Core/environments@2023-10-01-preview' = {
  name: 'my-env'
  properties: {
    compute: {
      kind: 'kubernetes'
      resourceId: 'self'
      namespace: 'my-namespace'
    }
    recipeConfig: {
      terraform: {
        providers: {
          postgresql: [ {
              sslmode: 'disable'
              secrets: {
                username: {
                  source: pgsSecretStore.id
                  key: username
                }
                password: {
                  source: pgsSecretStore.id
                  key: password
                }
              }
            } ]
        }
      }
      env: {
        PGPORT: '5432'
      }
      envSecrets: {
        PGHOST: {
          source: pgsSecretStore.id
          key: 'host'
        }
      }
    }
  }
}

Step 3: Define a Terraform Recipe

Create a Terraform recipe which deploys a PostgreSQL database instance using custom Terraform provider cyrilgdn/postgresql.

terraform {
  required_providers {
    kubernetes = {
      source  = "hashicorp/kubernetes"
      version = ">= 2.0"
    }
    postgresql = {
      source  = "cyrilgdn/postgresql"
      version = "1.16.0"
    }
  }
}

variable "context" {
  description = "This variable contains Radius recipe context."
  type        = any
}

variable "password" {
  description = "The password for the PostgreSQL database"
  type        = string
}

resource "kubernetes_deployment" "postgres" {
  metadata {
    name      = "postgres"
    namespace = var.context.runtime.kubernetes.namespace
  }

  spec {
    selector {
      match_labels = {
        app = "postgres"
      }
    }

    template {
      metadata {
        labels = {
          app = "postgres"
        }
      }

      spec {
        container {
          image = "postgres:latest"
          name  = "postgres"

          env {
            name  = "POSTGRES_PASSWORD"
            value = var.password
          }

          port {
            container_port = 5432
          }
        }
      }
    }
  }
}

resource "kubernetes_service" "postgres" {
  metadata {
    name      = "postgres"
    namespace = var.context.runtime.kubernetes.namespace
  }

  spec {
    selector = {
      app = "postgres"
    }

    port {
      port        = 5432
      target_port = 5432
    }
  }
}

resource "time_sleep" "wait_20_seconds" {
  depends_on      = [kubernetes_service.postgres]
  create_duration = "20s"
}

resource "postgresql_database" "pg_db_test" {
  depends_on = [time_sleep.wait_20_seconds]
  name       = "pg_db_test"
}

Step 4: Add a Terraform Recipe to the Environment

Update your Environment with the Terraform Recipe.

resource env 'Applications.Core/environments@2023-10-01-preview' = {
  name: 'my-env'
  properties: {
    compute: {
      kind: 'kubernetes'
      resourceId: 'self'
      namespace: 'my-namespace'
    }
    recipeConfig: {
      terraform: {
        providers: {
          postgresql: [ {
              sslmode: 'disable'
              secrets: {
                username: {
                  source: pgsSecretStore.id
                  key: username
                }
                password: {
                  source: pgsSecretStore.id
                  key: password
                }
              }
            } ]
        }
      }
      env: {
        PGPORT: '5432'
      }
      envSecrets: {
        PGHOST: {
          source: pgsSecretStore.id
          key: 'host'
        }
      }
    }
    recipes: {
      'Applications.Core/extenders': {
        defaultpostgres: {
          templateKind: 'terraform'
          // Recipe template path
          templatePath: 'git::https://github.com/my-org/my-repo'
        }
      }
    }
  }
}

Step 5: Deploy your Radius Environment

Deploy your new Radius Environment passing in values for username and password needed to authenticate into the provider. The superuser for this PostgreSQL Recipe is postgres which is the expected input for username:

rad deploy ./env.bicep -p username=****** -p password=******

Done

Your Radius Environment is now configured with a custom Terraform Provider which you can use to deploy Radius Terraform Recipes. For more information on Radius Recipes visit the Recipes overview page.

Cleanup

You can delete the Radius Environment by running the following command:

rad env delete my-env

Further reading