diff --git a/docs/resources/betteruptime_catalog_record.md b/docs/resources/betteruptime_catalog_record.md index d58122b..e9c789e 100644 --- a/docs/resources/betteruptime_catalog_record.md +++ b/docs/resources/betteruptime_catalog_record.md @@ -17,12 +17,12 @@ https://betterstack.com/docs/uptime/api/catalog-integrations-records/ ### Required -- **attribute** (Block List, Min: 1) List of attribute values for the Catalog record. (see [below for nested schema](#nestedblock--attribute)) +- **attribute** (Block List, Min: 1) List of attribute values for the Catalog record. You can have multiple blocks with same `attribute_id` for multiple values. (see [below for nested schema](#nestedblock--attribute)) - **relation_id** (String) The ID of the Catalog relation this record belongs to. ### Read-Only -- **id** (String) The ID of this Catalog Record. +- **id** (String) The ID of this Catalog record. ### Nested Schema for `attribute` @@ -33,10 +33,21 @@ Required: Optional: -- **email** (String) Email of the referenced user when type is User. -- **item_id** (String) ID of the referenced item when type is different than String. -- **name** (String) Human readable name of the referenced item when type is different than String and the item has a name. -- **type** (String) Type of the value. When left empty, the String type is used. +- **email** (String) Email of the referenced user when type is `User`. +- **item_id** (String) ID of the referenced item when type is different than `String`. +- **name** (String) Name of the referenced item when type is different than `String`. +- **type** (String) Value types can be grouped into 2 main categories: + - **Scalar**: `String` + - **Reference**: `User`, `Team`, `Policy`, `Schedule`, `SlackIntegration`, `LinearIntegration`, `JiraIntegration`, `MicrosoftTeamsWebhook`, `ZapierWebhook`, `NativeWebhook`, `PagerDutyWebhook` + + The value of a **Scalar** type is defined using the value field. + + The value of a **Reference** type is defined using one of the following fields: + - `item_id` - great choice when you know the ID of the target item. + - `email` - your go-to choice when you're referencing users. + - `name` - can be used to reference other items like teams, policies, etc. + + **The reference types require the presence of at least one of the three fields: `item_id`, `name`, `email`.** - **value** (String) Value when type is String. Read-Only: diff --git a/docs/resources/betteruptime_catalog_relation.md b/docs/resources/betteruptime_catalog_relation.md index d890f04..e3c68b4 100644 --- a/docs/resources/betteruptime_catalog_relation.md +++ b/docs/resources/betteruptime_catalog_relation.md @@ -17,14 +17,14 @@ https://betterstack.com/docs/uptime/api/catalog-integrations-relations/ ### Required -- **name** (String) The name of the Catalog Relation. +- **name** (String) The name of the Catalog relation. ### Optional -- **description** (String) A description of the Catalog Relation. +- **description** (String) A description of the Catalog relation. ### Read-Only -- **id** (String) The ID of this Catalog Relation. +- **id** (String) The ID of this Catalog relation. diff --git a/docs/resources/betteruptime_policy.md b/docs/resources/betteruptime_policy.md index 5314844..8ac351b 100644 --- a/docs/resources/betteruptime_policy.md +++ b/docs/resources/betteruptime_policy.md @@ -61,10 +61,21 @@ Optional: Optional: -- **email** (String) Email of the referenced user when type is User. -- **item_id** (String) ID of the referenced item when type is different than String. -- **name** (String) Human readable name of the referenced item when type is different than String and the item has a name. -- **type** (String) Type of the value. When left empty, the String type is used. +- **email** (String) Email of the referenced user when type is `User`. +- **item_id** (String) ID of the referenced item when type is different than `String`. +- **name** (String) Name of the referenced item when type is different than `String`. +- **type** (String) Value types can be grouped into 2 main categories: + - **Scalar**: `String` + - **Reference**: `User`, `Team`, `Policy`, `Schedule`, `SlackIntegration`, `LinearIntegration`, `JiraIntegration`, `MicrosoftTeamsWebhook`, `ZapierWebhook`, `NativeWebhook`, `PagerDutyWebhook` + + The value of a **Scalar** type is defined using the value field. + + The value of a **Reference** type is defined using one of the following fields: + - `item_id` - great choice when you know the ID of the target item. + - `email` - your go-to choice when you're referencing users. + - `name` - can be used to reference other items like teams, policies, etc. + + **The reference types require the presence of at least one of the three fields: `item_id`, `name`, `email`.** - **value** (String) Value when type is String. diff --git a/examples/catalog/main.tf b/examples/catalog/main.tf index 0978eb5..b1152a3 100644 --- a/examples/catalog/main.tf +++ b/examples/catalog/main.tf @@ -2,145 +2,136 @@ provider "betteruptime" { api_token = var.betteruptime_api_token } -resource "betteruptime_catalog_relation" "country" { - name = "Country" +### On-call team catalog relation + +resource "betteruptime_catalog_relation" "on_call_team" { + name = "On-call team" + description = "Teams with on-call responsibilities" } -resource "betteruptime_catalog_attribute" "country_code" { - # Primary attributes can be referenced in other relations - relation_id = betteruptime_catalog_relation.country.id - name = "Country code" +resource "betteruptime_catalog_attribute" "on_call_team_name" { + relation_id = betteruptime_catalog_relation.on_call_team.id + name = "On-call team" primary = true } -resource "betteruptime_catalog_attribute" "country_name" { - relation_id = betteruptime_catalog_relation.country.id - name = "Country name" +resource "betteruptime_catalog_attribute" "on_call_team_lead" { + relation_id = betteruptime_catalog_relation.on_call_team.id + name = "Team lead" +} + +resource "betteruptime_catalog_attribute" "on_call_team_business_unit" { + relation_id = betteruptime_catalog_relation.on_call_team.id + name = "Business unit" } -resource "betteruptime_catalog_record" "germany" { - relation_id = betteruptime_catalog_relation.country.id +resource "betteruptime_catalog_record" "demo_team" { + relation_id = betteruptime_catalog_relation.on_call_team.id attribute { - # String values are sent in value field - attribute_id = betteruptime_catalog_attribute.country_code.id + attribute_id = betteruptime_catalog_attribute.on_call_team_name.id type = "String" - value = "DE" + value = "Demo team" } + attribute { - attribute_id = betteruptime_catalog_attribute.country_name.id + attribute_id = betteruptime_catalog_attribute.on_call_team_lead.id + type = "User" + email = "petr@betterstack.com" + } + + attribute { + attribute_id = betteruptime_catalog_attribute.on_call_team_business_unit.id type = "String" - value = "Germany" + value = "Customer success" } } -resource "betteruptime_catalog_record" "czechia" { - relation_id = betteruptime_catalog_relation.country.id +resource "betteruptime_catalog_record" "backend_team" { + relation_id = betteruptime_catalog_relation.on_call_team.id attribute { - attribute_id = betteruptime_catalog_attribute.country_code.id + attribute_id = betteruptime_catalog_attribute.on_call_team_name.id type = "String" - value = "CZ" + value = "Backend team" } + attribute { - attribute_id = betteruptime_catalog_attribute.country_name.id - type = "String" - value = "Czechia" + attribute_id = betteruptime_catalog_attribute.on_call_team_lead.id + type = "User" + email = "juraj@betterstack.com" } -} -resource "betteruptime_catalog_relation" "office" { - name = "Office" - description = "A physical office building representing ACME Group" -} - -resource "betteruptime_catalog_attribute" "office_address" { - relation_id = betteruptime_catalog_relation.office.id - name = "Office address" - primary = true -} - -resource "betteruptime_catalog_attribute" "office_country" { - # Creating a reference to Country by using the same name as its primary attribute - relation_id = betteruptime_catalog_relation.office.id - name = betteruptime_catalog_attribute.country_code.name + attribute { + attribute_id = betteruptime_catalog_attribute.on_call_team_business_unit.id + type = "String" + value = "Engineering" + } } -resource "betteruptime_catalog_attribute" "office_contact_person" { - relation_id = betteruptime_catalog_relation.office.id - name = "Office contact" -} +### Service catalog relation -resource "betteruptime_catalog_attribute" "office_schedule" { - relation_id = betteruptime_catalog_relation.office.id - name = "Office on-call" +resource "betteruptime_catalog_relation" "service" { + name = "Service" + description = "Services with responsible teams" } -data "betteruptime_on_call_calendar" "primary" { +resource "betteruptime_catalog_attribute" "affected_service" { + relation_id = betteruptime_catalog_relation.service.id + name = "Affected service" + primary = true } -data "betteruptime_on_call_calendar" "prague" { - name = "Prague On-call" +# Creating a reference to On-call team by using the same name as its primary attribute +resource "betteruptime_catalog_attribute" "service_on_call_team" { + relation_id = betteruptime_catalog_relation.service.id + name = betteruptime_catalog_attribute.on_call_team_name.name } -resource "betteruptime_catalog_record" "office_prague" { - relation_id = betteruptime_catalog_relation.office.id +resource "betteruptime_catalog_record" "homepage" { + relation_id = betteruptime_catalog_relation.service.id attribute { - attribute_id = betteruptime_catalog_attribute.office_address.id + attribute_id = betteruptime_catalog_attribute.affected_service.id type = "String" - value = "123 Charles Street, Prague" + value = "Homepage" } + attribute { - attribute_id = betteruptime_catalog_attribute.office_country.id + attribute_id = betteruptime_catalog_attribute.service_on_call_team.id type = "String" - value = "CZ" - } - attribute { - # Users can be referenced using email - attribute_id = betteruptime_catalog_attribute.office_contact_person.id - type = "User" - email = "petr@betterstack.com" - } - attribute { - # Non-string values can be referenced using item_id - attribute_id = betteruptime_catalog_attribute.office_schedule.id - type = "Schedule" - item_id = data.betteruptime_on_call_calendar.prague.id - } - attribute { - # Multiple values for a single attribute can be provided - attribute_id = betteruptime_catalog_attribute.office_schedule.id - type = "Schedule" - item_id = data.betteruptime_on_call_calendar.primary.id + value = "Backend team" } } -data "betteruptime_on_call_calendar" "berlin" { - name = "Berlin On-call" -} - -resource "betteruptime_catalog_record" "office_berlin" { - relation_id = betteruptime_catalog_relation.office.id +resource "betteruptime_catalog_record" "api" { + relation_id = betteruptime_catalog_relation.service.id attribute { - attribute_id = betteruptime_catalog_attribute.office_address.id + attribute_id = betteruptime_catalog_attribute.affected_service.id type = "String" - value = "45 Brandenburg Gate, Berlin" + value = "API Services" } + attribute { - attribute_id = betteruptime_catalog_attribute.office_country.id + attribute_id = betteruptime_catalog_attribute.service_on_call_team.id type = "String" - value = "DE" + value = "Backend team" } +} + +resource "betteruptime_catalog_record" "landing_page" { + relation_id = betteruptime_catalog_relation.service.id + attribute { - attribute_id = betteruptime_catalog_attribute.office_contact_person.id - type = "User" - email = "juraj@betterstack.com" + attribute_id = betteruptime_catalog_attribute.affected_service.id + type = "String" + value = "Landing page" } + attribute { - attribute_id = betteruptime_catalog_attribute.office_schedule.id - type = "Schedule" - item_id = data.betteruptime_on_call_calendar.berlin.id + attribute_id = betteruptime_catalog_attribute.service_on_call_team.id + type = "String" + value = "Demo team" } } diff --git a/internal/provider/resource_catalog_record.go b/internal/provider/resource_catalog_record.go index dd5b5bb..43b2909 100644 --- a/internal/provider/resource_catalog_record.go +++ b/internal/provider/resource_catalog_record.go @@ -14,7 +14,7 @@ import ( var catalogRecordSchema = map[string]*schema.Schema{ "id": { - Description: "The ID of this Catalog Record.", + Description: "The ID of this Catalog record.", Type: schema.TypeString, Computed: true, }, @@ -25,7 +25,7 @@ var catalogRecordSchema = map[string]*schema.Schema{ Required: true, }, "attribute": { - Description: "List of attribute values for the Catalog record.", + Description: "List of attribute values for the Catalog record. You can have multiple blocks with same `attribute_id` for multiple values.", Type: schema.TypeList, Required: true, Elem: &schema.Resource{ diff --git a/internal/provider/resource_catalog_relation.go b/internal/provider/resource_catalog_relation.go index 32e0554..f73f3d0 100644 --- a/internal/provider/resource_catalog_relation.go +++ b/internal/provider/resource_catalog_relation.go @@ -12,17 +12,17 @@ import ( var catalogRelationSchema = map[string]*schema.Schema{ "id": { - Description: "The ID of this Catalog Relation.", + Description: "The ID of this Catalog relation.", Type: schema.TypeString, Computed: true, }, "name": { - Description: "The name of the Catalog Relation.", + Description: "The name of the Catalog relation.", Type: schema.TypeString, Required: true, }, "description": { - Description: "A description of the Catalog Relation.", + Description: "A description of the Catalog relation.", Type: schema.TypeString, Optional: true, }, diff --git a/internal/provider/resource_metadata.go b/internal/provider/resource_metadata.go index a8eb12e..3fe9b79 100644 --- a/internal/provider/resource_metadata.go +++ b/internal/provider/resource_metadata.go @@ -6,6 +6,7 @@ import ( "fmt" "net/url" "reflect" + "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -70,9 +71,22 @@ var metadataTypes = []string{ "PagerDutyWebhook", } +const scalarMetadataTypesCount = 1 + var metadataValueSchema = map[string]*schema.Schema{ "type": { - Description: "Type of the value. When left empty, the String type is used.", + Description: "Value types can be grouped into 2 main categories:\n" + + " - **Scalar**: `" + strings.Join(metadataTypes[:scalarMetadataTypesCount], "`, `") + "`\n" + + " - **Reference**: `" + strings.Join(metadataTypes[scalarMetadataTypesCount:], "`, `") + "`\n" + + " \n" + + " The value of a **Scalar** type is defined using the value field.\n" + + " \n" + + " The value of a **Reference** type is defined using one of the following fields:\n" + + " - `item_id` - great choice when you know the ID of the target item.\n" + + " - `email` - your go-to choice when you're referencing users.\n" + + " - `name` - can be used to reference other items like teams, policies, etc.\n" + + " \n" + + " **The reference types require the presence of at least one of the three fields: `item_id`, `name`, `email`.**\n", Type: schema.TypeString, Optional: true, Default: "String", @@ -84,17 +98,17 @@ var metadataValueSchema = map[string]*schema.Schema{ Optional: true, }, "item_id": { - Description: "ID of the referenced item when type is different than String.", + Description: "ID of the referenced item when type is different than `String`.", Type: schema.TypeString, Optional: true, }, "name": { - Description: "Human readable name of the referenced item when type is different than String and the item has a name.", + Description: "Name of the referenced item when type is different than `String`.", Type: schema.TypeString, Optional: true, }, "email": { - Description: "Email of the referenced user when type is User.", + Description: "Email of the referenced user when type is `User`.", Type: schema.TypeString, Optional: true, }, diff --git a/internal/provider/resource_monitor.go b/internal/provider/resource_monitor.go index f374307..1392702 100644 --- a/internal/provider/resource_monitor.go +++ b/internal/provider/resource_monitor.go @@ -579,7 +579,7 @@ func validateRequestHeaders(ctx context.Context, diff *schema.ResourceDiff, v in for _, header := range headers.([]interface{}) { headerMap := header.(map[string]interface{}) if err := validateRequestHeader(headerMap); err != nil { - return fmt.Errorf("Invalid request header %v: %v", headerMap, err) + return fmt.Errorf("invalid request header %v: %v", headerMap, err) } } } @@ -619,7 +619,7 @@ func loadRequestHeaders(d *schema.ResourceData, receiver **[]map[string]interfac // Validation at apply time, empty map is considered invalid (fields should be known at this point) if len(header) == 0 { - return fmt.Errorf("Invalid request header %v: map cannot be empty", header) + return fmt.Errorf("invalid request header %v: map cannot be empty", header) } // Headers can have ID at apply time, temporarily remove it before validation and reattach it afterwards id, idPresent := header["id"] @@ -629,7 +629,7 @@ func loadRequestHeaders(d *schema.ResourceData, receiver **[]map[string]interfac header["id"] = id } if err != nil { - return fmt.Errorf("Invalid request header %v: %v", header, err) + return fmt.Errorf("invalid request header %v: %v", header, err) } newHeader := map[string]interface{}{"name": header["name"], "value": header["value"]} diff --git a/internal/provider/resource_monitor_test.go b/internal/provider/resource_monitor_test.go index 7134e0f..ac0ac29 100644 --- a/internal/provider/resource_monitor_test.go +++ b/internal/provider/resource_monitor_test.go @@ -238,7 +238,7 @@ func TestResourceMonitorWithHeaders(t *testing.T) { } `, url, monitorType), PlanOnly: true, - ExpectError: regexp.MustCompile(`Invalid request header map\[name: value:test\]: must contain 'name' key with a non-empty string value`), + ExpectError: regexp.MustCompile(`invalid request header map\[name: value:test\]: must contain 'name' key with a non-empty string value`), }, // Step 5 - invalid header with empty value. { @@ -259,7 +259,7 @@ func TestResourceMonitorWithHeaders(t *testing.T) { } `, url, monitorType), PlanOnly: true, - ExpectError: regexp.MustCompile(`Invalid request header map\[name:X-TEST value:\]: must contain 'value' key with a non-empty string value`), + ExpectError: regexp.MustCompile(`invalid request header map\[name:X-TEST value:\]: must contain 'value' key with a non-empty string value`), }, // Step 6 - invalid header with extra keys. { @@ -281,7 +281,7 @@ func TestResourceMonitorWithHeaders(t *testing.T) { } `, url, monitorType), PlanOnly: true, - ExpectError: regexp.MustCompile(`Invalid request header map\[extra:invalid name:X-TEST value:test\]: must only contain 'name' and 'value' keys`), + ExpectError: regexp.MustCompile(`invalid request header map\[extra:invalid name:X-TEST value:test\]: must only contain 'name' and 'value' keys`), }, // Step 7 - invalid header with incorrect format. { @@ -301,7 +301,7 @@ func TestResourceMonitorWithHeaders(t *testing.T) { } `, url, monitorType), PlanOnly: true, - ExpectError: regexp.MustCompile(`Invalid request header map\[X-TEST:test\]: must contain 'name' key with a non-empty string value`), + ExpectError: regexp.MustCompile(`invalid request header map\[X-TEST:test\]: must contain 'name' key with a non-empty string value`), }, }, })