diff --git a/docs/resources/account_audit_log_sink.md b/docs/resources/account_audit_log_sink.md
new file mode 100644
index 0000000..20f64c3
--- /dev/null
+++ b/docs/resources/account_audit_log_sink.md
@@ -0,0 +1,95 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "temporalcloud_account_audit_log_sink Resource - terraform-provider-temporalcloud"
+subcategory: ""
+description: |-
+ Provisions an account audit log sink.
+---
+
+# temporalcloud_account_audit_log_sink (Resource)
+
+Provisions an account audit log sink.
+
+## Example Usage
+
+```terraform
+terraform {
+ required_providers {
+ temporalcloud = {
+ source = "temporalio/temporalcloud"
+ }
+ }
+}
+
+provider "temporalcloud" {
+
+}
+
+# Example with Kinesis
+resource "temporalcloud_account_audit_log_sink" "kinesis_sink" {
+ sink_name = "my-kinesis-sink"
+ enabled = true
+ kinesis = {
+ role_name = "arn:aws:iam::123456789012:role/TemporalCloudKinesisRole"
+ destination_uri = "arn:aws:kinesis:us-east-1:123456789012:stream/my-audit-stream"
+ region = "us-east-1"
+ }
+}
+
+# Example with PubSub
+resource "temporalcloud_account_audit_log_sink" "pubsub_sink" {
+ sink_name = "my-pubsub-sink"
+ enabled = true
+ pubsub = {
+ service_account_id = "my-service-account-id"
+ topic_name = "temporal-audit-logs"
+ gcp_project_id = "my-gcp-project"
+ }
+}
+```
+
+
+## Schema
+
+### Required
+
+- `sink_name` (String) The unique name of the audit log sink, it can't be changed once set.
+
+### Optional
+
+- `enabled` (Boolean) A flag indicating whether the audit log sink is enabled or not.
+- `kinesis` (Attributes) The Kinesis configuration details when destination_type is Kinesis. (see [below for nested schema](#nestedatt--kinesis))
+- `pubsub` (Attributes) The PubSub configuration details when destination_type is PubSub. (see [below for nested schema](#nestedatt--pubsub))
+- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
+
+### Read-Only
+
+- `id` (String) The unique identifier of the account audit log sink.
+
+
+### Nested Schema for `kinesis`
+
+Required:
+
+- `destination_uri` (String) The destination URI of the Kinesis stream where Temporal will send data.
+- `region` (String) The region of the Kinesis stream.
+- `role_name` (String) The IAM role that Temporal Cloud assumes for writing records to the customer's Kinesis stream.
+
+
+
+### Nested Schema for `pubsub`
+
+Required:
+
+- `gcp_project_id` (String) The GCP project ID of the PubSub topic and service account.
+- `service_account_id` (String) The customer service account ID that Temporal Cloud impersonates for writing records to the customer's PubSub topic.
+- `topic_name` (String) The destination PubSub topic name for Temporal.
+
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).
+- `delete` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Setting a timeout for a Delete operation is only applicable if changes are saved into state before the destroy operation occurs.
diff --git a/examples/resources/temporalcloud_account_audit_log_sink/resource.tf b/examples/resources/temporalcloud_account_audit_log_sink/resource.tf
new file mode 100644
index 0000000..c531e64
--- /dev/null
+++ b/examples/resources/temporalcloud_account_audit_log_sink/resource.tf
@@ -0,0 +1,33 @@
+terraform {
+ required_providers {
+ temporalcloud = {
+ source = "temporalio/temporalcloud"
+ }
+ }
+}
+
+provider "temporalcloud" {
+
+}
+
+# Example with Kinesis
+resource "temporalcloud_account_audit_log_sink" "kinesis_sink" {
+ sink_name = "my-kinesis-sink"
+ enabled = true
+ kinesis = {
+ role_name = "arn:aws:iam::123456789012:role/TemporalCloudKinesisRole"
+ destination_uri = "arn:aws:kinesis:us-east-1:123456789012:stream/my-audit-stream"
+ region = "us-east-1"
+ }
+}
+
+# Example with PubSub
+resource "temporalcloud_account_audit_log_sink" "pubsub_sink" {
+ sink_name = "my-pubsub-sink"
+ enabled = true
+ pubsub = {
+ service_account_id = "my-service-account-id"
+ topic_name = "temporal-audit-logs"
+ gcp_project_id = "my-gcp-project"
+ }
+}
diff --git a/go.mod b/go.mod
index 689dc2c..1a897b6 100644
--- a/go.mod
+++ b/go.mod
@@ -95,3 +95,5 @@ require (
gopkg.in/yaml.v2 v2.3.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+
+replace go.temporal.io/cloud-sdk => github.com/temporalio/cloud-sdk-go v0.6.1-0.20251031194819-5117604c8a4f
diff --git a/go.sum b/go.sum
index 49a21ef..eca75c5 100644
--- a/go.sum
+++ b/go.sum
@@ -202,6 +202,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/temporalio/cloud-sdk-go v0.6.1-0.20251031194819-5117604c8a4f h1:i8w+OmC4ocK72/LdwKXnXawHC6CCMHrtzVNYI4+6tGk=
+github.com/temporalio/cloud-sdk-go v0.6.1-0.20251031194819-5117604c8a4f/go.mod h1:AueDDyuayosk+zalfrnuftRqnRQTHwD0HYwNgEQc0YE=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
@@ -238,8 +240,6 @@ go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mx
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.temporal.io/api v1.53.0 h1:6vAFpXaC584AIELa6pONV56MTpkm4Ha7gPWL2acNAjo=
go.temporal.io/api v1.53.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
-go.temporal.io/cloud-sdk v0.5.0 h1:6PdA6D8I/PiFLLpYwinre7ffPTct49zhapMAN5rJjmw=
-go.temporal.io/cloud-sdk v0.5.0/go.mod h1:AueDDyuayosk+zalfrnuftRqnRQTHwD0HYwNgEQc0YE=
go.temporal.io/sdk v1.36.0 h1:WO9zetpybBNK7xsQth4Z+3Zzw1zSaM9MOUGrnnUjZMo=
go.temporal.io/sdk v1.36.0/go.mod h1:8BxGRF0LcQlfQrLLGkgVajbsKUp/PY7280XTdcKc18Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
diff --git a/internal/provider/account_audit_log_sink_datasource.go b/internal/provider/account_audit_log_sink_datasource.go
new file mode 100644
index 0000000..a1dbd59
--- /dev/null
+++ b/internal/provider/account_audit_log_sink_datasource.go
@@ -0,0 +1,207 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/temporalio/terraform-provider-temporalcloud/internal/client"
+ "github.com/temporalio/terraform-provider-temporalcloud/internal/provider/enums"
+ internaltypes "github.com/temporalio/terraform-provider-temporalcloud/internal/types"
+ accountv1 "go.temporal.io/cloud-sdk/api/account/v1"
+ cloudservicev1 "go.temporal.io/cloud-sdk/api/cloudservice/v1"
+)
+
+var (
+ _ datasource.DataSource = &accountAuditLogSinkDataSource{}
+ _ datasource.DataSourceWithConfigure = &accountAuditLogSinkDataSource{}
+)
+
+func NewAccountAuditLogSinkDataSource() datasource.DataSource {
+ return &accountAuditLogSinkDataSource{}
+}
+
+type (
+ accountAuditLogSinkDataSource struct {
+ client *client.Client
+ }
+
+ accountAuditLogSinkDataModel struct {
+ ID types.String `tfsdk:"id"`
+ SinkName types.String `tfsdk:"sink_name"`
+ Enabled types.Bool `tfsdk:"enabled"`
+ Kinesis types.Object `tfsdk:"kinesis"`
+ PubSub types.Object `tfsdk:"pubsub"`
+ State types.String `tfsdk:"state"`
+ }
+)
+
+func accountAuditLogSinkDataSourceSchema(idRequired bool) map[string]schema.Attribute {
+ idAttribute := schema.StringAttribute{
+ Description: "The unique identifier of the account audit log sink.",
+ }
+
+ switch idRequired {
+ case true:
+ idAttribute.Required = true
+ case false:
+ idAttribute.Computed = true
+ }
+
+ return map[string]schema.Attribute{
+ "id": idAttribute,
+ "sink_name": schema.StringAttribute{
+ Description: "The unique name of the audit log sink.",
+ Required: true,
+ },
+ "enabled": schema.BoolAttribute{
+ Description: "A flag indicating whether the audit log sink is enabled or not.",
+ Computed: true,
+ },
+ "kinesis": schema.SingleNestedAttribute{
+ Description: "The Kinesis configuration details when destination_type is Kinesis.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "role_name": schema.StringAttribute{
+ Description: "The IAM role that Temporal Cloud assumes for writing records to the customer's Kinesis stream.",
+ Computed: true,
+ },
+ "destination_uri": schema.StringAttribute{
+ Description: "The destination URI of the Kinesis stream where Temporal will send data.",
+ Computed: true,
+ },
+ "region": schema.StringAttribute{
+ Description: "The region of the Kinesis stream.",
+ Computed: true,
+ },
+ },
+ },
+ "pubsub": schema.SingleNestedAttribute{
+ Description: "The PubSub configuration details when destination_type is PubSub.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "service_account_id": schema.StringAttribute{
+ Description: "The customer service account ID that Temporal Cloud impersonates for writing records to the customer's PubSub topic.",
+ Computed: true,
+ },
+ "topic_name": schema.StringAttribute{
+ Description: "The destination PubSub topic name for Temporal.",
+ Computed: true,
+ },
+ "gcp_project_id": schema.StringAttribute{
+ Description: "The GCP project ID of the PubSub topic and service account.",
+ Computed: true,
+ },
+ },
+ },
+ "state": schema.StringAttribute{
+ Description: "The current state of the audit log sink.",
+ Computed: true,
+ },
+ }
+}
+
+func (d *accountAuditLogSinkDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_account_audit_log_sink"
+}
+
+func (d *accountAuditLogSinkDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ client, ok := req.ProviderData.(*client.Client)
+ if !ok {
+ resp.Diagnostics.AddError("Unexpected Data Source Configure Type", fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData))
+ return
+ }
+
+ d.client = client
+}
+
+func (d *accountAuditLogSinkDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Fetches details about an account audit log sink.",
+ Attributes: accountAuditLogSinkDataSourceSchema(false),
+ }
+}
+
+func (d *accountAuditLogSinkDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var input accountAuditLogSinkDataModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &input)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ if len(input.SinkName.ValueString()) == 0 {
+ resp.Diagnostics.AddError("invalid account audit log sink sink_name", "account audit log sink sink_name is required")
+ return
+ }
+
+ auditLogSinkResp, err := d.client.CloudService().GetAccountAuditLogSink(ctx, &cloudservicev1.GetAccountAuditLogSinkRequest{
+ Name: input.SinkName.ValueString(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to get account audit log sink", err.Error())
+ return
+ }
+
+ model, diags := accountAuditLogSinkToAccountAuditLogSinkDataModel(ctx, auditLogSinkResp.GetSink())
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+}
+
+func accountAuditLogSinkToAccountAuditLogSinkDataModel(ctx context.Context, auditLogSink *accountv1.AuditLogSink) (*accountAuditLogSinkDataModel, diag.Diagnostics) {
+ var diags diag.Diagnostics
+ stateStr, err := enums.FromResourceState(auditLogSink.State)
+ if err != nil {
+ diags.AddError("Failed to convert resource state", err.Error())
+ return nil, diags
+ }
+
+ model := new(accountAuditLogSinkDataModel)
+ model.ID = types.StringValue(auditLogSink.GetName())
+ model.SinkName = types.StringValue(auditLogSink.GetName())
+ model.Enabled = types.BoolValue(auditLogSink.GetSpec().GetEnabled())
+ model.State = types.StringValue(stateStr)
+
+ kinesisObj := types.ObjectNull(internaltypes.KinesisSpecModelAttrTypes)
+ if auditLogSink.GetSpec().GetKinesisSink() != nil {
+ kinesisSpec := internaltypes.KinesisSpecModel{
+ RoleName: types.StringValue(auditLogSink.GetSpec().GetKinesisSink().GetRoleName()),
+ DestinationUri: types.StringValue(auditLogSink.GetSpec().GetKinesisSink().GetDestinationUri()),
+ Region: types.StringValue(auditLogSink.GetSpec().GetKinesisSink().GetRegion()),
+ }
+
+ kinesisObj, diags = types.ObjectValueFrom(ctx, internaltypes.KinesisSpecModelAttrTypes, kinesisSpec)
+ if diags.HasError() {
+ return nil, diags
+ }
+ }
+
+ pubsubObj := types.ObjectNull(internaltypes.PubSubSpecModelAttrTypes)
+ if auditLogSink.GetSpec().GetPubSubSink() != nil {
+ pubsubSpec := internaltypes.PubSubSpecModel{
+ ServiceAccountId: types.StringValue(auditLogSink.GetSpec().GetPubSubSink().GetServiceAccountId()),
+ TopicName: types.StringValue(auditLogSink.GetSpec().GetPubSubSink().GetTopicName()),
+ GcpProjectId: types.StringValue(auditLogSink.GetSpec().GetPubSubSink().GetGcpProjectId()),
+ }
+
+ pubsubObj, diags = types.ObjectValueFrom(ctx, internaltypes.PubSubSpecModelAttrTypes, pubsubSpec)
+ if diags.HasError() {
+ return nil, diags
+ }
+ }
+
+ model.Kinesis = kinesisObj
+ model.PubSub = pubsubObj
+ return model, diags
+}
diff --git a/internal/provider/account_audit_log_sink_datasource_test.go b/internal/provider/account_audit_log_sink_datasource_test.go
new file mode 100644
index 0000000..acf34c0
--- /dev/null
+++ b/internal/provider/account_audit_log_sink_datasource_test.go
@@ -0,0 +1,282 @@
+package provider
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+)
+
+func TestAccDataSource_AccountAuditLogSink_Kinesis(t *testing.T) {
+ accountAuditLogSinkTestLocks.Lock("account")
+ defer func() {
+ _ = accountAuditLogSinkTestLocks.Unlock("account")
+ }()
+
+ sinkRegion := "us-east-1"
+ sinkName := fmt.Sprintf("tf-test-sink-%s", randomString(8))
+
+ config := func(name, region string) string {
+ return fmt.Sprintf(`
+provider "temporalcloud" {
+
+}
+
+resource "temporalcloud_account_audit_log_sink" "test" {
+ sink_name = %[1]q
+ enabled = true
+ kinesis = {
+ role_name = "test-role"
+ destination_uri = "test-uri"
+ region = %[2]q
+ }
+}
+
+data "temporalcloud_account_audit_log_sink" "test" {
+ sink_name = temporalcloud_account_audit_log_sink.test.sink_name
+}
+
+output "account_audit_log_sink" {
+ value = data.temporalcloud_account_audit_log_sink.test
+}
+`, name, region)
+ }
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ testAccPreCheck(t)
+ },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: config(sinkName, sinkRegion),
+ Check: func(s *terraform.State) error {
+ output, ok := s.RootModule().Outputs["account_audit_log_sink"]
+ if !ok {
+ return fmt.Errorf("missing expected output")
+ }
+
+ outputValue, ok := output.Value.(map[string]interface{})
+ if !ok {
+ return fmt.Errorf("expected value to be map")
+ }
+
+ outputSinkName, ok := outputValue["sink_name"].(string)
+ if !ok {
+ return fmt.Errorf("expected sink_name to be a string")
+ }
+ if outputSinkName != sinkName {
+ return fmt.Errorf("expected sink_name to be %q, got: %q", sinkName, outputSinkName)
+ }
+
+ outputID, ok := outputValue["id"].(string)
+ if !ok {
+ return fmt.Errorf("expected id to be a string")
+ }
+ if outputID != sinkName {
+ return fmt.Errorf("expected id to be %q, got: %q", sinkName, outputID)
+ }
+
+ outputEnabled, ok := outputValue["enabled"].(bool)
+ if !ok {
+ return fmt.Errorf("expected enabled to be a bool")
+ }
+ if !outputEnabled {
+ return fmt.Errorf("expected enabled to be true, got: false")
+ }
+
+ outputState, ok := outputValue["state"].(string)
+ if !ok {
+ return fmt.Errorf("expected state to be a string")
+ }
+ if outputState == "" {
+ return fmt.Errorf("expected state to not be empty")
+ }
+
+ // Check Kinesis configuration
+ kinesisMap, ok := outputValue["kinesis"].(map[string]interface{})
+ if !ok {
+ return fmt.Errorf("expected kinesis to be a map")
+ }
+ if kinesisMap == nil {
+ return fmt.Errorf("expected kinesis to not be null")
+ }
+
+ roleName, ok := kinesisMap["role_name"].(string)
+ if !ok {
+ return fmt.Errorf("expected kinesis.role_name to be a string")
+ }
+ if roleName != "test-role" {
+ return fmt.Errorf("expected kinesis.role_name to be 'test-role', got: %q", roleName)
+ }
+
+ destinationURI, ok := kinesisMap["destination_uri"].(string)
+ if !ok {
+ return fmt.Errorf("expected kinesis.destination_uri to be a string")
+ }
+ if destinationURI != "test-uri" {
+ return fmt.Errorf("expected kinesis.destination_uri to be 'test-uri', got: %q", destinationURI)
+ }
+
+ region, ok := kinesisMap["region"].(string)
+ if !ok {
+ return fmt.Errorf("expected kinesis.region to be a string")
+ }
+ if region != sinkRegion {
+ return fmt.Errorf("expected kinesis.region to be %q, got: %q", sinkRegion, region)
+ }
+
+ // For Kinesis sink, pubsub should be null/empty
+ if pubsub, exists := outputValue["pubsub"]; exists && pubsub != nil {
+ return fmt.Errorf("expected pubsub to be null for Kinesis sink")
+ }
+
+ return nil
+ },
+ },
+ {
+ ResourceName: "temporalcloud_account_audit_log_sink.test",
+ ImportState: true,
+ ImportStateVerify: true,
+ Destroy: true,
+ },
+ },
+ })
+}
+
+func TestAccDataSource_AccountAuditLogSink_PubSub(t *testing.T) {
+ accountAuditLogSinkTestLocks.Lock("account")
+ defer func() {
+ _ = accountAuditLogSinkTestLocks.Unlock("account")
+ }()
+
+ sinkName := fmt.Sprintf("tf-test-sink-%s", randomString(8))
+
+ config := func(name string) string {
+ return fmt.Sprintf(`
+provider "temporalcloud" {
+
+}
+
+resource "temporalcloud_account_audit_log_sink" "test" {
+ sink_name = %[1]q
+ enabled = true
+ pubsub = {
+ service_account_id = "test-sa"
+ topic_name = "test-topic"
+ gcp_project_id = "test-project"
+ }
+}
+
+data "temporalcloud_account_audit_log_sink" "test" {
+ sink_name = temporalcloud_account_audit_log_sink.test.sink_name
+}
+
+output "account_audit_log_sink" {
+ value = data.temporalcloud_account_audit_log_sink.test
+}
+`, name)
+ }
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ testAccPreCheck(t)
+ },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: config(sinkName),
+ Check: func(s *terraform.State) error {
+ output, ok := s.RootModule().Outputs["account_audit_log_sink"]
+ if !ok {
+ return fmt.Errorf("missing expected output")
+ }
+
+ outputValue, ok := output.Value.(map[string]interface{})
+ if !ok {
+ return fmt.Errorf("expected value to be map")
+ }
+
+ outputSinkName, ok := outputValue["sink_name"].(string)
+ if !ok {
+ return fmt.Errorf("expected sink_name to be a string")
+ }
+ if outputSinkName != sinkName {
+ return fmt.Errorf("expected sink_name to be %q, got: %q", sinkName, outputSinkName)
+ }
+
+ outputID, ok := outputValue["id"].(string)
+ if !ok {
+ return fmt.Errorf("expected id to be a string")
+ }
+ if outputID != sinkName {
+ return fmt.Errorf("expected id to be %q, got: %q", sinkName, outputID)
+ }
+
+ outputEnabled, ok := outputValue["enabled"].(bool)
+ if !ok {
+ return fmt.Errorf("expected enabled to be a bool")
+ }
+ if !outputEnabled {
+ return fmt.Errorf("expected enabled to be true, got: false")
+ }
+
+ outputState, ok := outputValue["state"].(string)
+ if !ok {
+ return fmt.Errorf("expected state to be a string")
+ }
+ if outputState == "" {
+ return fmt.Errorf("expected state to not be empty")
+ }
+
+ // Check PubSub configuration
+ pubsubMap, ok := outputValue["pubsub"].(map[string]interface{})
+ if !ok {
+ return fmt.Errorf("expected pubsub to be a map")
+ }
+ if pubsubMap == nil {
+ return fmt.Errorf("expected pubsub to not be null")
+ }
+
+ serviceAccountID, ok := pubsubMap["service_account_id"].(string)
+ if !ok {
+ return fmt.Errorf("expected pubsub.service_account_id to be a string")
+ }
+ if serviceAccountID != "test-sa" {
+ return fmt.Errorf("expected pubsub.service_account_id to be 'test-sa', got: %q", serviceAccountID)
+ }
+
+ topicName, ok := pubsubMap["topic_name"].(string)
+ if !ok {
+ return fmt.Errorf("expected pubsub.topic_name to be a string")
+ }
+ if topicName != "test-topic" {
+ return fmt.Errorf("expected pubsub.topic_name to be 'test-topic', got: %q", topicName)
+ }
+
+ gcpProjectID, ok := pubsubMap["gcp_project_id"].(string)
+ if !ok {
+ return fmt.Errorf("expected pubsub.gcp_project_id to be a string")
+ }
+ if gcpProjectID != "test-project" {
+ return fmt.Errorf("expected pubsub.gcp_project_id to be 'test-project', got: %q", gcpProjectID)
+ }
+
+ // For PubSub sink, kinesis should be null/empty
+ if kinesis, exists := outputValue["kinesis"]; exists && kinesis != nil {
+ return fmt.Errorf("expected kinesis to be null for PubSub sink")
+ }
+
+ return nil
+ },
+ },
+ {
+ ResourceName: "temporalcloud_account_audit_log_sink.test",
+ ImportState: true,
+ ImportStateVerify: true,
+ Destroy: true,
+ },
+ },
+ })
+}
diff --git a/internal/provider/account_audit_log_sink_resource.go b/internal/provider/account_audit_log_sink_resource.go
new file mode 100644
index 0000000..72f738b
--- /dev/null
+++ b/internal/provider/account_audit_log_sink_resource.go
@@ -0,0 +1,478 @@
+// The MIT License
+//
+// Copyright (c) 2023 Temporal Technologies Inc. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package provider
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
+ "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/temporalio/terraform-provider-temporalcloud/internal/client"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+
+ internaltypes "github.com/temporalio/terraform-provider-temporalcloud/internal/types"
+ accountv1 "go.temporal.io/cloud-sdk/api/account/v1"
+ cloudservicev1 "go.temporal.io/cloud-sdk/api/cloudservice/v1"
+ sinkv1 "go.temporal.io/cloud-sdk/api/sink/v1"
+)
+
+type (
+ accountAuditLogSinkResource struct {
+ client *client.Client
+ }
+
+ accountAuditLogSinkResourceModel struct {
+ ID types.String `tfsdk:"id"`
+ SinkName types.String `tfsdk:"sink_name"`
+ Enabled types.Bool `tfsdk:"enabled"`
+ Kinesis types.Object `tfsdk:"kinesis"`
+ PubSub types.Object `tfsdk:"pubsub"`
+ Timeouts timeouts.Value `tfsdk:"timeouts"`
+ }
+)
+
+var (
+ _ resource.Resource = (*accountAuditLogSinkResource)(nil)
+ _ resource.ResourceWithConfigure = (*accountAuditLogSinkResource)(nil)
+ _ resource.ResourceWithImportState = (*accountAuditLogSinkResource)(nil)
+)
+
+func NewAccountAuditLogSinkResource() resource.Resource {
+ return &accountAuditLogSinkResource{}
+}
+
+func (r *accountAuditLogSinkResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ client, ok := req.ProviderData.(*client.Client)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ r.client = client
+}
+
+func (r *accountAuditLogSinkResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_account_audit_log_sink"
+}
+
+func (r *accountAuditLogSinkResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Provisions an account audit log sink.",
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "The unique identifier of the account audit log sink.",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "sink_name": schema.StringAttribute{
+ Description: "The unique name of the audit log sink, it can't be changed once set.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "enabled": schema.BoolAttribute{
+ Description: "A flag indicating whether the audit log sink is enabled or not.",
+ Computed: true,
+ Default: booldefault.StaticBool(true),
+ Optional: true,
+ },
+ "kinesis": schema.SingleNestedAttribute{
+ Description: "The Kinesis configuration details when destination_type is Kinesis.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "role_name": schema.StringAttribute{
+ Description: "The IAM role that Temporal Cloud assumes for writing records to the customer's Kinesis stream.",
+ Required: true,
+ },
+ "destination_uri": schema.StringAttribute{
+ Description: "The destination URI of the Kinesis stream where Temporal will send data.",
+ Required: true,
+ },
+ "region": schema.StringAttribute{
+ Description: "The region of the Kinesis stream.",
+ Required: true,
+ },
+ },
+ Validators: []validator.Object{
+ objectvalidator.ExactlyOneOf(path.Expressions{
+ path.MatchRoot("pubsub"),
+ }...),
+ },
+ },
+ "pubsub": schema.SingleNestedAttribute{
+ Description: "The PubSub configuration details when destination_type is PubSub.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "service_account_id": schema.StringAttribute{
+ Description: "The customer service account ID that Temporal Cloud impersonates for writing records to the customer's PubSub topic.",
+ Required: true,
+ },
+ "topic_name": schema.StringAttribute{
+ Description: "The destination PubSub topic name for Temporal.",
+ Required: true,
+ },
+ "gcp_project_id": schema.StringAttribute{
+ Description: "The GCP project ID of the PubSub topic and service account.",
+ Required: true,
+ },
+ },
+ Validators: []validator.Object{
+ objectvalidator.ExactlyOneOf(path.Expressions{
+ path.MatchRoot("kinesis"),
+ }...),
+ },
+ },
+ },
+ Blocks: map[string]schema.Block{
+ "timeouts": timeouts.Block(ctx, timeouts.Opts{
+ Create: true,
+ Delete: true,
+ }),
+ },
+ }
+}
+
+func (r *accountAuditLogSinkResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var plan accountAuditLogSinkResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ createTimeout, diags := plan.Timeouts.Create(ctx, defaultCreateTimeout)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, createTimeout)
+ defer cancel()
+
+ sinkSpec, d := getAccountAuditLogSinkSpecFromModel(ctx, &plan)
+ resp.Diagnostics.Append(d...)
+ if resp.Diagnostics.HasError() || sinkSpec == nil {
+ return
+ }
+
+ svcResp, err := r.client.CloudService().CreateAccountAuditLogSink(ctx, &cloudservicev1.CreateAccountAuditLogSinkRequest{
+ Spec: sinkSpec,
+ AsyncOperationId: uuid.New().String(),
+ })
+
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to create account audit log sink", err.Error())
+ return
+ }
+
+ if err := client.AwaitAsyncOperation(ctx, r.client, svcResp.AsyncOperation); err != nil {
+ resp.Diagnostics.AddError("Failed to get account audit log sink creation status", err.Error())
+ return
+ }
+
+ sink, err := r.client.CloudService().GetAccountAuditLogSink(ctx, &cloudservicev1.GetAccountAuditLogSinkRequest{
+ Name: sinkSpec.GetName(),
+ })
+
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to get account audit log sink", err.Error())
+ return
+ }
+
+ resp.Diagnostics.Append(updateAccountAuditLogSinkModelFromSpec(ctx, &plan, sink.GetSink())...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
+}
+
+func updateAccountAuditLogSinkModelFromSpec(ctx context.Context, state *accountAuditLogSinkResourceModel, sink *accountv1.AuditLogSink) diag.Diagnostics {
+ var diags diag.Diagnostics
+
+ kinesisObj := types.ObjectNull(internaltypes.KinesisSpecModelAttrTypes)
+ if sink.GetSpec().GetKinesisSink() != nil {
+ kinesisSpec := internaltypes.KinesisSpecModel{
+ RoleName: types.StringValue(sink.GetSpec().GetKinesisSink().GetRoleName()),
+ DestinationUri: types.StringValue(sink.GetSpec().GetKinesisSink().GetDestinationUri()),
+ Region: types.StringValue(sink.GetSpec().GetKinesisSink().GetRegion()),
+ }
+
+ kinesisObj, diags = types.ObjectValueFrom(ctx, internaltypes.KinesisSpecModelAttrTypes, kinesisSpec)
+ if diags.HasError() {
+ return diags
+ }
+ }
+
+ pubsubObj := types.ObjectNull(internaltypes.PubSubSpecModelAttrTypes)
+ if sink.GetSpec().GetPubSubSink() != nil {
+ pubsubSpec := internaltypes.PubSubSpecModel{
+ ServiceAccountId: types.StringValue(sink.GetSpec().GetPubSubSink().GetServiceAccountId()),
+ TopicName: types.StringValue(sink.GetSpec().GetPubSubSink().GetTopicName()),
+ GcpProjectId: types.StringValue(sink.GetSpec().GetPubSubSink().GetGcpProjectId()),
+ }
+
+ pubsubObj, diags = types.ObjectValueFrom(ctx, internaltypes.PubSubSpecModelAttrTypes, pubsubSpec)
+ if diags.HasError() {
+ return diags
+ }
+ }
+
+ state.SinkName = types.StringValue(sink.GetName())
+ state.Enabled = types.BoolValue(sink.GetSpec().GetEnabled())
+ state.Kinesis = kinesisObj
+ state.PubSub = pubsubObj
+ state.ID = types.StringValue(sink.GetName())
+
+ return diags
+}
+
+func (r *accountAuditLogSinkResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var plan accountAuditLogSinkResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ deleteTimeout, diags := plan.Timeouts.Delete(ctx, defaultDeleteTimeout)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ sinkName := plan.ID.ValueString()
+ currentSink, err := r.client.CloudService().GetAccountAuditLogSink(ctx, &cloudservicev1.GetAccountAuditLogSinkRequest{
+ Name: sinkName,
+ })
+
+ if err != nil {
+ switch status.Code(err) {
+ case codes.NotFound:
+ tflog.Warn(ctx, "Account Audit Log Sink Resource not found, removing from state", map[string]interface{}{
+ "id": plan.ID.ValueString(),
+ })
+ return
+ }
+
+ resp.Diagnostics.AddError("Failed to get account audit log sink", err.Error())
+ return
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, deleteTimeout)
+ defer cancel()
+
+ svcResp, err := r.client.CloudService().DeleteAccountAuditLogSink(ctx, &cloudservicev1.DeleteAccountAuditLogSinkRequest{
+ Name: sinkName,
+ ResourceVersion: currentSink.GetSink().GetResourceVersion(),
+ AsyncOperationId: uuid.New().String(),
+ })
+
+ if err != nil {
+ switch status.Code(err) {
+ case codes.NotFound:
+ tflog.Warn(ctx, "Account Audit Log Sink Resource not found, removing from state", map[string]interface{}{
+ "id": plan.ID.ValueString(),
+ })
+ return
+ }
+
+ resp.Diagnostics.AddError("Failed to delete account audit log sink", err.Error())
+ return
+ }
+
+ if err := client.AwaitAsyncOperation(ctx, r.client, svcResp.AsyncOperation); err != nil {
+ resp.Diagnostics.AddError("Failed to get account audit log sink deletion status", err.Error())
+ return
+ }
+}
+
+func (r *accountAuditLogSinkResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
+
+func getAccountAuditLogSinkSpecFromModel(ctx context.Context, plan *accountAuditLogSinkResourceModel) (*accountv1.AuditLogSinkSpec, diag.Diagnostics) {
+ var diags diag.Diagnostics
+
+ // Check that only one of Kinesis or PubSub is set
+ if !plan.Kinesis.IsNull() && !plan.PubSub.IsNull() {
+ diags.AddError("Invalid sink configuration", "Only one of Kinesis or PubSub can be configured")
+ return nil, diags
+ }
+
+ if !plan.Kinesis.IsNull() {
+ var kinesisSpec internaltypes.KinesisSpecModel
+ diags.Append(plan.Kinesis.As(ctx, &kinesisSpec, basetypes.ObjectAsOptions{})...)
+ if diags.HasError() {
+ return nil, diags
+ }
+
+ kinesisSinkSpec := &sinkv1.KinesisSpec{
+ RoleName: kinesisSpec.RoleName.ValueString(),
+ DestinationUri: kinesisSpec.DestinationUri.ValueString(),
+ Region: kinesisSpec.Region.ValueString(),
+ }
+
+ return &accountv1.AuditLogSinkSpec{
+ Name: plan.SinkName.ValueString(),
+ Enabled: plan.Enabled.ValueBool(),
+ SinkType: &accountv1.AuditLogSinkSpec_KinesisSink{
+ KinesisSink: kinesisSinkSpec,
+ },
+ }, nil
+ } else if !plan.PubSub.IsNull() {
+ var pubsubSpec internaltypes.PubSubSpecModel
+ diags.Append(plan.PubSub.As(ctx, &pubsubSpec, basetypes.ObjectAsOptions{})...)
+ if diags.HasError() {
+ return nil, diags
+ }
+
+ pubsubSinkSpec := &sinkv1.PubSubSpec{
+ ServiceAccountId: pubsubSpec.ServiceAccountId.ValueString(),
+ TopicName: pubsubSpec.TopicName.ValueString(),
+ GcpProjectId: pubsubSpec.GcpProjectId.ValueString(),
+ }
+
+ return &accountv1.AuditLogSinkSpec{
+ Name: plan.SinkName.ValueString(),
+ Enabled: plan.Enabled.ValueBool(),
+ SinkType: &accountv1.AuditLogSinkSpec_PubSubSink{
+ PubSubSink: pubsubSinkSpec,
+ },
+ }, nil
+ }
+
+ return nil, diags
+}
+
+func (r *accountAuditLogSinkResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var state accountAuditLogSinkResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ sinkName := state.ID.ValueString()
+
+ sink, err := r.client.CloudService().GetAccountAuditLogSink(ctx, &cloudservicev1.GetAccountAuditLogSinkRequest{
+ Name: sinkName,
+ })
+ if err != nil {
+ switch status.Code(err) {
+ case codes.NotFound:
+ tflog.Warn(ctx, "Account Audit Log Sink Resource not found, removing from state", map[string]interface{}{
+ "id": state.ID.ValueString(),
+ })
+ resp.State.RemoveResource(ctx)
+ return
+ }
+
+ resp.Diagnostics.AddError("Failed to get account audit log sink", err.Error())
+ return
+ }
+
+ resp.Diagnostics.Append(updateAccountAuditLogSinkModelFromSpec(ctx, &state, sink.GetSink())...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
+}
+
+func (r *accountAuditLogSinkResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var plan accountAuditLogSinkResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ sinkSpec, diags := getAccountAuditLogSinkSpecFromModel(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() || sinkSpec == nil {
+ return
+ }
+
+ sinkName := plan.ID.ValueString()
+
+ currentSink, err := r.client.CloudService().GetAccountAuditLogSink(ctx, &cloudservicev1.GetAccountAuditLogSinkRequest{
+ Name: sinkName,
+ })
+
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to get account audit log sink", err.Error())
+ return
+ }
+
+ svcResp, err := r.client.CloudService().UpdateAccountAuditLogSink(ctx, &cloudservicev1.UpdateAccountAuditLogSinkRequest{
+ Spec: sinkSpec,
+ ResourceVersion: currentSink.GetSink().GetResourceVersion(),
+ AsyncOperationId: uuid.New().String(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to update account audit log sink", err.Error())
+ return
+ }
+
+ if err := client.AwaitAsyncOperation(ctx, r.client, svcResp.AsyncOperation); err != nil {
+ resp.Diagnostics.AddError("Failed to get account audit log sink update status", err.Error())
+ return
+ }
+
+ sink, err := r.client.CloudService().GetAccountAuditLogSink(ctx, &cloudservicev1.GetAccountAuditLogSinkRequest{
+ Name: sinkName,
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to get account audit log sink", err.Error())
+ return
+ }
+
+ resp.Diagnostics.Append(updateAccountAuditLogSinkModelFromSpec(ctx, &plan, sink.GetSink())...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
+}
diff --git a/internal/provider/account_audit_log_sink_resource_test.go b/internal/provider/account_audit_log_sink_resource_test.go
new file mode 100644
index 0000000..e7ca0bf
--- /dev/null
+++ b/internal/provider/account_audit_log_sink_resource_test.go
@@ -0,0 +1,198 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ fwresource "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/jpillora/maplock"
+)
+
+// accountAuditLogSinkTestLocks is a per-account mutex that protects against concurrent sink operations
+// across all test files, since an account can only have one audit log sink at a time.
+var accountAuditLogSinkTestLocks = maplock.New()
+
+func TestAccountAuditLogSinkResource_Schema(t *testing.T) {
+ t.Parallel()
+
+ ctx := context.Background()
+ schemaRequest := fwresource.SchemaRequest{}
+ schemaResponse := &fwresource.SchemaResponse{}
+
+ NewAccountAuditLogSinkResource().Schema(ctx, schemaRequest, schemaResponse)
+
+ if schemaResponse.Diagnostics.HasError() {
+ t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics)
+ }
+
+ diagnostics := schemaResponse.Schema.ValidateImplementation(ctx)
+
+ if diagnostics.HasError() {
+ t.Fatalf("Schema validation diagnostics: %+v", diagnostics)
+ }
+}
+
+func TestAccAccountAuditLogSink_Kinesis(t *testing.T) {
+ accountAuditLogSinkTestLocks.Lock("account")
+ defer func() {
+ _ = accountAuditLogSinkTestLocks.Unlock("account")
+ }()
+
+ sinkRegion := "us-east-1"
+ sinkName := fmt.Sprintf("tf-test-sink-%s", randomString(8))
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // Create and Read testing
+ {
+ Config: testAccAccountAuditLogSinkKinesisConfig(sinkName, sinkRegion),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "sink_name", sinkName),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "enabled", "true"),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "kinesis.role_name", "test-role"),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "kinesis.destination_uri", "test-uri"),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "kinesis.region", sinkRegion),
+ ),
+ },
+ // ImportState testing
+ {
+ ResourceName: "temporalcloud_account_audit_log_sink.test",
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ // Update testing
+ {
+ Config: testAccAccountAuditLogSinkKinesisConfigUpdate(sinkName, sinkRegion),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "enabled", "false"),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "kinesis.role_name", "test-updated-role"),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "kinesis.destination_uri", "test-updated-uri"),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "kinesis.region", sinkRegion),
+ ),
+ },
+ // Delete testing
+ {
+ ResourceName: "temporalcloud_account_audit_log_sink.test",
+ ImportState: true,
+ ImportStateVerify: true,
+ Destroy: true,
+ },
+ },
+ })
+}
+
+func TestAccAccountAuditLogSink_PubSub(t *testing.T) {
+ accountAuditLogSinkTestLocks.Lock("account")
+ defer func() {
+ _ = accountAuditLogSinkTestLocks.Unlock("account")
+ }()
+
+ sinkName := fmt.Sprintf("tf-test-sink-%s", randomString(8))
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // Create and Read testing
+ {
+ Config: testAccAccountAuditLogSinkPubSubConfig(sinkName),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "sink_name", sinkName),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "enabled", "true"),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "pubsub.service_account_id", "test-sa"),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "pubsub.topic_name", "test-topic"),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "pubsub.gcp_project_id", "test-project"),
+ ),
+ },
+ // ImportState testing
+ {
+ ResourceName: "temporalcloud_account_audit_log_sink.test",
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ // Update testing
+ {
+ Config: testAccAccountAuditLogSinkPubSubConfigUpdate(sinkName),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "enabled", "false"),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "pubsub.service_account_id", "test-updated-sa"),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "pubsub.topic_name", "test-updated-topic"),
+ resource.TestCheckResourceAttr("temporalcloud_account_audit_log_sink.test", "pubsub.gcp_project_id", "test-updated-project"),
+ ),
+ },
+ // Delete testing
+ {
+ ResourceName: "temporalcloud_account_audit_log_sink.test",
+ ImportState: true,
+ ImportStateVerify: true,
+ Destroy: true,
+ },
+ },
+ })
+}
+
+func testAccAccountAuditLogSinkKinesisConfig(sinkName, sinkRegion string) string {
+ return fmt.Sprintf(`
+provider "temporalcloud" {
+}
+
+resource "temporalcloud_account_audit_log_sink" "test" {
+ sink_name = %[1]q
+ enabled = true
+ kinesis = {
+ role_name = "test-role"
+ destination_uri = "test-uri"
+ region = %[2]q
+ }
+}
+`, sinkName, sinkRegion)
+}
+
+func testAccAccountAuditLogSinkKinesisConfigUpdate(sinkName, sinkRegion string) string {
+ return fmt.Sprintf(`
+resource "temporalcloud_account_audit_log_sink" "test" {
+ sink_name = %[1]q
+ enabled = false
+ kinesis = {
+ role_name = "test-updated-role"
+ destination_uri = "test-updated-uri"
+ region = %[2]q
+ }
+}
+`, sinkName, sinkRegion)
+}
+
+func testAccAccountAuditLogSinkPubSubConfig(sinkName string) string {
+ return fmt.Sprintf(`
+provider "temporalcloud" {
+}
+
+resource "temporalcloud_account_audit_log_sink" "test" {
+ sink_name = %[1]q
+ enabled = true
+ pubsub = {
+ service_account_id = "test-sa"
+ topic_name = "test-topic"
+ gcp_project_id = "test-project"
+ }
+}
+`, sinkName)
+}
+
+func testAccAccountAuditLogSinkPubSubConfigUpdate(sinkName string) string {
+ return fmt.Sprintf(`
+resource "temporalcloud_account_audit_log_sink" "test" {
+ sink_name = %[1]q
+ enabled = false
+ pubsub = {
+ service_account_id = "test-updated-sa"
+ topic_name = "test-updated-topic"
+ gcp_project_id = "test-updated-project"
+ }
+}
+`, sinkName)
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index dcc0db7..553f294 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -179,6 +179,7 @@ func (p *TerraformCloudProvider) Resources(ctx context.Context) []func() resourc
NewUserGroupMembersResource,
NewGroupAccessResource,
NewConnectivityRuleResource,
+ NewAccountAuditLogSinkResource,
}
}
@@ -195,6 +196,7 @@ func (p *TerraformCloudProvider) DataSources(ctx context.Context) []func() datas
NewNexusEndpointDataSource,
NewNexusEndpointsDataSource,
NewConnectivityRuleDataSource,
+ NewAccountAuditLogSinkDataSource,
}
}
diff --git a/internal/types/sink.go b/internal/types/sink.go
index b851fe4..41dc446 100644
--- a/internal/types/sink.go
+++ b/internal/types/sink.go
@@ -21,6 +21,18 @@ var (
"region": types.StringType,
"service_account_email": types.StringType,
}
+
+ KinesisSpecModelAttrTypes = map[string]attr.Type{
+ "role_name": types.StringType,
+ "destination_uri": types.StringType,
+ "region": types.StringType,
+ }
+
+ PubSubSpecModelAttrTypes = map[string]attr.Type{
+ "service_account_id": types.StringType,
+ "topic_name": types.StringType,
+ "gcp_project_id": types.StringType,
+ }
)
type S3SpecModel struct {
@@ -56,3 +68,25 @@ type GCSSpecModel struct {
// The service account email associated with the GCS bucket and service account
ServiceAccountEmail types.String `tfsdk:"service_account_email"`
}
+
+type KinesisSpecModel struct {
+ // The IAM role that Temporal Cloud assumes for writing records to the customer's Kinesis stream
+ RoleName types.String `tfsdk:"role_name"`
+
+ // The destination URI of the Kinesis stream where Temporal will send data
+ DestinationUri types.String `tfsdk:"destination_uri"`
+
+ // The region of the Kinesis stream
+ Region types.String `tfsdk:"region"`
+}
+
+type PubSubSpecModel struct {
+ // The customer service account ID that Temporal Cloud impersonates for writing records to the customer's PubSub topic
+ ServiceAccountId types.String `tfsdk:"service_account_id"`
+
+ // The destination PubSub topic name for Temporal
+ TopicName types.String `tfsdk:"topic_name"`
+
+ // The GCP project ID of the PubSub topic and service account
+ GcpProjectId types.String `tfsdk:"gcp_project_id"`
+}