Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,31 @@ Changelog for the Cortex terraform provider.

## Unreleased

## 0.5.0

### Breaking Changes
* **Scorecard Filter V2**: Migrated scorecard `filter` to new V2 format
- **REMOVED**: `category` field (use `types.include`/`types.exclude` instead)
- **ADDED**: `types` object with `include` and `exclude` string sets for filtering by entity types
- **ADDED**: `groups` object with `include` and `exclude` string sets for filtering by entity groups
- **CHANGED**: `query` field remains but works with the new filter structure
- Migration example:
```hcl
# Old (V1) - DEPRECATED
filter = {
category = "SERVICE"
query = "description != null"
}

# New (V2) - Required
filter = {
types = {
include = ["service"]
}
query = "description != null"
}
```

## 0.4.7
* Updates to `cortex_catalog_entity` resource to force resource recreation when the type is changed.

Expand Down
99 changes: 99 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

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.

## Development Commands

### Environment Setup
- Copy `.env-example` to `.env` and set `CORTEX_API_TOKEN` before running tests
- The Makefile loads environment variables from `.env` file automatically

### Build and Installation
- `make build` - Build the provider binary to `./bin/terraform-provider-cortex`
- `make install` - Build and install to local Terraform plugins directory (`~/.terraform.d/plugins/`)
- `make release` - Build release binaries for Linux (amd64) and Darwin (amd64, arm64)

### Testing
- `make test` - Run unit tests (clears test cache first)
- `make testacc` - Run acceptance tests (requires valid `CORTEX_API_TOKEN`, creates real resources)
- `go test -v ./internal/provider -run TestName` - Run a specific test

### Code Quality
- `make lint` - Run golangci-lint
- `make format` - Format code with go fmt
- `make docs` - Generate provider documentation via `go generate`

### Local Development
To use a locally built provider:
1. Build: `go build -o build/terraform-provider-cortex .`
2. Create `~/.terraformrc` with dev_overrides pointing to the build directory
3. Use `source = "cortexlocal/cortex"` in your terraform configuration

## Architecture

### Package Structure
- `main.go` - Provider entry point, runs the provider server
- `internal/provider/` - Terraform provider implementation (resources, data sources, schemas)
- `internal/cortex/` - Cortex API client library

### API Client Architecture (`internal/cortex/`)
The HTTP client uses a functional options pattern for initialization:
- `HttpClient` - Core client with two sling clients (JSON and YAML decoders)
- Client interfaces accessed via methods like `client.CatalogEntities()`, `client.Teams()`, etc.
- `BaseUris` map defines API endpoints for different resource types
- `Route(domain, path)` helper constructs full API paths
- Error handling via `ApiError` type with special handling for 404 and 401

### Provider Architecture (`internal/provider/`)
- `provider.go` - Main provider configuration, registers all resources and data sources
- Resources implement `resource.Resource` and `resource.ResourceWithImportState` interfaces
- Data sources implement `datasource.DataSource` interface
- Each resource/data source has corresponding `*_models.go` files for Terraform state models
- Models use `tfsdk` struct tags to map to Terraform schema attributes

### Resource/Data Source Pattern
Each resource follows this structure:
1. Resource struct with `client *cortex.HttpClient` field
2. Model struct(s) with `types.String`, `types.List`, etc. for state management
3. CRUD methods: `Create`, `Read`, `Update`, `Delete` (resources only)
4. Schema definition with attributes, validators, and plan modifiers
5. Conversion methods between Terraform models and API models (e.g., `ToApiModel`, `FromApiResponse`)

### Available Resources
- `cortex_catalog_entity` - Catalog entities (services, resources, teams, domains)
- `cortex_catalog_entity_custom_data` - Custom data for catalog entities
- `cortex_catalog_entity_openapi` - OpenAPI specs for catalog entities (YAML format)
- `cortex_department` - Departments
- `cortex_resource_definition` - Resource type definitions
- `cortex_scorecard` - Scorecards

### Available Data Sources
- `cortex_catalog_entity` - Read catalog entities
- `cortex_catalog_entity_custom_data` - Read custom data
- `cortex_department` - Read departments
- `cortex_resource_definition` - Read resource definitions
- `cortex_scorecard` - Read scorecards
- `cortex_team` - Read teams

### Special Parsers
- `catalog_entity_parser.go` - Handles conversion between Cortex API entity format and Terraform state
- `scorecard_parser.go` - Handles scorecard-specific conversions and validation

## Provider Configuration

Environment variables:
- `CORTEX_API_TOKEN` - Required API token for authentication
- `CORTEX_API_URL` - Optional API URL (defaults to `https://api.getcortexapp.com`)
- `HTTP_DEBUG=1` - Enable HTTP request/response logging via go-loghttp

## Important Notes

- The provider uses version from Makefile (`VERSION=0.4.6-dev`), injected at build time via ldflags
- Acceptance tests create real resources and require a valid API token
- Documentation is auto-generated via terraform-plugin-docs (run `go generate`)
- The `catalog_entity_openapi` resource uses YAML decoder for OpenAPI specs
- Many resources support both JSON and YAML format responses from the API
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ HOSTNAME=github.com
NAMESPACE=cortexapps
NAME=cortex
BINARY=terraform-provider-${NAME}
VERSION=0.4.7-dev
VERSION=0.5.0-dev

GOOS?=$(shell go env | grep GOOS | cut -d '=' -f2 | tr -d "'")
GOARCH?=$(shell go env | grep GOARCH | cut -d '=' -f2 | tr -d "'")
Expand Down
21 changes: 20 additions & 1 deletion docs/data-sources/scorecard.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,27 @@ Read-Only:

Read-Only:

- `category` (String)
- `groups` (Attributes) (see [below for nested schema](#nestedatt--filter--groups))
- `query` (String)
- `types` (Attributes) (see [below for nested schema](#nestedatt--filter--types))

<a id="nestedatt--filter--groups"></a>
### Nested Schema for `filter.groups`

Read-Only:

- `exclude` (Set of String)
- `include` (Set of String)


<a id="nestedatt--filter--types"></a>
### Nested Schema for `filter.types`

Read-Only:

- `exclude` (Set of String)
- `include` (Set of String)



<a id="nestedatt--ladder"></a>
Expand Down
22 changes: 20 additions & 2 deletions docs/resources/scorecard.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,23 @@ Optional:

Optional:

- `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.
- `query` (String) A CQL query that is run against the category; only entities matching this query will be evaluated by the Scorecard.
- `groups` (Attributes) Filter by entity groups. (see [below for nested schema](#nestedatt--filter--groups))
- `query` (String) A CQL query that is run against the filtered entities; only entities matching this query will be evaluated by the Scorecard.
- `types` (Attributes) Filter by entity types. (see [below for nested schema](#nestedatt--filter--types))

<a id="nestedatt--filter--groups"></a>
### Nested Schema for `filter.groups`

Optional:

- `exclude` (Set of String) Entity groups to exclude from the scorecard evaluation.
- `include` (Set of String) Entity groups to include in the scorecard evaluation.


<a id="nestedatt--filter--types"></a>
### Nested Schema for `filter.types`

Optional:

- `exclude` (Set of String) Entity types to exclude from the scorecard evaluation. Cannot be used with include.
- `include` (Set of String) Entity types to include in the scorecard evaluation. Cannot be used with exclude.
6 changes: 4 additions & 2 deletions examples/resources/scorecard/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ resource "cortex_scorecard" "dora-metrics" {
}
]
filter = {
category = "SERVICE"
query = "description != null"
types = {
include = ["service"]
}
query = "entity.description() != null"
}
evaluation = {
window = 24
Expand Down
52 changes: 50 additions & 2 deletions internal/cortex/scorecard_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,56 @@ func (c *ScorecardParser) interpolateLadderLevels(entity *Scorecard, levels []in

func (c *ScorecardParser) interpolateFilter(entity *Scorecard, filter map[string]interface{}) {
entity.Filter = ScorecardFilter{
Category: MapFetchToString(filter, "category"),
Query: MapFetchToString(filter, "query"),
Kind: MapFetchToString(filter, "kind"),
Query: MapFetchToString(filter, "query"),
}

// Parse Types if present
if filter["types"] != nil {
typesMap := filter["types"].(map[string]interface{})
types := &ScorecardFilterTypes{}

if typesMap["include"] != nil {
includeList := typesMap["include"].([]interface{})
types.Include = make([]string, len(includeList))
for i, v := range includeList {
types.Include[i] = v.(string)
}
}

if typesMap["exclude"] != nil {
excludeList := typesMap["exclude"].([]interface{})
types.Exclude = make([]string, len(excludeList))
for i, v := range excludeList {
types.Exclude[i] = v.(string)
}
}

entity.Filter.Types = types
}

// Parse Groups if present
if filter["groups"] != nil {
groupsMap := filter["groups"].(map[string]interface{})
groups := &ScorecardFilterGroups{}

if groupsMap["include"] != nil {
includeList := groupsMap["include"].([]interface{})
groups.Include = make([]string, len(includeList))
for i, v := range includeList {
groups.Include[i] = v.(string)
}
}

if groupsMap["exclude"] != nil {
excludeList := groupsMap["exclude"].([]interface{})
groups.Exclude = make([]string, len(excludeList))
for i, v := range excludeList {
groups.Exclude[i] = v.(string)
}
}

entity.Filter.Groups = groups
}
}

Expand Down
18 changes: 15 additions & 3 deletions internal/cortex/scorecards.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,24 @@ type ScorecardRule struct {
}

type ScorecardFilter struct {
Category string `json:"category,omitempty" yaml:"category,omitempty"`
Query string `json:"query,omitempty" yaml:"query,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
Types *ScorecardFilterTypes `json:"types,omitempty" yaml:"types,omitempty"`
Groups *ScorecardFilterGroups `json:"groups,omitempty" yaml:"groups,omitempty"`
Query string `json:"query,omitempty" yaml:"query,omitempty"`
}

type ScorecardFilterTypes struct {
Include []string `json:"include,omitempty" yaml:"include,omitempty"`
Exclude []string `json:"exclude,omitempty" yaml:"exclude,omitempty"`
}

type ScorecardFilterGroups struct {
Include []string `json:"include,omitempty" yaml:"include,omitempty"`
Exclude []string `json:"exclude,omitempty" yaml:"exclude,omitempty"`
}

func (s *ScorecardFilter) Enabled() bool {
return s.Category != "" || s.Query != ""
return s.Types != nil || s.Groups != nil || s.Query != ""
}

type ScorecardEvaluation struct {
Expand Down
26 changes: 25 additions & 1 deletion internal/provider/scorecard_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/cortexapps/terraform-provider-cortex/internal/cortex"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)

// Ensure provider defined types fully satisfy framework interfaces.
Expand Down Expand Up @@ -112,8 +113,31 @@ func (d *ScorecardDataSource) Schema(ctx context.Context, req datasource.SchemaR
MarkdownDescription: "Filter of the scorecard.",
Computed: true,
Attributes: map[string]schema.Attribute{
"category": schema.StringAttribute{
"types": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"include": schema.SetAttribute{
ElementType: types.StringType,
Computed: true,
},
"exclude": schema.SetAttribute{
ElementType: types.StringType,
Computed: true,
},
},
},
"groups": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"include": schema.SetAttribute{
ElementType: types.StringType,
Computed: true,
},
"exclude": schema.SetAttribute{
ElementType: types.StringType,
Computed: true,
},
},
},
"query": schema.StringAttribute{
Computed: true,
Expand Down
Loading