Skip to content

Commit a3e711b

Browse files
jaymclaudeslntopp
authored
✨ Add asset routing table and rule resources (#395)
* ✨ Add asset routing table and rule resources Add two new Terraform resources for managing org-level asset routing: - mondoo_asset_routing_table (authoritative): manages the entire routing table for an org, replacing all rules atomically on apply - mondoo_asset_routing_rule (non-authoritative): manages individual routing rules independently, ideal for multi-team setups Depends on server PR #14344 for the GraphQL API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix tflint: add version constraint to example provider configs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update examples to use variables and create spaces Use org_id variable with data source lookup and create the target spaces within the example, matching the pattern from other examples. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Switch condition and rule from ListNestedAttribute to ListNestedBlock Fixes "Unsupported block type" error by using schema.ListNestedBlock for rule and condition, which supports the natural HCL block syntax. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Remove redundant provider block from asset routing examples * © * Enhance asset routing tests and error handling - Add conditional execution for asset routing tests based on environment variable. - Implement error handling for not found errors in asset routing resources. - Update test cases to reflect new error handling and conditional execution logic. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Mikita Iwanowski <mik@mondoo.com>
1 parent b8f66ee commit a3e711b

12 files changed

Lines changed: 1102 additions & 0 deletions

File tree

.github/workflows/test.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ jobs:
6565
- '1.11.*'
6666
- '1.12.*'
6767
- '1.13.*'
68+
include:
69+
# Org-scoped asset routing tests mutate shared state, so only run them
70+
# on a single Terraform version to avoid parallel conflicts.
71+
# Other entries get run_asset_routing_tests="" which the Go tests treat as false.
72+
- terraform: '1.13.*'
73+
run_asset_routing_tests: true
6874
steps:
6975
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
7076
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
@@ -79,6 +85,7 @@ jobs:
7985
- env:
8086
TF_ACC: "1"
8187
MONDOO_CONFIG_BASE64: ${{ secrets.MONDOO_CONFIG_BASE64 }}
88+
RUN_ASSET_ROUTING_TESTS: ${{ matrix.run_asset_routing_tests }}
8289
run: go test -v -cover ./internal/provider/
8390
timeout-minutes: 10
8491

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
terraform {
2+
required_providers {
3+
mondoo = {
4+
source = "mondoohq/mondoo"
5+
version = ">= 0.19"
6+
}
7+
}
8+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
variable "org_id" {
2+
description = "The ID of the organization"
3+
type = string
4+
}
5+
6+
provider "mondoo" {}
7+
8+
data "mondoo_organization" "current" {
9+
id = var.org_id
10+
}
11+
12+
# Create spaces for routing targets
13+
resource "mondoo_space" "production" {
14+
name = "production"
15+
org_id = var.org_id
16+
}
17+
18+
resource "mondoo_space" "staging" {
19+
name = "staging"
20+
org_id = var.org_id
21+
}
22+
23+
# Manage individual routing rules independently.
24+
# Ideal for multi-team setups where each team manages their own rules.
25+
resource "mondoo_asset_routing_rule" "production" {
26+
org_mrn = data.mondoo_organization.current.mrn
27+
target_space_mrn = mondoo_space.production.mrn
28+
priority = 10
29+
30+
condition {
31+
field = "LABEL"
32+
operator = "EQUAL"
33+
key = "env"
34+
values = ["production"]
35+
}
36+
}
37+
38+
resource "mondoo_asset_routing_rule" "staging" {
39+
org_mrn = data.mondoo_organization.current.mrn
40+
target_space_mrn = mondoo_space.staging.mrn
41+
priority = 20
42+
43+
condition {
44+
field = "LABEL"
45+
operator = "EQUAL"
46+
key = "env"
47+
values = ["staging"]
48+
}
49+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
terraform {
2+
required_providers {
3+
mondoo = {
4+
source = "mondoohq/mondoo"
5+
version = ">= 0.19"
6+
}
7+
}
8+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
variable "org_id" {
2+
description = "The ID of the organization"
3+
type = string
4+
}
5+
6+
provider "mondoo" {}
7+
8+
data "mondoo_organization" "current" {
9+
id = var.org_id
10+
}
11+
12+
# Create spaces for routing targets
13+
resource "mondoo_space" "linux" {
14+
name = "linux-assets"
15+
org_id = var.org_id
16+
}
17+
18+
resource "mondoo_space" "windows" {
19+
name = "windows-assets"
20+
org_id = var.org_id
21+
}
22+
23+
resource "mondoo_space" "catch_all" {
24+
name = "catch-all"
25+
org_id = var.org_id
26+
}
27+
28+
# Manage the entire routing table for an organization.
29+
# Priority is derived from the order of rules (first = highest priority).
30+
resource "mondoo_asset_routing_table" "example" {
31+
org_mrn = data.mondoo_organization.current.mrn
32+
33+
# Rule 1: Route Linux assets
34+
rule {
35+
target_space_mrn = mondoo_space.linux.mrn
36+
37+
condition {
38+
field = "PLATFORM"
39+
operator = "EQUAL"
40+
values = ["ubuntu", "debian", "rhel", "amazonlinux"]
41+
}
42+
}
43+
44+
# Rule 2: Route Windows assets
45+
rule {
46+
target_space_mrn = mondoo_space.windows.mrn
47+
48+
condition {
49+
field = "PLATFORM"
50+
operator = "EQUAL"
51+
values = ["windows"]
52+
}
53+
}
54+
55+
# Rule 3: Catch-all for everything else
56+
rule {
57+
target_space_mrn = mondoo_space.catch_all.mrn
58+
}
59+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright Mondoo, Inc. 2024, 2026
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package provider
5+
6+
import (
7+
"strings"
8+
9+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
10+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
11+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
12+
"github.com/hashicorp/terraform-plugin-framework/types"
13+
mondoov1 "go.mondoo.com/mondoo-go"
14+
)
15+
16+
// AssetRoutingConditionModel is the Terraform model for a single routing condition.
17+
// Shared between mondoo_asset_routing_table and mondoo_asset_routing_rule.
18+
type AssetRoutingConditionModel struct {
19+
Field types.String `tfsdk:"field"`
20+
Operator types.String `tfsdk:"operator"`
21+
Values types.List `tfsdk:"values"`
22+
Key types.String `tfsdk:"key"`
23+
}
24+
25+
// assetRoutingConditionSchemaBlock returns a ListNestedBlock for routing conditions,
26+
// reusable by both the table and rule resources.
27+
func assetRoutingConditionSchemaBlock() schema.ListNestedBlock {
28+
return schema.ListNestedBlock{
29+
MarkdownDescription: "Conditions that must all match for this rule to apply (AND logic). If empty, the rule matches all assets (catch-all).",
30+
NestedObject: schema.NestedBlockObject{
31+
Attributes: map[string]schema.Attribute{
32+
"field": schema.StringAttribute{
33+
MarkdownDescription: "The field to match on. Valid values: `HOSTNAME`, `PLATFORM`, `LABEL`.",
34+
Required: true,
35+
Validators: []validator.String{
36+
stringvalidator.OneOf("HOSTNAME", "PLATFORM", "LABEL"),
37+
},
38+
},
39+
"operator": schema.StringAttribute{
40+
MarkdownDescription: "The comparison operator. Valid values: `EQUAL`, `NOT_EQUAL`, `CONTAINS`, `MATCHES`.",
41+
Required: true,
42+
Validators: []validator.String{
43+
stringvalidator.OneOf("EQUAL", "NOT_EQUAL", "CONTAINS", "MATCHES"),
44+
},
45+
},
46+
"values": schema.ListAttribute{
47+
MarkdownDescription: "List of values to match against. A condition matches if the field matches any of the listed values (OR logic).",
48+
Required: true,
49+
ElementType: types.StringType,
50+
},
51+
"key": schema.StringAttribute{
52+
MarkdownDescription: "The label key to match on. Required when `field` is `LABEL`.",
53+
Optional: true,
54+
},
55+
},
56+
},
57+
}
58+
}
59+
60+
// conditionsFromModel converts Terraform condition models to GraphQL input types.
61+
func conditionsFromModel(conditions []AssetRoutingConditionModel) []AssetRoutingConditionInput {
62+
result := make([]AssetRoutingConditionInput, len(conditions))
63+
for i, c := range conditions {
64+
values := make([]mondoov1.String, 0)
65+
if !c.Values.IsNull() && !c.Values.IsUnknown() {
66+
for _, v := range c.Values.Elements() {
67+
if sv, ok := v.(types.String); ok {
68+
values = append(values, mondoov1.String(sv.ValueString()))
69+
}
70+
}
71+
}
72+
73+
input := AssetRoutingConditionInput{
74+
Field: AssetRoutingConditionField(c.Field.ValueString()),
75+
Operator: AssetRoutingConditionOperator(c.Operator.ValueString()),
76+
Values: values,
77+
}
78+
if !c.Key.IsNull() && !c.Key.IsUnknown() && c.Key.ValueString() != "" {
79+
key := mondoov1.String(c.Key.ValueString())
80+
input.Key = &key
81+
}
82+
result[i] = input
83+
}
84+
return result
85+
}
86+
87+
func isNotFoundError(err error) bool {
88+
return err != nil && strings.Contains(err.Error(), "code = NotFound")
89+
}
90+
91+
// conditionsToModel converts GraphQL condition payloads to Terraform models.
92+
func conditionsToModel(conditions []AssetRoutingConditionPayload) []AssetRoutingConditionModel {
93+
result := make([]AssetRoutingConditionModel, len(conditions))
94+
for i, c := range conditions {
95+
values := make([]string, len(c.Values))
96+
copy(values, c.Values)
97+
98+
model := AssetRoutingConditionModel{
99+
Field: types.StringValue(c.Field),
100+
Operator: types.StringValue(c.Operator),
101+
Values: ConvertListValue(values),
102+
}
103+
if c.Key != "" {
104+
model.Key = types.StringValue(c.Key)
105+
} else {
106+
model.Key = types.StringNull()
107+
}
108+
result[i] = model
109+
}
110+
return result
111+
}

0 commit comments

Comments
 (0)