Skip to content

Commit e12ff0c

Browse files
authored
fix: support nested dataset rules with combinator groups in action schema (#324)
- Make operator optional in dataset rules (required only for leaf rules) - Add combinator and rules attributes for group rules (logical AND/OR) - Support up to 2 levels of nesting for complex filtering scenarios - Add recursive conversion logic for state-to-body and body-to-state - Add acceptance test for nested dataset rules - Add example demonstrating nested dataset rules usage
1 parent d44de35 commit e12ff0c

File tree

10 files changed

+574
-46
lines changed

10 files changed

+574
-46
lines changed

docs/resources/action.md

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,7 @@ Optional:
705705

706706
- `blueprint` (String) The blueprint identifier the string property relates to
707707
- `client_side_encryption` (Attributes) Client-side encryption configuration for the property. The value will be encrypted on the client side before being sent to Port. Cannot be used with `encryption`. (see [below for nested schema](#nestedatt--self_service_trigger--user_properties--string_props--client_side_encryption))
708-
- `dataset` (Attributes) The dataset of an the entity-format property (see [below for nested schema](#nestedatt--self_service_trigger--user_properties--string_props--dataset))
708+
- `dataset` (Attributes) The dataset of an the entity-format property. Supports nested rules with combinator groups. (see [below for nested schema](#nestedatt--self_service_trigger--user_properties--string_props--dataset))
709709
- `default` (String) The default of the string property
710710
- `default_jq_query` (String) The default jq query of the string property
711711
- `depends_on` (List of String) The properties that this property depends on
@@ -743,20 +743,60 @@ Required:
743743
Required:
744744

745745
- `combinator` (String) The combinator of the dataset
746-
- `rules` (Attributes List) The rules of the dataset (see [below for nested schema](#nestedatt--self_service_trigger--user_properties--string_props--dataset--rules))
746+
- `rules` (Attributes List) The rules of the dataset. Can be leaf rules (with operator) or group rules (with combinator and nested rules). (see [below for nested schema](#nestedatt--self_service_trigger--user_properties--string_props--dataset--rules))
747747

748748
<a id="nestedatt--self_service_trigger--user_properties--string_props--dataset--rules"></a>
749749
### Nested Schema for `self_service_trigger.user_properties.string_props.dataset.rules`
750750

751-
Required:
751+
Optional:
752752

753-
- `operator` (String) The operator of the rule
753+
- `blueprint` (String) The blueprint identifier of the rule
754+
- `combinator` (String) The combinator for a group rule (and/or). Used with nested rules instead of operator.
755+
- `operator` (String) The operator of the rule. Required for leaf rules, should not be set for group rules.
756+
- `property` (String) The property identifier of the rule
757+
- `rules` (Attributes List) Nested rules for a group rule. Used with combinator for logical grouping. (see [below for nested schema](#nestedatt--self_service_trigger--user_properties--string_props--dataset--rules--rules))
758+
- `value` (Object) The value of the rule (see [below for nested schema](#nestedatt--self_service_trigger--user_properties--string_props--dataset--rules--value))
759+
760+
<a id="nestedatt--self_service_trigger--user_properties--string_props--dataset--rules--rules"></a>
761+
### Nested Schema for `self_service_trigger.user_properties.string_props.dataset.rules.rules`
754762

755763
Optional:
756764

757765
- `blueprint` (String) The blueprint identifier of the rule
766+
- `combinator` (String) The combinator for a group rule (and/or). Used with nested rules instead of operator.
767+
- `operator` (String) The operator of the rule. Required for leaf rules, should not be set for group rules.
758768
- `property` (String) The property identifier of the rule
759-
- `value` (Object) The value of the rule (see [below for nested schema](#nestedatt--self_service_trigger--user_properties--string_props--dataset--rules--value))
769+
- `rules` (Attributes List) Nested rules for a group rule. Used with combinator for logical grouping. (see [below for nested schema](#nestedatt--self_service_trigger--user_properties--string_props--dataset--rules--rules--rules))
770+
- `value` (Object) The value of the rule (see [below for nested schema](#nestedatt--self_service_trigger--user_properties--string_props--dataset--rules--rules--value))
771+
772+
<a id="nestedatt--self_service_trigger--user_properties--string_props--dataset--rules--rules--rules"></a>
773+
### Nested Schema for `self_service_trigger.user_properties.string_props.dataset.rules.rules.rules`
774+
775+
Optional:
776+
777+
- `blueprint` (String) The blueprint identifier of the rule
778+
- `combinator` (String) The combinator for a group rule (and/or). Used with nested rules instead of operator.
779+
- `operator` (String) The operator of the rule. Required for leaf rules, should not be set for group rules.
780+
- `property` (String) The property identifier of the rule
781+
- `value` (Object) The value of the rule (see [below for nested schema](#nestedatt--self_service_trigger--user_properties--string_props--dataset--rules--rules--rules--value))
782+
783+
<a id="nestedatt--self_service_trigger--user_properties--string_props--dataset--rules--rules--rules--value"></a>
784+
### Nested Schema for `self_service_trigger.user_properties.string_props.dataset.rules.rules.rules.value`
785+
786+
Optional:
787+
788+
- `jq_query` (String)
789+
790+
791+
792+
<a id="nestedatt--self_service_trigger--user_properties--string_props--dataset--rules--rules--value"></a>
793+
### Nested Schema for `self_service_trigger.user_properties.string_props.dataset.rules.rules.value`
794+
795+
Optional:
796+
797+
- `jq_query` (String)
798+
799+
760800

761801
<a id="nestedatt--self_service_trigger--user_properties--string_props--dataset--rules--value"></a>
762802
### Nested Schema for `self_service_trigger.user_properties.string_props.dataset.rules.value`
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../provider.tf
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
# This example demonstrates different ways to use nested dataset rules with combinator groups
2+
# Note: Change the identifier prefix if these conflict with existing blueprints in your org
3+
4+
locals {
5+
# Change this prefix if you have naming conflicts
6+
prefix = "nested_example"
7+
}
8+
9+
resource "port_blueprint" "service" {
10+
title = "Service (Nested Rules Example)"
11+
icon = "Microservice"
12+
identifier = "${local.prefix}_service"
13+
properties = {
14+
string_props = {
15+
"environment" = {
16+
title = "Environment"
17+
enum = ["development", "staging", "production"]
18+
}
19+
"team" = {
20+
title = "Team"
21+
}
22+
"region" = {
23+
title = "Region"
24+
enum = ["us-east-1", "us-west-2", "eu-west-1"]
25+
}
26+
}
27+
boolean_props = {
28+
"user_assignable" = {
29+
title = "User Assignable"
30+
}
31+
}
32+
}
33+
}
34+
35+
resource "port_blueprint" "permission_set" {
36+
title = "Permission Set (Nested Rules Example)"
37+
icon = "Lock"
38+
identifier = "${local.prefix}_permission_set"
39+
properties = {
40+
string_props = {
41+
"name" = {
42+
title = "Name"
43+
}
44+
"level" = {
45+
title = "Level"
46+
enum = ["read", "write", "admin"]
47+
}
48+
}
49+
boolean_props = {
50+
"user_assignable" = {
51+
title = "User Assignable"
52+
}
53+
}
54+
}
55+
relations = {
56+
"business_domain" = {
57+
title = "Business Domain"
58+
target = port_blueprint.service.identifier
59+
}
60+
}
61+
}
62+
63+
# Example 1: Simple nested dataset with OR combinator
64+
# Use case: Filter entities where property matches one of multiple values
65+
resource "port_action" "or_combinator_example" {
66+
title = "Assign Permission"
67+
identifier = "${local.prefix}_assign_permission_or"
68+
self_service_trigger = {
69+
operation = "DAY-2"
70+
blueprint_identifier = port_blueprint.service.identifier
71+
user_properties = {
72+
string_props = {
73+
permission = {
74+
title = "Permission"
75+
format = "entity"
76+
blueprint = port_blueprint.permission_set.identifier
77+
dataset = {
78+
combinator = "and"
79+
rules = [
80+
# First rule: must be user assignable
81+
{
82+
property = "user_assignable"
83+
operator = "="
84+
value = {
85+
jq_query = "true"
86+
}
87+
},
88+
# Second rule: level must be read OR write (not admin)
89+
{
90+
combinator = "or"
91+
rules = [
92+
{
93+
property = "level"
94+
operator = "="
95+
value = {
96+
jq_query = "\"read\""
97+
}
98+
},
99+
{
100+
property = "level"
101+
operator = "="
102+
value = {
103+
jq_query = "\"write\""
104+
}
105+
}
106+
]
107+
}
108+
]
109+
}
110+
}
111+
}
112+
}
113+
}
114+
description = "Assign a read or write permission to a service"
115+
webhook_method = {
116+
url = "https://api.example.com/assign-permission"
117+
}
118+
}
119+
120+
# Example 2: Nested dataset with dynamic values from form
121+
# Use case: Filter based on user selection plus additional conditions
122+
resource "port_action" "dynamic_filter_example" {
123+
title = "Select Service"
124+
identifier = "${local.prefix}_select_service_dynamic"
125+
self_service_trigger = {
126+
operation = "DAY-2"
127+
blueprint_identifier = port_blueprint.service.identifier
128+
user_properties = {
129+
string_props = {
130+
environment = {
131+
title = "Environment"
132+
enum = ["development", "staging", "production"]
133+
}
134+
target_service = {
135+
title = "Target Service"
136+
format = "entity"
137+
blueprint = port_blueprint.service.identifier
138+
depends_on = ["environment"]
139+
dataset = {
140+
combinator = "and"
141+
rules = [
142+
# Match the selected environment
143+
{
144+
property = "environment"
145+
operator = "="
146+
value = {
147+
jq_query = ".form.environment"
148+
}
149+
},
150+
# AND (team is platform OR team is devops)
151+
{
152+
combinator = "or"
153+
rules = [
154+
{
155+
property = "team"
156+
operator = "="
157+
value = {
158+
jq_query = "\"platform\""
159+
}
160+
},
161+
{
162+
property = "team"
163+
operator = "="
164+
value = {
165+
jq_query = "\"devops\""
166+
}
167+
}
168+
]
169+
}
170+
]
171+
}
172+
}
173+
}
174+
}
175+
}
176+
description = "Select a service from platform or devops team in the chosen environment"
177+
webhook_method = {
178+
url = "https://api.example.com/select-service"
179+
}
180+
}
181+
182+
# Example 3: Complex nested rules with multiple conditions
183+
# Use case: Filter with complex business logic (AWS SSO style permissions)
184+
resource "port_action" "complex_nested_example" {
185+
title = "Assign AWS Permission Set"
186+
identifier = "${local.prefix}_assign_aws_permission"
187+
self_service_trigger = {
188+
operation = "DAY-2"
189+
blueprint_identifier = port_blueprint.service.identifier
190+
user_properties = {
191+
string_props = {
192+
business_domain = {
193+
title = "Business Domain"
194+
enum = ["engineering", "finance", "operations"]
195+
}
196+
permission_set = {
197+
title = "Permission Set"
198+
required = true
199+
format = "entity"
200+
blueprint = port_blueprint.permission_set.identifier
201+
depends_on = ["business_domain"]
202+
dataset = {
203+
combinator = "and"
204+
rules = [
205+
# Rule 1: Permission must be user assignable
206+
{
207+
property = "user_assignable"
208+
operator = "!="
209+
value = {
210+
jq_query = "false"
211+
}
212+
},
213+
# Rule 2: OR condition for business domain matching
214+
{
215+
combinator = "or"
216+
rules = [
217+
# Option A: Related to "engineering" domain
218+
{
219+
property = "$identifier"
220+
operator = "contains"
221+
value = {
222+
jq_query = "\"engineering\""
223+
}
224+
},
225+
# Option B: Related to user-selected business domain
226+
{
227+
property = "$identifier"
228+
operator = "contains"
229+
value = {
230+
jq_query = ".form.business_domain"
231+
}
232+
}
233+
]
234+
}
235+
]
236+
}
237+
}
238+
}
239+
}
240+
}
241+
description = "Assign AWS permission set based on business domain"
242+
webhook_method = {
243+
url = "https://api.example.com/assign-aws-permission"
244+
}
245+
}
246+
247+
# Example 4: Two levels of nesting
248+
# Use case: (A AND B) OR (C AND D)
249+
resource "port_action" "two_level_nesting_example" {
250+
title = "Select Eligible Service"
251+
identifier = "${local.prefix}_select_eligible_service"
252+
self_service_trigger = {
253+
operation = "DAY-2"
254+
blueprint_identifier = port_blueprint.service.identifier
255+
user_properties = {
256+
string_props = {
257+
eligible_service = {
258+
title = "Eligible Service"
259+
format = "entity"
260+
blueprint = port_blueprint.service.identifier
261+
dataset = {
262+
# Top level: OR - match either condition group
263+
combinator = "or"
264+
rules = [
265+
# Group 1: Production services in us-east-1
266+
{
267+
combinator = "and"
268+
rules = [
269+
{
270+
property = "environment"
271+
operator = "="
272+
value = {
273+
jq_query = "\"production\""
274+
}
275+
},
276+
{
277+
property = "region"
278+
operator = "="
279+
value = {
280+
jq_query = "\"us-east-1\""
281+
}
282+
}
283+
]
284+
},
285+
# Group 2: Staging services in eu-west-1
286+
{
287+
combinator = "and"
288+
rules = [
289+
{
290+
property = "environment"
291+
operator = "="
292+
value = {
293+
jq_query = "\"staging\""
294+
}
295+
},
296+
{
297+
property = "region"
298+
operator = "="
299+
value = {
300+
jq_query = "\"eu-west-1\""
301+
}
302+
}
303+
]
304+
}
305+
]
306+
}
307+
}
308+
}
309+
}
310+
}
311+
description = "Select production in us-east-1 OR staging in eu-west-1"
312+
webhook_method = {
313+
url = "https://api.example.com/select-eligible"
314+
}
315+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../provider.tf

0 commit comments

Comments
 (0)