Skip to content

Commit 2eda495

Browse files
PER-13046-add-tenant-tf (#51)
* PER-13046-add-tenant-tf * lint fix
1 parent a477960 commit 2eda495

File tree

5 files changed

+278
-0
lines changed

5 files changed

+278
-0
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,20 @@ resource "permitio_user_attribute" "department" {
8989
}
9090
```
9191

92+
#### Create a Tenant
93+
94+
```hcl
95+
resource "permitio_tenant" "acme_corp" {
96+
key = "acme-corp"
97+
name = "Acme Corporation"
98+
description = "Main tenant for Acme Corporation"
99+
attributes = jsonencode({
100+
region = "us-west"
101+
tier = "enterprise"
102+
})
103+
}
104+
```
105+
92106
## Requirements
93107

94108
- [Terraform](https://developer.hashicorp.com/terraform/downloads) >= 1.0

internal/provider/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/permitio/terraform-provider-permit-io/internal/provider/resources"
2121
"github.com/permitio/terraform-provider-permit-io/internal/provider/role_derivations"
2222
"github.com/permitio/terraform-provider-permit-io/internal/provider/roles"
23+
"github.com/permitio/terraform-provider-permit-io/internal/provider/tenants"
2324
"github.com/permitio/terraform-provider-permit-io/internal/provider/user_attributes"
2425

2526
"github.com/hashicorp/terraform-plugin-framework/datasource"
@@ -186,6 +187,7 @@ func (p *PermitProvider) Resources(_ context.Context) []func() resource.Resource
186187
proxy_configs.NewProxyConfigResource,
187188
relations.NewRelationResource,
188189
role_derivations.NewRoleDerivationResource,
190+
tenants.NewTenantResource,
189191
user_attributes.NewUserAttributeResource,
190192
}
191193
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package tenants
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"github.com/permitio/permit-golang/pkg/models"
7+
"github.com/permitio/permit-golang/pkg/permit"
8+
)
9+
10+
type tenantClient struct {
11+
client *permit.Client
12+
}
13+
14+
func (c *tenantClient) Create(ctx context.Context, plan tenantModel) (tenantModel, error) {
15+
// Parse attributes from JSON string
16+
var attributes map[string]interface{}
17+
if !plan.Attributes.IsNull() && plan.Attributes.ValueString() != "" {
18+
err := json.Unmarshal([]byte(plan.Attributes.ValueString()), &attributes)
19+
if err != nil {
20+
return tenantModel{}, err
21+
}
22+
}
23+
24+
tenantCreate := models.TenantCreate{
25+
Key: plan.Key.ValueString(),
26+
Name: plan.Name.ValueString(),
27+
Description: plan.Description.ValueStringPointer(),
28+
Attributes: attributes,
29+
}
30+
31+
createdTenant, err := c.client.Api.Tenants.Create(ctx, tenantCreate)
32+
33+
if err != nil {
34+
return tenantModel{}, err
35+
}
36+
37+
return tfModelFromTenantRead(*createdTenant), nil
38+
}
39+
40+
func (c *tenantClient) Read(ctx context.Context, key string) (tenantModel, error) {
41+
tenantRead, err := c.client.Api.Tenants.Get(ctx, key)
42+
43+
if err != nil {
44+
return tenantModel{}, err
45+
}
46+
47+
return tfModelFromTenantRead(*tenantRead), nil
48+
}
49+
50+
func (c *tenantClient) Update(ctx context.Context, plan tenantModel) (tenantModel, error) {
51+
// Parse attributes from JSON string
52+
var attributes map[string]interface{}
53+
if !plan.Attributes.IsNull() && plan.Attributes.ValueString() != "" {
54+
err := json.Unmarshal([]byte(plan.Attributes.ValueString()), &attributes)
55+
if err != nil {
56+
return tenantModel{}, err
57+
}
58+
}
59+
60+
tenantUpdate := models.TenantUpdate{
61+
Name: plan.Name.ValueStringPointer(),
62+
Description: plan.Description.ValueStringPointer(),
63+
Attributes: attributes,
64+
}
65+
66+
updatedTenant, err := c.client.Api.Tenants.Update(ctx, plan.Key.ValueString(), tenantUpdate)
67+
68+
if err != nil {
69+
return tenantModel{}, err
70+
}
71+
72+
return tfModelFromTenantRead(*updatedTenant), nil
73+
}
74+
75+
func (c *tenantClient) Delete(ctx context.Context, key string) error {
76+
return c.client.Api.Tenants.Delete(ctx, key)
77+
}

internal/provider/tenants/model.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package tenants
2+
3+
import (
4+
"encoding/json"
5+
"github.com/hashicorp/terraform-plugin-framework/types"
6+
"github.com/permitio/permit-golang/pkg/models"
7+
)
8+
9+
type tenantModel struct {
10+
Id types.String `tfsdk:"id"`
11+
OrganizationId types.String `tfsdk:"organization_id"`
12+
ProjectId types.String `tfsdk:"project_id"`
13+
EnvironmentId types.String `tfsdk:"environment_id"`
14+
CreatedAt types.String `tfsdk:"created_at"`
15+
UpdatedAt types.String `tfsdk:"updated_at"`
16+
LastActionAt types.String `tfsdk:"last_action_at"`
17+
Key types.String `tfsdk:"key"`
18+
Name types.String `tfsdk:"name"`
19+
Description types.String `tfsdk:"description"`
20+
Attributes types.String `tfsdk:"attributes"`
21+
}
22+
23+
func tfModelFromTenantRead(m models.TenantRead) tenantModel {
24+
r := tenantModel{}
25+
r.Id = types.StringValue(m.Id)
26+
r.Key = types.StringValue(m.Key)
27+
r.Name = types.StringValue(m.Name)
28+
r.Description = types.StringPointerValue(m.Description)
29+
r.EnvironmentId = types.StringValue(m.EnvironmentId)
30+
r.ProjectId = types.StringValue(m.ProjectId)
31+
r.OrganizationId = types.StringValue(m.OrganizationId)
32+
r.CreatedAt = types.StringValue(m.CreatedAt.String())
33+
r.UpdatedAt = types.StringValue(m.UpdatedAt.String())
34+
r.LastActionAt = types.StringValue(m.LastActionAt.String())
35+
36+
// Convert attributes map to JSON string
37+
if len(m.Attributes) > 0 {
38+
attributesJSON, err := json.Marshal(m.Attributes)
39+
if err == nil {
40+
r.Attributes = types.StringValue(string(attributesJSON))
41+
} else {
42+
r.Attributes = types.StringValue("{}")
43+
}
44+
} else {
45+
r.Attributes = types.StringNull()
46+
}
47+
48+
return r
49+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package tenants
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/hashicorp/terraform-plugin-framework/resource"
7+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
8+
"github.com/permitio/terraform-provider-permit-io/internal/provider/common"
9+
)
10+
11+
// Ensure the implementation satisfies the expected interfaces.
12+
var (
13+
_ resource.Resource = &TenantResource{}
14+
_ resource.ResourceWithConfigure = &TenantResource{}
15+
)
16+
17+
func NewTenantResource() resource.Resource {
18+
return &TenantResource{}
19+
}
20+
21+
type TenantResource struct {
22+
client tenantClient
23+
}
24+
25+
func (r *TenantResource) Configure(ctx context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
26+
permitClient := common.Configure(ctx, request, response)
27+
r.client = tenantClient{client: permitClient}
28+
}
29+
30+
func (r *TenantResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
31+
response.TypeName = request.ProviderTypeName + "_tenant"
32+
}
33+
34+
func (r *TenantResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
35+
attributes := common.CreateBaseResourceSchema()
36+
37+
attributes["last_action_at"] = schema.StringAttribute{
38+
MarkdownDescription: "Date and time when the tenant was last active (ISO_8601 format). In other words, this is the last time a permission check was done on a resource belonging to this tenant.",
39+
Computed: true,
40+
}
41+
42+
attributes["attributes"] = schema.StringAttribute{
43+
MarkdownDescription: "Arbitrary tenant attributes in JSON format that will be used to enforce attribute-based access control policies.",
44+
Optional: true,
45+
Computed: true,
46+
}
47+
48+
resp.Schema = schema.Schema{
49+
Attributes: attributes,
50+
MarkdownDescription: "Manages a Permit.io tenant. Tenants represent isolated groups or organizations within your application. See [the documentation](https://api.permit.io/v2/redoc#tag/Tenants) for more information about tenants.",
51+
}
52+
}
53+
54+
func (r *TenantResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
55+
var plan tenantModel
56+
57+
response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...)
58+
59+
if response.Diagnostics.HasError() {
60+
return
61+
}
62+
63+
tenantRead, err := r.client.Create(ctx, plan)
64+
65+
if err != nil {
66+
response.Diagnostics.AddError(
67+
"Unable to create tenant",
68+
fmt.Errorf("unable to create tenant: %w", err).Error(),
69+
)
70+
return
71+
}
72+
73+
response.Diagnostics.Append(response.State.Set(ctx, tenantRead)...)
74+
}
75+
76+
func (r *TenantResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
77+
var model tenantModel
78+
79+
response.Diagnostics.Append(request.State.Get(ctx, &model)...)
80+
81+
if response.Diagnostics.HasError() {
82+
return
83+
}
84+
85+
tenantRead, err := r.client.Read(ctx, model.Key.ValueString())
86+
87+
if err != nil {
88+
response.Diagnostics.AddError(
89+
"Unable to read tenant",
90+
fmt.Errorf("unable to read tenant: %w", err).Error(),
91+
)
92+
return
93+
}
94+
95+
response.Diagnostics.Append(response.State.Set(ctx, &tenantRead)...)
96+
}
97+
98+
func (r *TenantResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
99+
var plan tenantModel
100+
101+
response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...)
102+
103+
if response.Diagnostics.HasError() {
104+
return
105+
}
106+
107+
tenantRead, err := r.client.Update(ctx, plan)
108+
109+
if err != nil {
110+
response.Diagnostics.AddError(
111+
"Unable to update tenant",
112+
fmt.Errorf("unable to update tenant: %w", err).Error(),
113+
)
114+
return
115+
}
116+
117+
response.Diagnostics.Append(response.State.Set(ctx, tenantRead)...)
118+
}
119+
120+
func (r *TenantResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
121+
var model tenantModel
122+
response.Diagnostics.Append(request.State.Get(ctx, &model)...)
123+
124+
if response.Diagnostics.HasError() {
125+
return
126+
}
127+
128+
err := r.client.Delete(ctx, model.Key.ValueString())
129+
130+
if err != nil {
131+
response.Diagnostics.AddError(
132+
"Unable to delete tenant",
133+
fmt.Errorf("unable to delete tenant: %w", err).Error(),
134+
)
135+
}
136+
}

0 commit comments

Comments
 (0)