diff --git a/docs/resources/port_scorecard.md b/docs/resources/port_scorecard.md index 8a3fe24c..09bb905e 100644 --- a/docs/resources/port_scorecard.md +++ b/docs/resources/port_scorecard.md @@ -312,7 +312,7 @@ resource "port_scorecard" "readiness" { ``` -## Example Usage with Levels +## Example Usage with Levels and Filter This will override the default levels (Basic, Bronze, Silver, Gold) with the provided levels: Not Ready, Partially Ready, Ready. @@ -349,6 +349,21 @@ resource "port_scorecard" "readiness" { identifier = "Readiness" title = "Readiness" blueprint = port_blueprint.microservice.identifier + filter = { + combinator = "and" + conditions = [ + jsonencode({ + property = "required" + operator = "=" + value = true + }), + jsonencode({ + property = "sum" + operator = ">" + value = 5 + }) + ] + } levels = [ { color = "red" @@ -439,6 +454,7 @@ resource "port_scorecard" "readiness" { ### Optional +- `filter` (Attributes) The filter to apply on the entities before calculating the scorecard (see [below for nested schema](#nestedatt--filter)) - `levels` (Attributes List) The levels of the scorecard. This overrides the default levels (Basic, Bronze, Silver, Gold) if provided (see [below for nested schema](#nestedatt--levels)) ### Read-Only @@ -473,6 +489,15 @@ Required: + +### Nested Schema for `filter` + +Required: + +- `combinator` (String) The combinator of the filter +- `conditions` (List of String) The conditions of the filter. Each condition object should be encoded to a string + + ### Nested Schema for `levels` diff --git a/internal/cli/models.go b/internal/cli/models.go index 48ec4f48..ce0234a9 100644 --- a/internal/cli/models.go +++ b/internal/cli/models.go @@ -349,6 +349,7 @@ type ( Identifier string `json:"identifier,omitempty"` Title string `json:"title,omitempty"` Blueprint string `json:"blueprint,omitempty"` + Filter *Query `json:"filter,omitempty"` Levels []Level `json:"levels,omitempty"` Rules []Rule `json:"rules,omitempty"` } diff --git a/port/scorecard/model.go b/port/scorecard/model.go index 43db81ff..34bc58fb 100644 --- a/port/scorecard/model.go +++ b/port/scorecard/model.go @@ -27,6 +27,7 @@ type ScorecardModel struct { Identifier types.String `tfsdk:"identifier"` Blueprint types.String `tfsdk:"blueprint"` Title types.String `tfsdk:"title"` + Filter *Query `tfsdk:"filter"` Levels []Level `tfsdk:"levels"` Rules []Rule `tfsdk:"rules"` CreatedAt types.String `tfsdk:"created_at"` diff --git a/port/scorecard/refreshScorecardState.go b/port/scorecard/refreshScorecardState.go index bc82830b..70120b3c 100644 --- a/port/scorecard/refreshScorecardState.go +++ b/port/scorecard/refreshScorecardState.go @@ -76,6 +76,18 @@ func refreshScorecardState(ctx context.Context, state *ScorecardModel, s *cli.Sc state.UpdatedAt = types.StringValue(s.UpdatedAt.String()) state.UpdatedBy = types.StringValue(s.UpdatedBy) + if s.Filter != nil { + stateFilter := &Query{ + Combinator: types.StringValue(s.Filter.Combinator), + } + stateFilter.Conditions = make([]types.String, len(s.Filter.Conditions)) + for i, u := range s.Filter.Conditions { + cond, _ := utils.GoObjectToTerraformString(u) + stateFilter.Conditions[i] = cond + } + state.Filter = stateFilter + } + stateRules := []Rule{} for _, rule := range s.Rules { stateRule := &Rule{ diff --git a/port/scorecard/resource_test.go b/port/scorecard/resource_test.go index 3d61ef5d..f4697b6e 100644 --- a/port/scorecard/resource_test.go +++ b/port/scorecard/resource_test.go @@ -192,6 +192,16 @@ func TestAccPortScorecardUpdate(t *testing.T) { identifier = "%s" title = "Scorecard 1" blueprint = "%s" + filter = { + combinator = "and" + conditions = [ + jsonencode({ + property = "required" + operator = "=" + value = true + }) + ] + } rules = [{ identifier = "hasTeam" title = "Has Team" @@ -216,6 +226,21 @@ func TestAccPortScorecardUpdate(t *testing.T) { identifier = "%s" title = "Scorecard 2" blueprint = "%s" + filter = { + combinator = "or" + conditions = [ + jsonencode({ + property = "required" + operator = "=" + value = true + }), + jsonencode({ + property = "sum" + operator = ">" + value = 10 + }) + ] + } rules = [{ identifier = "hasTeam" title = "Has Team" @@ -244,6 +269,9 @@ func TestAccPortScorecardUpdate(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("port_scorecard.test", "title", "Scorecard 1"), resource.TestCheckResourceAttr("port_scorecard.test", "blueprint", blueprintIdentifier), + resource.TestCheckResourceAttr("port_scorecard.test", "filter.combinator", "and"), + resource.TestCheckResourceAttr("port_scorecard.test", "filter.conditions.#", "1"), + resource.TestCheckResourceAttr("port_scorecard.test", "filter.conditions.0", "{\"operator\":\"=\",\"property\":\"required\",\"value\":true}"), resource.TestCheckResourceAttr("port_scorecard.test", "rules.#", "1"), resource.TestCheckResourceAttr("port_scorecard.test", "rules.0.identifier", "hasTeam"), resource.TestCheckResourceAttr("port_scorecard.test", "rules.0.title", "Has Team"), @@ -259,6 +287,10 @@ func TestAccPortScorecardUpdate(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("port_scorecard.test", "title", "Scorecard 2"), resource.TestCheckResourceAttr("port_scorecard.test", "blueprint", blueprintIdentifier), + resource.TestCheckResourceAttr("port_scorecard.test", "filter.combinator", "or"), + resource.TestCheckResourceAttr("port_scorecard.test", "filter.conditions.#", "2"), + resource.TestCheckResourceAttr("port_scorecard.test", "filter.conditions.0", "{\"operator\":\"=\",\"property\":\"required\",\"value\":true}"), + resource.TestCheckResourceAttr("port_scorecard.test", "filter.conditions.1", "{\"operator\":\"\\u003e\",\"property\":\"sum\",\"value\":10}"), // u003e is how > is returned resource.TestCheckResourceAttr("port_scorecard.test", "rules.#", "1"), resource.TestCheckResourceAttr("port_scorecard.test", "rules.0.identifier", "hasTeam"), resource.TestCheckResourceAttr("port_scorecard.test", "rules.0.title", "Has Team"), diff --git a/port/scorecard/schema.go b/port/scorecard/schema.go index e2cab003..cfb82c14 100644 --- a/port/scorecard/schema.go +++ b/port/scorecard/schema.go @@ -85,6 +85,27 @@ func ScorecardSchema() map[string]schema.Attribute { MarkdownDescription: "The title of the scorecard", Required: true, }, + "filter": schema.SingleNestedAttribute{ + MarkdownDescription: "The filter to apply on the entities before calculating the scorecard", + Optional: true, + Attributes: map[string]schema.Attribute{ + "combinator": schema.StringAttribute{ + MarkdownDescription: "The combinator of the filter", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("and", "or"), + }, + }, + "conditions": schema.ListAttribute{ + MarkdownDescription: "The conditions of the filter. Each condition object should be encoded to a string", + Required: true, + ElementType: types.StringType, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + }, + }, "levels": schema.ListNestedAttribute{ MarkdownDescription: "The levels of the scorecard. This overrides the default levels (Basic, Bronze, Silver, Gold) if provided", Optional: true, diff --git a/port/scorecard/scorecardResourceToPortBody.go b/port/scorecard/scorecardResourceToPortBody.go index b0c2ad0f..baf0272d 100644 --- a/port/scorecard/scorecardResourceToPortBody.go +++ b/port/scorecard/scorecardResourceToPortBody.go @@ -25,6 +25,26 @@ func scorecardResourceToPortBody(ctx context.Context, state *ScorecardModel) (*c Title: state.Title.ValueString(), } + if state.Filter != nil { + filter := &cli.Query{ + Combinator: state.Filter.Combinator.ValueString(), + } + var conditions []interface{} + for _, stateCondition := range state.Filter.Conditions { + if !stateCondition.IsNull() { + stringCond := stateCondition.ValueString() + cond := map[string]interface{}{} + err := json.Unmarshal([]byte(stringCond), &cond) + if err != nil { + return nil, err + } + conditions = append(conditions, cond) + } + } + filter.Conditions = conditions + s.Filter = filter + } + var rules []cli.Rule for _, stateRule := range state.Rules {