diff --git a/docs/resources/nexus_endpoint.md b/docs/resources/nexus_endpoint.md
new file mode 100644
index 0000000..e1bea0c
--- /dev/null
+++ b/docs/resources/nexus_endpoint.md
@@ -0,0 +1,126 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "temporalcloud_nexus_endpoint Resource - terraform-provider-temporalcloud"
+subcategory: ""
+description: |-
+ Provisions a Temporal Cloud Nexus endpoint.
+---
+
+# temporalcloud_nexus_endpoint (Resource)
+
+Provisions a Temporal Cloud Nexus endpoint.
+
+## Example Usage
+
+```terraform
+terraform {
+ required_providers {
+ temporalcloud = {
+ source = "temporalio/temporalcloud"
+ }
+ }
+}
+
+resource "temporalcloud_namespace" "target_namespace" {
+ name = "terraform-target-namespace"
+ regions = ["aws-us-west-2"]
+ api_key_auth = true
+ retention_days = 14
+ timeouts {
+ create = "10m"
+ delete = "10m"
+ }
+}
+
+resource "temporalcloud_namespace" "caller_namespace" {
+ name = "terraform-caller-namespace"
+ regions = ["aws-us-east-1"]
+ api_key_auth = true
+ retention_days = 14
+ timeouts {
+ create = "10m"
+ delete = "10m"
+ }
+}
+
+resource "temporalcloud_namespace" "caller_namespace_2" {
+ name = "terraform-caller-namespace-2"
+ regions = ["gcp-us-central1"]
+ api_key_auth = true
+ retention_days = 14
+ timeouts {
+ create = "10m"
+ delete = "10m"
+ }
+}
+
+resource "temporalcloud_nexus_endpoint" "nexus_endpoint" {
+ name = "terraform-nexus-endpoint"
+ description = <<-EOT
+ Service Name:
+ my-hello-service
+ Operation Names:
+ echo
+ say-hello
+
+ Input / Output arguments are in the following repository:
+ https://github.com/temporalio/samples-go/blob/main/nexus/service/api.go
+ EOT
+ worker_target_spec = {
+ namespace_id = temporalcloud_namespace.target_namespace.id
+ task_queue = "terraform-task-queue"
+ }
+ allowed_caller_namespaces = [
+ temporalcloud_namespace.caller_namespace.id,
+ temporalcloud_namespace.caller_namespace_2.id,
+ ]
+}
+```
+
+
+## Schema
+
+### Required
+
+- `allowed_caller_namespaces` (Set of String) Namespace(s) that are allowed to call this Endpoint.
+- `name` (String) The name of the endpoint. Must be unique within an account and match `^[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9]$`
+- `worker_target_spec` (Attributes) A target spec for routing nexus requests to a specific cloud namespace worker. (see [below for nested schema](#nestedatt--worker_target_spec))
+
+### Optional
+
+- `description` (String, Sensitive) The description for the Nexus endpoint.
+- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
+
+### Read-Only
+
+- `id` (String) The unique identifier of the Nexus endpoint.
+
+
+### Nested Schema for `worker_target_spec`
+
+Required:
+
+- `namespace_id` (String) The target cloud namespace to route requests to. Namespace must be in same account as the endpoint.
+- `task_queue` (String) The task queue on the cloud namespace to route requests to.
+
+
+
+### 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.
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+# Nexus Endpoints can be imported to incorporate existing Nexus Endpoints into your Terraform pipeline.
+# To import a Nexus Endpoint, you need
+# - a resource configuration in your Terraform configuration file/module to accept the imported Nexus Endpoint. In the example below, the placeholder is "temporalcloud_nexus_endpoint" "nexus_endpoint"
+# - the Nexus Endpoint's ID, which is found using the Temporal Cloud CLI tcld nexus endpoint list. In the example below, this is 405f7da4224a43d99c211904ed9b3819
+
+terraform import temporalcloud_nexus_endpoint.nexus_endpoint 405f7da4224a43d99c211904ed9b3819
+```
diff --git a/examples/resources/temporalcloud_nexus_endpoint/import.sh b/examples/resources/temporalcloud_nexus_endpoint/import.sh
new file mode 100644
index 0000000..1b2b73c
--- /dev/null
+++ b/examples/resources/temporalcloud_nexus_endpoint/import.sh
@@ -0,0 +1,6 @@
+# Nexus Endpoints can be imported to incorporate existing Nexus Endpoints into your Terraform pipeline.
+# To import a Nexus Endpoint, you need
+# - a resource configuration in your Terraform configuration file/module to accept the imported Nexus Endpoint. In the example below, the placeholder is "temporalcloud_nexus_endpoint" "nexus_endpoint"
+# - the Nexus Endpoint's ID, which is found using the Temporal Cloud CLI tcld nexus endpoint list. In the example below, this is 405f7da4224a43d99c211904ed9b3819
+
+terraform import temporalcloud_nexus_endpoint.nexus_endpoint 405f7da4224a43d99c211904ed9b3819
\ No newline at end of file
diff --git a/examples/resources/temporalcloud_nexus_endpoint/resource.tf b/examples/resources/temporalcloud_nexus_endpoint/resource.tf
new file mode 100644
index 0000000..fcfb73a
--- /dev/null
+++ b/examples/resources/temporalcloud_nexus_endpoint/resource.tf
@@ -0,0 +1,62 @@
+terraform {
+ required_providers {
+ temporalcloud = {
+ source = "temporalio/temporalcloud"
+ }
+ }
+}
+
+resource "temporalcloud_namespace" "target_namespace" {
+ name = "terraform-target-namespace"
+ regions = ["aws-us-west-2"]
+ api_key_auth = true
+ retention_days = 14
+ timeouts {
+ create = "10m"
+ delete = "10m"
+ }
+}
+
+resource "temporalcloud_namespace" "caller_namespace" {
+ name = "terraform-caller-namespace"
+ regions = ["aws-us-east-1"]
+ api_key_auth = true
+ retention_days = 14
+ timeouts {
+ create = "10m"
+ delete = "10m"
+ }
+}
+
+resource "temporalcloud_namespace" "caller_namespace_2" {
+ name = "terraform-caller-namespace-2"
+ regions = ["gcp-us-central1"]
+ api_key_auth = true
+ retention_days = 14
+ timeouts {
+ create = "10m"
+ delete = "10m"
+ }
+}
+
+resource "temporalcloud_nexus_endpoint" "nexus_endpoint" {
+ name = "terraform-nexus-endpoint"
+ description = <<-EOT
+ Service Name:
+ my-hello-service
+ Operation Names:
+ echo
+ say-hello
+
+ Input / Output arguments are in the following repository:
+ https://github.com/temporalio/samples-go/blob/main/nexus/service/api.go
+ EOT
+ worker_target_spec = {
+ namespace_id = temporalcloud_namespace.target_namespace.id
+ task_queue = "terraform-task-queue"
+ }
+ allowed_caller_namespaces = [
+ temporalcloud_namespace.caller_namespace.id,
+ temporalcloud_namespace.caller_namespace_2.id,
+ ]
+}
diff --git a/internal/provider/nexus_endpoint_resource.go b/internal/provider/nexus_endpoint_resource.go
new file mode 100644
index 0000000..0309ad3
--- /dev/null
+++ b/internal/provider/nexus_endpoint_resource.go
@@ -0,0 +1,403 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/temporalio/terraform-provider-temporalcloud/internal/client"
+ cloudservicev1 "go.temporal.io/api/cloud/cloudservice/v1"
+ nexusv1 "go.temporal.io/api/cloud/nexus/v1"
+)
+
+type (
+ nexusEndpointResource struct {
+ client *client.Client
+ }
+
+ nexusEndpointResourceModel struct {
+ ID types.String `tfsdk:"id"`
+ Name types.String `tfsdk:"name"`
+ Description types.String `tfsdk:"description"`
+ WorkerTargetSpec types.Object `tfsdk:"worker_target_spec"`
+ AllowedCallerNamespaces types.Set `tfsdk:"allowed_caller_namespaces"`
+
+ Timeouts timeouts.Value `tfsdk:"timeouts"`
+ }
+
+ nexusEndpointWorkerTargetModel struct {
+ NamespaceID types.String `tfsdk:"namespace_id"`
+ TaskQueue types.String `tfsdk:"task_queue"`
+ }
+)
+
+var (
+ _ resource.Resource = (*nexusEndpointResource)(nil)
+ _ resource.ResourceWithConfigure = (*nexusEndpointResource)(nil)
+ _ resource.ResourceWithImportState = (*nexusEndpointResource)(nil)
+
+ workerTargetSpecAttrs = map[string]attr.Type{
+ "namespace_id": types.StringType,
+ "task_queue": types.StringType,
+ }
+)
+
+func NewNexusEndpointResource() resource.Resource {
+ return &nexusEndpointResource{}
+}
+
+func (r *nexusEndpointResource) Configure(_ 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 *nexusEndpointResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_nexus_endpoint"
+}
+
+func (r *nexusEndpointResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Provisions a Temporal Cloud Nexus endpoint.",
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "The unique identifier of the Nexus endpoint.",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "name": schema.StringAttribute{
+ Description: "The name of the endpoint. Must be unique within an account and match `^[a-zA-Z][a-zA-Z0-9\\-]*[a-zA-Z0-9]$`",
+ Required: true,
+ },
+ "description": schema.StringAttribute{
+ Description: "The description for the Nexus endpoint.",
+ Optional: true,
+ Sensitive: true,
+ },
+ "worker_target_spec": schema.SingleNestedAttribute{
+ Description: "A target spec for routing nexus requests to a specific cloud namespace worker.",
+ Attributes: map[string]schema.Attribute{
+ "namespace_id": schema.StringAttribute{
+ Description: "The target cloud namespace to route requests to. Namespace must be in same account as the endpoint.",
+ Required: true,
+ },
+ "task_queue": schema.StringAttribute{
+ Description: "The task queue on the cloud namespace to route requests to.",
+ Required: true,
+ },
+ },
+ Required: true,
+ },
+ "allowed_caller_namespaces": schema.SetAttribute{
+ Description: "Namespace(s) that are allowed to call this Endpoint.",
+ ElementType: types.StringType,
+ Required: true,
+ },
+ },
+ Blocks: map[string]schema.Block{
+ "timeouts": timeouts.Block(ctx, timeouts.Opts{
+ Create: true,
+ Delete: true,
+ }),
+ },
+ }
+}
+
+func (r *nexusEndpointResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var plan nexusEndpointResourceModel
+ 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()
+
+ description := ""
+ if !plan.Description.IsNull() {
+ description = plan.Description.ValueString()
+ }
+
+ targetSpec, diags := getTargetSpecFromModel(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ policySpecs, diags := getPolicySpecsFromModel(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ svcResp, err := r.client.CloudService().CreateNexusEndpoint(ctx, &cloudservicev1.CreateNexusEndpointRequest{
+ Spec: &nexusv1.EndpointSpec{
+ Name: plan.Name.ValueString(),
+ Description: description,
+ TargetSpec: targetSpec,
+ PolicySpecs: policySpecs,
+ },
+ })
+
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to create Nexus endpoint", err.Error())
+ return
+ }
+ if err := client.AwaitAsyncOperation(ctx, r.client, svcResp.AsyncOperation); err != nil {
+ resp.Diagnostics.AddError("Failed to create Nexus endpoint", err.Error())
+ return
+ }
+
+ nexusEndpoint, err := r.client.CloudService().GetNexusEndpoint(ctx, &cloudservicev1.GetNexusEndpointRequest{
+ EndpointId: svcResp.GetEndpointId(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to get Nexus endpoint after creation", err.Error())
+ return
+ }
+
+ resp.Diagnostics.Append(updateNexusEndpointModelFromSpec(ctx, &plan, nexusEndpoint.Endpoint)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
+}
+
+func (r *nexusEndpointResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var state nexusEndpointResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ nexusEndpoint, err := r.client.CloudService().GetNexusEndpoint(ctx, &cloudservicev1.GetNexusEndpointRequest{
+ EndpointId: state.ID.ValueString(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to get Nexus endpoint", err.Error())
+ return
+ }
+
+ resp.Diagnostics.Append(updateNexusEndpointModelFromSpec(ctx, &state, nexusEndpoint.Endpoint)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
+}
+
+func (r *nexusEndpointResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var plan nexusEndpointResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ nexusEndpoint, err := r.client.CloudService().GetNexusEndpoint(ctx, &cloudservicev1.GetNexusEndpointRequest{
+ EndpointId: plan.ID.ValueString(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to get current Nexus endpoint status", err.Error())
+ return
+ }
+
+ description := ""
+ if !plan.Description.IsNull() {
+ description = plan.Description.ValueString()
+ }
+
+ targetSpec, diags := getTargetSpecFromModel(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ policySpecs, diags := getPolicySpecsFromModel(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ svcResp, err := r.client.CloudService().UpdateNexusEndpoint(ctx, &cloudservicev1.UpdateNexusEndpointRequest{
+ EndpointId: plan.ID.ValueString(),
+ Spec: &nexusv1.EndpointSpec{
+ Name: plan.Name.ValueString(),
+ Description: description,
+ TargetSpec: targetSpec,
+ PolicySpecs: policySpecs,
+ },
+ ResourceVersion: nexusEndpoint.GetEndpoint().GetResourceVersion(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to update Nexus endpoint", err.Error())
+ return
+ }
+
+ if err := client.AwaitAsyncOperation(ctx, r.client, svcResp.GetAsyncOperation()); err != nil {
+ resp.Diagnostics.AddError("Failed to update Nexus endpoint", err.Error())
+ return
+ }
+
+ nexusEndpoint, err = r.client.CloudService().GetNexusEndpoint(ctx, &cloudservicev1.GetNexusEndpointRequest{
+ EndpointId: plan.ID.ValueString(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to get Nexus endpoint after update", err.Error())
+ return
+ }
+
+ resp.Diagnostics.Append(updateNexusEndpointModelFromSpec(ctx, &plan, nexusEndpoint.Endpoint)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
+}
+
+func (r *nexusEndpointResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var state nexusEndpointResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ deleteTimeout, diags := state.Timeouts.Delete(ctx, defaultDeleteTimeout)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ nexusEndpoint, err := r.client.CloudService().GetNexusEndpoint(ctx, &cloudservicev1.GetNexusEndpointRequest{
+ EndpointId: state.ID.ValueString(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to get current Nexus endpoint status", err.Error())
+ return
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, deleteTimeout)
+ defer cancel()
+
+ svcResp, err := r.client.CloudService().DeleteNexusEndpoint(ctx, &cloudservicev1.DeleteNexusEndpointRequest{
+ EndpointId: state.ID.ValueString(),
+ ResourceVersion: nexusEndpoint.GetEndpoint().GetResourceVersion(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to delete Nexus endpoint", err.Error())
+ return
+ }
+
+ if err := client.AwaitAsyncOperation(ctx, r.client, svcResp.AsyncOperation); err != nil {
+ resp.Diagnostics.AddError("Failed to delete Nexus endpoint", err.Error())
+ }
+}
+
+func (r *nexusEndpointResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
+
+func updateNexusEndpointModelFromSpec(ctx context.Context, model *nexusEndpointResourceModel, nexusEndpoint *nexusv1.Endpoint) diag.Diagnostics {
+ var diags diag.Diagnostics
+
+ model.ID = types.StringValue(nexusEndpoint.GetId())
+
+ model.Name = types.StringValue(nexusEndpoint.GetSpec().GetName())
+
+ if nexusEndpoint.GetSpec().GetDescription() != "" {
+ model.Description = types.StringValue(nexusEndpoint.GetSpec().GetDescription())
+ }
+
+ nexusEndpointTargetSpec := nexusEndpoint.GetSpec().GetTargetSpec()
+ if workerSpec := nexusEndpointTargetSpec.GetWorkerTargetSpec(); workerSpec != nil {
+ workerTargetSpec := &nexusEndpointWorkerTargetModel{
+ NamespaceID: types.StringValue(workerSpec.GetNamespaceId()),
+ TaskQueue: types.StringValue(workerSpec.GetTaskQueue()),
+ }
+ model.WorkerTargetSpec, diags = types.ObjectValueFrom(ctx, workerTargetSpecAttrs, workerTargetSpec)
+ if diags.HasError() {
+ return diags
+ }
+ }
+
+ allowedNamespaces := make([]types.String, 0)
+ nexusEndpointPolicySpecs := nexusEndpoint.GetSpec().GetPolicySpecs()
+ for _, policySpec := range nexusEndpointPolicySpecs {
+ if policySpec.GetAllowedCloudNamespacePolicySpec() != nil {
+ allowedNamespaces = append(allowedNamespaces, types.StringValue(policySpec.GetAllowedCloudNamespacePolicySpec().GetNamespaceId()))
+ }
+ }
+ model.AllowedCallerNamespaces, diags = types.SetValueFrom(ctx, types.StringType, allowedNamespaces)
+ if diags.HasError() {
+ return diags
+ }
+
+ return diags
+}
+
+func getTargetSpecFromModel(ctx context.Context, model *nexusEndpointResourceModel) (*nexusv1.EndpointTargetSpec, diag.Diagnostics) {
+ var diags diag.Diagnostics
+
+ var workerTargetSpecModel nexusEndpointWorkerTargetModel
+ diags.Append(model.WorkerTargetSpec.As(ctx, &workerTargetSpecModel, basetypes.ObjectAsOptions{})...)
+ if diags.HasError() {
+ return nil, diags
+ }
+ workerTargetSpec := &nexusv1.WorkerTargetSpec{
+ NamespaceId: workerTargetSpecModel.NamespaceID.ValueString(),
+ TaskQueue: workerTargetSpecModel.TaskQueue.ValueString(),
+ }
+
+ return &nexusv1.EndpointTargetSpec{
+ Variant: &nexusv1.EndpointTargetSpec_WorkerTargetSpec{
+ WorkerTargetSpec: workerTargetSpec,
+ },
+ }, diags
+}
+
+func getPolicySpecsFromModel(_ context.Context, model *nexusEndpointResourceModel) ([]*nexusv1.EndpointPolicySpec, diag.Diagnostics) {
+ policySpecs := make([]*nexusv1.EndpointPolicySpec, 0, len(model.AllowedCallerNamespaces.Elements()))
+ for _, namespace := range model.AllowedCallerNamespaces.Elements() {
+ ns, ok := namespace.(types.String)
+ if !ok {
+ return nil, diag.Diagnostics{diag.NewErrorDiagnostic("Invalid namespace type", "Expected types.String")}
+ }
+ policySpecs = append(policySpecs, &nexusv1.EndpointPolicySpec{
+ Variant: &nexusv1.EndpointPolicySpec_AllowedCloudNamespacePolicySpec{
+ AllowedCloudNamespacePolicySpec: &nexusv1.AllowedCloudNamespacePolicySpec{
+ NamespaceId: ns.ValueString(),
+ },
+ },
+ })
+ }
+
+ return policySpecs, nil
+}
diff --git a/internal/provider/nexus_endpoint_resource_test.go b/internal/provider/nexus_endpoint_resource_test.go
new file mode 100644
index 0000000..2606bf8
--- /dev/null
+++ b/internal/provider/nexus_endpoint_resource_test.go
@@ -0,0 +1,108 @@
+package provider
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func TestAccNexusEndpointResource(t *testing.T) {
+ timeSuffix := time.Now().Format("060102150405")
+ endpointName := fmt.Sprintf("tf-nexus-endpoint-%s-%s", timeSuffix, randomStringWithLength(3))
+ description := "test description"
+ targetNamespaceName := fmt.Sprintf("tf-nexus-target-%s-%s", timeSuffix, randomStringWithLength(4))
+ taskQueue := "task-queue-1"
+ callerNamespaceName := fmt.Sprintf("tf-nexus-caller-%s-%s", timeSuffix, randomStringWithLength(4))
+ callerNamespace2Name := fmt.Sprintf("tf-nexus-caller2-%s-%s", timeSuffix, randomStringWithLength(3))
+
+ updatedDescription := "updated description"
+ updatedTaskQueue := "task-queue-2"
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // Create and Read testing
+ {
+ Config: testAccNexusEndpointResourceConfig(endpointName, description, targetNamespaceName, taskQueue, []string{callerNamespaceName, callerNamespace2Name}),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("temporalcloud_nexus_endpoint.test", "name", endpointName),
+ resource.TestCheckResourceAttr("temporalcloud_nexus_endpoint.test", "description", description),
+ resource.TestCheckResourceAttrSet("temporalcloud_nexus_endpoint.test", "worker_target_spec.namespace_id"),
+ // resource.TestCheckResourceAttr("temporalcloud_nexus_endpoint.test", "worker_target_spec.namespace_id", targetNamespaceName + "." + accountID),
+ resource.TestCheckResourceAttr("temporalcloud_nexus_endpoint.test", "worker_target_spec.task_queue", taskQueue),
+ resource.TestCheckResourceAttr("temporalcloud_nexus_endpoint.test", "allowed_caller_namespaces.#", "2"),
+ resource.TestCheckResourceAttrSet("temporalcloud_nexus_endpoint.test", "id"),
+ ),
+ },
+ // ImportState testing
+ {
+ ResourceName: "temporalcloud_nexus_endpoint.test",
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ // Update and Read testing
+ {
+ Config: testAccNexusEndpointResourceConfig(endpointName, updatedDescription, targetNamespaceName, updatedTaskQueue, []string{callerNamespaceName}),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("temporalcloud_nexus_endpoint.test", "description", updatedDescription),
+ resource.TestCheckResourceAttr("temporalcloud_nexus_endpoint.test", "worker_target_spec.task_queue", updatedTaskQueue),
+ resource.TestCheckResourceAttr("temporalcloud_nexus_endpoint.test", "allowed_caller_namespaces.#", "1"),
+ ),
+ },
+ // Delete testing automatically occurs in TestCase
+ },
+ })
+}
+
+func testAccNamespaceResourceConfig(resourceName, name, region string, retentionDays int) string {
+ return fmt.Sprintf(`
+resource "temporalcloud_namespace" %[1]q {
+ name = %[2]q
+ regions = [%[3]q]
+ api_key_auth = true
+ retention_days = %[4]d
+ timeouts {
+ create = "15m"
+ delete = "15m"
+ }
+}
+`, resourceName, name, region, retentionDays)
+}
+
+func testAccNexusEndpointResourceConfig(name, description, targetNamespaceName, taskQueue string, allowedNamespaces []string) string {
+ region := "aws-us-west-2"
+ retentionDays := 1
+ allowedNamespaceIDs := []string{}
+ namespacesConfig := testAccNamespaceResourceConfig("target_namespace", targetNamespaceName, region, retentionDays)
+ for i, allowedNamespace := range allowedNamespaces {
+ namespacesConfig += testAccNamespaceResourceConfig("allowed_namespace_"+strconv.Itoa(i), allowedNamespace, region, retentionDays)
+ allowedNamespaceIDs = append(allowedNamespaceIDs, "temporalcloud_namespace.allowed_namespace_"+strconv.Itoa(i)+".id")
+ }
+ allowedNamespaceIDsStr := fmt.Sprintf("[%s]", strings.Join(allowedNamespaceIDs, ", "))
+
+ return fmt.Sprintf(`
+%[1]s
+
+resource "temporalcloud_nexus_endpoint" "test" {
+ name = %[2]q
+ description = %[3]q
+
+ worker_target_spec = {
+ namespace_id = temporalcloud_namespace.target_namespace.id
+ task_queue = %[4]q
+ }
+
+ allowed_caller_namespaces = %[5]s
+
+ timeouts {
+ create = "4m"
+ delete = "4m"
+ }
+}
+`, namespacesConfig, name, description, taskQueue, allowedNamespaceIDsStr)
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index 69f75b5..619d2e8 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -140,6 +140,7 @@ func (p *TerraformCloudProvider) Resources(ctx context.Context) []func() resourc
NewUserResource,
NewServiceAccountResource,
NewApiKeyResource,
+ NewNexusEndpointResource,
}
}
diff --git a/internal/provider/utils_test.go b/internal/provider/utils_test.go
new file mode 100644
index 0000000..f1cef06
--- /dev/null
+++ b/internal/provider/utils_test.go
@@ -0,0 +1,16 @@
+package provider
+
+import (
+ "math/rand"
+ "time"
+)
+
+func randomStringWithLength(length int) string {
+ r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
+ const charset = "abcdefghijklmnopqrstuvwxyz"
+ b := make([]byte, length)
+ for i := range b {
+ b[i] = charset[r.Intn(len(charset))]
+ }
+ return string(b)
+}