Skip to content

Commit

Permalink
routes: fix jwt_format_issuer (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
wasaga authored Feb 14, 2025
1 parent fec4af7 commit 3bebbe1
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 23 deletions.
14 changes: 2 additions & 12 deletions example/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ terraform {
required_providers {
pomerium = {
source = "pomerium/pomerium"
version = "0.0.7"
version = "0.0.8"
}
}
}
Expand Down Expand Up @@ -249,15 +249,5 @@ data "pomerium_route" "existing_route" {
id = pomerium_route.test_route.id
}

# Output examples
output "namespace_name" {
value = data.pomerium_namespace.existing_namespace.name
}

# output "route_from" {
# value = data.pomerium_route.existing_route.from
# }
data "pomerium_routes" "all_routes" {}

output "all_namespaces" {
value = data.pomerium_namespaces.all_namespaces.namespaces
}
62 changes: 62 additions & 0 deletions internal/provider/enum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package provider

import (
"fmt"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"google.golang.org/protobuf/reflect/protoreflect"
)

// GetValidEnumValues returns a list of valid enum values for a given protobuf enum type.
// it includes zero value as well to match its use in the current api
func GetValidEnumValues[T protoreflect.Enum]() []string {
var values []string
var v T
descriptor := v.Descriptor()
for i := 0; i < descriptor.Values().Len(); i++ {
values = append(values, string(descriptor.Values().Get(i).Name()))
}
return values
}

// EnumValueToPBWithDefault converts a string to a protobuf enum value.
func EnumValueToPBWithDefault[T interface {
~int32
protoreflect.Enum
}](
dst *T,
src types.String,
defaultValue T,
diagnostics *diag.Diagnostics,
) {
if src.IsNull() || src.ValueString() == "" {
*dst = defaultValue
return
}

var v T
enumValue := v.Descriptor().Values().ByName(protoreflect.Name(src.ValueString()))
if enumValue == nil {
diagnostics.AddError(
"InvalidEnumValue",
fmt.Sprintf("The provided %s enum value %q is not valid.", v.Descriptor().FullName(), src.ValueString()),
)
return
}

*dst = T(enumValue.Number())
}

func EnumValueFromPB[T interface {
~int32
protoreflect.Enum
}](
src T,
) types.String {
v := src.Descriptor().Values().ByNumber(protoreflect.EnumNumber(src))
if v == nil {
return types.StringNull()
}
return types.StringValue(string(v.Name()))
}
63 changes: 63 additions & 0 deletions internal/provider/enum_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package provider_test

import (
"testing"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/pomerium/enterprise-client-go/pb"
"github.com/pomerium/enterprise-terraform-provider/internal/provider"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestEnumValueToPB(t *testing.T) {
t.Parallel()

defaultValue := pb.IssuerFormat(-1)
tests := []struct {
name types.String
expect pb.IssuerFormat
expectError bool
}{
{types.StringValue("IssuerHostOnly"), pb.IssuerFormat_IssuerHostOnly, false},
{types.StringValue("IssuerURI"), pb.IssuerFormat_IssuerURI, false},
{types.StringValue("InvalidInexistentTest"), pb.IssuerFormat(-2), true},
{types.StringNull(), defaultValue, false},
{types.StringValue(""), defaultValue, false},
}

for _, tt := range tests {
t.Run(tt.name.String(), func(t *testing.T) {
var got pb.IssuerFormat
var diagnostics diag.Diagnostics
provider.EnumValueToPBWithDefault(&got, tt.name, defaultValue, &diagnostics)
if tt.expectError {
assert.True(t, diagnostics.HasError())
} else {
require.False(t, diagnostics.HasError(), diagnostics.Errors())
assert.Equal(t, tt.expect, got)
}
})
}
}

func TestEnumValueFromPB(t *testing.T) {
t.Parallel()

tests := []struct {
name pb.IssuerFormat
expect types.String
}{
{pb.IssuerFormat_IssuerHostOnly, types.StringValue("IssuerHostOnly")},
{pb.IssuerFormat_IssuerURI, types.StringValue("IssuerURI")},
{pb.IssuerFormat(-1), types.StringNull()},
}

for _, tt := range tests {
t.Run(tt.expect.String(), func(t *testing.T) {
got := provider.EnumValueFromPB(tt.name)
assert.Equal(t, tt.expect, got)
})
}
}
12 changes: 7 additions & 5 deletions internal/provider/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import (
"fmt"

"github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"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/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-log/tflog"
client "github.com/pomerium/enterprise-client-go"
Expand Down Expand Up @@ -200,11 +201,12 @@ func (r *RouteResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
Computed: true,
},
"jwt_groups_filter": JWTGroupsFilterSchema,
"jwt_issuer_format": schema.ObjectAttribute{
Description: "JWT issuer format configuration.",
"jwt_issuer_format": schema.StringAttribute{
Optional: true,
AttributeTypes: map[string]attr.Type{
"format": types.StringType,
Computed: true,
Description: "Format for JWT issuer strings. Use 'IssuerHostOnly' for hostname without scheme or trailing slash, or 'IssuerURI' for complete URI including scheme and trailing slash.",
Validators: []validator.String{
stringvalidator.OneOf(GetValidEnumValues[pb.IssuerFormat]()...),
},
},
"rewrite_response_headers": schema.SetNestedAttribute{
Expand Down
12 changes: 7 additions & 5 deletions internal/provider/route_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import (
"fmt"

"github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"

client "github.com/pomerium/enterprise-client-go"
Expand Down Expand Up @@ -167,11 +168,12 @@ func getRouteDataSourceAttributes(idRequired bool) map[string]schema.Attribute {
Description: "Show error details.",
},
"jwt_groups_filter": JWTGroupsFilterSchema,
"jwt_issuer_format": schema.ObjectAttribute{
Description: "JWT issuer format configuration.",
"jwt_issuer_format": schema.StringAttribute{
Optional: true,
Computed: true,
AttributeTypes: map[string]attr.Type{
"format": types.StringType,
Description: "Format for JWT issuer strings. Use 'IssuerHostOnly' for hostname without scheme or trailing slash, or 'IssuerURI' for complete URI including scheme and trailing slash.",
Validators: []validator.String{
stringvalidator.OneOf(GetValidEnumValues[pb.IssuerFormat]()...),
},
},
"rewrite_response_headers": schema.SetNestedAttribute{
Expand Down
4 changes: 3 additions & 1 deletion internal/provider/route_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type RouteModel struct {
IDPClientID types.String `tfsdk:"idp_client_id"`
IDPClientSecret types.String `tfsdk:"idp_client_secret"`
JWTGroupsFilter types.Object `tfsdk:"jwt_groups_filter"`
JWTIssuerFormat types.Object `tfsdk:"jwt_issuer_format"`
JWTIssuerFormat types.String `tfsdk:"jwt_issuer_format"`
KubernetesServiceAccountToken types.String `tfsdk:"kubernetes_service_account_token"`
KubernetesServiceAccountTokenFile types.String `tfsdk:"kubernetes_service_account_token_file"`
LogoURL types.String `tfsdk:"logo_url"`
Expand Down Expand Up @@ -174,6 +174,7 @@ func ConvertRouteToPB(
pbRoute.EnableGoogleCloudServerlessAuthentication = src.EnableGoogleCloudServerlessAuthentication.ValueBool()
}
pbRoute.KubernetesServiceAccountTokenFile = src.KubernetesServiceAccountTokenFile.ValueStringPointer()
EnumValueToPBWithDefault(&pbRoute.JwtIssuerFormat, src.JWTIssuerFormat, pb.IssuerFormat_IssuerHostOnly, &diagnostics)

pbRoute.RewriteResponseHeaders = rewriteHeadersToPB(src.RewriteResponseHeaders)

Expand Down Expand Up @@ -232,6 +233,7 @@ func ConvertRouteFromPB(
}
dst.KubernetesServiceAccountTokenFile = types.StringPointerValue(src.KubernetesServiceAccountTokenFile)

dst.JWTIssuerFormat = EnumValueFromPB(src.JwtIssuerFormat)
dst.RewriteResponseHeaders = rewriteHeadersFromPB(src.RewriteResponseHeaders)

return diagnostics
Expand Down
85 changes: 85 additions & 0 deletions internal/provider/route_model_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package provider_test

import (
"context"
"testing"

"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/pomerium/enterprise-client-go/pb"
"github.com/pomerium/enterprise-terraform-provider/internal/provider"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestConvertRouteFromPB(t *testing.T) {
t.Run("jwt_issuer_format", func(t *testing.T) {
testCases := []struct {
name string
input pb.IssuerFormat
expected string
isNull bool
}{
{
name: "host_only",
input: pb.IssuerFormat_IssuerHostOnly,
expected: "IssuerHostOnly",
},
{
name: "uri",
input: pb.IssuerFormat_IssuerURI,
expected: "IssuerURI",
},
{
name: "invalid value",
input: pb.IssuerFormat(999),
isNull: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
m := &provider.RouteModel{}
r := &pb.Route{
JwtIssuerFormat: tc.input,
}
diags := provider.ConvertRouteFromPB(m, r)
require.False(t, diags.HasError())
if tc.isNull {
assert.True(t, m.JWTIssuerFormat.IsNull())
} else {
assert.Equal(t, tc.expected, m.JWTIssuerFormat.ValueString())
}
})
}
})
}

func TestConvertRouteToPB(t *testing.T) {
t.Run("jwt_issuer_format", func(t *testing.T) {
testCases := []struct {
name string
input string
expected pb.IssuerFormat
expectError bool
}{
{"host_only", "IssuerHostOnly", pb.IssuerFormat_IssuerHostOnly, false},
{"uri", "IssuerURI", pb.IssuerFormat_IssuerURI, false},
{"invalid_value", "invalid_value", -1, true},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
m := &provider.RouteModel{
JWTIssuerFormat: types.StringValue(tc.input),
}
r, diag := provider.ConvertRouteToPB(context.Background(), m)
if tc.expectError {
require.True(t, diag.HasError())
} else {
require.False(t, diag.HasError())
assert.Equal(t, tc.expected, r.JwtIssuerFormat)
}
})
}
})
}
2 changes: 2 additions & 0 deletions internal/provider/route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func TestConvertRoute(t *testing.T) {
EnableGoogleCloudServerlessAuthentication: true,
TlsCustomCaKeyPairId: P("custom-ca-1"),
KubernetesServiceAccountTokenFile: P("/path/to/token"),
JwtIssuerFormat: pb.IssuerFormat_IssuerURI,
}

var actual provider.RouteResourceModel
Expand Down Expand Up @@ -108,6 +109,7 @@ func TestConvertRoute(t *testing.T) {
EnableGoogleCloudServerlessAuthentication: types.BoolValue(true),
TLSCustomCAKeyPairID: types.StringValue("custom-ca-1"),
KubernetesServiceAccountTokenFile: types.StringValue("/path/to/token"),
JWTIssuerFormat: types.StringValue("IssuerURI"),
}

if diff := cmp.Diff(expected, actual); diff != "" {
Expand Down

0 comments on commit 3bebbe1

Please sign in to comment.