Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add codec_server attribute to Namespace #53

Merged
merged 1 commit into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/resources/namespace.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ description: |-
### Optional

- `certificate_filters` (Attributes List) (see [below for nested schema](#nestedatt--certificate_filters))
- `codec_server` (Attributes) (see [below for nested schema](#nestedatt--codec_server))
- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))

### Read-Only
Expand All @@ -42,6 +43,19 @@ Optional:
- `subject_alternative_name` (String)


<a id="nestedatt--codec_server"></a>
### Nested Schema for `codec_server`

Required:

- `endpoint` (String)

Optional:

- `include_cross_origin_credentials` (Boolean)
- `pass_access_token` (Boolean)


<a id="nestedblock--timeouts"></a>
### Nested Schema for `timeouts`

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,5 @@ require (
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
4 changes: 3 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyX
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
Expand Down Expand Up @@ -265,8 +266,9 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
77 changes: 77 additions & 0 deletions internal/provider/namespace_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/diag"
"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/types"
Expand All @@ -58,6 +59,7 @@ type (
AcceptedClientCA types.String `tfsdk:"accepted_client_ca"`
RetentionDays types.Int64 `tfsdk:"retention_days"`
CertificateFilters types.List `tfsdk:"certificate_filters"`
CodecServer types.Object `tfsdk:"codec_server"`

Timeouts timeouts.Value `tfsdk:"timeouts"`
}
Expand All @@ -68,6 +70,12 @@ type (
OrganizationalUnit types.String `tfsdk:"organizational_unit"`
SubjectAlternativeName types.String `tfsdk:"subject_alternative_name"`
}

codecServerModel struct {
Endpoint types.String `tfsdk:"endpoint"`
PassAccessToken types.Bool `tfsdk:"pass_access_token"`
IncludeCrossOriginCredentials types.Bool `tfsdk:"include_cross_origin_credentials"`
}
)

var (
Expand All @@ -80,6 +88,12 @@ var (
"organizational_unit": types.StringType,
"subject_alternative_name": types.StringType,
}

codecServerAttrs = map[string]attr.Type{
"endpoint": types.StringType,
"pass_access_token": types.BoolType,
"include_cross_origin_credentials": types.BoolType,
}
)

func NewNamespaceResource() resource.Resource {
Expand Down Expand Up @@ -151,6 +165,24 @@ func (r *namespaceResource) Schema(ctx context.Context, _ resource.SchemaRequest
},
},
},
"codec_server": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
"endpoint": schema.StringAttribute{
Required: true,
},
"pass_access_token": schema.BoolAttribute{
Computed: true,
Default: booldefault.StaticBool(false),
Optional: true,
},
"include_cross_origin_credentials": schema.BoolAttribute{
Computed: true,
Default: booldefault.StaticBool(false),
Optional: true,
},
},
Optional: true,
},
},
Blocks: map[string]schema.Block{
"timeouts": timeouts.Block(ctx, timeouts.Opts{
Expand Down Expand Up @@ -186,6 +218,13 @@ func (r *namespaceResource) Create(ctx context.Context, req resource.CreateReque
if resp.Diagnostics.HasError() {
return
}
var codecServer *namespacev1.CodecServerSpec
if !plan.CodecServer.IsNull() {
codecServer = getCodecServerFromModel(ctx, resp.Diagnostics, &plan)
if resp.Diagnostics.HasError() {
return
}
}
svcResp, err := r.client.CreateNamespace(ctx, &cloudservicev1.CreateNamespaceRequest{
Spec: &namespacev1.NamespaceSpec{
Name: plan.Name.ValueString(),
Expand All @@ -195,6 +234,7 @@ func (r *namespaceResource) Create(ctx context.Context, req resource.CreateReque
AcceptedClientCa: plan.AcceptedClientCA.ValueString(),
CertificateFilters: certFilters,
},
CodecServer: codecServer,
},
})
if err != nil {
Expand Down Expand Up @@ -260,6 +300,10 @@ func (r *namespaceResource) Update(ctx context.Context, req resource.UpdateReque
resp.Diagnostics.AddError("Failed to get current resource version", err.Error())
return
}
codecServer := getCodecServerFromModel(ctx, resp.Diagnostics, &plan)
if resp.Diagnostics.HasError() {
return
}
svcResp, err := r.client.UpdateNamespace(ctx, &cloudservicev1.UpdateNamespaceRequest{
Namespace: plan.ID.ValueString(),
Spec: &namespacev1.NamespaceSpec{
Expand All @@ -270,6 +314,7 @@ func (r *namespaceResource) Update(ctx context.Context, req resource.UpdateReque
AcceptedClientCa: plan.AcceptedClientCA.ValueString(),
CertificateFilters: certFilters,
},
CodecServer: codecServer,
},
ResourceVersion: resourceVersion,
})
Expand Down Expand Up @@ -380,6 +425,25 @@ func updateModelFromSpec(ctx context.Context, diags diag.Diagnostics, state *nam
certificateFilter = filters
}

var codecServerState basetypes.ObjectValue
// The API always returns a non-empty CodecServerSpec, even if it wasn't specified on object creation. We explicitly
// map an endpoint whose value is the empty string to `null`, since an empty endpoint implies that the codec server
// was not set via config.
if ns.GetSpec().GetCodecServer().GetEndpoint() != "" {
codecServer := &codecServerModel{
Endpoint: stringOrNull(ns.GetSpec().GetCodecServer().GetEndpoint()),
PassAccessToken: types.BoolValue(ns.GetSpec().GetCodecServer().GetPassAccessToken()),
IncludeCrossOriginCredentials: types.BoolValue(ns.GetSpec().GetCodecServer().GetIncludeCrossOriginCredentials()),
}

state, objectDiags := types.ObjectValueFrom(ctx, codecServerAttrs, codecServer)
diags.Append(objectDiags...)
codecServerState = state
} else {
codecServerState = types.ObjectNull(codecServerAttrs)
}

state.CodecServer = codecServerState
state.Regions = planRegions
state.CertificateFilters = certificateFilter
state.AcceptedClientCA = types.StringValue(ns.GetSpec().GetMtlsAuth().GetAcceptedClientCa())
Expand Down Expand Up @@ -427,6 +491,19 @@ func getCurrentResourceVersion(ctx context.Context, client cloudservicev1.CloudS
return ns.GetNamespace().GetResourceVersion(), nil
}

func getCodecServerFromModel(ctx context.Context, diags diag.Diagnostics, model *namespaceResourceModel) *namespacev1.CodecServerSpec {
var codecServer codecServerModel
diags.Append(model.CodecServer.As(ctx, &codecServer, basetypes.ObjectAsOptions{})...)
if diags.HasError() {
return nil
}
return &namespacev1.CodecServerSpec{
Endpoint: codecServer.Endpoint.ValueString(),
PassAccessToken: codecServer.PassAccessToken.ValueBool(),
IncludeCrossOriginCredentials: codecServer.IncludeCrossOriginCredentials.ValueBool(),
}
}

func stringOrNull(s string) types.String {
if s == "" {
return types.StringNull()
Expand Down
161 changes: 161 additions & 0 deletions internal/provider/namespace_resource_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
package provider

import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"math/rand"
"os"
"testing"
"text/template"
"time"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"

"github.com/temporalio/terraform-provider-temporalcloud/internal/client"
cloudservicev1 "github.com/temporalio/terraform-provider-temporalcloud/proto/go/temporal/api/cloud/cloudservice/v1"
)

func TestAccBasicNamespace(t *testing.T) {
Expand Down Expand Up @@ -114,6 +124,157 @@ PEM
})
}

func TestAccNamespaceWithCodecServer(t *testing.T) {
type (
codecServer struct {
Endpoint string
PassAccessToken bool
IncludeCrossOriginCredentials bool
}

configArgs struct {
Name string
RetentionDays int
CodecServer *codecServer
}
)

t.Parallel()
name := fmt.Sprintf("%s-%s", "tf-codec-server", randomString(10))
tmpl := template.Must(template.New("config").Parse(`
provider "temporalcloud" {

}

resource "temporalcloud_namespace" "test" {
name = "{{ .Name }}"
regions = ["aws-us-east-1"]
accepted_client_ca = base64encode(<<PEM
-----BEGIN CERTIFICATE-----
MIIByTCCAVCgAwIBAgIRAWHkC+6JUf3s9Tq43mdp2zgwCgYIKoZIzj0EAwMwEzER
MA8GA1UEChMIdGVtcG9yYWwwHhcNMjMwODEwMDAwOTQ1WhcNMjQwODA5MDAxMDQ1
WjATMREwDwYDVQQKEwh0ZW1wb3JhbDB2MBAGByqGSM49AgEGBSuBBAAiA2IABCzQ
7DwwGSQKM6Zrx3Qtw7IubfxiJ3RSXCqmcGhEbFVeocwAdEgMYlwSlUiWtDZVR2dM
XM9UZLWK4aGGnDNS5Mhcz6ibSBS7Owf4tRZZA9SpFCjNw2HraaiUVV+EUgxoe6No
MGYwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFG4N
8lIXqQKxwVs/ixVzdF6XGZm+MCQGA1UdEQQdMBuCGWNsaWVudC5yb290LnRlbXBv
cmFsLlB1VHMwCgYIKoZIzj0EAwMDZwAwZAIwRLfm9S7rKGd30KdQvUMcOcDJlmDw
6/oM6UOJFxLeGcpYbgxQ/bFize+Yx9Q9kNeMAjA7GiFsaipaKtWHy5MCOCas3ZP6
+ttLaXNXss3Z5Wk5vhDQnyE8JR3rPeQ2cHXLiA0=
-----END CERTIFICATE-----
PEM
)

retention_days = {{ .RetentionDays }}

{{ with .CodecServer }}
codec_server = {
endpoint = "{{ .Endpoint }}"
pass_access_token = {{ .PassAccessToken }}
include_cross_origin_credentials = {{ .IncludeCrossOriginCredentials }}
}
{{ end }}
}`))

config := func(args configArgs) string {
var buf bytes.Buffer
writer := bufio.NewWriter(&buf)
if err := tmpl.Execute(writer, args); err != nil {
t.Errorf("failed to execute template: %v", err)
t.FailNow()
}

writer.Flush()
return buf.String()
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: config(configArgs{
Name: name,
RetentionDays: 7,
}),
},
{
Config: config(configArgs{
Name: name,
RetentionDays: 7,
CodecServer: &codecServer{
Endpoint: "https://example.com",
PassAccessToken: true,
IncludeCrossOriginCredentials: true,
},
}),
Check: func(s *terraform.State) error {
id := s.RootModule().Resources["temporalcloud_namespace.test"].Primary.Attributes["id"]
conn := newConnection(t)
ns, err := conn.GetNamespace(context.Background(), &cloudservicev1.GetNamespaceRequest{
Namespace: id,
})
if err != nil {
return fmt.Errorf("failed to get namespace: %v", err)
}

spec := ns.Namespace.GetSpec()
if spec.GetCodecServer().GetEndpoint() != "https://example.com" {
return fmt.Errorf("unexpected endpoint: %s", spec.GetCodecServer().GetEndpoint())
}
if !spec.GetCodecServer().GetPassAccessToken() {
return errors.New("expected pass_access_token to be true")
}
if !spec.GetCodecServer().GetIncludeCrossOriginCredentials() {
return errors.New("expected include_cross_origin_credentials to be true")
}
return nil
},
},
{
Config: config(configArgs{
Name: name,
RetentionDays: 7,
}),
Check: func(s *terraform.State) error {
id := s.RootModule().Resources["temporalcloud_namespace.test"].Primary.Attributes["id"]
conn := newConnection(t)
ns, err := conn.GetNamespace(context.Background(), &cloudservicev1.GetNamespaceRequest{
Namespace: id,
})
if err != nil {
return fmt.Errorf("failed to get namespace: %v", err)
}

spec := ns.Namespace.GetSpec()
if spec.GetCodecServer().GetEndpoint() != "" {
return fmt.Errorf("unexpected endpoint: %s", spec.GetCodecServer().GetEndpoint())
}
return nil
},
},
// Delete testing automatically occurs in TestCase
},
})

}

func newConnection(t *testing.T) cloudservicev1.CloudServiceClient {
apiKey := os.Getenv("TEMPORAL_CLOUD_API_KEY")
endpoint := os.Getenv("TEMPORAL_CLOUD_ENDPOINT")
if endpoint == "" {
endpoint = "saas-api.tmprl.cloud:443"
}
allowInsecure := os.Getenv("TEMPORAL_CLOUD_ALLOW_INSECURE") == "true"

client, err := client.NewConnectionWithAPIKey(endpoint, allowInsecure, apiKey)
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}

return client
}

func randomString(length int) string {
r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
const charset = "abcdefghijklmnopqrstuvwxyz"
Expand Down
Loading