> ## Documentation Index
> Fetch the complete documentation index at: https://docs.streamnative.io/llms.txt
> Use this file to discover all available pages before exploring further.

# StreamNative Terraform Tutorial

This tutorial demonstrates how to use [StreamNative Terraform Provider](https://registry.terraform.io/providers/streamnative/streamnative/latest/docs) to deploy a Serverless cluster and use the [Pulsar Terraform Provider](https://registry.terraform.io/providers/streamnative/pulsar/latest/docs) to provision Pulsar resources in the Serverless cluster.

This tutorial provisions the following resources (assuming we name the application as `sl-app`):

1. Provisions a Serverless Instance (i.e., `sl-instance`).
2. Provisions a Serverless Cluster (i.e., `sl-clu`).
3. Provisions a Service Account named `sl-app-sa`.
4. Provisions an API Key for the Service Account `sl-app-apikey`.
5. Provisions a Pulsar Tenant named `sl-app-tenant`
6. Provisions a Pulsar Namespace `sl-app-ns` and grants `produce` and `consume` permissions to the Service Account on the namespace.
7. Provisions a Pulsar Topic with 4 partitions `sl-app-topic`.
8. After provisioning all the resources, we will verify the resources by using the [pulsarctl](/tools/cli/pulsarctl/pulsarctl-overview) command.

The code examples is available in the [examples/terraform/serverless](https://github.com/streamnative/terraform-provider/tree/main/examples/terraform/serverless) folder.

## 0. Prerequisites

Create a new directory anywhere you'd like for this project.

```bash theme={null}
mkdir terraform-getting-started && cd terraform-getting-started
```

## 1. Create a Super-Admin Service Account

First, you need to create a service account called `tf-runner` with **Super Admin** access. Please refer to [Create a Service Account](/cloud/security/authentication/service-accounts/manage-service-accounts#create-a-service-account) for details.

After you have created the service account, download the OAuth2 credentials file and save it as `tf-runner.json` in the `terraform-getting-started` folder that you created earlier.

## 2. Create Terraform Configuration Files to provision StreamNative Cloud resources

### 2.1 Create a Module Folder

Create a module folder `streamnative_cloud`.

```bash theme={null}
mkdir streamnative_cloud && cd streamnative_cloud
```

### 2.2 Create Variables File

Create a `variables.tf` file inside the `streamnative_cloud` folder and add the following code snippet to prepare the variables. Remember to replace `<your-organization-id>` with your StreamNative Cloud organization id.

* **org\_id**: Get your StreamNative Cloud organization id from [here](https://docs.streamnative.io/docs/organizations#cloud-organization-id) and replace `<your-organization-id>` with it.
* **instance\_name**: The name of the Pulsar instance. Default value is `sl-instance`.
* **cluster\_name**: The name of the Pulsar cluster. Default value is `sl-clu`.
* **app\_name**: The name of the application. Default value is `sl-app`.

```hcl theme={null}
variable "org_id" {
  type = string
}

variable "instance_name" {
  type = string
}

variable "cluster_name" {
  type = string
}

variable "app_name" {
  type = string
}

```

### 2.3 Create Terraform Configuration File

Create a `main.tf` file inside the `streamnative_cloud` folder and add the following code snippet to create the resources.

<Note title="Note">
  Use the StreamNative Terraform Provider version `0.7.0` or later.
</Note>

```hcl theme={null}

terraform {
  required_providers {
    streamnative = {
      source  = "streamnative/streamnative"
      version = ">= 0.7.0"
    }
  }
}

provider "streamnative" {
  key_file_path = "./tf-runner.json"
}

data "streamnative_service_account" "tf-runner" {
  organization = var.org_id
  name = "tf-runner"
}

resource "streamnative_pulsar_instance" "serverless-instance" {
  organization = var.org_id
  name = var.instance_name
  availability_mode = "regional"
  pool_name = "shared-gcp"
  pool_namespace = "streamnative"
  type = "serverless"
}

data "streamnative_pulsar_instance" "serverless-instance" {
  depends_on = [streamnative_pulsar_instance.serverless-instance]
  name = streamnative_pulsar_instance.serverless-instance.name
  organization = streamnative_pulsar_instance.serverless-instance.organization
}

resource "streamnative_pulsar_cluster" "serverless-cluster" {
  depends_on = [streamnative_pulsar_instance.serverless-instance]
  organization    = streamnative_pulsar_instance.serverless-instance.organization
  name            = var.cluster_name
  display_name    = "serverless-cluster"
  instance_name   = streamnative_pulsar_instance.serverless-instance.name
  location        = "us-central1"
}

data "streamnative_pulsar_cluster" "serverless-cluster" {
  depends_on   = [streamnative_pulsar_cluster.serverless-cluster]
  organization = streamnative_pulsar_cluster.serverless-cluster.organization
  name         = split("/", streamnative_pulsar_cluster.serverless-cluster.id)[1]
}

resource "streamnative_service_account" "app-sa" {
  organization = var.org_id
  name = "${var.app_name}-sa"
  admin = false
}

data "streamnative_service_account" "app-sa" {
  depends_on = [streamnative_service_account.app-sa]
  organization = streamnative_service_account.app-sa.organization
  name = streamnative_service_account.app-sa.name
}

resource "streamnative_apikey" "app-apikey" {
  depends_on = [streamnative_pulsar_cluster.serverless-cluster, streamnative_service_account.app-sa]
  organization = var.org_id
  name = "${var.app_name}-apikey2"
  instance_name = streamnative_pulsar_instance.serverless-instance.name
  service_account_name = streamnative_service_account.app-sa.name
  description = "This is a test api key for ${var.app_name} in running the terraform tutorial"
  # If you don't want to set expiration time, you can set expiration_time to "0"
  # expiration_time = "2025-01-01T10:00:00Z"
  expiration_time = "0"
}

data "streamnative_apikey" "app-apikey" {
  depends_on = [streamnative_apikey.app-apikey]
  organization = streamnative_apikey.app-apikey.organization
  name = streamnative_apikey.app-apikey.name
  private_key = streamnative_apikey.app-apikey.private_key
}

output "apikey_token" {
  value = data.streamnative_apikey.app-apikey.token
}

output "pulsar_web_service_url" {
  value = data.streamnative_pulsar_cluster.serverless-cluster.http_tls_service_url
}

output "pulsar_cluster_name" {
  value = data.streamnative_pulsar_cluster.serverless-cluster.name
}

output "pulsar_instance_audience" {
  value = data.streamnative_pulsar_instance.serverless-instance.oauth2_audience
}

output "app_service_account_principal" {
  value = "${data.streamnative_service_account.app-sa.name}@${data.streamnative_service_account.app-sa.organization}.auth.streamnative.cloud"
}

output "service_urls" {
  value = {
    pulsar_web_service_url = data.streamnative_pulsar_cluster.serverless-cluster.http_tls_service_url
    pulsar_broker_service_url = data.streamnative_pulsar_cluster.serverless-cluster.pulsar_tls_service_url
    kafka_bootstrap_url = data.streamnative_pulsar_cluster.serverless-cluster.kafka_service_url
  }
}
```

## 3. Create Terraform Configuration Files to provision Pulsar resources

Go back to the root folder `terraform-getting-started`.

```bash theme={null}
cd ..
```

### 3.1 Create Variables File

You can copy the `variables.tf` file from the `streamnative_cloud` module folder.

```bash" { theme={null}
cp streamnative_cloud/variables.tf .
```

### 3.2 Create Terraform Configuration File

Create a `main.tf` file in the root folder and add the following code snippet to create the resources.

```hcl theme={null}
module "streamnative_cloud" {
  source = "./streamnative_cloud"
  org_id = var.org_id
  instance_name = var.instance_name
  cluster_name = var.cluster_name
  app_name = var.app_name
}

terraform {
  required_providers {
    pulsar = {
      source = "streamnative/pulsar"
    }
  }
}

provider "pulsar" {
  web_service_url = module.streamnative_cloud.pulsar_web_service_url
  key_file_path = "./tf-runner.json"
  audience = module.streamnative_cloud.pulsar_instance_audience
}

resource "pulsar_tenant" "app_tenant" {
  depends_on = [module.streamnative_cloud.depends_on]
  tenant = "${var.app_name}-tenant"
  allowed_clusters = [
    module.streamnative_cloud.pulsar_cluster_name,
  ]
}

resource "pulsar_namespace" "app_namespace" {
  depends_on = [pulsar_tenant.app_tenant, module.streamnative_cloud.depends_on]
  tenant    = pulsar_tenant.app_tenant.tenant
  namespace = "${var.app_name}-ns"

  namespace_config {
    replication_clusters = [
      module.streamnative_cloud.pulsar_cluster_name
    ]

  }
  permission_grant {
    role = module.streamnative_cloud.app_service_account_principal
    actions = ["produce", "consume"]
  }
}

resource "pulsar_topic" "app_topic" {
  depends_on = [pulsar_namespace.app_namespace, pulsar_tenant.app_tenant]
  tenant     = pulsar_tenant.app_tenant.tenant
  namespace  = pulsar_namespace.app_namespace.namespace
  topic_type = "persistent"
  topic_name = "${var.app_name}-topic"
  partitions =  4
}

output "apikey" {
  value = module.streamnative_cloud.apikey_token
}

output "pulsar_oauth2_audience" {
  value = module.streamnative_cloud.pulsar_instance_audience
}

output "service_urls" {
  value = module.streamnative_cloud.service_urls
}

output "pulsar_cluster_name" {
  value = module.streamnative_cloud.pulsar_cluster_name
}

output "pulsarctl_command" {
  value = "pulsarctl context set -s ${module.streamnative_cloud.pulsar_web_service_url} --token ${module.streamnative_cloud.apikey_token} ${module.streamnative_cloud.pulsar_cluster_name} && pulsarctl topics get ${var.app_name}-tenant/${var.app_name}-ns/${var.app_name}-topic"
}
```

## 4. Run Terraform Commands

Before running the Terraform commands, you need to expose the following variables:

```bash theme={null}
export TF_VAR_org_id=<your-org-id>
export TF_VAR_instance_name=<your-instance-name>
export TF_VAR_cluster_name=<your-cluster-name>
export TF_VAR_app_name=<your-app-name>
```

Please replace the above placeholders with your actual values:

* `<your-org-id>`: Your StreamNative Cloud organization ID
* `<your-instance-name>`: A unique name for your Pulsar instance (e.g., "sl-instance")
* `<your-cluster-name>`: A unique name for your Pulsar cluster (e.g., "sl-clu")
* `<your-app-name>`: A unique name for your application (e.g., "sl-app")

An example of exposing the variables is as follows:

```bash theme={null}
export TF_VAR_org_id=<your-org-id>
export TF_VAR_instance_name=sl-instance
export TF_VAR_cluster_name=sl-clu
export TF_VAR_app_name=sl-app
```

After exposing the variables, you can run the following Terraform commands to provision the resources.

First, initialize the Terraform working directory.

```bash theme={null}
terraform init
```

Secondly, validate the Terraform configuration files.

```bash theme={null}
terraform validate
```

Since we use two providers in this example (the **StreamNative Provider** and the **Pulsar Provider**), we need to provision the resources in two steps. The Pulsar Provider resources depend on the StreamNative Provider resources being created first.

### 4.1 Provision the Cloud Resources

Run a targeted plan to see the changes and preview the resources that will be created.

```bash theme={null}
terraform plan --target=module.streamnative_cloud.streamnative_apikey.app-apikey
```

After that, run a targeted apply to create the resources.

```bash theme={null}
terraform apply --target=module.streamnative_cloud.streamnative_apikey.app-apikey
```

### 4.2 Provision all the Resources

Run `terraform plan` to see the changes and preview the resources that will be created.

```bash theme={null}
terraform plan
```

After that, run `terraform apply` to create the resources.

```bash theme={null}
terraform apply
```

You should see a similar output as follows:

```bash theme={null}
apikey = "<...>"
pulsarctl_command = "pulsarctl context set -s <pulsar-web-service-url> --token <apikey> sl-clu && pulsarctl topics get sl-app-tenant/sl-app-ns/sl-app-topic"
service_urls = {
  "kafka_bootstrap_url" = "..."
  "pulsar_broker_service_url" = "..."
  "pulsar_web_service_url" = "..."
}
```

## 5. Verify the Resources

Use the [pulsarctl](/tools/cli/pulsarctl/pulsarctl-overview) command to verify all the resources created. Make sure you have installed pulsarctl before running the command.

Copy the `pulsarctl_command` output and run it in your terminal.

```bash theme={null}
pulsarctl context set -s <pulsar-web-service-url> --token <apikey> sl-clu && pulsarctl topics get sl-app-tenant/sl-app-ns/sl-app-topic
```

This command will set the context and get the topic details. It will verify the following resources are created:

* A Pulsar cluster named `sl-clu`
* A Pulsar tenant named `sl-app-tenant`
* A Pulsar namespace named `sl-app-ns`
* A Pulsar topic named `sl-app-topic`
* `produce` and `consume` permissions are granted to `sl-app-sa` on the namespace `sl-app-tenant/sl-app-ns`

You should see the output as follows:

```bash theme={null}
{
  "partitions": 4
}
```
