From fe096f60f9e97488c8c5961b121971f691598236 Mon Sep 17 00:00:00 2001 From: Wes Medford Date: Thu, 12 Dec 2024 10:30:46 -0700 Subject: [PATCH 01/11] (feat): Add data sources for all resources, and add service account resource. --- example/main.tf | 33 ++- internal/provider/namespace_data_source.go | 100 +++++++ internal/provider/policy_data_source.go | 106 ++++++++ internal/provider/provider.go | 8 +- internal/provider/route_data_source.go | 120 +++++++++ internal/provider/service_account.go | 246 ++++++++++++++++++ .../provider/service_account_data_source.go | 118 +++++++++ 7 files changed, 721 insertions(+), 10 deletions(-) create mode 100644 internal/provider/namespace_data_source.go create mode 100644 internal/provider/policy_data_source.go create mode 100644 internal/provider/route_data_source.go create mode 100644 internal/provider/service_account.go create mode 100644 internal/provider/service_account_data_source.go diff --git a/example/main.tf b/example/main.tf index cba84f1..412efc5 100644 --- a/example/main.tf +++ b/example/main.tf @@ -14,18 +14,15 @@ provider "pomerium" { shared_secret_b64 = "9OkZR6hwfmVD3a7Sfmgq58lUbFJGGz4hl/R9xbHFCAg=" } -# resource "pomerium_namespace" "test_namespace" { -# name = "test-namespace" -# parent_id = "9d8dbd2c-8cce-4e66-9c1f-c490b4a07243" -# } - -locals { - namespace_id = "9d8dbd2c-8cce-4e66-9c1f-c490b4a07243" +# Create resources +resource "pomerium_namespace" "test_namespace" { + name = "test-namespace" + parent_id = "9d8dbd2c-8cce-4e66-9c1f-c490b4a07243" } resource "pomerium_policy" "test_policy" { name = "test-policy" - namespace_id = local.namespace_id + namespace_id = pomerium_namespace.test_namespace.id ppl = < Date: Thu, 12 Dec 2024 10:40:43 -0700 Subject: [PATCH 02/11] (fix) typo --- internal/provider/service_account.go | 8 ++++---- internal/provider/service_account_data_source.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/provider/service_account.go b/internal/provider/service_account.go index d247e42..4a1879b 100644 --- a/internal/provider/service_account.go +++ b/internal/provider/service_account.go @@ -111,7 +111,7 @@ func (r *ServiceAccountResource) Create(ctx context.Context, req resource.Create return } - respServiceAccount, err := r.client.ServiceAccountService.AddPomeriumServiceAccount(ctx, &pb.AddPomeriumServiceAccountRequest{ + respServiceAccount, err := r.client.PomeriumServiceAccountService.AddPomeriumServiceAccount(ctx, &pb.AddPomeriumServiceAccountRequest{ ServiceAccount: pbServiceAccount, }) if err != nil { @@ -137,7 +137,7 @@ func (r *ServiceAccountResource) Read(ctx context.Context, req resource.ReadRequ return } - respServiceAccount, err := r.client.ServiceAccountService.GetPomeriumServiceAccount(ctx, &pb.GetPomeriumServiceAccountRequest{ + respServiceAccount, err := r.client.PomeriumServiceAccountService.GetPomeriumServiceAccount(ctx, &pb.GetPomeriumServiceAccountRequest{ Id: state.ID.ValueString(), }) if err != nil { @@ -168,7 +168,7 @@ func (r *ServiceAccountResource) Update(ctx context.Context, req resource.Update return } - _, err := r.client.ServiceAccountService.SetPomeriumServiceAccount(ctx, &pb.SetPomeriumServiceAccountRequest{ + _, err := r.client.PomeriumServiceAccountService.SetPomeriumServiceAccount(ctx, &pb.SetPomeriumServiceAccountRequest{ ServiceAccount: pbServiceAccount, }) if err != nil { @@ -187,7 +187,7 @@ func (r *ServiceAccountResource) Delete(ctx context.Context, req resource.Delete return } - _, err := r.client.ServiceAccountService.DeletePomeriumServiceAccount(ctx, &pb.DeletePomeriumServiceAccountRequest{ + _, err := r.client.PomeriumServiceAccountService.DeletePomeriumServiceAccount(ctx, &pb.DeletePomeriumServiceAccountRequest{ Id: state.ID.ValueString(), }) if err != nil { diff --git a/internal/provider/service_account_data_source.go b/internal/provider/service_account_data_source.go index d58863b..2cee01b 100644 --- a/internal/provider/service_account_data_source.go +++ b/internal/provider/service_account_data_source.go @@ -93,7 +93,7 @@ func (d *ServiceAccountDataSource) Read(ctx context.Context, req datasource.Read return } - serviceAccountResp, err := d.client.ServiceAccountService.GetPomeriumServiceAccount(ctx, &pb.GetPomeriumServiceAccountRequest{ + serviceAccountResp, err := d.client.PomeriumServiceAccountService.GetPomeriumServiceAccount(ctx, &pb.GetPomeriumServiceAccountRequest{ Id: data.ID.ValueString(), }) if err != nil { From 6b25df9e22d4c2271648d0db59137bc47cfdebde Mon Sep 17 00:00:00 2001 From: Wes Medford Date: Thu, 12 Dec 2024 11:03:34 -0700 Subject: [PATCH 03/11] (feat) add data source for all namespaces. --- internal/provider/namespaces_data_source.go | 108 ++++++++++++++++++++ internal/provider/provider.go | 1 + 2 files changed, 109 insertions(+) create mode 100644 internal/provider/namespaces_data_source.go diff --git a/internal/provider/namespaces_data_source.go b/internal/provider/namespaces_data_source.go new file mode 100644 index 0000000..83af9c1 --- /dev/null +++ b/internal/provider/namespaces_data_source.go @@ -0,0 +1,108 @@ +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/types" + + client "github.com/pomerium/enterprise-client-go" + "github.com/pomerium/enterprise-client-go/pb" +) + +var _ datasource.DataSource = &NamespacesDataSource{} + +func NewNamespacesDataSource() datasource.DataSource { + return &NamespacesDataSource{} +} + +type NamespacesDataSource struct { + client *client.Client +} + +type NamespaceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + ParentID types.String `tfsdk:"parent_id"` +} + +type NamespacesDataSourceModel struct { + Namespaces []NamespaceModel `tfsdk:"namespaces"` +} + +func (d *NamespacesDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_namespaces" +} + +func (d *NamespacesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "List all namespaces", + + Attributes: map[string]schema.Attribute{ + "namespaces": schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique identifier for the namespace.", + }, + "name": schema.StringAttribute{ + Computed: true, + Description: "Name of the namespace.", + }, + "parent_id": schema.StringAttribute{ + Computed: true, + Description: "ID of the parent namespace.", + }, + }, + }, + }, + }, + } +} + +func (d *NamespacesDataSource) Configure(_ 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.", req.ProviderData), + ) + return + } + + d.client = client +} + +func (d *NamespacesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data NamespacesDataSourceModel + + namespacesResp, err := d.client.NamespaceService.ListNamespaces(ctx, &pb.ListNamespacesRequest{}) + if err != nil { + resp.Diagnostics.AddError("Error reading namespaces", err.Error()) + return + } + + namespaces := make([]NamespaceModel, 0, len(namespacesResp.Namespaces)) + for _, ns := range namespacesResp.Namespaces { + var namespace NamespaceModel + namespace.ID = types.StringValue(ns.Id) + namespace.Name = types.StringValue(ns.Name) + if ns.ParentId != "" { + namespace.ParentID = types.StringValue(ns.ParentId) + } else { + namespace.ParentID = types.StringNull() + } + namespaces = append(namespaces, namespace) + } + + data.Namespaces = namespaces + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 3b8e827..0a5ae95 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -132,6 +132,7 @@ func (p *PomeriumProvider) DataSources(_ context.Context) []func() datasource.Da NewRouteDataSource, NewNamespaceDataSource, NewPolicyDataSource, + NewNamespacesDataSource, } } From f37b089c5ce54c37298b1cd8e4c2455e7e789cd3 Mon Sep 17 00:00:00 2001 From: Wes Medford Date: Thu, 12 Dec 2024 11:08:22 -0700 Subject: [PATCH 04/11] (lint) fix lint --- internal/provider/namespaces_data_source.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/namespaces_data_source.go b/internal/provider/namespaces_data_source.go index 83af9c1..2548921 100644 --- a/internal/provider/namespaces_data_source.go +++ b/internal/provider/namespaces_data_source.go @@ -81,7 +81,7 @@ func (d *NamespacesDataSource) Configure(_ context.Context, req datasource.Confi d.client = client } -func (d *NamespacesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { +func (d *NamespacesDataSource) Read(ctx context.Context, _ datasource.ReadRequest, resp *datasource.ReadResponse) { var data NamespacesDataSourceModel namespacesResp, err := d.client.NamespaceService.ListNamespaces(ctx, &pb.ListNamespacesRequest{}) From dd9d742eab8095d0fbe027c5b0ef40383cc734fa Mon Sep 17 00:00:00 2001 From: Wes Medford Date: Thu, 12 Dec 2024 11:46:45 -0700 Subject: [PATCH 05/11] (refactor) DRYer code. --- internal/provider/helpers.go | 49 ++++++ internal/provider/models.go | 40 +++++ internal/provider/namespace.go | 52 +------ internal/provider/namespace_data_source.go | 23 +-- internal/provider/namespaces_data_source.go | 6 - internal/provider/policies_data_source.go | 103 ++++++++++++ internal/provider/policy.go | 36 +---- internal/provider/policy_data_source.go | 8 +- internal/provider/provider.go | 5 +- internal/provider/route.go | 49 +----- internal/provider/route_data_source.go | 9 +- internal/provider/routes_data_source.go | 114 ++++++++++++++ internal/provider/schemas.go | 147 ++++++++++++++++++ internal/provider/service_account.go | 46 +----- .../provider/service_account_data_source.go | 40 +---- .../provider/service_accounts_data_source.go | 111 +++++++++++++ 16 files changed, 585 insertions(+), 253 deletions(-) create mode 100644 internal/provider/helpers.go create mode 100644 internal/provider/models.go create mode 100644 internal/provider/policies_data_source.go create mode 100644 internal/provider/routes_data_source.go create mode 100644 internal/provider/schemas.go create mode 100644 internal/provider/service_accounts_data_source.go diff --git a/internal/provider/helpers.go b/internal/provider/helpers.go new file mode 100644 index 0000000..c567a73 --- /dev/null +++ b/internal/provider/helpers.go @@ -0,0 +1,49 @@ +package provider + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + client "github.com/pomerium/enterprise-client-go" +) + +// ConfigureClient is a helper to configure resources and data sources with the API client +func ConfigureClient(req any, resp any, typeName string) *client.Client { + var providerData any + switch r := req.(type) { + case datasource.ConfigureRequest: + providerData = r.ProviderData + case resource.ConfigureRequest: + providerData = r.ProviderData + } + + if providerData == nil { + return nil + } + + client, ok := providerData.(*client.Client) + if !ok { + switch r := resp.(type) { + case *datasource.ConfigureResponse: + r.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *client.Client, got: %T.", providerData), + ) + case *resource.ConfigureResponse: + r.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *client.Client, got: %T.", providerData), + ) + } + return nil + } + + return client +} + +// ImportStatePassthroughID is a helper that implements the common import state pattern +func ImportStatePassthroughID(req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(nil, path.Root("id"), req, resp) +} diff --git a/internal/provider/models.go b/internal/provider/models.go new file mode 100644 index 0000000..1bea4eb --- /dev/null +++ b/internal/provider/models.go @@ -0,0 +1,40 @@ +package provider + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// ServiceAccountModel represents the shared model for service account resources and data sources +type ServiceAccountModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + NamespaceID types.String `tfsdk:"namespace_id"` + Description types.String `tfsdk:"description"` + UserID types.String `tfsdk:"user_id"` + ExpiresAt types.String `tfsdk:"expires_at"` +} + +// NamespaceModel represents the shared model for namespace resources and data sources +type NamespaceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + ParentID types.String `tfsdk:"parent_id"` +} + +// RouteModel represents the shared model for route resources and data sources +type RouteModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + From types.String `tfsdk:"from"` + To types.List `tfsdk:"to"` + NamespaceID types.String `tfsdk:"namespace_id"` + Policies types.List `tfsdk:"policies"` +} + +// PolicyModel represents the shared model for policy resources and data sources +type PolicyModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + NamespaceID types.String `tfsdk:"namespace_id"` + PPL types.String `tfsdk:"ppl"` +} diff --git a/internal/provider/namespace.go b/internal/provider/namespace.go index d4a66c4..3817705 100644 --- a/internal/provider/namespace.go +++ b/internal/provider/namespace.go @@ -2,14 +2,9 @@ package provider import ( "context" - "fmt" "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/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -34,55 +29,18 @@ type NamespaceResource struct { } // NamespaceResourceModel describes the resource data model. -type NamespaceResourceModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - ParentID types.String `tfsdk:"parent_id"` -} +type NamespaceResourceModel = NamespaceModel func (r *NamespaceResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_namespace" } func (r *NamespaceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - MarkdownDescription: "Namespace resource for Pomerium.", - - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "Unique identifier for the namespace.", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the namespace.", - Required: true, - }, - "parent_id": schema.StringAttribute{ - Description: "ID of the parent namespace (optional).", - Optional: true, - }, - }, - } + resp.Schema = NamespaceSchema(false) } func (r *NamespaceResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - if req.ProviderData == nil { - return - } - - c, ok := req.ProviderData.(*client.Client) - if !ok { - resp.Diagnostics.AddError( - "Unexpected Provider Data Type", - fmt.Sprintf("Expected *client.Client, got: %T.", req.ProviderData), - ) - return - } - - r.client = c + r.client = ConfigureClient(req, resp, "namespace") } func (r *NamespaceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -186,8 +144,8 @@ func (r *NamespaceResource) Delete(ctx context.Context, req resource.DeleteReque resp.State.RemoveResource(ctx) } -func (r *NamespaceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +func (r *NamespaceResource) ImportState(_ context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + ImportStatePassthroughID(req, resp) } func ConvertNamespaceToPB(_ context.Context, src *NamespaceResourceModel) (*pb.Namespace, diag.Diagnostics) { diff --git a/internal/provider/namespace_data_source.go b/internal/provider/namespace_data_source.go index 86f32a2..1d2232e 100644 --- a/internal/provider/namespace_data_source.go +++ b/internal/provider/namespace_data_source.go @@ -2,11 +2,9 @@ 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/types" client "github.com/pomerium/enterprise-client-go" "github.com/pomerium/enterprise-client-go/pb" @@ -22,11 +20,7 @@ type NamespaceDataSource struct { client *client.Client } -type NamespaceDataSourceModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - ParentID types.String `tfsdk:"parent_id"` -} +type NamespaceDataSourceModel = NamespaceModel func (d *NamespaceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_namespace" @@ -54,20 +48,7 @@ func (d *NamespaceDataSource) Schema(_ context.Context, _ datasource.SchemaReque } func (d *NamespaceDataSource) Configure(_ 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.", req.ProviderData), - ) - return - } - - d.client = client + d.client = ConfigureClient(req, resp, "namespace") } func (d *NamespaceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { diff --git a/internal/provider/namespaces_data_source.go b/internal/provider/namespaces_data_source.go index 2548921..8398091 100644 --- a/internal/provider/namespaces_data_source.go +++ b/internal/provider/namespaces_data_source.go @@ -22,12 +22,6 @@ type NamespacesDataSource struct { client *client.Client } -type NamespaceModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - ParentID types.String `tfsdk:"parent_id"` -} - type NamespacesDataSourceModel struct { Namespaces []NamespaceModel `tfsdk:"namespaces"` } diff --git a/internal/provider/policies_data_source.go b/internal/provider/policies_data_source.go new file mode 100644 index 0000000..5ffaa08 --- /dev/null +++ b/internal/provider/policies_data_source.go @@ -0,0 +1,103 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + + client "github.com/pomerium/enterprise-client-go" + "github.com/pomerium/enterprise-client-go/pb" +) + +var _ datasource.DataSource = &PoliciesDataSource{} + +func NewPoliciesDataSource() datasource.DataSource { + return &PoliciesDataSource{} +} + +type PoliciesDataSource struct { + client *client.Client +} + +type PoliciesDataSourceModel struct { + Policies []PolicyModel `tfsdk:"policies"` +} + +func (d *PoliciesDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_policies" +} + +func (d *PoliciesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "List all policies", + + Attributes: map[string]schema.Attribute{ + "policies": schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique identifier for the policy.", + }, + "name": schema.StringAttribute{ + Computed: true, + Description: "Name of the policy.", + }, + "namespace_id": schema.StringAttribute{ + Computed: true, + Description: "ID of the namespace the policy belongs to.", + }, + "ppl": schema.StringAttribute{ + Computed: true, + Description: "Policy Policy Language (PPL) string.", + }, + }, + }, + }, + }, + } +} + +func (d *PoliciesDataSource) Configure(_ 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.", req.ProviderData), + ) + return + } + + d.client = client +} + +func (d *PoliciesDataSource) Read(ctx context.Context, _ datasource.ReadRequest, resp *datasource.ReadResponse) { + var data PoliciesDataSourceModel + + policiesResp, err := d.client.PolicyService.ListPolicies(ctx, &pb.ListPoliciesRequest{}) + if err != nil { + resp.Diagnostics.AddError("Error reading policies", err.Error()) + return + } + + policies := make([]PolicyModel, 0, len(policiesResp.Policies)) + for _, policy := range policiesResp.Policies { + var policyModel PolicyModel + diags := ConvertPolicyFromPB(&policyModel, policy) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + policies = append(policies, policyModel) + } + + data.Policies = policies + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/policy.go b/internal/provider/policy.go index 4b07cb0..206ca8f 100644 --- a/internal/provider/policy.go +++ b/internal/provider/policy.go @@ -7,9 +7,6 @@ import ( "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/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -34,43 +31,14 @@ type PolicyResource struct { } // PolicyResourceModel describes the resource data model. -type PolicyResourceModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - NamespaceID types.String `tfsdk:"namespace_id"` - PPL types.String `tfsdk:"ppl"` -} +type PolicyResourceModel = PolicyModel func (r *PolicyResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_policy" } func (r *PolicyResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - MarkdownDescription: "Policy resource for Pomerium.", - - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "Unique identifier for the policy.", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the policy.", - Required: true, - }, - "namespace_id": schema.StringAttribute{ - Description: "ID of the namespace the policy belongs to.", - Required: true, - }, - "ppl": schema.StringAttribute{ - Description: "Policy Policy Language (PPL) string.", - Required: true, - }, - }, - } + resp.Schema = PolicySchema(false) } func (r *PolicyResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { diff --git a/internal/provider/policy_data_source.go b/internal/provider/policy_data_source.go index 250673a..3b079de 100644 --- a/internal/provider/policy_data_source.go +++ b/internal/provider/policy_data_source.go @@ -6,7 +6,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" client "github.com/pomerium/enterprise-client-go" "github.com/pomerium/enterprise-client-go/pb" @@ -22,12 +21,7 @@ type PolicyDataSource struct { client *client.Client } -type PolicyDataSourceModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - NamespaceID types.String `tfsdk:"namespace_id"` - PPL types.String `tfsdk:"ppl"` -} +type PolicyDataSourceModel = PolicyModel func (d *PolicyDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_policy" diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 0a5ae95..7856499 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -129,10 +129,13 @@ func (p *PomeriumProvider) Resources(_ context.Context) []func() resource.Resour func (p *PomeriumProvider) DataSources(_ context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ NewServiceAccountDataSource, + NewServiceAccountsDataSource, NewRouteDataSource, + NewRoutesDataSource, NewNamespaceDataSource, - NewPolicyDataSource, NewNamespacesDataSource, + NewPolicyDataSource, + NewPoliciesDataSource, } } diff --git a/internal/provider/route.go b/internal/provider/route.go index 2c53875..cd50bad 100644 --- a/internal/provider/route.go +++ b/internal/provider/route.go @@ -8,9 +8,6 @@ import ( "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/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" client "github.com/pomerium/enterprise-client-go" @@ -33,56 +30,14 @@ type RouteResource struct { } // RouteResourceModel describes the resource data model. -type RouteResourceModel struct { - From types.String `tfsdk:"from"` - To types.List `tfsdk:"to"` - Name types.String `tfsdk:"name"` - ID types.String `tfsdk:"id"` - NamespaceID types.String `tfsdk:"namespace_id"` - Policies types.List `tfsdk:"policies"` -} +type RouteResourceModel = RouteModel func (r *RouteResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_route" } func (r *RouteResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - // This description is used by the documentation generator and the language server. - MarkdownDescription: "Route", - - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "Route ID", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Route name", - Required: true, - }, - "from": schema.StringAttribute{ - Description: "From URL", - Required: true, - }, - "to": schema.ListAttribute{ - ElementType: types.StringType, - Description: "To URL", - Required: true, - }, - "namespace_id": schema.StringAttribute{ - Description: "ID of the namespace the route belongs to", - Required: true, - }, - "policies": schema.ListAttribute{ - ElementType: types.StringType, - Description: "List of policy IDs to associate with the route", - Optional: true, - }, - }, - } + resp.Schema = RouteSchema(false) } func (r *RouteResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { diff --git a/internal/provider/route_data_source.go b/internal/provider/route_data_source.go index 1ecdfe2..b070b4e 100644 --- a/internal/provider/route_data_source.go +++ b/internal/provider/route_data_source.go @@ -22,14 +22,7 @@ type RouteDataSource struct { client *client.Client } -type RouteDataSourceModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - From types.String `tfsdk:"from"` - To types.List `tfsdk:"to"` - NamespaceID types.String `tfsdk:"namespace_id"` - Policies types.List `tfsdk:"policies"` -} +type RouteDataSourceModel = RouteModel func (d *RouteDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_route" diff --git a/internal/provider/routes_data_source.go b/internal/provider/routes_data_source.go new file mode 100644 index 0000000..c3b2f73 --- /dev/null +++ b/internal/provider/routes_data_source.go @@ -0,0 +1,114 @@ +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/types" + + client "github.com/pomerium/enterprise-client-go" + "github.com/pomerium/enterprise-client-go/pb" +) + +var _ datasource.DataSource = &RoutesDataSource{} + +func NewRoutesDataSource() datasource.DataSource { + return &RoutesDataSource{} +} + +type RoutesDataSource struct { + client *client.Client +} + +type RoutesDataSourceModel struct { + Routes []RouteModel `tfsdk:"routes"` +} + +func (d *RoutesDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_routes" +} + +func (d *RoutesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "List all routes", + + Attributes: map[string]schema.Attribute{ + "routes": schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique identifier for the route.", + }, + "name": schema.StringAttribute{ + Computed: true, + Description: "Name of the route.", + }, + "from": schema.StringAttribute{ + Computed: true, + Description: "From URL.", + }, + "to": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + Description: "To URLs.", + }, + "namespace_id": schema.StringAttribute{ + Computed: true, + Description: "ID of the namespace the route belongs to.", + }, + "policies": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + Description: "List of policy IDs associated with the route.", + }, + }, + }, + }, + }, + } +} + +func (d *RoutesDataSource) Configure(_ 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.", req.ProviderData), + ) + return + } + + d.client = client +} + +func (d *RoutesDataSource) Read(ctx context.Context, _ datasource.ReadRequest, resp *datasource.ReadResponse) { + var data RoutesDataSourceModel + + routesResp, err := d.client.RouteService.ListRoutes(ctx, &pb.ListRoutesRequest{}) + if err != nil { + resp.Diagnostics.AddError("Error reading routes", err.Error()) + return + } + + routes := make([]RouteModel, 0, len(routesResp.Routes)) + for _, route := range routesResp.Routes { + var routeModel RouteModel + diags := ConvertRouteFromPB(&routeModel, route) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + routes = append(routes, routeModel) + } + + data.Routes = routes + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/schemas.go b/internal/provider/schemas.go new file mode 100644 index 0000000..ad486f5 --- /dev/null +++ b/internal/provider/schemas.go @@ -0,0 +1,147 @@ +package provider + +import ( + "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" +) + +// ServiceAccountSchema returns the schema for service account resources and data sources +func ServiceAccountSchema(computed bool) schema.Schema { + return schema.Schema{ + MarkdownDescription: "Service Account for Pomerium.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique identifier for the service account.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the service account.", + Required: !computed, + Computed: computed, + }, + "namespace_id": schema.StringAttribute{ + Description: "ID of the namespace the service account belongs to.", + Required: !computed, + Computed: computed, + }, + "description": schema.StringAttribute{ + Description: "Description of the service account.", + Optional: !computed, + Computed: computed, + }, + "user_id": schema.StringAttribute{ + Description: "User ID associated with the service account.", + Computed: true, + }, + "expires_at": schema.StringAttribute{ + Description: "Timestamp when the service account expires.", + Computed: true, + }, + }, + } +} + +// NamespaceSchema returns the schema for namespace resources and data sources +func NamespaceSchema(computed bool) schema.Schema { + return schema.Schema{ + MarkdownDescription: "Namespace for Pomerium.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique identifier for the namespace.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the namespace.", + Required: !computed, + Computed: computed, + }, + "parent_id": schema.StringAttribute{ + Description: "ID of the parent namespace (optional).", + Optional: !computed, + Computed: computed, + }, + }, + } +} + +// RouteSchema returns the schema for route resources and data sources +func RouteSchema(computed bool) schema.Schema { + return schema.Schema{ + MarkdownDescription: "Route for Pomerium.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique identifier for the route.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the route.", + Required: !computed, + Computed: computed, + }, + "from": schema.StringAttribute{ + Description: "From URL.", + Required: !computed, + Computed: computed, + }, + "to": schema.ListAttribute{ + ElementType: types.StringType, + Description: "To URLs.", + Required: !computed, + Computed: computed, + }, + "namespace_id": schema.StringAttribute{ + Description: "ID of the namespace the route belongs to.", + Required: !computed, + Computed: computed, + }, + "policies": schema.ListAttribute{ + ElementType: types.StringType, + Description: "List of policy IDs associated with the route.", + Optional: !computed, + Computed: computed, + }, + }, + } +} + +// PolicySchema returns the schema for policy resources and data sources +func PolicySchema(computed bool) schema.Schema { + return schema.Schema{ + MarkdownDescription: "Policy for Pomerium.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique identifier for the policy.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the policy.", + Required: !computed, + Computed: computed, + }, + "namespace_id": schema.StringAttribute{ + Description: "ID of the namespace the policy belongs to.", + Required: !computed, + Computed: computed, + }, + "ppl": schema.StringAttribute{ + Description: "Policy Policy Language (PPL) string.", + Required: !computed, + Computed: computed, + }, + }, + } +} diff --git a/internal/provider/service_account.go b/internal/provider/service_account.go index 4a1879b..17653df 100644 --- a/internal/provider/service_account.go +++ b/internal/provider/service_account.go @@ -8,9 +8,6 @@ import ( "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/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -31,53 +28,14 @@ type ServiceAccountResource struct { client *client.Client } -type ServiceAccountResourceModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - NamespaceID types.String `tfsdk:"namespace_id"` - Description types.String `tfsdk:"description"` - UserID types.String `tfsdk:"user_id"` - ExpiresAt types.String `tfsdk:"expires_at"` -} +type ServiceAccountResourceModel = ServiceAccountModel func (r *ServiceAccountResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_service_account" } func (r *ServiceAccountResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - MarkdownDescription: "Service Account resource for Pomerium.", - - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "Unique identifier for the service account.", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the service account.", - Required: true, - }, - "namespace_id": schema.StringAttribute{ - Description: "ID of the namespace the service account belongs to.", - Required: true, - }, - "description": schema.StringAttribute{ - Description: "Description of the service account.", - Optional: true, - }, - "user_id": schema.StringAttribute{ - Description: "User ID associated with the service account.", - Computed: true, - }, - "expires_at": schema.StringAttribute{ - Description: "Timestamp when the service account expires.", - Computed: true, - }, - }, - } + resp.Schema = ServiceAccountSchema(false) } func (r *ServiceAccountResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { diff --git a/internal/provider/service_account_data_source.go b/internal/provider/service_account_data_source.go index 2cee01b..1f48381 100644 --- a/internal/provider/service_account_data_source.go +++ b/internal/provider/service_account_data_source.go @@ -22,50 +22,14 @@ type ServiceAccountDataSource struct { client *client.Client } -type ServiceAccountDataSourceModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - NamespaceID types.String `tfsdk:"namespace_id"` - Description types.String `tfsdk:"description"` - UserID types.String `tfsdk:"user_id"` - ExpiresAt types.String `tfsdk:"expires_at"` -} +type ServiceAccountDataSourceModel = ServiceAccountModel func (d *ServiceAccountDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_service_account" } func (d *ServiceAccountDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = schema.Schema{ - MarkdownDescription: "Service Account data source", - - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Required: true, - Description: "Unique identifier for the service account.", - }, - "name": schema.StringAttribute{ - Computed: true, - Description: "Name of the service account.", - }, - "namespace_id": schema.StringAttribute{ - Computed: true, - Description: "ID of the namespace the service account belongs to.", - }, - "description": schema.StringAttribute{ - Computed: true, - Description: "Description of the service account.", - }, - "user_id": schema.StringAttribute{ - Computed: true, - Description: "User ID associated with the service account.", - }, - "expires_at": schema.StringAttribute{ - Computed: true, - Description: "Timestamp when the service account expires.", - }, - }, - } + resp.Schema = ServiceAccountSchema(true) } func (d *ServiceAccountDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { diff --git a/internal/provider/service_accounts_data_source.go b/internal/provider/service_accounts_data_source.go new file mode 100644 index 0000000..0e27a16 --- /dev/null +++ b/internal/provider/service_accounts_data_source.go @@ -0,0 +1,111 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + + client "github.com/pomerium/enterprise-client-go" + "github.com/pomerium/enterprise-client-go/pb" +) + +var _ datasource.DataSource = &ServiceAccountsDataSource{} + +func NewServiceAccountsDataSource() datasource.DataSource { + return &ServiceAccountsDataSource{} +} + +type ServiceAccountsDataSource struct { + client *client.Client +} + +type ServiceAccountsDataSourceModel struct { + ServiceAccounts []ServiceAccountModel `tfsdk:"service_accounts"` +} + +func (d *ServiceAccountsDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_service_accounts" +} + +func (d *ServiceAccountsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "List all service accounts", + + Attributes: map[string]schema.Attribute{ + "service_accounts": schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique identifier for the service account.", + }, + "name": schema.StringAttribute{ + Computed: true, + Description: "Name of the service account.", + }, + "namespace_id": schema.StringAttribute{ + Computed: true, + Description: "ID of the namespace the service account belongs to.", + }, + "description": schema.StringAttribute{ + Computed: true, + Description: "Description of the service account.", + }, + "user_id": schema.StringAttribute{ + Computed: true, + Description: "User ID associated with the service account.", + }, + "expires_at": schema.StringAttribute{ + Computed: true, + Description: "Timestamp when the service account expires.", + }, + }, + }, + }, + }, + } +} + +func (d *ServiceAccountsDataSource) Configure(_ 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.", req.ProviderData), + ) + return + } + + d.client = client +} + +func (d *ServiceAccountsDataSource) Read(ctx context.Context, _ datasource.ReadRequest, resp *datasource.ReadResponse) { + var data ServiceAccountsDataSourceModel + + serviceAccountsResp, err := d.client.PomeriumServiceAccountService.ListPomeriumServiceAccounts(ctx, &pb.ListPomeriumServiceAccountsRequest{}) + if err != nil { + resp.Diagnostics.AddError("Error reading service accounts", err.Error()) + return + } + + serviceAccounts := make([]ServiceAccountModel, 0, len(serviceAccountsResp.ServiceAccounts)) + for _, sa := range serviceAccountsResp.ServiceAccounts { + var saModel ServiceAccountModel + diags := ConvertServiceAccountFromPB(&saModel, sa) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + serviceAccounts = append(serviceAccounts, saModel) + } + + data.ServiceAccounts = serviceAccounts + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} From 42ff8e88281d3415e89dd68476aa24070383de98 Mon Sep 17 00:00:00 2001 From: Wes Medford Date: Thu, 12 Dec 2024 12:13:22 -0700 Subject: [PATCH 06/11] (ci) add tests --- .../provider/bootstrap_service_account.go | 2 +- .../bootstrap_service_account_test.go | 57 ++++++++++ internal/provider/helpers_test.go | 100 ++++++++++++++++++ internal/provider/provider.go | 2 +- 4 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 internal/provider/bootstrap_service_account_test.go create mode 100644 internal/provider/helpers_test.go diff --git a/internal/provider/bootstrap_service_account.go b/internal/provider/bootstrap_service_account.go index 3e8a581..b669e9d 100644 --- a/internal/provider/bootstrap_service_account.go +++ b/internal/provider/bootstrap_service_account.go @@ -11,7 +11,7 @@ import ( "google.golang.org/grpc/status" ) -func generateBootstrapServiceAccountToken( +func GenerateBootstrapServiceAccountToken( sharedSecretB64 string, ) (string, error) { sharedSecret, err := base64.StdEncoding.DecodeString(sharedSecretB64) diff --git a/internal/provider/bootstrap_service_account_test.go b/internal/provider/bootstrap_service_account_test.go new file mode 100644 index 0000000..6cc86b9 --- /dev/null +++ b/internal/provider/bootstrap_service_account_test.go @@ -0,0 +1,57 @@ +package provider_test + +import ( + "strings" + "testing" + + "github.com/pomerium/enterprise-terraform-provider/internal/provider" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenerateBootstrapServiceAccountToken(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + sharedSecret string + expectError bool + validateToken func(t *testing.T, token string) + }{ + { + name: "invalid base64", + sharedSecret: "not-base64", + expectError: true, + }, + { + name: "valid base64", + sharedSecret: "dGVzdC1zZWNyZXQ=", // "test-secret" in base64 + expectError: false, + validateToken: func(t *testing.T, token string) { + // JWT format: header.payload.signature + parts := strings.Split(token, ".") + require.Len(t, parts, 3) + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + token, err := provider.GenerateBootstrapServiceAccountToken(tt.sharedSecret) + if tt.expectError { + assert.Error(t, err) + return + } + + require.NoError(t, err) + assert.NotEmpty(t, token) + + if tt.validateToken != nil { + tt.validateToken(t, token) + } + }) + } +} diff --git a/internal/provider/helpers_test.go b/internal/provider/helpers_test.go new file mode 100644 index 0000000..698de97 --- /dev/null +++ b/internal/provider/helpers_test.go @@ -0,0 +1,100 @@ +package provider_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/resource" + client "github.com/pomerium/enterprise-client-go" + "github.com/pomerium/enterprise-terraform-provider/internal/provider" + "github.com/stretchr/testify/assert" +) + +func TestConfigureClient(t *testing.T) { + t.Parallel() + + mockClient := &client.Client{} + + tests := []struct { + name string + req any + resp any + expectedClient *client.Client + expectError bool + }{ + { + name: "valid datasource request", + req: datasource.ConfigureRequest{ + ProviderData: mockClient, + }, + resp: &datasource.ConfigureResponse{}, + expectedClient: mockClient, + expectError: false, + }, + { + name: "valid resource request", + req: resource.ConfigureRequest{ + ProviderData: mockClient, + }, + resp: &resource.ConfigureResponse{}, + expectedClient: mockClient, + expectError: false, + }, + { + name: "nil provider data", + req: datasource.ConfigureRequest{ + ProviderData: nil, + }, + resp: &datasource.ConfigureResponse{}, + expectedClient: nil, + expectError: false, + }, + { + name: "invalid provider data type - datasource", + req: datasource.ConfigureRequest{ + ProviderData: "invalid", + }, + resp: &datasource.ConfigureResponse{}, + expectedClient: nil, + expectError: true, + }, + { + name: "invalid provider data type - resource", + req: resource.ConfigureRequest{ + ProviderData: "invalid", + }, + resp: &resource.ConfigureResponse{}, + expectedClient: nil, + expectError: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + client := provider.ConfigureClient(tt.req, tt.resp, "test") + assert.Equal(t, tt.expectedClient, client) + + switch resp := tt.resp.(type) { + case *datasource.ConfigureResponse: + assert.Equal(t, tt.expectError, resp.Diagnostics.HasError()) + case *resource.ConfigureResponse: + assert.Equal(t, tt.expectError, resp.Diagnostics.HasError()) + } + }) + } +} + +func TestImportStatePassthroughID(t *testing.T) { + t.Parallel() + + req := resource.ImportStateRequest{ + ID: "test-id", + } + resp := &resource.ImportStateResponse{} + + provider.ImportStatePassthroughID(req, resp) + assert.False(t, resp.Diagnostics.HasError()) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 7856499..a7dadde 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -99,7 +99,7 @@ func (p *PomeriumProvider) Configure(ctx context.Context, req provider.Configure if !data.ServiceAccountToken.IsNull() { token = data.ServiceAccountToken.ValueString() } else if !data.SharedSecretB64.IsNull() { - token, err = generateBootstrapServiceAccountToken(data.SharedSecretB64.ValueString()) + token, err = GenerateBootstrapServiceAccountToken(data.SharedSecretB64.ValueString()) if err != nil { resp.Diagnostics.AddError("failed to decode shared_secret_b64", err.Error()) return From e1aa0e243aa05d84cb15a7c89e769a368f3971b6 Mon Sep 17 00:00:00 2001 From: Wes Medford Date: Thu, 12 Dec 2024 12:31:45 -0700 Subject: [PATCH 07/11] (refactor) remove schema.go due to needing individual implementations per data source and resource. --- internal/provider/namespace.go | 24 ++- internal/provider/namespace_data_source.go | 3 +- internal/provider/policy.go | 28 +++- internal/provider/policy_data_source.go | 3 +- internal/provider/route.go | 38 ++++- internal/provider/schemas.go | 147 ------------------ internal/provider/service_account.go | 36 ++++- .../provider/service_account_data_source.go | 31 +++- 8 files changed, 153 insertions(+), 157 deletions(-) delete mode 100644 internal/provider/schemas.go diff --git a/internal/provider/namespace.go b/internal/provider/namespace.go index 3817705..c2354ba 100644 --- a/internal/provider/namespace.go +++ b/internal/provider/namespace.go @@ -5,6 +5,9 @@ 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/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -36,7 +39,26 @@ func (r *NamespaceResource) Metadata(_ context.Context, req resource.MetadataReq } func (r *NamespaceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = NamespaceSchema(false) + resp.Schema = schema.Schema{ + MarkdownDescription: "Namespace for Pomerium.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique identifier for the namespace.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the namespace.", + Required: true, + }, + "parent_id": schema.StringAttribute{ + Description: "ID of the parent namespace (optional).", + Optional: true, + }, + }, + } } func (r *NamespaceResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { diff --git a/internal/provider/namespace_data_source.go b/internal/provider/namespace_data_source.go index 1d2232e..3d2b720 100644 --- a/internal/provider/namespace_data_source.go +++ b/internal/provider/namespace_data_source.go @@ -28,8 +28,7 @@ func (d *NamespaceDataSource) Metadata(_ context.Context, req datasource.Metadat func (d *NamespaceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: "Namespace data source", - + MarkdownDescription: "Namespace for Pomerium.", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Required: true, diff --git a/internal/provider/policy.go b/internal/provider/policy.go index 206ca8f..54844fc 100644 --- a/internal/provider/policy.go +++ b/internal/provider/policy.go @@ -7,6 +7,9 @@ import ( "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/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -38,7 +41,30 @@ func (r *PolicyResource) Metadata(_ context.Context, req resource.MetadataReques } func (r *PolicyResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = PolicySchema(false) + resp.Schema = schema.Schema{ + MarkdownDescription: "Policy for Pomerium.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique identifier for the policy.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the policy.", + Required: true, + }, + "namespace_id": schema.StringAttribute{ + Description: "ID of the namespace the policy belongs to.", + Required: true, + }, + "ppl": schema.StringAttribute{ + Description: "Policy Policy Language (PPL) string.", + Required: true, + }, + }, + } } func (r *PolicyResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { diff --git a/internal/provider/policy_data_source.go b/internal/provider/policy_data_source.go index 3b079de..59305fc 100644 --- a/internal/provider/policy_data_source.go +++ b/internal/provider/policy_data_source.go @@ -29,8 +29,7 @@ func (d *PolicyDataSource) Metadata(_ context.Context, req datasource.MetadataRe func (d *PolicyDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: "Policy data source", - + MarkdownDescription: "Policy for Pomerium.", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Required: true, diff --git a/internal/provider/route.go b/internal/provider/route.go index cd50bad..eee51be 100644 --- a/internal/provider/route.go +++ b/internal/provider/route.go @@ -8,6 +8,9 @@ import ( "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/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" client "github.com/pomerium/enterprise-client-go" @@ -37,7 +40,40 @@ func (r *RouteResource) Metadata(_ context.Context, req resource.MetadataRequest } func (r *RouteResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = RouteSchema(false) + resp.Schema = schema.Schema{ + MarkdownDescription: "Route for Pomerium.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique identifier for the route.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the route.", + Required: true, + }, + "from": schema.StringAttribute{ + Description: "From URL.", + Required: true, + }, + "to": schema.ListAttribute{ + ElementType: types.StringType, + Description: "To URLs.", + Required: true, + }, + "namespace_id": schema.StringAttribute{ + Description: "ID of the namespace the route belongs to.", + Required: true, + }, + "policies": schema.ListAttribute{ + ElementType: types.StringType, + Description: "List of policy IDs associated with the route.", + Optional: true, + }, + }, + } } func (r *RouteResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { diff --git a/internal/provider/schemas.go b/internal/provider/schemas.go deleted file mode 100644 index ad486f5..0000000 --- a/internal/provider/schemas.go +++ /dev/null @@ -1,147 +0,0 @@ -package provider - -import ( - "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" -) - -// ServiceAccountSchema returns the schema for service account resources and data sources -func ServiceAccountSchema(computed bool) schema.Schema { - return schema.Schema{ - MarkdownDescription: "Service Account for Pomerium.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "Unique identifier for the service account.", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the service account.", - Required: !computed, - Computed: computed, - }, - "namespace_id": schema.StringAttribute{ - Description: "ID of the namespace the service account belongs to.", - Required: !computed, - Computed: computed, - }, - "description": schema.StringAttribute{ - Description: "Description of the service account.", - Optional: !computed, - Computed: computed, - }, - "user_id": schema.StringAttribute{ - Description: "User ID associated with the service account.", - Computed: true, - }, - "expires_at": schema.StringAttribute{ - Description: "Timestamp when the service account expires.", - Computed: true, - }, - }, - } -} - -// NamespaceSchema returns the schema for namespace resources and data sources -func NamespaceSchema(computed bool) schema.Schema { - return schema.Schema{ - MarkdownDescription: "Namespace for Pomerium.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "Unique identifier for the namespace.", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the namespace.", - Required: !computed, - Computed: computed, - }, - "parent_id": schema.StringAttribute{ - Description: "ID of the parent namespace (optional).", - Optional: !computed, - Computed: computed, - }, - }, - } -} - -// RouteSchema returns the schema for route resources and data sources -func RouteSchema(computed bool) schema.Schema { - return schema.Schema{ - MarkdownDescription: "Route for Pomerium.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "Unique identifier for the route.", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the route.", - Required: !computed, - Computed: computed, - }, - "from": schema.StringAttribute{ - Description: "From URL.", - Required: !computed, - Computed: computed, - }, - "to": schema.ListAttribute{ - ElementType: types.StringType, - Description: "To URLs.", - Required: !computed, - Computed: computed, - }, - "namespace_id": schema.StringAttribute{ - Description: "ID of the namespace the route belongs to.", - Required: !computed, - Computed: computed, - }, - "policies": schema.ListAttribute{ - ElementType: types.StringType, - Description: "List of policy IDs associated with the route.", - Optional: !computed, - Computed: computed, - }, - }, - } -} - -// PolicySchema returns the schema for policy resources and data sources -func PolicySchema(computed bool) schema.Schema { - return schema.Schema{ - MarkdownDescription: "Policy for Pomerium.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "Unique identifier for the policy.", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the policy.", - Required: !computed, - Computed: computed, - }, - "namespace_id": schema.StringAttribute{ - Description: "ID of the namespace the policy belongs to.", - Required: !computed, - Computed: computed, - }, - "ppl": schema.StringAttribute{ - Description: "Policy Policy Language (PPL) string.", - Required: !computed, - Computed: computed, - }, - }, - } -} diff --git a/internal/provider/service_account.go b/internal/provider/service_account.go index 17653df..1f23abf 100644 --- a/internal/provider/service_account.go +++ b/internal/provider/service_account.go @@ -8,6 +8,9 @@ import ( "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/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -35,7 +38,38 @@ func (r *ServiceAccountResource) Metadata(_ context.Context, req resource.Metada } func (r *ServiceAccountResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = ServiceAccountSchema(false) + resp.Schema = schema.Schema{ + MarkdownDescription: "Service Account for Pomerium.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique identifier for the service account.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the service account.", + Required: true, + }, + "namespace_id": schema.StringAttribute{ + Description: "ID of the namespace the service account belongs to.", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the service account.", + Optional: true, + }, + "user_id": schema.StringAttribute{ + Computed: true, + Description: "User ID associated with the service account.", + }, + "expires_at": schema.StringAttribute{ + Computed: true, + Description: "Timestamp when the service account expires.", + }, + }, + } } func (r *ServiceAccountResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { diff --git a/internal/provider/service_account_data_source.go b/internal/provider/service_account_data_source.go index 1f48381..2fe21d3 100644 --- a/internal/provider/service_account_data_source.go +++ b/internal/provider/service_account_data_source.go @@ -6,7 +6,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" client "github.com/pomerium/enterprise-client-go" "github.com/pomerium/enterprise-client-go/pb" @@ -29,7 +28,35 @@ func (d *ServiceAccountDataSource) Metadata(_ context.Context, req datasource.Me } func (d *ServiceAccountDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = ServiceAccountSchema(true) + resp.Schema = schema.Schema{ + MarkdownDescription: "Service Account for Pomerium.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Required: true, + Description: "Unique identifier for the service account.", + }, + "name": schema.StringAttribute{ + Computed: true, + Description: "Name of the service account.", + }, + "namespace_id": schema.StringAttribute{ + Computed: true, + Description: "ID of the namespace the service account belongs to.", + }, + "description": schema.StringAttribute{ + Computed: true, + Description: "Description of the service account.", + }, + "user_id": schema.StringAttribute{ + Computed: true, + Description: "User ID associated with the service account.", + }, + "expires_at": schema.StringAttribute{ + Computed: true, + Description: "Timestamp when the service account expires.", + }, + }, + } } func (d *ServiceAccountDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { From c4be003f19c7e3d26eb75e5d2d08eede2ab98539 Mon Sep 17 00:00:00 2001 From: Wes Medford Date: Thu, 12 Dec 2024 12:36:38 -0700 Subject: [PATCH 08/11] (ci) lint fix --- internal/provider/bootstrap_service_account_test.go | 1 - internal/provider/helpers.go | 5 +++-- internal/provider/helpers_test.go | 1 - internal/provider/namespace.go | 2 +- internal/provider/namespace_data_source.go | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/provider/bootstrap_service_account_test.go b/internal/provider/bootstrap_service_account_test.go index 6cc86b9..328b0c8 100644 --- a/internal/provider/bootstrap_service_account_test.go +++ b/internal/provider/bootstrap_service_account_test.go @@ -36,7 +36,6 @@ func TestGenerateBootstrapServiceAccountToken(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/provider/helpers.go b/internal/provider/helpers.go index c567a73..0fc888b 100644 --- a/internal/provider/helpers.go +++ b/internal/provider/helpers.go @@ -1,6 +1,7 @@ package provider import ( + "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -10,7 +11,7 @@ import ( ) // ConfigureClient is a helper to configure resources and data sources with the API client -func ConfigureClient(req any, resp any, typeName string) *client.Client { +func ConfigureClient(req any, resp any) *client.Client { var providerData any switch r := req.(type) { case datasource.ConfigureRequest: @@ -45,5 +46,5 @@ func ConfigureClient(req any, resp any, typeName string) *client.Client { // ImportStatePassthroughID is a helper that implements the common import state pattern func ImportStatePassthroughID(req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(nil, path.Root("id"), req, resp) + resource.ImportStatePassthroughID(context.TODO(), path.Root("id"), req, resp) } diff --git a/internal/provider/helpers_test.go b/internal/provider/helpers_test.go index 698de97..263ba59 100644 --- a/internal/provider/helpers_test.go +++ b/internal/provider/helpers_test.go @@ -70,7 +70,6 @@ func TestConfigureClient(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/provider/namespace.go b/internal/provider/namespace.go index c2354ba..60b2653 100644 --- a/internal/provider/namespace.go +++ b/internal/provider/namespace.go @@ -62,7 +62,7 @@ func (r *NamespaceResource) Schema(_ context.Context, _ resource.SchemaRequest, } func (r *NamespaceResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - r.client = ConfigureClient(req, resp, "namespace") + r.client = ConfigureClient(req, resp) } func (r *NamespaceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { diff --git a/internal/provider/namespace_data_source.go b/internal/provider/namespace_data_source.go index 3d2b720..fbedb66 100644 --- a/internal/provider/namespace_data_source.go +++ b/internal/provider/namespace_data_source.go @@ -47,7 +47,7 @@ func (d *NamespaceDataSource) Schema(_ context.Context, _ datasource.SchemaReque } func (d *NamespaceDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { - d.client = ConfigureClient(req, resp, "namespace") + d.client = ConfigureClient(req, resp) } func (d *NamespaceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { From a0be182cca6264bdf391637e2fe21237b3967dd8 Mon Sep 17 00:00:00 2001 From: Wes Medford Date: Thu, 12 Dec 2024 12:37:29 -0700 Subject: [PATCH 09/11] (fix) missed file --- internal/provider/helpers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/helpers_test.go b/internal/provider/helpers_test.go index 263ba59..512d307 100644 --- a/internal/provider/helpers_test.go +++ b/internal/provider/helpers_test.go @@ -73,7 +73,7 @@ func TestConfigureClient(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - client := provider.ConfigureClient(tt.req, tt.resp, "test") + client := provider.ConfigureClient(tt.req, tt.resp) assert.Equal(t, tt.expectedClient, client) switch resp := tt.resp.(type) { From 35a5bff506b0d25ca59ef82edf3ba9d172c10aba Mon Sep 17 00:00:00 2001 From: Wes Medford Date: Thu, 12 Dec 2024 12:50:45 -0700 Subject: [PATCH 10/11] (test) remove pointless test --- internal/provider/helpers.go | 2 +- internal/provider/helpers_test.go | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/internal/provider/helpers.go b/internal/provider/helpers.go index 0fc888b..5b8747a 100644 --- a/internal/provider/helpers.go +++ b/internal/provider/helpers.go @@ -46,5 +46,5 @@ func ConfigureClient(req any, resp any) *client.Client { // ImportStatePassthroughID is a helper that implements the common import state pattern func ImportStatePassthroughID(req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(context.TODO(), path.Root("id"), req, resp) + resp.Diagnostics.Append(resp.State.SetAttribute(context.Background(), path.Root("id"), req.ID)...) } diff --git a/internal/provider/helpers_test.go b/internal/provider/helpers_test.go index 512d307..ca85a7a 100644 --- a/internal/provider/helpers_test.go +++ b/internal/provider/helpers_test.go @@ -85,15 +85,3 @@ func TestConfigureClient(t *testing.T) { }) } } - -func TestImportStatePassthroughID(t *testing.T) { - t.Parallel() - - req := resource.ImportStateRequest{ - ID: "test-id", - } - resp := &resource.ImportStateResponse{} - - provider.ImportStatePassthroughID(req, resp) - assert.False(t, resp.Diagnostics.HasError()) -} From fa49757bdc54eed808dcfebe7d5e7a4be7234478 Mon Sep 17 00:00:00 2001 From: Wes Medford Date: Thu, 12 Dec 2024 13:32:23 -0700 Subject: [PATCH 11/11] (refactor) clean up unnecessary types and move conversion functions to models.go --- internal/provider/models.go | 151 ++++++++++++++++++ internal/provider/namespace.go | 31 ---- internal/provider/namespace_data_source.go | 10 +- internal/provider/policy.go | 25 --- internal/provider/policy_data_source.go | 11 +- internal/provider/route.go | 50 ------ internal/provider/route_data_source.go | 13 +- internal/provider/service_account.go | 45 ------ .../provider/service_account_data_source.go | 13 +- 9 files changed, 159 insertions(+), 190 deletions(-) diff --git a/internal/provider/models.go b/internal/provider/models.go index 1bea4eb..7a7f2a8 100644 --- a/internal/provider/models.go +++ b/internal/provider/models.go @@ -1,7 +1,13 @@ package provider import ( + "context" + "time" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/pomerium/enterprise-client-go/pb" ) // ServiceAccountModel represents the shared model for service account resources and data sources @@ -14,6 +20,49 @@ type ServiceAccountModel struct { ExpiresAt types.String `tfsdk:"expires_at"` } +func ConvertServiceAccountToPB(_ context.Context, src *ServiceAccountResourceModel) (*pb.PomeriumServiceAccount, diag.Diagnostics) { + var diagnostics diag.Diagnostics + + namespaceID := src.NamespaceID.ValueString() + pbServiceAccount := &pb.PomeriumServiceAccount{ + Id: src.ID.ValueString(), + UserId: src.Name.ValueString(), + NamespaceId: &namespaceID, + } + + if !src.Description.IsNull() { + desc := src.Description.ValueString() + pbServiceAccount.Description = &desc + } + + return pbServiceAccount, diagnostics +} + +func ConvertServiceAccountFromPB(dst *ServiceAccountResourceModel, src *pb.PomeriumServiceAccount) diag.Diagnostics { + var diagnostics diag.Diagnostics + + dst.ID = types.StringValue(src.Id) + dst.Name = types.StringValue(src.UserId) + if src.NamespaceId != nil { + dst.NamespaceID = types.StringValue(*src.NamespaceId) + } else { + dst.NamespaceID = types.StringNull() + } + if src.Description != nil { + dst.Description = types.StringValue(*src.Description) + } else { + dst.Description = types.StringNull() + } + dst.UserID = types.StringValue(src.UserId) + if src.ExpiresAt != nil { + dst.ExpiresAt = types.StringValue(src.ExpiresAt.AsTime().Format(time.RFC3339)) + } else { + dst.ExpiresAt = types.StringNull() + } + + return diagnostics +} + // NamespaceModel represents the shared model for namespace resources and data sources type NamespaceModel struct { ID types.String `tfsdk:"id"` @@ -21,6 +70,36 @@ type NamespaceModel struct { ParentID types.String `tfsdk:"parent_id"` } +func ConvertNamespaceToPB(_ context.Context, src *NamespaceResourceModel) (*pb.Namespace, diag.Diagnostics) { + var diagnostics diag.Diagnostics + + pbNamespace := &pb.Namespace{ + Id: src.ID.ValueString(), + Name: src.Name.ValueString(), + } + + if !src.ParentID.IsNull() { + pbNamespace.ParentId = src.ParentID.ValueString() + } + + return pbNamespace, diagnostics +} + +func ConvertNamespaceFromPB(dst *NamespaceResourceModel, src *pb.Namespace) diag.Diagnostics { + var diagnostics diag.Diagnostics + + dst.ID = types.StringValue(src.Id) + dst.Name = types.StringValue(src.Name) + + if src.ParentId != "" { + dst.ParentID = types.StringValue(src.ParentId) + } else { + dst.ParentID = types.StringNull() + } + + return diagnostics +} + // RouteModel represents the shared model for route resources and data sources type RouteModel struct { ID types.String `tfsdk:"id"` @@ -31,6 +110,54 @@ type RouteModel struct { Policies types.List `tfsdk:"policies"` } +func ConvertRouteToPB( + ctx context.Context, + src *RouteResourceModel, +) (*pb.Route, diag.Diagnostics) { + pbRoute := new(pb.Route) + var diagnostics diag.Diagnostics + + pbRoute.Id = src.ID.ValueString() + pbRoute.Name = src.Name.ValueString() + pbRoute.From = src.From.ValueString() + pbRoute.NamespaceId = src.NamespaceID.ValueString() + + diags := src.To.ElementsAs(ctx, &pbRoute.To, false) + diagnostics.Append(diags...) + + if !src.Policies.IsNull() { + diags = src.Policies.ElementsAs(ctx, &pbRoute.PolicyIds, false) + diagnostics.Append(diags...) + } + return pbRoute, diagnostics +} + +func ConvertRouteFromPB( + dst *RouteResourceModel, + src *pb.Route, +) diag.Diagnostics { + var diagnostics diag.Diagnostics + + dst.ID = types.StringValue(src.Id) + dst.Name = types.StringValue(src.Name) + dst.From = types.StringValue(src.From) + dst.NamespaceID = types.StringValue(src.NamespaceId) + + toList := make([]attr.Value, len(src.To)) + for i, v := range src.To { + toList[i] = types.StringValue(v) + } + dst.To = types.ListValueMust(types.StringType, toList) + + policiesList := make([]attr.Value, len(src.PolicyIds)) + for i, v := range src.PolicyIds { + policiesList[i] = types.StringValue(v) + } + dst.Policies = types.ListValueMust(types.StringType, policiesList) + + return diagnostics +} + // PolicyModel represents the shared model for policy resources and data sources type PolicyModel struct { ID types.String `tfsdk:"id"` @@ -38,3 +165,27 @@ type PolicyModel struct { NamespaceID types.String `tfsdk:"namespace_id"` PPL types.String `tfsdk:"ppl"` } + +func ConvertPolicyToPB(_ context.Context, src *PolicyResourceModel) (*pb.Policy, diag.Diagnostics) { + var diagnostics diag.Diagnostics + + pbPolicy := &pb.Policy{ + Id: src.ID.ValueString(), + Name: src.Name.ValueString(), + NamespaceId: src.NamespaceID.ValueString(), + Ppl: src.PPL.ValueString(), + } + + return pbPolicy, diagnostics +} + +func ConvertPolicyFromPB(dst *PolicyResourceModel, src *pb.Policy) diag.Diagnostics { + var diagnostics diag.Diagnostics + + dst.ID = types.StringValue(src.Id) + dst.Name = types.StringValue(src.Name) + dst.NamespaceID = types.StringValue(src.NamespaceId) + dst.PPL = types.StringValue(src.Ppl) + + return diagnostics +} diff --git a/internal/provider/namespace.go b/internal/provider/namespace.go index 60b2653..4dddd5e 100644 --- a/internal/provider/namespace.go +++ b/internal/provider/namespace.go @@ -3,7 +3,6 @@ package provider import ( "context" - "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/planmodifier" @@ -169,33 +168,3 @@ func (r *NamespaceResource) Delete(ctx context.Context, req resource.DeleteReque func (r *NamespaceResource) ImportState(_ context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { ImportStatePassthroughID(req, resp) } - -func ConvertNamespaceToPB(_ context.Context, src *NamespaceResourceModel) (*pb.Namespace, diag.Diagnostics) { - var diagnostics diag.Diagnostics - - pbNamespace := &pb.Namespace{ - Id: src.ID.ValueString(), - Name: src.Name.ValueString(), - } - - if !src.ParentID.IsNull() { - pbNamespace.ParentId = src.ParentID.ValueString() - } - - return pbNamespace, diagnostics -} - -func ConvertNamespaceFromPB(dst *NamespaceResourceModel, src *pb.Namespace) diag.Diagnostics { - var diagnostics diag.Diagnostics - - dst.ID = types.StringValue(src.Id) - dst.Name = types.StringValue(src.Name) - - if src.ParentId != "" { - dst.ParentID = types.StringValue(src.ParentId) - } else { - dst.ParentID = types.StringNull() - } - - return diagnostics -} diff --git a/internal/provider/namespace_data_source.go b/internal/provider/namespace_data_source.go index fbedb66..867daeb 100644 --- a/internal/provider/namespace_data_source.go +++ b/internal/provider/namespace_data_source.go @@ -20,8 +20,6 @@ type NamespaceDataSource struct { client *client.Client } -type NamespaceDataSourceModel = NamespaceModel - func (d *NamespaceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_namespace" } @@ -51,7 +49,7 @@ func (d *NamespaceDataSource) Configure(_ context.Context, req datasource.Config } func (d *NamespaceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data NamespaceDataSourceModel + var data NamespaceModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -66,11 +64,7 @@ func (d *NamespaceDataSource) Read(ctx context.Context, req datasource.ReadReque return } - diags := ConvertNamespaceFromPB(&NamespaceResourceModel{ - ID: data.ID, - Name: data.Name, - ParentID: data.ParentID, - }, namespaceResp.Namespace) + diags := ConvertNamespaceFromPB(&data, namespaceResp.Namespace) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return diff --git a/internal/provider/policy.go b/internal/provider/policy.go index 54844fc..c6f2904 100644 --- a/internal/provider/policy.go +++ b/internal/provider/policy.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "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" @@ -189,27 +188,3 @@ func (r *PolicyResource) ImportState(ctx context.Context, req resource.ImportSta // Import by ID resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } - -func ConvertPolicyToPB(_ context.Context, src *PolicyResourceModel) (*pb.Policy, diag.Diagnostics) { - var diagnostics diag.Diagnostics - - pbPolicy := &pb.Policy{ - Id: src.ID.ValueString(), - Name: src.Name.ValueString(), - NamespaceId: src.NamespaceID.ValueString(), - Ppl: src.PPL.ValueString(), - } - - return pbPolicy, diagnostics -} - -func ConvertPolicyFromPB(dst *PolicyResourceModel, src *pb.Policy) diag.Diagnostics { - var diagnostics diag.Diagnostics - - dst.ID = types.StringValue(src.Id) - dst.Name = types.StringValue(src.Name) - dst.NamespaceID = types.StringValue(src.NamespaceId) - dst.PPL = types.StringValue(src.Ppl) - - return diagnostics -} diff --git a/internal/provider/policy_data_source.go b/internal/provider/policy_data_source.go index 59305fc..94092b5 100644 --- a/internal/provider/policy_data_source.go +++ b/internal/provider/policy_data_source.go @@ -21,8 +21,6 @@ type PolicyDataSource struct { client *client.Client } -type PolicyDataSourceModel = PolicyModel - func (d *PolicyDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_policy" } @@ -69,7 +67,7 @@ func (d *PolicyDataSource) Configure(_ context.Context, req datasource.Configure } func (d *PolicyDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data PolicyDataSourceModel + var data PolicyModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -84,12 +82,7 @@ func (d *PolicyDataSource) Read(ctx context.Context, req datasource.ReadRequest, return } - diags := ConvertPolicyFromPB(&PolicyResourceModel{ - ID: data.ID, - Name: data.Name, - NamespaceID: data.NamespaceID, - PPL: data.PPL, - }, policyResp.Policy) + diags := ConvertPolicyFromPB(&data, policyResp.Policy) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return diff --git a/internal/provider/route.go b/internal/provider/route.go index eee51be..0aedca3 100644 --- a/internal/provider/route.go +++ b/internal/provider/route.go @@ -4,8 +4,6 @@ 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/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -199,51 +197,3 @@ func (r *RouteResource) Delete(ctx context.Context, req resource.DeleteRequest, func (r *RouteResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } - -func ConvertRouteToPB( - ctx context.Context, - src *RouteResourceModel, -) (*pb.Route, diag.Diagnostics) { - pbRoute := new(pb.Route) - var diagnostics diag.Diagnostics - - pbRoute.Id = src.ID.ValueString() - pbRoute.Name = src.Name.ValueString() - pbRoute.From = src.From.ValueString() - pbRoute.NamespaceId = src.NamespaceID.ValueString() - - diags := src.To.ElementsAs(ctx, &pbRoute.To, false) - diagnostics.Append(diags...) - - if !src.Policies.IsNull() { - diags = src.Policies.ElementsAs(ctx, &pbRoute.PolicyIds, false) - diagnostics.Append(diags...) - } - return pbRoute, diagnostics -} - -func ConvertRouteFromPB( - dst *RouteResourceModel, - src *pb.Route, -) diag.Diagnostics { - var diagnostics diag.Diagnostics - - dst.ID = types.StringValue(src.Id) - dst.Name = types.StringValue(src.Name) - dst.From = types.StringValue(src.From) - dst.NamespaceID = types.StringValue(src.NamespaceId) - - toList := make([]attr.Value, len(src.To)) - for i, v := range src.To { - toList[i] = types.StringValue(v) - } - dst.To = types.ListValueMust(types.StringType, toList) - - policiesList := make([]attr.Value, len(src.PolicyIds)) - for i, v := range src.PolicyIds { - policiesList[i] = types.StringValue(v) - } - dst.Policies = types.ListValueMust(types.StringType, policiesList) - - return diagnostics -} diff --git a/internal/provider/route_data_source.go b/internal/provider/route_data_source.go index b070b4e..8cf8db9 100644 --- a/internal/provider/route_data_source.go +++ b/internal/provider/route_data_source.go @@ -22,8 +22,6 @@ type RouteDataSource struct { client *client.Client } -type RouteDataSourceModel = RouteModel - func (d *RouteDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_route" } @@ -81,7 +79,7 @@ func (d *RouteDataSource) Configure(_ context.Context, req datasource.ConfigureR } func (d *RouteDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data RouteDataSourceModel + var data RouteModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -96,14 +94,7 @@ func (d *RouteDataSource) Read(ctx context.Context, req datasource.ReadRequest, return } - diags := ConvertRouteFromPB(&RouteResourceModel{ - ID: data.ID, - Name: data.Name, - From: data.From, - To: data.To, - NamespaceID: data.NamespaceID, - Policies: data.Policies, - }, routeResp.Route) + diags := ConvertRouteFromPB(&data, routeResp.Route) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return diff --git a/internal/provider/service_account.go b/internal/provider/service_account.go index 1f23abf..495bc14 100644 --- a/internal/provider/service_account.go +++ b/internal/provider/service_account.go @@ -3,9 +3,7 @@ package provider import ( "context" "fmt" - "time" - "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" @@ -193,46 +191,3 @@ func (r *ServiceAccountResource) Delete(ctx context.Context, req resource.Delete func (r *ServiceAccountResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } - -func ConvertServiceAccountToPB(_ context.Context, src *ServiceAccountResourceModel) (*pb.PomeriumServiceAccount, diag.Diagnostics) { - var diagnostics diag.Diagnostics - - namespaceID := src.NamespaceID.ValueString() - pbServiceAccount := &pb.PomeriumServiceAccount{ - Id: src.ID.ValueString(), - UserId: src.Name.ValueString(), - NamespaceId: &namespaceID, - } - - if !src.Description.IsNull() { - desc := src.Description.ValueString() - pbServiceAccount.Description = &desc - } - - return pbServiceAccount, diagnostics -} - -func ConvertServiceAccountFromPB(dst *ServiceAccountResourceModel, src *pb.PomeriumServiceAccount) diag.Diagnostics { - var diagnostics diag.Diagnostics - - dst.ID = types.StringValue(src.Id) - dst.Name = types.StringValue(src.UserId) - if src.NamespaceId != nil { - dst.NamespaceID = types.StringValue(*src.NamespaceId) - } else { - dst.NamespaceID = types.StringNull() - } - if src.Description != nil { - dst.Description = types.StringValue(*src.Description) - } else { - dst.Description = types.StringNull() - } - dst.UserID = types.StringValue(src.UserId) - if src.ExpiresAt != nil { - dst.ExpiresAt = types.StringValue(src.ExpiresAt.AsTime().Format(time.RFC3339)) - } else { - dst.ExpiresAt = types.StringNull() - } - - return diagnostics -} diff --git a/internal/provider/service_account_data_source.go b/internal/provider/service_account_data_source.go index 2fe21d3..05d112b 100644 --- a/internal/provider/service_account_data_source.go +++ b/internal/provider/service_account_data_source.go @@ -21,8 +21,6 @@ type ServiceAccountDataSource struct { client *client.Client } -type ServiceAccountDataSourceModel = ServiceAccountModel - func (d *ServiceAccountDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_service_account" } @@ -77,7 +75,7 @@ func (d *ServiceAccountDataSource) Configure(_ context.Context, req datasource.C } func (d *ServiceAccountDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data ServiceAccountDataSourceModel + var data ServiceAccountModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -92,14 +90,7 @@ func (d *ServiceAccountDataSource) Read(ctx context.Context, req datasource.Read return } - diags := ConvertServiceAccountFromPB(&ServiceAccountResourceModel{ - ID: data.ID, - Name: data.Name, - NamespaceID: data.NamespaceID, - Description: data.Description, - UserID: data.UserID, - ExpiresAt: data.ExpiresAt, - }, serviceAccountResp.ServiceAccount) + diags := ConvertServiceAccountFromPB(&data, serviceAccountResp.ServiceAccount) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return