diff --git a/docs/resources/port_action.md b/docs/resources/port_action.md index c4d1bf1d..b1daa57c 100644 --- a/docs/resources/port_action.md +++ b/docs/resources/port_action.md @@ -3,12 +3,376 @@ page_title: "port_action Resource - terraform-provider-port-labs" subcategory: "" description: |- - Action resource + Action + This resource allows you to manage self-service action. + See the Port documentation https://docs.getport.io/create-self-service-experiences/ for more information about self-service actions. + Example Usage + Create a blueprint and an action relating to that blueprint which triggers a github workflow: + ```hcl + resource "portblueprint" "myBlueprint" { + icon = "Terraform" + identifier = "myBlueprint" + title = "My Blueprint" + properties = { + numberprops = { + "numberProp" = { + title = "Number Property" + required = false + } + } + } + } + resource "portaction" "myAction" { + title = "My Action" + blueprint = portblueprint.myBlueprint.identifier + identifier = "myAction" + trigger = "CREATE" + requiredapproval = false + githubmethod = { + org = "your-org" + repo = "your-repo" + workflow = "your-workflow" + } + userproperties = { + stringprops = { + stringValue = { + title = "String Value" + } + } + number_props = { + "numberProp" = { + title = "Number Value" + required = true + } + } + } + } + ``` + Create related "parent" and "child" blueprints and a CREATE action for the child blueprint with user inputs to select entities from the parent blueprint and triggers a github workflow: + ```hcl + resource "port_blueprint" "parent" { + icon = "Terraform" + title = "Parent" + identifier = "parent" + properties = {} + } + resource "portblueprint" "child" { + icon = "Terraform" + title = "Child" + identifier = "child" + properties = {} + relations = { + "childOf" = { + title = "Child Of" + many = true + required = false + target = portblueprint.parent.identifier + } + } + } + resource "portaction" "myAction" { + title = "My Action" + blueprint = portblueprint.child.identifier + identifier = "myAction" + trigger = "CREATE" + requiredapproval = false + githubmethod = { + org = "your-org" + repo = "your-repo" + workflow = "your-workflow" + } + userproperties = { + stringprops = { + singleParent = { + title = "Single Parent Entity Selection" + format = "entity" + blueprint = portblueprint.parent.identifier + } + } + arrayprops = { + miltipleParents = { + title = "Single Parent Entity Selection" + stringitems = { + format = "entity" + blueprint = portblueprint.parent.identifier + } + } + } + } + } + ``` + Create the same resources as in the previous example, but the action's entity selection properties will only allow entities which pass the datasets: + ```hcl + resource "port_blueprint" "parent" { + icon = "Terraform" + title = "Parent" + identifier = "parent" + properties = {} + } + resource "portblueprint" "child" { + icon = "Terraform" + title = "Child" + identifier = "child" + properties = {} + relations = { + "childOf" = { + title = "Child Of" + many = true + required = false + target = portblueprint.parent.identifier + } + } + } + resource "portaction" "myAction" { + title = "My Action" + blueprint = portblueprint.child.identifier + identifier = "myAction" + trigger = "CREATE" + requiredapproval = false + githubmethod = { + org = "your-org" + repo = "your-repo" + workflow = "your-workflow" + omitpayload = true + omituserinputs = true + reportworkflowstatus = true + } + userproperties = { + stringprops = { + singleParent = { + title = "Single Parent Entity Selection" + format = "entity" + blueprint = portblueprint.parent.identifier + dataset = { + combinator = "and" + rules = [{ + property = "$title" + operator = "contains" + value = { + jqquery = "\"specificValue\"" + } + }] + } + } + } + arrayprops = { + miltipleParents = { + title = "Single Parent Entity Selection" + stringitems = { + format = "entity" + blueprint = portblueprint.parent.identifier + dataset = jsonencode({ + combinator = "and" + rules = [{ + property = "$title" + operator = "contains" + value = "specificValue" + }] + }) + } + } + } + } + } + ``` --- # port_action (Resource) -Action resource +# Action + +This resource allows you to manage self-service action. + +See the [Port documentation](https://docs.getport.io/create-self-service-experiences/) for more information about self-service actions. + +## Example Usage + +Create a blueprint and an action relating to that blueprint which triggers a github workflow: + +```hcl + +resource "port_blueprint" "myBlueprint" { + icon = "Terraform" + identifier = "myBlueprint" + title = "My Blueprint" + properties = { + number_props = { + "numberProp" = { + title = "Number Property" + required = false + } + } + } +} + +resource "port_action" "myAction" { + title = "My Action" + blueprint = port_blueprint.myBlueprint.identifier + identifier = "myAction" + trigger = "CREATE" + required_approval = false + github_method = { + org = "your-org" + repo = "your-repo" + workflow = "your-workflow" + } + user_properties = { + string_props = { + stringValue = { + title = "String Value" + } + } + number_props = { + "numberProp" = { + title = "Number Value" + required = true + } + } + } +} + +``` + +Create related "parent" and "child" blueprints and a CREATE action for the child blueprint with user inputs to select entities from the parent blueprint and triggers a github workflow: + +```hcl + + +resource "port_blueprint" "parent" { + icon = "Terraform" + title = "Parent" + identifier = "parent" + properties = {} +} + +resource "port_blueprint" "child" { + icon = "Terraform" + title = "Child" + identifier = "child" + properties = {} + relations = { + "childOf" = { + title = "Child Of" + many = true + required = false + target = port_blueprint.parent.identifier + } + } +} + +resource "port_action" "myAction" { + title = "My Action" + blueprint = port_blueprint.child.identifier + identifier = "myAction" + trigger = "CREATE" + required_approval = false + github_method = { + org = "your-org" + repo = "your-repo" + workflow = "your-workflow" + } + user_properties = { + string_props = { + singleParent = { + title = "Single Parent Entity Selection" + format = "entity" + blueprint = port_blueprint.parent.identifier + } + } + array_props = { + miltipleParents = { + title = "Single Parent Entity Selection" + string_items = { + format = "entity" + blueprint = port_blueprint.parent.identifier + } + } + } + } +} + +``` + + +Create the same resources as in the previous example, but the action's entity selection properties will only allow entities which pass the `dataset`s: + +```hcl + +resource "port_blueprint" "parent" { + icon = "Terraform" + title = "Parent" + identifier = "parent" + properties = {} +} + +resource "port_blueprint" "child" { + icon = "Terraform" + title = "Child" + identifier = "child" + properties = {} + relations = { + "childOf" = { + title = "Child Of" + many = true + required = false + target = port_blueprint.parent.identifier + } + } +} + +resource "port_action" "myAction" { + title = "My Action" + blueprint = port_blueprint.child.identifier + identifier = "myAction" + trigger = "CREATE" + required_approval = false + github_method = { + org = "your-org" + repo = "your-repo" + workflow = "your-workflow" + omit_payload = true + omit_user_inputs = true + report_workflow_status = true + } + user_properties = { + string_props = { + singleParent = { + title = "Single Parent Entity Selection" + format = "entity" + blueprint = port_blueprint.parent.identifier + dataset = { + combinator = "and" + rules = [{ + property = "$title" + operator = "contains" + value = { + jq_query = "\"specificValue\"" + } + }] + } + } + } + array_props = { + miltipleParents = { + title = "Single Parent Entity Selection" + string_items = { + format = "entity" + blueprint = port_blueprint.parent.identifier + dataset = jsonencode({ + combinator = "and" + rules = [{ + property = "$title" + operator = "contains" + value = "specificValue" + }] + }) + } + } + } + } +} + +``` @@ -126,7 +490,6 @@ Optional: Optional: - `boolean_items` (Attributes) The items of the array property (see [below for nested schema](#nestedatt--user_properties--array_props--boolean_items)) -- `dataset` (Attributes) The dataset of the property (see [below for nested schema](#nestedatt--user_properties--array_props--dataset)) - `default_jq_query` (String) The default jq query of the array property - `depends_on` (List of String) The properties that this property depends on - `description` (String) The description of the property @@ -149,37 +512,6 @@ Optional: - `default` (List of Boolean) The default of the items - -### Nested Schema for `user_properties.array_props.dataset` - -Required: - -- `combinator` (String) The combinator of the dataset -- `rules` (Attributes List) The rules of the dataset (see [below for nested schema](#nestedatt--user_properties--array_props--dataset--rules)) - - -### Nested Schema for `user_properties.array_props.dataset.rules` - -Required: - -- `operator` (String) The operator of the rule -- `value` (Object) The value of the rule (see [below for nested schema](#nestedatt--user_properties--array_props--dataset--rules--value)) - -Optional: - -- `blueprint` (String) The blueprint identifier of the rule -- `property` (String) The property identifier of the rule - - -### Nested Schema for `user_properties.array_props.dataset.rules.value` - -Optional: - -- `jq_query` (String) - - - - ### Nested Schema for `user_properties.array_props.number_items` @@ -204,6 +536,7 @@ Optional: Optional: - `blueprint` (String) The blueprint identifier the property relates to +- `dataset` (String) The dataset of an the entity-format items - `default` (List of String) The default of the items - `enum` (List of String) The enum of the items - `enum_jq_query` (String) The enum jq query of the string items @@ -216,7 +549,6 @@ Optional: Optional: -- `dataset` (Attributes) The dataset of the property (see [below for nested schema](#nestedatt--user_properties--boolean_props--dataset)) - `default` (Boolean) The default of the boolean property - `default_jq_query` (String) The default jq query of the boolean property - `depends_on` (List of String) The properties that this property depends on @@ -227,44 +559,12 @@ Optional: - `visible` (Boolean) The visibility of the boolean property - `visible_jq_query` (String) The visibility condition jq query of the boolean property - -### Nested Schema for `user_properties.boolean_props.dataset` - -Required: - -- `combinator` (String) The combinator of the dataset -- `rules` (Attributes List) The rules of the dataset (see [below for nested schema](#nestedatt--user_properties--boolean_props--dataset--rules)) - - -### Nested Schema for `user_properties.boolean_props.dataset.rules` - -Required: - -- `operator` (String) The operator of the rule -- `value` (Object) The value of the rule (see [below for nested schema](#nestedatt--user_properties--boolean_props--dataset--rules--value)) - -Optional: - -- `blueprint` (String) The blueprint identifier of the rule -- `property` (String) The property identifier of the rule - - -### Nested Schema for `user_properties.boolean_props.dataset.rules.value` - -Optional: - -- `jq_query` (String) - - - - ### Nested Schema for `user_properties.number_props` Optional: -- `dataset` (Attributes) The dataset of the property (see [below for nested schema](#nestedatt--user_properties--number_props--dataset)) - `default` (Number) The default of the number property - `default_jq_query` (String) The default jq query of the number property - `depends_on` (List of String) The properties that this property depends on @@ -279,44 +579,12 @@ Optional: - `visible` (Boolean) The visibility of the number property - `visible_jq_query` (String) The visibility condition jq query of the number property - -### Nested Schema for `user_properties.number_props.dataset` - -Required: - -- `combinator` (String) The combinator of the dataset -- `rules` (Attributes List) The rules of the dataset (see [below for nested schema](#nestedatt--user_properties--number_props--dataset--rules)) - - -### Nested Schema for `user_properties.number_props.dataset.rules` - -Required: - -- `operator` (String) The operator of the rule -- `value` (Object) The value of the rule (see [below for nested schema](#nestedatt--user_properties--number_props--dataset--rules--value)) - -Optional: - -- `blueprint` (String) The blueprint identifier of the rule -- `property` (String) The property identifier of the rule - - -### Nested Schema for `user_properties.number_props.dataset.rules.value` - -Optional: - -- `jq_query` (String) - - - - ### Nested Schema for `user_properties.object_props` Optional: -- `dataset` (Attributes) The dataset of the property (see [below for nested schema](#nestedatt--user_properties--object_props--dataset)) - `default` (String) The default of the object property - `default_jq_query` (String) The default jq query of the object property - `depends_on` (List of String) The properties that this property depends on @@ -328,37 +596,6 @@ Optional: - `visible` (Boolean) The visibility of the object property - `visible_jq_query` (String) The visibility condition jq query of the object property - -### Nested Schema for `user_properties.object_props.dataset` - -Required: - -- `combinator` (String) The combinator of the dataset -- `rules` (Attributes List) The rules of the dataset (see [below for nested schema](#nestedatt--user_properties--object_props--dataset--rules)) - - -### Nested Schema for `user_properties.object_props.dataset.rules` - -Required: - -- `operator` (String) The operator of the rule -- `value` (Object) The value of the rule (see [below for nested schema](#nestedatt--user_properties--object_props--dataset--rules--value)) - -Optional: - -- `blueprint` (String) The blueprint identifier of the rule -- `property` (String) The property identifier of the rule - - -### Nested Schema for `user_properties.object_props.dataset.rules.value` - -Optional: - -- `jq_query` (String) - - - - ### Nested Schema for `user_properties.string_props` @@ -366,7 +603,7 @@ Optional: Optional: - `blueprint` (String) The blueprint identifier the string property relates to -- `dataset` (Attributes) The dataset of the property (see [below for nested schema](#nestedatt--user_properties--string_props--dataset)) +- `dataset` (Attributes) The dataset of an the entity-format property (see [below for nested schema](#nestedatt--user_properties--string_props--dataset)) - `default` (String) The default of the string property - `default_jq_query` (String) The default jq query of the string property - `depends_on` (List of String) The properties that this property depends on diff --git a/internal/utils/utils.go b/internal/utils/utils.go index f012d021..f1407731 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -85,6 +85,19 @@ func GoObjectToTerraformString(v interface{}) (types.String, error) { return types.StringValue(value), nil } +func TerraformJsonStringToGoObject(v *string) (*map[string]any, error) { + if v == nil || *v == "" { + return nil, nil + } + + vMap := make(map[string]any) + if err := json.Unmarshal([]byte(*v), &vMap); err != nil { + return nil, err + } + + return &vMap, nil +} + func InterfaceToStringArray(o interface{}) []string { items := o.([]interface{}) res := make([]string, len(items)) diff --git a/port/action-permissions/actionPermissionToPortBody.go b/port/action-permissions/actionPermissionToPortBody.go index d5d996a9..00d93904 100644 --- a/port/action-permissions/actionPermissionToPortBody.go +++ b/port/action-permissions/actionPermissionToPortBody.go @@ -1,27 +1,11 @@ package action_permissions import ( - "encoding/json" "github.com/port-labs/terraform-provider-port-labs/internal/cli" "github.com/port-labs/terraform-provider-port-labs/internal/flex" + "github.com/port-labs/terraform-provider-port-labs/internal/utils" ) -func policyToPortBody(policy *string) (*map[string]any, error) { - // if policy is empty, set it to nil, so it will override the existing policy on server, - // as opposed to merging it, due to only having a PATCH endpoint - - if policy == nil || *policy == "" { - return nil, nil - } - - policyMap := make(map[string]any) - if err := json.Unmarshal([]byte(*policy), &policyMap); err != nil { - return nil, err - } - - return &policyMap, nil -} - func actionPermissionsToPortBody(state *PermissionsModel) (*cli.ActionPermissions, error) { if state == nil { return nil, nil @@ -41,12 +25,12 @@ func actionPermissionsToPortBody(state *PermissionsModel) (*cli.ActionPermission }, } - approvePolicyMap, err := policyToPortBody(state.Approve.Policy.ValueStringPointer()) + approvePolicyMap, err := utils.TerraformJsonStringToGoObject(state.Approve.Policy.ValueStringPointer()) if err != nil { return nil, err } - executePolicyMap, err := policyToPortBody(state.Execute.Policy.ValueStringPointer()) + executePolicyMap, err := utils.TerraformJsonStringToGoObject(state.Execute.Policy.ValueStringPointer()) if err != nil { return nil, err } diff --git a/port/action/array.go b/port/action/array.go index 24dafd10..c4149107 100644 --- a/port/action/array.go +++ b/port/action/array.go @@ -38,6 +38,15 @@ func handleArrayItemsToBody(ctx context.Context, property *cli.ActionProperty, p items["enum"] = enumList } + if !prop.StringItems.Dataset.IsNull() { + v, err := utils.TerraformJsonStringToGoObject(prop.StringItems.Dataset.ValueStringPointer()) + if err != nil { + return err + } + + items["dataset"] = v + } + if !prop.StringItems.Format.IsNull() { items["format"] = prop.StringItems.Format.ValueString() } @@ -165,9 +174,6 @@ func arrayPropResourceToBody(ctx context.Context, d *ActionModel, props map[stri property.DependsOn = utils.InterfaceToStringArray(dependsOn) } - if prop.Dataset != nil { - property.Dataset = actionDataSetToPortBody(prop.Dataset) - } err := handleArrayItemsToBody(ctx, &property, prop, required) if err != nil { @@ -233,6 +239,13 @@ func addArrayPropertiesToResource(v *cli.ActionProperty) (*ArrayPropModel, error if value, ok := v.Items["blueprint"]; ok && value != nil { arrayProp.StringItems.Blueprint = types.StringValue(v.Items["blueprint"].(string)) } + if value, ok := v.Items["dataset"]; ok && value != nil { + ds, err := utils.GoObjectToTerraformString(v.Items["dataset"]) + if err != nil { + return nil, err + } + arrayProp.StringItems.Dataset = ds + } if value, ok := v.Items["enum"]; ok && value != nil { v := reflect.ValueOf(value) diff --git a/port/action/boolean.go b/port/action/boolean.go index 1b5b7263..51a95457 100644 --- a/port/action/boolean.go +++ b/port/action/boolean.go @@ -49,9 +49,6 @@ func booleanPropResourceToBody(ctx context.Context, d *ActionModel, props map[st property.DependsOn = utils.InterfaceToStringArray(dependsOn) } - if prop.Dataset != nil { - property.Dataset = actionDataSetToPortBody(prop.Dataset) - } if !prop.Visible.IsNull() { property.Visible = prop.Visible.ValueBoolPointer() diff --git a/port/action/model.go b/port/action/model.go index ca017c73..5123375b 100644 --- a/port/action/model.go +++ b/port/action/model.go @@ -71,15 +71,14 @@ type StringPropModel struct { } type NumberPropModel struct { - Title types.String `tfsdk:"title"` - Icon types.String `tfsdk:"icon"` - Description types.String `tfsdk:"description"` - Required types.Bool `tfsdk:"required"` - DependsOn types.List `tfsdk:"depends_on"` - Dataset *DatasetModel `tfsdk:"dataset"` - DefaultJqQuery types.String `tfsdk:"default_jq_query"` - Visible types.Bool `tfsdk:"visible"` - VisibleJqQuery types.String `tfsdk:"visible_jq_query"` + Title types.String `tfsdk:"title"` + Icon types.String `tfsdk:"icon"` + Description types.String `tfsdk:"description"` + Required types.Bool `tfsdk:"required"` + DependsOn types.List `tfsdk:"depends_on"` + DefaultJqQuery types.String `tfsdk:"default_jq_query"` + Visible types.Bool `tfsdk:"visible"` + VisibleJqQuery types.String `tfsdk:"visible_jq_query"` Default types.Float64 `tfsdk:"default"` Maximum types.Float64 `tfsdk:"maximum"` @@ -89,29 +88,27 @@ type NumberPropModel struct { } type BooleanPropModel struct { - Title types.String `tfsdk:"title"` - Icon types.String `tfsdk:"icon"` - Description types.String `tfsdk:"description"` - Required types.Bool `tfsdk:"required"` - DependsOn types.List `tfsdk:"depends_on"` - Dataset *DatasetModel `tfsdk:"dataset"` - DefaultJqQuery types.String `tfsdk:"default_jq_query"` - Visible types.Bool `tfsdk:"visible"` - VisibleJqQuery types.String `tfsdk:"visible_jq_query"` + Title types.String `tfsdk:"title"` + Icon types.String `tfsdk:"icon"` + Description types.String `tfsdk:"description"` + Required types.Bool `tfsdk:"required"` + DependsOn types.List `tfsdk:"depends_on"` + DefaultJqQuery types.String `tfsdk:"default_jq_query"` + Visible types.Bool `tfsdk:"visible"` + VisibleJqQuery types.String `tfsdk:"visible_jq_query"` Default types.Bool `tfsdk:"default"` } type ArrayPropModel struct { - Title types.String `tfsdk:"title"` - Icon types.String `tfsdk:"icon"` - Description types.String `tfsdk:"description"` - Required types.Bool `tfsdk:"required"` - DependsOn types.List `tfsdk:"depends_on"` - Dataset *DatasetModel `tfsdk:"dataset"` - DefaultJqQuery types.String `tfsdk:"default_jq_query"` - Visible types.Bool `tfsdk:"visible"` - VisibleJqQuery types.String `tfsdk:"visible_jq_query"` + Title types.String `tfsdk:"title"` + Icon types.String `tfsdk:"icon"` + Description types.String `tfsdk:"description"` + Required types.Bool `tfsdk:"required"` + DependsOn types.List `tfsdk:"depends_on"` + DefaultJqQuery types.String `tfsdk:"default_jq_query"` + Visible types.Bool `tfsdk:"visible"` + VisibleJqQuery types.String `tfsdk:"visible_jq_query"` MaxItems types.Int64 `tfsdk:"max_items"` MinItems types.Int64 `tfsdk:"min_items"` @@ -122,15 +119,14 @@ type ArrayPropModel struct { } type ObjectPropModel struct { - Title types.String `tfsdk:"title"` - Icon types.String `tfsdk:"icon"` - Description types.String `tfsdk:"description"` - Required types.Bool `tfsdk:"required"` - DependsOn types.List `tfsdk:"depends_on"` - Dataset *DatasetModel `tfsdk:"dataset"` - DefaultJqQuery types.String `tfsdk:"default_jq_query"` - Visible types.Bool `tfsdk:"visible"` - VisibleJqQuery types.String `tfsdk:"visible_jq_query"` + Title types.String `tfsdk:"title"` + Icon types.String `tfsdk:"icon"` + Description types.String `tfsdk:"description"` + Required types.Bool `tfsdk:"required"` + DependsOn types.List `tfsdk:"depends_on"` + DefaultJqQuery types.String `tfsdk:"default_jq_query"` + Visible types.Bool `tfsdk:"visible"` + VisibleJqQuery types.String `tfsdk:"visible_jq_query"` Default types.String `tfsdk:"default"` Encryption types.String `tfsdk:"encryption"` @@ -142,6 +138,7 @@ type StringItems struct { Default types.List `tfsdk:"default"` Enum types.List `tfsdk:"enum"` EnumJqQuery types.String `tfsdk:"enum_jq_query"` + Dataset types.String `tfsdk:"dataset"` } type NumberItems struct { diff --git a/port/action/number.go b/port/action/number.go index a5dfa73a..4bd30cfe 100644 --- a/port/action/number.go +++ b/port/action/number.go @@ -81,10 +81,6 @@ func numberPropResourceToBody(ctx context.Context, state *ActionModel, props map } - if prop.Dataset != nil { - property.Dataset = actionDataSetToPortBody(prop.Dataset) - } - if !prop.Visible.IsNull() { property.Visible = prop.Visible.ValueBoolPointer() } diff --git a/port/action/object.go b/port/action/object.go index 49ac66a1..2c2f2d6d 100644 --- a/port/action/object.go +++ b/port/action/object.go @@ -63,10 +63,6 @@ func objectPropResourceToBody(ctx context.Context, d *ActionModel, props map[str property.Encryption = &encryption } - if prop.Dataset != nil { - property.Dataset = actionDataSetToPortBody(prop.Dataset) - } - if !prop.Visible.IsNull() { property.Visible = prop.Visible.ValueBoolPointer() } diff --git a/port/action/refreshActionState.go b/port/action/refreshActionState.go index 525b9734..1eb37d17 100644 --- a/port/action/refreshActionState.go +++ b/port/action/refreshActionState.go @@ -3,9 +3,10 @@ package action import ( "context" "fmt" - "github.com/samber/lo" "reflect" + "github.com/samber/lo" + "github.com/hashicorp/terraform-plugin-framework/types" "github.com/port-labs/terraform-provider-port-labs/internal/cli" "github.com/port-labs/terraform-provider-port-labs/internal/consts" @@ -57,18 +58,16 @@ func writeInvocationMethodToResource(a *cli.Action, state *ActionModel) { } } -func writeDatasetToResource(v cli.ActionProperty) *DatasetModel { - if v.Dataset == nil { +func writeDatasetToResource(ds *cli.Dataset) *DatasetModel { + if ds == nil { return nil } - dataset := v.Dataset - datasetModel := &DatasetModel{ - Combinator: types.StringValue(dataset.Combinator), + Combinator: types.StringValue(ds.Combinator), } - for _, v := range dataset.Rules { + for _, v := range ds.Rules { rule := &Rule{ Blueprint: flex.GoStringToFramework(v.Blueprint), Property: flex.GoStringToFramework(v.Property), @@ -390,23 +389,6 @@ func setCommonProperties(ctx context.Context, v cli.ActionProperty, prop interfa p.DependsOn = flex.GoArrayStringToTerraformList(ctx, v.DependsOn) } - case "Dataset": - dataset := writeDatasetToResource(v) - if dataset != nil { - switch p := prop.(type) { - case *StringPropModel: - p.Dataset = dataset - case *NumberPropModel: - p.Dataset = dataset - case *BooleanPropModel: - p.Dataset = dataset - case *ArrayPropModel: - p.Dataset = dataset - case *ObjectPropModel: - p.Dataset = dataset - } - } - case "Visible": visible, visibleJq := writeVisibleToResource(v) if !visible.IsNull() { diff --git a/port/action/resource_test.go b/port/action/resource_test.go index abda8d55..8ff6a39c 100644 --- a/port/action/resource_test.go +++ b/port/action/resource_test.go @@ -470,50 +470,72 @@ func TestAccPortActionAdvancedFormConfigurations(t *testing.T) { identifier := utils.GenID() actionIdentifier := utils.GenID() var testAccActionConfigCreate = testAccCreateBlueprintConfig(identifier) + fmt.Sprintf(` - resource "port_action" "action1" { - title = "Action 1" - blueprint = port_blueprint.microservice.id - identifier = "%s" - trigger = "DAY-2" - description = "This is a test action" - required_approval = true - github_method = { - org = "port-labs" - repo = "Port" - workflow = "lint" + +resource "port_action" "action1" { + title = "Action 1" + blueprint = port_blueprint.microservice.id + identifier = "%s" + trigger = "DAY-2" + description = "This is a test action" + required_approval = true + github_method = { + org = "port-labs" + repo = "Port" + workflow = "lint" + } + user_properties = { + string_props = { + myStringIdentifier = { + title = "myStringIdentifier" + default = "default" } - user_properties = { - string_props = { - myStringIdentifier = { - title = "myStringIdentifier" - default = "default" - } - myStringIdentifier2 = { - title = "myStringIdentifier2" - default = "default" - depends_on = ["myStringIdentifier"] - } - myStringIdentifier3 = { - title = "myStringIdentifier3" - required = true - format = "entity" - blueprint = port_blueprint.microservice.id - dataset = { - "combinator" : "and", - "rules" : [ - { - "property" : "$team", - "operator" : "containsAny", - "value" : { - "jq_query" : "Test" - } - } - ] + myStringIdentifier2 = { + title = "myStringIdentifier2" + default = "default" + depends_on = ["myStringIdentifier"] + } + myStringIdentifier3 = { + title = "myStringIdentifier3" + required = true + format = "entity" + blueprint = port_blueprint.microservice.id + dataset = { + "combinator" : "and", + "rules" : [ + { + "property" : "$team", + "operator" : "containsAny", + "value" : { + "jq_query" : "Test" + } } - } + ] } } - }`, actionIdentifier) + } + array_props = { + myArrayPropIdentifier = { + title = "myArrayPropIdentifier" + required = true + blueprint = port_blueprint.microservice.id + string_items = { + blueprint = port_blueprint.microservice.id + format = "entity" + dataset = jsonencode({ + "combinator" : "and", + "rules" : [ + { + "property" : "$identifier", + "operator" : "containsAny", + "value" : "Test" + } + ] + }) + } + } + } + } + }`, actionIdentifier) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, @@ -544,7 +566,7 @@ func TestAccPortActionAdvancedFormConfigurations(t *testing.T) { resource.TestCheckResourceAttr("port_action.action1", "user_properties.string_props.myStringIdentifier3.dataset.rules.0.property", "$team"), resource.TestCheckResourceAttr("port_action.action1", "user_properties.string_props.myStringIdentifier3.dataset.rules.0.operator", "containsAny"), resource.TestCheckResourceAttr("port_action.action1", "user_properties.string_props.myStringIdentifier3.dataset.rules.0.value.jq_query", "Test"), - ), + resource.TestCheckResourceAttr("port_action.action1", "user_properties.array_props.myArrayPropIdentifier.string_items.dataset", "{\"combinator\":\"and\",\"rules\":[{\"operator\":\"containsAny\",\"property\":\"$identifier\",\"value\":\"Test\"}]}")), }, }, }) diff --git a/port/action/schema.go b/port/action/schema.go index a9a6e29e..348157c6 100644 --- a/port/action/schema.go +++ b/port/action/schema.go @@ -3,6 +3,7 @@ package action import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" @@ -43,46 +44,6 @@ func MetadataProperties() map[string]schema.Attribute { Optional: true, ElementType: types.StringType, }, - "dataset": schema.SingleNestedAttribute{ - MarkdownDescription: "The dataset of the property", - Optional: true, - Attributes: map[string]schema.Attribute{ - "combinator": schema.StringAttribute{ - MarkdownDescription: "The combinator of the dataset", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf("and", "or"), - }, - }, - "rules": schema.ListNestedAttribute{ - MarkdownDescription: "The rules of the dataset", - Required: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "blueprint": schema.StringAttribute{ - MarkdownDescription: "The blueprint identifier of the rule", - Optional: true, - }, - "property": schema.StringAttribute{ - MarkdownDescription: "The property identifier of the rule", - Optional: true, - }, - "operator": schema.StringAttribute{ - MarkdownDescription: "The operator of the rule", - Required: true, - }, - "value": schema.ObjectAttribute{ - MarkdownDescription: "The value of the rule", - Required: true, - AttributeTypes: map[string]attr.Type{ - "jq_query": types.StringType, - }, - }, - }, - }, - }, - }, - }, } } @@ -355,6 +316,46 @@ func StringPropertySchema() schema.Attribute { stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("visible")), }, }, + "dataset": schema.SingleNestedAttribute{ + MarkdownDescription: "The dataset of an the entity-format property", + Optional: true, + Attributes: map[string]schema.Attribute{ + "combinator": schema.StringAttribute{ + MarkdownDescription: "The combinator of the dataset", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("and", "or"), + }, + }, + "rules": schema.ListNestedAttribute{ + MarkdownDescription: "The rules of the dataset", + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "blueprint": schema.StringAttribute{ + MarkdownDescription: "The blueprint identifier of the rule", + Optional: true, + }, + "property": schema.StringAttribute{ + MarkdownDescription: "The property identifier of the rule", + Optional: true, + }, + "operator": schema.StringAttribute{ + MarkdownDescription: "The operator of the rule", + Required: true, + }, + "value": schema.ObjectAttribute{ + MarkdownDescription: "The value of the rule", + Required: true, + AttributeTypes: map[string]attr.Type{ + "jq_query": types.StringType, + }, + }, + }, + }, + }, + }, + }, } utils.CopyMaps(stringPropertySchema, MetadataProperties()) @@ -564,6 +565,10 @@ func ArrayPropertySchema() schema.Attribute { stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("enum")), }, }, + "dataset": schema.StringAttribute{ + MarkdownDescription: "The dataset of an the entity-format items", + Optional: true, + }, }, }, "number_items": schema.SingleNestedAttribute{ @@ -640,7 +645,7 @@ func ArrayPropertySchema() schema.Attribute { func (r *ActionResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: "Action resource", + MarkdownDescription: actionResourceMarkdownDescription, Attributes: ActionSchema(), } } @@ -696,3 +701,204 @@ func validateUserInputRequiredNotSetToFalse(state *ActionModel, resp *resource.V } } } + +var actionResourceMarkdownDescription = ` + +# Action + +This resource allows you to manage self-service action. + +See the [Port documentation](https://docs.getport.io/create-self-service-experiences/) for more information about self-service actions. + +## Example Usage + +Create a blueprint and an action relating to that blueprint which triggers a github workflow: + +` + "```hcl" + ` + +resource "port_blueprint" "myBlueprint" { + icon = "Terraform" + identifier = "myBlueprint" + title = "My Blueprint" + properties = { + number_props = { + "numberProp" = { + title = "Number Property" + required = false + } + } + } +} + +resource "port_action" "myAction" { + title = "My Action" + blueprint = port_blueprint.myBlueprint.identifier + identifier = "myAction" + trigger = "CREATE" + required_approval = false + github_method = { + org = "your-org" + repo = "your-repo" + workflow = "your-workflow" + } + user_properties = { + string_props = { + stringValue = { + title = "String Value" + } + } + number_props = { + "numberProp" = { + title = "Number Value" + required = true + } + } + } +} + +` + "```" + ` + +Create related "parent" and "child" blueprints and a CREATE action for the child blueprint with user inputs to select entities from the parent blueprint and triggers a github workflow: + +` + "```hcl" + ` + + +resource "port_blueprint" "parent" { + icon = "Terraform" + title = "Parent" + identifier = "parent" + properties = {} +} + +resource "port_blueprint" "child" { + icon = "Terraform" + title = "Child" + identifier = "child" + properties = {} + relations = { + "childOf" = { + title = "Child Of" + many = true + required = false + target = port_blueprint.parent.identifier + } + } +} + +resource "port_action" "myAction" { + title = "My Action" + blueprint = port_blueprint.child.identifier + identifier = "myAction" + trigger = "CREATE" + required_approval = false + github_method = { + org = "your-org" + repo = "your-repo" + workflow = "your-workflow" + } + user_properties = { + string_props = { + singleParent = { + title = "Single Parent Entity Selection" + format = "entity" + blueprint = port_blueprint.parent.identifier + } + } + array_props = { + miltipleParents = { + title = "Single Parent Entity Selection" + string_items = { + format = "entity" + blueprint = port_blueprint.parent.identifier + } + } + } + } +} + +` + "```" + ` + + +Create the same resources as in the previous example, but the action's entity selection properties will only allow entities which pass the ` + "`dataset`s" + `: + +` + "```hcl" + ` + +resource "port_blueprint" "parent" { + icon = "Terraform" + title = "Parent" + identifier = "parent" + properties = {} +} + +resource "port_blueprint" "child" { + icon = "Terraform" + title = "Child" + identifier = "child" + properties = {} + relations = { + "childOf" = { + title = "Child Of" + many = true + required = false + target = port_blueprint.parent.identifier + } + } +} + +resource "port_action" "myAction" { + title = "My Action" + blueprint = port_blueprint.child.identifier + identifier = "myAction" + trigger = "CREATE" + required_approval = false + github_method = { + org = "your-org" + repo = "your-repo" + workflow = "your-workflow" + omit_payload = true + omit_user_inputs = true + report_workflow_status = true + } + user_properties = { + string_props = { + singleParent = { + title = "Single Parent Entity Selection" + format = "entity" + blueprint = port_blueprint.parent.identifier + dataset = { + combinator = "and" + rules = [{ + property = "$title" + operator = "contains" + value = { + jq_query = "\"specificValue\"" + } + }] + } + } + } + array_props = { + miltipleParents = { + title = "Single Parent Entity Selection" + string_items = { + format = "entity" + blueprint = port_blueprint.parent.identifier + dataset = jsonencode({ + combinator = "and" + rules = [{ + property = "$title" + operator = "contains" + value = "specificValue" + }] + }) + } + } + } + } +} + +` + "```" + ` + + + +` diff --git a/port/action/string.go b/port/action/string.go index 8e967e13..dd7442d1 100644 --- a/port/action/string.go +++ b/port/action/string.go @@ -131,6 +131,7 @@ func addStringPropertiesToResource(ctx context.Context, v *cli.ActionProperty) * Format: flex.GoStringToFramework(v.Format), Blueprint: flex.GoStringToFramework(v.Blueprint), Encryption: flex.GoStringToFramework(v.Encryption), + Dataset: writeDatasetToResource(v.Dataset), } if v.Enum != nil { diff --git a/port/aggregation-properties/readStateToPortBody.go b/port/aggregation-properties/readStateToPortBody.go index b6445291..809836c9 100644 --- a/port/aggregation-properties/readStateToPortBody.go +++ b/port/aggregation-properties/readStateToPortBody.go @@ -1,8 +1,8 @@ package aggregation_properties import ( - "encoding/json" "github.com/port-labs/terraform-provider-port-labs/internal/cli" + "github.com/port-labs/terraform-provider-port-labs/internal/utils" ) func aggregationPropertiesToBody(state *AggregationPropertiesModel) (*map[string]cli.BlueprintAggregationProperty, error) { @@ -49,7 +49,7 @@ func aggregationPropertiesToBody(state *AggregationPropertiesModel) (*map[string } } - query, err := queryToPortBody(aggregationProperty.Query.ValueStringPointer()) + query, err := utils.TerraformJsonStringToGoObject(aggregationProperty.Query.ValueStringPointer()) if err != nil { return nil, err @@ -66,16 +66,3 @@ func aggregationPropertiesToBody(state *AggregationPropertiesModel) (*map[string return &aggregationProperties, nil } - -func queryToPortBody(query *string) (*map[string]any, error) { - if query == nil || *query == "" { - return nil, nil - } - - queryMap := make(map[string]any) - if err := json.Unmarshal([]byte(*query), &queryMap); err != nil { - return nil, err - } - - return &queryMap, nil -} diff --git a/port/blueprint/schema.go b/port/blueprint/schema.go index f789e9b7..c9008ea8 100644 --- a/port/blueprint/schema.go +++ b/port/blueprint/schema.go @@ -2,6 +2,7 @@ package blueprint import ( "context" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" diff --git a/port/page/pageToPortBody.go b/port/page/pageToPortBody.go index 5c909792..d3c4e506 100644 --- a/port/page/pageToPortBody.go +++ b/port/page/pageToPortBody.go @@ -1,9 +1,9 @@ package page import ( - "encoding/json" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/port-labs/terraform-provider-port-labs/internal/cli" + "github.com/port-labs/terraform-provider-port-labs/internal/utils" ) func PageToPortBody(pm *PageModel) (*cli.Page, error) { @@ -33,11 +33,13 @@ func widgetsToPortBody(widgets []types.String) (*[]map[string]any, error) { } widgetsBody := make([]map[string]any, len(widgets)) for i, w := range widgets { - var widgetObject map[string]any - if err := json.Unmarshal([]byte(w.ValueString()), &widgetObject); err != nil { + v, err := utils.TerraformJsonStringToGoObject(w.ValueStringPointer()) + + if err != nil { return nil, err } - widgetsBody[i] = widgetObject + + widgetsBody[i] = *v } return &widgetsBody, nil