Skip to content

Commit 22f31de

Browse files
committed
Update scorecards to use new filter format
1 parent 9876410 commit 22f31de

File tree

12 files changed

+500
-31
lines changed

12 files changed

+500
-31
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@ Changelog for the Cortex terraform provider.
22

33
## Unreleased
44

5+
## 0.5.0
6+
7+
### Breaking Changes
8+
* **Scorecard Filter V2**: Migrated scorecard `filter` to new V2 format
9+
- **REMOVED**: `category` field (use `types.include`/`types.exclude` instead)
10+
- **ADDED**: `types` object with `include` and `exclude` string sets for filtering by entity types
11+
- **ADDED**: `groups` object with `include` and `exclude` string sets for filtering by entity groups
12+
- **CHANGED**: `query` field remains but works with the new filter structure
13+
- Migration example:
14+
```hcl
15+
# Old (V1) - DEPRECATED
16+
filter = {
17+
category = "SERVICE"
18+
query = "description != null"
19+
}
20+
21+
# New (V2) - Required
22+
filter = {
23+
types = {
24+
include = ["service"]
25+
}
26+
query = "description != null"
27+
}
28+
```
29+
530
## 0.4.2
631
* Fixes `cortex_scorecard` resource so that `rules` are a set. Order doesn't matter.
732

CLAUDE.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Overview
6+
7+
This is a Terraform provider for Cortex.io (https://www.cortexapp.com), built using the terraform-plugin-framework. It allows users to manage Cortex resources (catalog entities, departments, scorecards, resource definitions, etc.) through Terraform.
8+
9+
## Development Commands
10+
11+
### Environment Setup
12+
- Copy `.env-example` to `.env` and set `CORTEX_API_TOKEN` before running tests
13+
- The Makefile loads environment variables from `.env` file automatically
14+
15+
### Build and Installation
16+
- `make build` - Build the provider binary to `./bin/terraform-provider-cortex`
17+
- `make install` - Build and install to local Terraform plugins directory (`~/.terraform.d/plugins/`)
18+
- `make release` - Build release binaries for Linux (amd64) and Darwin (amd64, arm64)
19+
20+
### Testing
21+
- `make test` - Run unit tests (clears test cache first)
22+
- `make testacc` - Run acceptance tests (requires valid `CORTEX_API_TOKEN`, creates real resources)
23+
- `go test -v ./internal/provider -run TestName` - Run a specific test
24+
25+
### Code Quality
26+
- `make lint` - Run golangci-lint
27+
- `make format` - Format code with go fmt
28+
- `make docs` - Generate provider documentation via `go generate`
29+
30+
### Local Development
31+
To use a locally built provider:
32+
1. Build: `go build -o build/terraform-provider-cortex .`
33+
2. Create `~/.terraformrc` with dev_overrides pointing to the build directory
34+
3. Use `source = "cortexlocal/cortex"` in your terraform configuration
35+
36+
## Architecture
37+
38+
### Package Structure
39+
- `main.go` - Provider entry point, runs the provider server
40+
- `internal/provider/` - Terraform provider implementation (resources, data sources, schemas)
41+
- `internal/cortex/` - Cortex API client library
42+
43+
### API Client Architecture (`internal/cortex/`)
44+
The HTTP client uses a functional options pattern for initialization:
45+
- `HttpClient` - Core client with two sling clients (JSON and YAML decoders)
46+
- Client interfaces accessed via methods like `client.CatalogEntities()`, `client.Teams()`, etc.
47+
- `BaseUris` map defines API endpoints for different resource types
48+
- `Route(domain, path)` helper constructs full API paths
49+
- Error handling via `ApiError` type with special handling for 404 and 401
50+
51+
### Provider Architecture (`internal/provider/`)
52+
- `provider.go` - Main provider configuration, registers all resources and data sources
53+
- Resources implement `resource.Resource` and `resource.ResourceWithImportState` interfaces
54+
- Data sources implement `datasource.DataSource` interface
55+
- Each resource/data source has corresponding `*_models.go` files for Terraform state models
56+
- Models use `tfsdk` struct tags to map to Terraform schema attributes
57+
58+
### Resource/Data Source Pattern
59+
Each resource follows this structure:
60+
1. Resource struct with `client *cortex.HttpClient` field
61+
2. Model struct(s) with `types.String`, `types.List`, etc. for state management
62+
3. CRUD methods: `Create`, `Read`, `Update`, `Delete` (resources only)
63+
4. Schema definition with attributes, validators, and plan modifiers
64+
5. Conversion methods between Terraform models and API models (e.g., `ToApiModel`, `FromApiResponse`)
65+
66+
### Available Resources
67+
- `cortex_catalog_entity` - Catalog entities (services, resources, teams, domains)
68+
- `cortex_catalog_entity_custom_data` - Custom data for catalog entities
69+
- `cortex_catalog_entity_openapi` - OpenAPI specs for catalog entities (YAML format)
70+
- `cortex_department` - Departments
71+
- `cortex_resource_definition` - Resource type definitions
72+
- `cortex_scorecard` - Scorecards
73+
74+
### Available Data Sources
75+
- `cortex_catalog_entity` - Read catalog entities
76+
- `cortex_catalog_entity_custom_data` - Read custom data
77+
- `cortex_department` - Read departments
78+
- `cortex_resource_definition` - Read resource definitions
79+
- `cortex_scorecard` - Read scorecards
80+
- `cortex_team` - Read teams
81+
82+
### Special Parsers
83+
- `catalog_entity_parser.go` - Handles conversion between Cortex API entity format and Terraform state
84+
- `scorecard_parser.go` - Handles scorecard-specific conversions and validation
85+
86+
## Provider Configuration
87+
88+
Environment variables:
89+
- `CORTEX_API_TOKEN` - Required API token for authentication
90+
- `CORTEX_API_URL` - Optional API URL (defaults to `https://api.getcortexapp.com`)
91+
- `HTTP_DEBUG=1` - Enable HTTP request/response logging via go-loghttp
92+
93+
## Important Notes
94+
95+
- The provider uses version from Makefile (`VERSION=0.4.6-dev`), injected at build time via ldflags
96+
- Acceptance tests create real resources and require a valid API token
97+
- Documentation is auto-generated via terraform-plugin-docs (run `go generate`)
98+
- The `catalog_entity_openapi` resource uses YAML decoder for OpenAPI specs
99+
- Many resources support both JSON and YAML format responses from the API

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ HOSTNAME=github.com
22
NAMESPACE=cortexapps
33
NAME=cortex
44
BINARY=terraform-provider-${NAME}
5-
VERSION=0.4.6-dev
5+
VERSION=0.5.0
66

77
GOOS?=$(shell go env | grep GOOS | cut -d '=' -f2 | tr -d "'")
88
GOARCH?=$(shell go env | grep GOARCH | cut -d '=' -f2 | tr -d "'")

docs/data-sources/scorecard.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,27 @@ Read-Only:
4343

4444
Read-Only:
4545

46-
- `category` (String)
46+
- `groups` (Attributes) (see [below for nested schema](#nestedatt--filter--groups))
4747
- `query` (String)
48+
- `types` (Attributes) (see [below for nested schema](#nestedatt--filter--types))
49+
50+
<a id="nestedatt--filter--groups"></a>
51+
### Nested Schema for `filter.groups`
52+
53+
Read-Only:
54+
55+
- `exclude` (Set of String)
56+
- `include` (Set of String)
57+
58+
59+
<a id="nestedatt--filter--types"></a>
60+
### Nested Schema for `filter.types`
61+
62+
Read-Only:
63+
64+
- `exclude` (Set of String)
65+
- `include` (Set of String)
66+
4867

4968

5069
<a id="nestedatt--ladder"></a>

docs/resources/scorecard.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,23 @@ Optional:
8484

8585
Optional:
8686

87-
- `category` (String, Deprecated) By default, Scorecards are evaluated against all services. You can specify the category as RESOURCE to evaluate a Scorecard against resources or DOMAIN to evaluate a Scorecard against domains.
88-
- `query` (String) A CQL query that is run against the category; only entities matching this query will be evaluated by the Scorecard.
87+
- `groups` (Attributes) Filter by entity groups. (see [below for nested schema](#nestedatt--filter--groups))
88+
- `query` (String) A CQL query that is run against the filtered entities; only entities matching this query will be evaluated by the Scorecard.
89+
- `types` (Attributes) Filter by entity types. (see [below for nested schema](#nestedatt--filter--types))
90+
91+
<a id="nestedatt--filter--groups"></a>
92+
### Nested Schema for `filter.groups`
93+
94+
Optional:
95+
96+
- `exclude` (Set of String) Entity groups to exclude from the scorecard evaluation.
97+
- `include` (Set of String) Entity groups to include in the scorecard evaluation.
98+
99+
100+
<a id="nestedatt--filter--types"></a>
101+
### Nested Schema for `filter.types`
102+
103+
Optional:
104+
105+
- `exclude` (Set of String) Entity types to exclude from the scorecard evaluation.
106+
- `include` (Set of String) Entity types to include in the scorecard evaluation.

examples/resources/scorecard/resource.tf

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ resource "cortex_scorecard" "dora-metrics" {
3434
}
3535
]
3636
filter = {
37-
category = "SERVICE"
38-
query = "description != null"
37+
types = {
38+
include = ["service"]
39+
}
40+
query = "description != null"
3941
}
4042
evaluation = {
4143
window = 24

internal/cortex/scorecard_parser.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,56 @@ func (c *ScorecardParser) interpolateLadderLevels(entity *Scorecard, levels []in
6868

6969
func (c *ScorecardParser) interpolateFilter(entity *Scorecard, filter map[string]interface{}) {
7070
entity.Filter = ScorecardFilter{
71-
Category: MapFetchToString(filter, "category"),
72-
Query: MapFetchToString(filter, "query"),
71+
Kind: MapFetchToString(filter, "kind"),
72+
Query: MapFetchToString(filter, "query"),
73+
}
74+
75+
// Parse Types if present
76+
if filter["types"] != nil {
77+
typesMap := filter["types"].(map[string]interface{})
78+
types := &ScorecardFilterTypes{}
79+
80+
if typesMap["include"] != nil {
81+
includeList := typesMap["include"].([]interface{})
82+
types.Include = make([]string, len(includeList))
83+
for i, v := range includeList {
84+
types.Include[i] = v.(string)
85+
}
86+
}
87+
88+
if typesMap["exclude"] != nil {
89+
excludeList := typesMap["exclude"].([]interface{})
90+
types.Exclude = make([]string, len(excludeList))
91+
for i, v := range excludeList {
92+
types.Exclude[i] = v.(string)
93+
}
94+
}
95+
96+
entity.Filter.Types = types
97+
}
98+
99+
// Parse Groups if present
100+
if filter["groups"] != nil {
101+
groupsMap := filter["groups"].(map[string]interface{})
102+
groups := &ScorecardFilterGroups{}
103+
104+
if groupsMap["include"] != nil {
105+
includeList := groupsMap["include"].([]interface{})
106+
groups.Include = make([]string, len(includeList))
107+
for i, v := range includeList {
108+
groups.Include[i] = v.(string)
109+
}
110+
}
111+
112+
if groupsMap["exclude"] != nil {
113+
excludeList := groupsMap["exclude"].([]interface{})
114+
groups.Exclude = make([]string, len(excludeList))
115+
for i, v := range excludeList {
116+
groups.Exclude[i] = v.(string)
117+
}
118+
}
119+
120+
entity.Filter.Groups = groups
73121
}
74122
}
75123

internal/cortex/scorecards.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,24 @@ type ScorecardRule struct {
9393
}
9494

9595
type ScorecardFilter struct {
96-
Category string `json:"category,omitempty" yaml:"category,omitempty"`
97-
Query string `json:"query,omitempty" yaml:"query,omitempty"`
96+
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
97+
Types *ScorecardFilterTypes `json:"types,omitempty" yaml:"types,omitempty"`
98+
Groups *ScorecardFilterGroups `json:"groups,omitempty" yaml:"groups,omitempty"`
99+
Query string `json:"query,omitempty" yaml:"query,omitempty"`
100+
}
101+
102+
type ScorecardFilterTypes struct {
103+
Include []string `json:"include,omitempty" yaml:"include,omitempty"`
104+
Exclude []string `json:"exclude,omitempty" yaml:"exclude,omitempty"`
105+
}
106+
107+
type ScorecardFilterGroups struct {
108+
Include []string `json:"include,omitempty" yaml:"include,omitempty"`
109+
Exclude []string `json:"exclude,omitempty" yaml:"exclude,omitempty"`
98110
}
99111

100112
func (s *ScorecardFilter) Enabled() bool {
101-
return s.Category != "" || s.Query != ""
113+
return s.Types != nil || s.Groups != nil || s.Query != ""
102114
}
103115

104116
type ScorecardEvaluation struct {

internal/provider/scorecard_data_source.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/cortexapps/terraform-provider-cortex/internal/cortex"
77
"github.com/hashicorp/terraform-plugin-framework/datasource"
88
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
910
)
1011

1112
// Ensure provider defined types fully satisfy framework interfaces.
@@ -112,8 +113,31 @@ func (d *ScorecardDataSource) Schema(ctx context.Context, req datasource.SchemaR
112113
MarkdownDescription: "Filter of the scorecard.",
113114
Computed: true,
114115
Attributes: map[string]schema.Attribute{
115-
"category": schema.StringAttribute{
116+
"types": schema.SingleNestedAttribute{
116117
Computed: true,
118+
Attributes: map[string]schema.Attribute{
119+
"include": schema.SetAttribute{
120+
ElementType: types.StringType,
121+
Computed: true,
122+
},
123+
"exclude": schema.SetAttribute{
124+
ElementType: types.StringType,
125+
Computed: true,
126+
},
127+
},
128+
},
129+
"groups": schema.SingleNestedAttribute{
130+
Computed: true,
131+
Attributes: map[string]schema.Attribute{
132+
"include": schema.SetAttribute{
133+
ElementType: types.StringType,
134+
Computed: true,
135+
},
136+
"exclude": schema.SetAttribute{
137+
ElementType: types.StringType,
138+
Computed: true,
139+
},
140+
},
117141
},
118142
"query": schema.StringAttribute{
119143
Computed: true,

internal/provider/scorecard_resource.go

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
1212
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
1313
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
14+
"github.com/hashicorp/terraform-plugin-framework/types"
1415
)
1516

1617
// Ensure provider defined types fully satisfy framework interfaces.
@@ -138,13 +139,40 @@ func (r *ScorecardResource) Schema(ctx context.Context, req resource.SchemaReque
138139
Optional: true,
139140
Computed: true,
140141
Attributes: map[string]schema.Attribute{
141-
"category": schema.StringAttribute{
142-
MarkdownDescription: "By default, Scorecards are evaluated against all services. You can specify the category as RESOURCE to evaluate a Scorecard against resources or DOMAIN to evaluate a Scorecard against domains.",
142+
"types": schema.SingleNestedAttribute{
143+
MarkdownDescription: "Filter by entity types.",
143144
Optional: true,
144-
DeprecationMessage: "`category` is deprecated and will be removed in a future release. Forthcoming filters will use the include/exclude functionality that is more commonly used",
145+
Attributes: map[string]schema.Attribute{
146+
"include": schema.SetAttribute{
147+
MarkdownDescription: "Entity types to include in the scorecard evaluation.",
148+
ElementType: types.StringType,
149+
Optional: true,
150+
},
151+
"exclude": schema.SetAttribute{
152+
MarkdownDescription: "Entity types to exclude from the scorecard evaluation.",
153+
ElementType: types.StringType,
154+
Optional: true,
155+
},
156+
},
157+
},
158+
"groups": schema.SingleNestedAttribute{
159+
MarkdownDescription: "Filter by entity groups.",
160+
Optional: true,
161+
Attributes: map[string]schema.Attribute{
162+
"include": schema.SetAttribute{
163+
MarkdownDescription: "Entity groups to include in the scorecard evaluation.",
164+
ElementType: types.StringType,
165+
Optional: true,
166+
},
167+
"exclude": schema.SetAttribute{
168+
MarkdownDescription: "Entity groups to exclude from the scorecard evaluation.",
169+
ElementType: types.StringType,
170+
Optional: true,
171+
},
172+
},
145173
},
146174
"query": schema.StringAttribute{
147-
MarkdownDescription: "A CQL query that is run against the category; only entities matching this query will be evaluated by the Scorecard.",
175+
MarkdownDescription: "A CQL query that is run against the filtered entities; only entities matching this query will be evaluated by the Scorecard.",
148176
Optional: true,
149177
},
150178
},

0 commit comments

Comments
 (0)