Skip to content

Commit d1b1601

Browse files
committed
feat: add parent_id field support to condition sets
1 parent 2eda495 commit d1b1601

3 files changed

Lines changed: 290 additions & 0 deletions

File tree

internal/provider/conditionsets/client.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package conditionsets
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67
"github.com/hashicorp/terraform-plugin-framework/types"
78
"github.com/permitio/permit-golang/pkg/models"
89
"github.com/permitio/permit-golang/pkg/permit"
@@ -18,6 +19,7 @@ type ConditionSetModel struct {
1819
Description types.String `tfsdk:"description"`
1920
Conditions types.String `tfsdk:"conditions"`
2021
Resource types.String `tfsdk:"resource"`
22+
ParentId types.String `tfsdk:"parent_id"`
2123
}
2224

2325
type ConditionSetClient struct {
@@ -63,6 +65,23 @@ func (c *ConditionSetClient) Read(ctx context.Context, data ConditionSetModel) (
6365
description = types.StringPointerValue(nil)
6466
}
6567

68+
// Handle parent_id: if API returns null, keep it null to maintain consistency
69+
var parentId types.String
70+
if conditionSet.ParentId != nil {
71+
parentIdBytes, err := json.Marshal(conditionSet.ParentId)
72+
if err != nil {
73+
return ConditionSetModel{}, err
74+
}
75+
var parentIdStr string
76+
err = json.Unmarshal(parentIdBytes, &parentIdStr)
77+
if err != nil {
78+
return ConditionSetModel{}, err
79+
}
80+
parentId = types.StringValue(parentIdStr)
81+
} else {
82+
parentId = types.StringPointerValue(nil)
83+
}
84+
6685
state := ConditionSetModel{
6786
Id: types.StringValue(conditionSet.Id),
6887
OrganizationId: types.StringValue(conditionSet.OrganizationId),
@@ -72,6 +91,7 @@ func (c *ConditionSetClient) Read(ctx context.Context, data ConditionSetModel) (
7291
Name: types.StringValue(conditionSet.Name),
7392
Description: description,
7493
Resource: resource,
94+
ParentId: parentId,
7595
Conditions: types.StringValue(string(conditionsMarshalled)),
7696
}
7797

@@ -106,6 +126,18 @@ func (c *ConditionSetClient) Create(ctx context.Context, conditionSetType models
106126
conditionSetCreate.ResourceId = &resourceId
107127
}
108128

129+
if !conditionSetPlan.ParentId.IsNull() {
130+
var parentId models.ParentId
131+
parentIdStr := conditionSetPlan.ParentId.ValueString()
132+
err = json.Unmarshal([]byte(fmt.Sprintf("\"%s\"", parentIdStr)), &parentId)
133+
134+
if err != nil {
135+
return err
136+
}
137+
138+
conditionSetCreate.ParentId = &parentId
139+
}
140+
109141
conditionSetRead, err := c.client.Api.ConditionSets.Create(ctx, conditionSetCreate)
110142

111143
if err != nil {
@@ -116,6 +148,21 @@ func (c *ConditionSetClient) Create(ctx context.Context, conditionSetType models
116148
if conditionSetRead.Description != nil {
117149
conditionSetPlan.Description = types.StringPointerValue(conditionSetRead.Description)
118150
}
151+
// Handle parent_id from API response
152+
if conditionSetRead.ParentId != nil {
153+
parentIdBytes, err := json.Marshal(conditionSetRead.ParentId)
154+
if err != nil {
155+
return err
156+
}
157+
var parentIdStr string
158+
err = json.Unmarshal(parentIdBytes, &parentIdStr)
159+
if err != nil {
160+
return err
161+
}
162+
conditionSetPlan.ParentId = types.StringValue(parentIdStr)
163+
} else {
164+
conditionSetPlan.ParentId = types.StringPointerValue(nil)
165+
}
119166
conditionSetPlan.Id = types.StringValue(conditionSetRead.Id)
120167
conditionSetPlan.OrganizationId = types.StringValue(conditionSetRead.OrganizationId)
121168
conditionSetPlan.ProjectId = types.StringValue(conditionSetRead.ProjectId)
@@ -138,6 +185,18 @@ func (c *ConditionSetClient) Update(ctx context.Context, conditionSetPlan *Condi
138185
Conditions: conditions,
139186
}
140187

188+
if !conditionSetPlan.ParentId.IsNull() {
189+
var parentId models.ParentId
190+
parentIdStr := conditionSetPlan.ParentId.ValueString()
191+
err = json.Unmarshal([]byte(fmt.Sprintf("\"%s\"", parentIdStr)), &parentId)
192+
193+
if err != nil {
194+
return err
195+
}
196+
197+
csUpdate.ParentId = &parentId
198+
}
199+
141200
conditionSetRead, err := c.client.Api.ConditionSets.Update(ctx, conditionSetPlan.Key.ValueString(), csUpdate)
142201

143202
if err != nil {
@@ -155,6 +214,21 @@ func (c *ConditionSetClient) Update(ctx context.Context, conditionSetPlan *Condi
155214
if conditionSetRead.Description != nil {
156215
conditionSetPlan.Description = types.StringPointerValue(conditionSetRead.Description)
157216
}
217+
// Handle parent_id from API response
218+
if conditionSetRead.ParentId != nil {
219+
parentIdBytes, err := json.Marshal(conditionSetRead.ParentId)
220+
if err != nil {
221+
return err
222+
}
223+
var parentIdStr string
224+
err = json.Unmarshal(parentIdBytes, &parentIdStr)
225+
if err != nil {
226+
return err
227+
}
228+
conditionSetPlan.ParentId = types.StringValue(parentIdStr)
229+
} else {
230+
conditionSetPlan.ParentId = types.StringPointerValue(nil)
231+
}
158232
conditionSetPlan.EnvironmentId = types.StringValue(conditionSetRead.EnvironmentId)
159233
conditionSetPlan.ProjectId = types.StringValue(conditionSetRead.ProjectId)
160234
conditionSetPlan.Id = types.StringValue(conditionSetRead.Id)

internal/provider/conditionsets/resource.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ func (c *conditionSetResource) baseAttributes() map[string]schema.Attribute {
148148
MarkdownDescription: "The resource id to which the condition set applies. This is only required for resource sets.",
149149
Optional: true,
150150
},
151+
"parent_id": schema.StringAttribute{
152+
MarkdownDescription: "The parent condition set id. Allows creating a nested condition set hierarchy.",
153+
Optional: true,
154+
Computed: true,
155+
PlanModifiers: []planmodifier.String{
156+
stringplanmodifier.UseStateForUnknown(),
157+
},
158+
},
151159
}
152160
}
153161

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package provider
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
7+
)
8+
9+
// TestUserSetWithContains tests that the contains operator in conditions is correctly preserved
10+
func TestUserSetWithContains(t *testing.T) {
11+
resource.Test(t, resource.TestCase{
12+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
13+
Steps: []resource.TestStep{
14+
// Create and Read testing
15+
{
16+
Config: providerConfig +
17+
`resource "permitio_user_set" "test_contains" {
18+
key = "test-contains-user-set"
19+
name = "Test Contains Operator"
20+
conditions = jsonencode({
21+
"allOf" : [
22+
{
23+
"allOf" : [
24+
{
25+
"subject.email" : {
26+
"contains" : "@test.com"
27+
}
28+
}
29+
]
30+
}
31+
]
32+
})
33+
}`,
34+
Check: resource.ComposeAggregateTestCheckFunc(
35+
resource.TestCheckResourceAttr("permitio_user_set.test_contains", "key", "test-contains-user-set"),
36+
resource.TestCheckResourceAttr("permitio_user_set.test_contains", "name", "Test Contains Operator"),
37+
// Check that the conditions contain the 'contains' operator
38+
resource.TestCheckResourceAttrSet("permitio_user_set.test_contains", "conditions"),
39+
),
40+
},
41+
// Update testing
42+
{
43+
Config: providerConfig +
44+
`resource "permitio_user_set" "test_contains" {
45+
key = "test-contains-user-set"
46+
name = "Test Contains Operator Updated"
47+
conditions = jsonencode({
48+
"allOf" : [
49+
{
50+
"allOf" : [
51+
{
52+
"subject.email" : {
53+
"contains" : "@updated.com"
54+
}
55+
}
56+
]
57+
}
58+
]
59+
})
60+
}`,
61+
Check: resource.ComposeAggregateTestCheckFunc(
62+
resource.TestCheckResourceAttr("permitio_user_set.test_contains", "key", "test-contains-user-set"),
63+
resource.TestCheckResourceAttr("permitio_user_set.test_contains", "name", "Test Contains Operator Updated"),
64+
resource.TestCheckResourceAttrSet("permitio_user_set.test_contains", "conditions"),
65+
),
66+
},
67+
},
68+
})
69+
}
70+
71+
// TestUserSetWithParentId tests that parent_id field is correctly handled
72+
func TestUserSetWithParentId(t *testing.T) {
73+
resource.Test(t, resource.TestCase{
74+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
75+
Steps: []resource.TestStep{
76+
// Create parent user set first
77+
{
78+
Config: providerConfig +
79+
`resource "permitio_user_set" "parent" {
80+
key = "parent-user-set"
81+
name = "Parent User Set"
82+
conditions = jsonencode({
83+
"allOf" : [
84+
{
85+
"allOf" : [
86+
{
87+
"subject.email" : {
88+
"equals" : "admin@test.com"
89+
}
90+
}
91+
]
92+
}
93+
]
94+
})
95+
}
96+
97+
resource "permitio_user_set" "child" {
98+
key = "child-user-set"
99+
name = "Child User Set"
100+
parent_id = permitio_user_set.parent.id
101+
conditions = jsonencode({
102+
"allOf" : [
103+
{
104+
"allOf" : [
105+
{
106+
"subject.email" : {
107+
"contains" : "@child.com"
108+
}
109+
}
110+
]
111+
}
112+
]
113+
})
114+
depends_on = [permitio_user_set.parent]
115+
}`,
116+
Check: resource.ComposeAggregateTestCheckFunc(
117+
resource.TestCheckResourceAttr("permitio_user_set.parent", "key", "parent-user-set"),
118+
resource.TestCheckResourceAttr("permitio_user_set.parent", "name", "Parent User Set"),
119+
resource.TestCheckResourceAttr("permitio_user_set.child", "key", "child-user-set"),
120+
resource.TestCheckResourceAttr("permitio_user_set.child", "name", "Child User Set"),
121+
resource.TestCheckResourceAttrSet("permitio_user_set.child", "parent_id"),
122+
),
123+
},
124+
},
125+
})
126+
}
127+
128+
// TestResourceSetWithContains tests that the contains operator works for resource sets
129+
func TestResourceSetWithContains(t *testing.T) {
130+
resource.Test(t, resource.TestCase{
131+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
132+
Steps: []resource.TestStep{
133+
{
134+
Config: providerConfig +
135+
`resource "permitio_resource" "test_doc" {
136+
key = "test-document"
137+
name = "test document"
138+
description = "a test document"
139+
actions = {
140+
"read" = {
141+
"name" = "read"
142+
}
143+
}
144+
}
145+
146+
resource "permitio_resource_set" "test_contains" {
147+
key = "test-contains-resource-set"
148+
name = "Test Resource Set with Contains"
149+
resource = permitio_resource.test_doc.key
150+
conditions = jsonencode({
151+
"allOf" : [
152+
{
153+
"allOf" : [
154+
{
155+
"resource.title" : {
156+
"contains" : "secret"
157+
}
158+
}
159+
]
160+
}
161+
]
162+
})
163+
depends_on = [permitio_resource.test_doc]
164+
}`,
165+
Check: resource.ComposeAggregateTestCheckFunc(
166+
resource.TestCheckResourceAttr("permitio_resource_set.test_contains", "key", "test-contains-resource-set"),
167+
resource.TestCheckResourceAttr("permitio_resource_set.test_contains", "name", "Test Resource Set with Contains"),
168+
resource.TestCheckResourceAttrSet("permitio_resource_set.test_contains", "conditions"),
169+
),
170+
},
171+
},
172+
})
173+
}
174+
175+
// TestUserSetMultipleOperators tests complex conditions with multiple operators
176+
func TestUserSetMultipleOperators(t *testing.T) {
177+
resource.Test(t, resource.TestCase{
178+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
179+
Steps: []resource.TestStep{
180+
{
181+
Config: providerConfig +
182+
`resource "permitio_user_set" "test_multiple" {
183+
key = "test-multiple-operators"
184+
name = "Test Multiple Operators"
185+
conditions = jsonencode({
186+
"allOf" : [
187+
{
188+
"subject.email" : {
189+
"contains" : "@company.com"
190+
}
191+
},
192+
{
193+
"subject.key" : {
194+
"equals" : "engineering_user"
195+
}
196+
}
197+
]
198+
})
199+
}`,
200+
Check: resource.ComposeAggregateTestCheckFunc(
201+
resource.TestCheckResourceAttr("permitio_user_set.test_multiple", "key", "test-multiple-operators"),
202+
resource.TestCheckResourceAttr("permitio_user_set.test_multiple", "name", "Test Multiple Operators"),
203+
resource.TestCheckResourceAttrSet("permitio_user_set.test_multiple", "conditions"),
204+
),
205+
},
206+
},
207+
})
208+
}

0 commit comments

Comments
 (0)