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
3 changes: 3 additions & 0 deletions .changes/unreleased/Added-20260414-120000.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Added
body: Add system_relationship attribute to opslevel_component_type resource and datasources
time: 2026-04-14T12:00:00.000000+00:00
25 changes: 25 additions & 0 deletions docs/data-sources/component_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ data "opslevel_component_type" "example" {
- `name` (String) The unique name of the component type.
- `owner_relationship` (Attributes) The owner relationship configuration for this component type. (see [below for nested schema](#nestedatt--owner_relationship))
- `properties` (Attributes Map) The properties of this component type. (see [below for nested schema](#nestedatt--properties))
- `system_relationship` (Attributes) The system relationship configuration for this component type. (see [below for nested schema](#nestedatt--system_relationship))

<a id="nestedatt--icon"></a>
### Nested Schema for `icon`
Expand Down Expand Up @@ -68,6 +69,30 @@ Read-Only:



<a id="nestedatt--system_relationship"></a>
### Nested Schema for `system_relationship`

Read-Only:

- `management_rules` (Attributes List) Rules that automatically manage relationships based on property matching conditions. (see [below for nested schema](#nestedatt--system_relationship--management_rules))

<a id="nestedatt--system_relationship--management_rules"></a>
### Nested Schema for `system_relationship.management_rules`

Read-Only:

- `operator` (String) The condition operator for this rule.
- `source_property` (String) The property on the source component to evaluate.
- `source_tag_key` (String) When source_property is 'tag', this specifies the tag key to match.
- `source_tag_operation` (String) When source_property is 'tag', this specifies the matching operation.
- `target_category` (String) The category of the target resource.
- `target_property` (String) The property on the target resource to match against.
- `target_tag_key` (String) When target_property is 'tag', this specifies the tag key to match.
- `target_tag_operation` (String) When target_property is 'tag', this specifies the matching operation.
- `target_type` (String) The type of the target resource.



<a id="nestedatt--properties"></a>
### Nested Schema for `properties`

Expand Down
25 changes: 25 additions & 0 deletions docs/data-sources/component_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Read-Only:
- `name` (String) The unique name of the component type.
- `owner_relationship` (Attributes) The owner relationship configuration for this component type. (see [below for nested schema](#nestedatt--all--owner_relationship))
- `properties` (Attributes Map) The properties of this component type. (see [below for nested schema](#nestedatt--all--properties))
- `system_relationship` (Attributes) The system relationship configuration for this component type. (see [below for nested schema](#nestedatt--all--system_relationship))

<a id="nestedatt--all--icon"></a>
### Nested Schema for `all.icon`
Expand Down Expand Up @@ -73,6 +74,30 @@ Read-Only:



<a id="nestedatt--all--system_relationship"></a>
### Nested Schema for `all.system_relationship`

Read-Only:

- `management_rules` (Attributes List) Rules that automatically manage relationships based on property matching conditions. (see [below for nested schema](#nestedatt--all--system_relationship--management_rules))

<a id="nestedatt--all--system_relationship--management_rules"></a>
### Nested Schema for `all.system_relationship.management_rules`

Read-Only:

- `operator` (String) The condition operator for this rule.
- `source_property` (String) The property on the source component to evaluate.
- `source_tag_key` (String) When source_property is 'tag', this specifies the tag key to match.
- `source_tag_operation` (String) When source_property is 'tag', this specifies the matching operation.
- `target_category` (String) The category of the target resource.
- `target_property` (String) The property on the target resource to match against.
- `target_tag_key` (String) When target_property is 'tag', this specifies the tag key to match.
- `target_tag_operation` (String) When target_property is 'tag', this specifies the matching operation.
- `target_type` (String) The type of the target resource.



<a id="nestedatt--all--properties"></a>
### Nested Schema for `all.properties`

Expand Down
28 changes: 28 additions & 0 deletions docs/resources/component_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ resource "opslevel_component_type" "ml-ai" {
- `icon` (Attributes) The icon associated with the component type (see [below for nested schema](#nestedatt--icon))
- `owner_relationship` (Attributes) The owner relationship configuration for this component type. (see [below for nested schema](#nestedatt--owner_relationship))
- `relationships` (Attributes Map) The relationships that can be defined for this component type. (see [below for nested schema](#nestedatt--relationships))
- `system_relationship` (Attributes) The system relationship configuration for this component type. (see [below for nested schema](#nestedatt--system_relationship))

### Read-Only

Expand Down Expand Up @@ -471,6 +472,33 @@ Optional:



<a id="nestedatt--system_relationship"></a>
### Nested Schema for `system_relationship`

Optional:

- `management_rules` (Attributes List) Rules that automatically manage relationships based on property matching conditions. (see [below for nested schema](#nestedatt--system_relationship--management_rules))

<a id="nestedatt--system_relationship--management_rules"></a>
### Nested Schema for `system_relationship.management_rules`

Required:

- `operator` (String) The condition operator for this rule. Either EQUALS or ARRAY_CONTAINS.
- `source_property` (String) The property on the source component to evaluate.
- `target_property` (String) The property on the target resource to match against.

Optional:

- `source_tag_key` (String) When source_property is 'tag', this specifies the tag key to match. Required if source_property is 'tag', must not be set otherwise.
- `source_tag_operation` (String) When source_property is 'tag', this specifies the matching operation. Either 'equals' or 'starts_with'. Defaults to 'equals'. Required if source_property is 'tag', must not be set otherwise
- `target_category` (String) The category of the target resource. Either target_category or target_type must be specified, but not both.
- `target_tag_key` (String) When target_property is 'tag', this specifies the tag key to match. Required if target_property is 'tag', must not be set otherwise.
- `target_tag_operation` (String) When target_property is 'tag', this specifies the matching operation. Either 'equals' or 'starts_with'. Defaults to 'equals'. Required if target_property is 'tag', must not be set otherwise.
- `target_type` (String) The type of the target resource. Either target_category or target_type must be specified, but not both.



<a id="nestedatt--relationships"></a>
### Nested Schema for `relationships`

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/hashicorp/terraform-plugin-framework v1.17.0
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0
github.com/hashicorp/terraform-plugin-log v0.10.0
github.com/opslevel/opslevel-go/v2026 v2026.3.6
github.com/opslevel/opslevel-go/v2026 v2026.4.13
github.com/relvacode/iso8601 v1.7.0
golang.org/x/net v0.49.0
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E=
github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk=
github.com/opslevel/moredefaults v0.0.0-20240529152742-17d1318a3c12 h1:OQZ3W8kbyCcdS8QUWFTnZd6xtdkfhdckc7Paro7nXio=
github.com/opslevel/moredefaults v0.0.0-20240529152742-17d1318a3c12/go.mod h1:g2GSXVP6LO+5+AIsnMRPN+BeV86OXuFRTX7HXCDtYeI=
github.com/opslevel/opslevel-go/v2026 v2026.3.6 h1:XdAmWIrzKYUTOHIHV42B5bVTBzU2j4OQzhDiaQ75m7I=
github.com/opslevel/opslevel-go/v2026 v2026.3.6/go.mod h1:FClwt6mxlVa2f4l+z/dUi5u8eYEiNJuSOWMhB6Y9JqI=
github.com/opslevel/opslevel-go/v2026 v2026.4.13 h1:H2CZasDoyJMoXA+IWOEVqxPo08zhQKWT17nxymSsCkk=
github.com/opslevel/opslevel-go/v2026 v2026.4.13/go.mod h1:FClwt6mxlVa2f4l+z/dUi5u8eYEiNJuSOWMhB6Y9JqI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down
55 changes: 32 additions & 23 deletions opslevel/datasource_opslevel_component_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ var ComponentTypeDataSourceSchema = map[string]schema.Attribute{
"management_rules": ManagementRulesDataSourceAttribute(),
},
},
"system_relationship": schema.SingleNestedAttribute{
MarkdownDescription: "The system relationship configuration for this component type.",
Computed: true,
Attributes: map[string]schema.Attribute{
"management_rules": ManagementRulesDataSourceAttribute(),
},
},
"properties": schema.MapNestedAttribute{
Description: "The properties of this component type.",
Computed: true,
Expand Down Expand Up @@ -86,13 +93,14 @@ var ComponentTypeDataSourceSchema = map[string]schema.Attribute{
// ComponentTypeDataSourceModel is a simplified version of ComponentTypeModel for data sources
// It excludes relationships, because opslevel-go does not yet have Relationships on ComponentType
type ComponentTypeDataSourceModel struct {
Id types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Alias types.String `tfsdk:"alias"`
Description types.String `tfsdk:"description"`
Icon *ComponentTypeIconModel `tfsdk:"icon"`
OwnerRelationship *OwnerRelationshipModel `tfsdk:"owner_relationship"`
Properties map[string]PropertyModel `tfsdk:"properties"`
Id types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Alias types.String `tfsdk:"alias"`
Description types.String `tfsdk:"description"`
Icon *ComponentTypeIconModel `tfsdk:"icon"`
OwnerRelationship *RelationshipConfigModel `tfsdk:"owner_relationship"`
SystemRelationship *RelationshipConfigModel `tfsdk:"system_relationship"`
Properties map[string]PropertyModel `tfsdk:"properties"`
}

type ComponentTypeDataSourceSingleModel struct {
Expand Down Expand Up @@ -129,7 +137,8 @@ func NewComponentTypeDataSourceSingle() datasource.DataSource {
},
Properties: map[string]PropertyModel{},
}
baseModel.OwnerRelationship = buildOwnerRelationshipModel(data)
baseModel.OwnerRelationship = buildRelationshipModel(data.OwnerRelationship.ManagementRules)
baseModel.SystemRelationship = buildRelationshipModel(data.SystemRelationship.ManagementRules)
for _, prop := range data.Properties.Nodes {
baseModel.Properties[prop.Aliases[0]] = PropertyModel{
Name: types.StringValue(prop.Name),
Expand Down Expand Up @@ -180,7 +189,8 @@ func NewComponentTypeDataSourceMulti() datasource.DataSource {
},
Properties: map[string]PropertyModel{},
}
model.OwnerRelationship = buildOwnerRelationshipModel(data)
model.OwnerRelationship = buildRelationshipModel(data.OwnerRelationship.ManagementRules)
model.SystemRelationship = buildRelationshipModel(data.SystemRelationship.ManagementRules)
for _, prop := range data.Properties.Nodes {
model.Properties[prop.Aliases[0]] = PropertyModel{
Name: types.StringValue(prop.Name),
Expand All @@ -196,19 +206,18 @@ func NewComponentTypeDataSourceMulti() datasource.DataSource {
}
}

func buildOwnerRelationshipModel(data opslevel.ComponentType) *OwnerRelationshipModel {
if len(data.OwnerRelationship.ManagementRules) > 0 {
ruleValues := make([]attr.Value, len(data.OwnerRelationship.ManagementRules))
for i, rule := range data.OwnerRelationship.ManagementRules {
ruleValues[i] = NewManagementRuleValue(rule)
}

return &OwnerRelationshipModel{
ManagementRules: types.ListValueMust(
types.ObjectType{AttrTypes: ManagementRuleModelAttrs()},
ruleValues,
),
}
func buildRelationshipModel(rules []opslevel.RelationshipDefinitionManagementRule) *RelationshipConfigModel {
if len(rules) == 0 {
return nil
}
ruleValues := make([]attr.Value, len(rules))
for i, rule := range rules {
ruleValues[i] = NewManagementRuleValue(rule)
}
return &RelationshipConfigModel{
ManagementRules: types.ListValueMust(
types.ObjectType{AttrTypes: ManagementRuleModelAttrs()},
ruleValues,
),
}
return nil
}
85 changes: 65 additions & 20 deletions opslevel/resource_opslevel_component_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,18 @@ type RelationshipModel struct {
}

type ComponentTypeModel struct {
Id types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Alias types.String `tfsdk:"alias"`
Description types.String `tfsdk:"description"`
Icon *ComponentTypeIconModel `tfsdk:"icon"`
OwnerRelationship *OwnerRelationshipModel `tfsdk:"owner_relationship"`
Properties map[string]PropertyModel `tfsdk:"properties"`
Relationships map[string]RelationshipModel `tfsdk:"relationships"`
Id types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Alias types.String `tfsdk:"alias"`
Description types.String `tfsdk:"description"`
Icon *ComponentTypeIconModel `tfsdk:"icon"`
OwnerRelationship *RelationshipConfigModel `tfsdk:"owner_relationship"`
SystemRelationship *RelationshipConfigModel `tfsdk:"system_relationship"`
Properties map[string]PropertyModel `tfsdk:"properties"`
Relationships map[string]RelationshipModel `tfsdk:"relationships"`
}

type OwnerRelationshipModel struct {
type RelationshipConfigModel struct {
ManagementRules types.List `tfsdk:"management_rules"`
}

Expand All @@ -80,7 +81,7 @@ func (s ComponentTypeResource) NewModel(res *opslevel.ComponentType, stateModel
}

if stateModel.OwnerRelationship != nil {
stateModel.OwnerRelationship = &OwnerRelationshipModel{
stateModel.OwnerRelationship = &RelationshipConfigModel{
ManagementRules: ManagementRuleListValueFromResourceAndModel(
res.OwnerRelationship.ManagementRules,
stateModel.OwnerRelationship.ManagementRules,
Expand All @@ -90,6 +91,17 @@ func (s ComponentTypeResource) NewModel(res *opslevel.ComponentType, stateModel
stateModel.OwnerRelationship = nil
}

if stateModel.SystemRelationship != nil {
stateModel.SystemRelationship = &RelationshipConfigModel{
ManagementRules: ManagementRuleListValueFromResourceAndModel(
res.SystemRelationship.ManagementRules,
stateModel.SystemRelationship.ManagementRules,
),
}
} else {
stateModel.SystemRelationship = nil
}

conn, err := res.GetProperties(s.client, nil)
if err != nil {
return stateModel, err
Expand Down Expand Up @@ -180,6 +192,13 @@ func (s ComponentTypeResource) Schema(ctx context.Context, req resource.SchemaRe
"management_rules": ManagementRulesResourceAttribute(),
},
},
"system_relationship": schema.SingleNestedAttribute{
Description: "The system relationship configuration for this component type.",
Optional: true,
Attributes: map[string]schema.Attribute{
"management_rules": ManagementRulesResourceAttribute(),
},
},
"properties": schema.MapNestedAttribute{
Description: "The properties of this component type.",
Required: true,
Expand Down Expand Up @@ -295,13 +314,26 @@ func (s ComponentTypeResource) Create(ctx context.Context, req resource.CreateRe
}
}

var systemRelInput *opslevel.SystemRelationshipInput
if planModel.SystemRelationship != nil && !planModel.SystemRelationship.ManagementRules.IsNull() {
managementRules := ParseManagementRules(ctx, planModel.SystemRelationship.ManagementRules, planModel.Alias.ValueString(), &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

systemRelInput = &opslevel.SystemRelationshipInput{
ManagementRules: &managementRules,
}
}

// Create the component type first
input := opslevel.ComponentTypeInput{
Name: nullable(planModel.Name.ValueStringPointer()),
Alias: nullable(planModel.Alias.ValueStringPointer()),
Description: nullable(planModel.Description.ValueStringPointer()),
OwnerRelationship: ownerRelInput,
Properties: properties,
Name: nullable(planModel.Name.ValueStringPointer()),
Alias: nullable(planModel.Alias.ValueStringPointer()),
Description: nullable(planModel.Description.ValueStringPointer()),
OwnerRelationship: ownerRelInput,
SystemRelationship: systemRelInput,
Properties: properties,
}
if !planModel.Icon.Color.IsNull() && !planModel.Icon.Name.IsNull() {
input.Icon = &opslevel.ComponentTypeIconInput{
Expand Down Expand Up @@ -440,13 +472,26 @@ func (s ComponentTypeResource) Update(ctx context.Context, req resource.UpdateRe
}
}

var systemRelInput *opslevel.SystemRelationshipInput
if planModel.SystemRelationship != nil && !planModel.SystemRelationship.ManagementRules.IsNull() {
managementRules := ParseManagementRules(ctx, planModel.SystemRelationship.ManagementRules, planModel.Alias.ValueString(), &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

systemRelInput = &opslevel.SystemRelationshipInput{
ManagementRules: &managementRules,
}
}

// Update the component type first
input := opslevel.ComponentTypeInput{
Name: nullable(planModel.Name.ValueStringPointer()),
Alias: nullable(planModel.Alias.ValueStringPointer()),
Description: nullable(planModel.Description.ValueStringPointer()),
OwnerRelationship: ownerRelInput,
Properties: properties,
Name: nullable(planModel.Name.ValueStringPointer()),
Alias: nullable(planModel.Alias.ValueStringPointer()),
Description: nullable(planModel.Description.ValueStringPointer()),
OwnerRelationship: ownerRelInput,
SystemRelationship: systemRelInput,
Properties: properties,
}
if !planModel.Icon.Color.IsNull() && !planModel.Icon.Name.IsNull() {
input.Icon = &opslevel.ComponentTypeIconInput{
Expand Down
1 change: 1 addition & 0 deletions tests/local/datasource_component_type.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ run "datasource_component_type_all_fields_accessible" {
can(data.opslevel_component_type.mock_component_type.alias),
can(data.opslevel_component_type.mock_component_type.description),
can(data.opslevel_component_type.mock_component_type.icon),
can(data.opslevel_component_type.mock_component_type.system_relationship),
can(data.opslevel_component_type.mock_component_type.properties),
])
error_message = "Not all expected fields are accessible from opslevel_component_type data source"
Expand Down
11 changes: 11 additions & 0 deletions tests/local/resource_component_type.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ run "resource_component_type_properties" {
}
}

run "resource_component_type_system_relationship" {
providers = {
opslevel = opslevel.fake
}

assert {
condition = can(opslevel_component_type.api.system_relationship)
error_message = "system_relationship attribute missing from opslevel_component_type.api"
}
}

run "resource_component_type_relationships" {
providers = {
opslevel = opslevel.fake
Expand Down
Loading