diff --git a/.github/workflows/default.yaml b/.github/workflows/default.yaml index eac7709..e407a6e 100644 --- a/.github/workflows/default.yaml +++ b/.github/workflows/default.yaml @@ -43,7 +43,8 @@ jobs: env: TF_ACC: 'yup' UPTIME_TOKEN: '${{ secrets.UPTIME_TOKEN }}' - run: go test -v ./... -run ^TestAcc[A-Z] + UPTIME_RATE_LIMIT: '0.15' + run: go test -test.timeout=50m -test.parallel=1 -v ./... -run ^TestAcc[A-Z] release: if: startsWith(github.ref, 'refs/tags/v') diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf3c16..3893edc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Uptime.com Terraform provider changelog +## v2.10.0 + +- Add `uptime_statuspage_component`, `uptime_statuspage_incident`, `uptime_statuspage_metric` + `uptime_statuspage_subscriber`, `uptime_statuspage_metric`, `uptime_statuspage_subscription_domain_allow` + `uptime_statuspage_subscription_domain_block` and `uptime_statuspage_user` resources. +- Add `uptime_credential` resource. +- Add `uptime_integration_opsgenie` resource. +- Remove ICMP check DNS hostname validation for `address` field. +- Upgrade dependencies + ## v2.9.0 - Add `uptime_check_maintenance` resource diff --git a/docs/resources/credential.md b/docs/resources/credential.md new file mode 100644 index 0000000..58348be --- /dev/null +++ b/docs/resources/credential.md @@ -0,0 +1,44 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "uptime_credential Resource - terraform-provider-uptime" +subcategory: "" +description: |- + +--- + +# uptime_credential (Resource) + + + + + + +## Schema + +### Required + +- `credential_type` (String) +- `display_name` (String) +- `secret` (Attributes) (see [below for nested schema](#nestedatt--secret)) + +### Optional + +- `description` (String) +- `username` (String) + +### Read-Only + +- `id` (Number) The ID of this resource. + + +### Nested Schema for `secret` + +Optional: + +- `certificate` (String, Sensitive) +- `key` (String, Sensitive) +- `passphrase` (String, Sensitive) +- `password` (String, Sensitive) +- `secret` (String, Sensitive) + + diff --git a/docs/resources/integration_opsgenie.md b/docs/resources/integration_opsgenie.md new file mode 100644 index 0000000..7908dab --- /dev/null +++ b/docs/resources/integration_opsgenie.md @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "uptime_integration_opsgenie Resource - terraform-provider-uptime" +subcategory: "" +description: |- + Opsgenie integration resource +--- + +# uptime_integration_opsgenie (Resource) + +Opsgenie integration resource + + + + +## Schema + +### Required + +- `api_endpoint` (String) +- `api_key` (String) +- `name` (String) + +### Optional + +- `auto_resolve` (Boolean) Automatically resolve incident once the check is back up. +- `contact_groups` (Set of String) +- `tags` (String) A comma separated list of labels attached to the alert. You may overwrite the quiet hours setting for urgent alerts by adding the OverwriteQuietHours tag. Leave blank to automatically pull the tags from the check instead. +- `teams` (String) A comma separated list of team names which will be responsible for the alert + +### Read-Only + +- `id` (Number) The ID of this resource. +- `url` (String) + + diff --git a/docs/resources/statuspage.md b/docs/resources/statuspage.md index 0e2bdea..5945c89 100644 --- a/docs/resources/statuspage.md +++ b/docs/resources/statuspage.md @@ -25,6 +25,11 @@ description: |- - `allow_pdf_report` (Boolean) - `allow_search_indexing` (Boolean) - `allow_subscriptions` (Boolean) +- `allow_subscriptions_email` (Boolean) +- `allow_subscriptions_rss` (Boolean) +- `allow_subscriptions_slack` (Boolean) +- `allow_subscriptions_sms` (Boolean) +- `allow_subscriptions_webhook` (Boolean) - `auth_password` (String, Sensitive) - `auth_username` (String) - `cname` (String) @@ -32,12 +37,15 @@ description: |- - `contact_email` (String) - `custom_css` (String) - `custom_footer_html` (String) +- `custom_header_bg_color_hex` (String) - `custom_header_html` (String) +- `custom_header_text_color_hex` (String) - `default_history_date_range` (Number) - `description` (String) - `email_from` (String) - `email_reply_to` (String) - `google_analytics_code` (String) +- `hide_empty_tabs_history` (Boolean) - `page_type` (String) - `show_active_incidents` (Boolean) - `show_component_history` (Boolean) @@ -48,6 +56,7 @@ description: |- - `show_status_tab` (Boolean) - `show_summary_metrics` (Boolean) - `slug` (String) +- `theme` (String) - `timezone` (String) - `uptime_calculation_type` (String) - `visibility_level` (String) diff --git a/docs/resources/statuspage_component.md b/docs/resources/statuspage_component.md new file mode 100644 index 0000000..eed420d --- /dev/null +++ b/docs/resources/statuspage_component.md @@ -0,0 +1,38 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "uptime_statuspage_component Resource - terraform-provider-uptime" +subcategory: "" +description: |- + Status page component resource +--- + +# uptime_statuspage_component (Resource) + +Status page component resource + + + + +## Schema + +### Required + +- `name` (String) +- `statuspage_id` (Number) + +### Optional + +- `auto_status_down` (String) +- `auto_status_up` (String) +- `description` (String) +- `group_id` (Number) +- `is_group` (Boolean) +- `service_id` (Number) +- `status` (String) + +### Read-Only + +- `id` (Number) The ID of this resource. +- `url` (String) + + diff --git a/docs/resources/statuspage_incident.md b/docs/resources/statuspage_incident.md new file mode 100644 index 0000000..0879ddb --- /dev/null +++ b/docs/resources/statuspage_incident.md @@ -0,0 +1,68 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "uptime_statuspage_incident Resource - terraform-provider-uptime" +subcategory: "" +description: |- + Status page incident or maintenance window resource +--- + +# uptime_statuspage_incident (Resource) + +Status page incident or maintenance window resource + + + + +## Schema + +### Required + +- `name` (String) +- `statuspage_id` (Number) +- `updates` (Attributes Set) (see [below for nested schema](#nestedatt--updates)) + +### Optional + +- `affected_components` (Attributes Set) (see [below for nested schema](#nestedatt--affected_components)) +- `ends_at` (String) +- `incident_type` (String) +- `include_in_global_metrics` (Boolean) +- `notify_subscribers` (Boolean) +- `send_maintenance_start_notification` (Boolean) +- `starts_at` (String) +- `update_component_status` (Boolean) + +### Read-Only + +- `id` (Number) The ID of this resource. +- `url` (String) + + +### Nested Schema for `updates` + +Optional: + +- `description` (String) +- `incident_state` (String) + +Read-Only: + +- `id` (Number) + + + +### Nested Schema for `affected_components` + +Required: + +- `component_id` (Number) + +Optional: + +- `status` (String) + +Read-Only: + +- `id` (Number) + + diff --git a/docs/resources/statuspage_metric.md b/docs/resources/statuspage_metric.md new file mode 100644 index 0000000..f26c2ec --- /dev/null +++ b/docs/resources/statuspage_metric.md @@ -0,0 +1,33 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "uptime_statuspage_metric Resource - terraform-provider-uptime" +subcategory: "" +description: |- + Status page metric resource +--- + +# uptime_statuspage_metric (Resource) + +Status page metric resource + + + + +## Schema + +### Required + +- `name` (String) +- `service_id` (Number) +- `statuspage_id` (Number) + +### Optional + +- `is_visible` (Boolean) + +### Read-Only + +- `id` (Number) The ID of this resource. +- `url` (String) + + diff --git a/docs/resources/statuspage_subscriber.md b/docs/resources/statuspage_subscriber.md new file mode 100644 index 0000000..798fc31 --- /dev/null +++ b/docs/resources/statuspage_subscriber.md @@ -0,0 +1,32 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "uptime_statuspage_subscriber Resource - terraform-provider-uptime" +subcategory: "" +description: |- + Status page subscriber resource +--- + +# uptime_statuspage_subscriber (Resource) + +Status page subscriber resource + + + + +## Schema + +### Required + +- `statuspage_id` (Number) +- `type` (String) + +### Optional + +- `force_validation_sms` (Boolean) +- `target` (String) + +### Read-Only + +- `id` (Number) The ID of this resource. + + diff --git a/docs/resources/statuspage_subscription_domain_allow.md b/docs/resources/statuspage_subscription_domain_allow.md new file mode 100644 index 0000000..c7fc1a1 --- /dev/null +++ b/docs/resources/statuspage_subscription_domain_allow.md @@ -0,0 +1,27 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "uptime_statuspage_subscription_domain_allow Resource - terraform-provider-uptime" +subcategory: "" +description: |- + Status page subscription domain allow resource +--- + +# uptime_statuspage_subscription_domain_allow (Resource) + +Status page subscription domain allow resource + + + + +## Schema + +### Required + +- `domain` (String) +- `statuspage_id` (Number) + +### Read-Only + +- `id` (Number) The ID of this resource. + + diff --git a/docs/resources/statuspage_subscription_domain_block.md b/docs/resources/statuspage_subscription_domain_block.md new file mode 100644 index 0000000..74613c7 --- /dev/null +++ b/docs/resources/statuspage_subscription_domain_block.md @@ -0,0 +1,27 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "uptime_statuspage_subscription_domain_block Resource - terraform-provider-uptime" +subcategory: "" +description: |- + Status page subscription domain block resource +--- + +# uptime_statuspage_subscription_domain_block (Resource) + +Status page subscription domain block resource + + + + +## Schema + +### Required + +- `domain` (String) +- `statuspage_id` (Number) + +### Read-Only + +- `id` (Number) The ID of this resource. + + diff --git a/docs/resources/statuspage_user.md b/docs/resources/statuspage_user.md new file mode 100644 index 0000000..d9bff1c --- /dev/null +++ b/docs/resources/statuspage_user.md @@ -0,0 +1,30 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "uptime_statuspage_user Resource - terraform-provider-uptime" +subcategory: "" +description: |- + Status page user resource +--- + +# uptime_statuspage_user (Resource) + +Status page user resource + + + + +## Schema + +### Required + +- `email` (String) +- `first_name` (String) +- `is_active` (Boolean) +- `last_name` (String) +- `statuspage_id` (Number) + +### Read-Only + +- `id` (Number) The ID of this resource. + + diff --git a/go.mod b/go.mod index 6336fb3..249cc7b 100644 --- a/go.mod +++ b/go.mod @@ -8,16 +8,16 @@ require ( github.com/dustinkirkland/golang-petname v0.0.0-20230927204539-348648eed816 github.com/google/go-cmp v0.6.0 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/terraform-plugin-framework v1.12.0 + github.com/hashicorp/terraform-plugin-framework v1.13.0 github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 - github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 - github.com/hashicorp/terraform-plugin-go v0.24.0 - github.com/hashicorp/terraform-plugin-testing v1.10.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 + github.com/hashicorp/terraform-plugin-go v0.25.0 + github.com/hashicorp/terraform-plugin-testing v1.11.0 github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 github.com/pkg/errors v0.9.1 github.com/shopspring/decimal v1.4.0 - github.com/stretchr/testify v1.9.0 - github.com/uptime-com/uptime-client-go/v2 v2.0.0-20241003114205-5aa12ad3c97a + github.com/stretchr/testify v1.10.0 + github.com/uptime-com/uptime-client-go/v2 v2.0.0-20241204093759-45854fa6a664 ) require ( @@ -34,17 +34,17 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect - github.com/hashicorp/go-plugin v1.6.1 // indirect + github.com/hashicorp/go-plugin v1.6.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.7.0 // indirect - github.com/hashicorp/hc-install v0.8.0 // indirect - github.com/hashicorp/hcl/v2 v2.21.0 // indirect + github.com/hashicorp/hc-install v0.9.0 // indirect + github.com/hashicorp/hcl/v2 v2.23.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect - github.com/hashicorp/terraform-json v0.22.1 // indirect + github.com/hashicorp/terraform-json v0.23.0 // indirect github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect - github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect + github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -62,17 +62,17 @@ require ( github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect - golang.org/x/crypto v0.26.0 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.26.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect - google.golang.org/grpc v1.66.2 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8c31ed8..a7031da 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1 github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= -github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= +github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= +github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -70,30 +70,30 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hc-install v0.8.0 h1:LdpZeXkZYMQhoKPCecJHlKvUkQFixN/nvyR1CdfOLjI= -github.com/hashicorp/hc-install v0.8.0/go.mod h1:+MwJYjDfCruSD/udvBmRB22Nlkwwkwf5sAB6uTIhSaU= -github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= -github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE= +github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg= +github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= +github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= -github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= -github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= -github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ= -github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE= +github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI= +github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= +github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw= +github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 h1:v3DapR8gsp3EM8fKMh6up9cJUFQ2iRaFsYLP8UJnCco= github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0/go.mod h1:c3PnGE9pHBDfdEVG9t1S1C9ia5LW+gkFR0CygXlM8ak= -github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 h1:bxZfGo9DIUoLLtHMElsu+zwqI4IsMZQBRRy4iLzZJ8E= -github.com/hashicorp/terraform-plugin-framework-validators v0.13.0/go.mod h1:wGeI02gEhj9nPANU62F2jCaHjXulejm/X+af4PdZaNo= -github.com/hashicorp/terraform-plugin-go v0.24.0 h1:2WpHhginCdVhFIrWHxDEg6RBn3YaWzR2o6qUeIEat2U= -github.com/hashicorp/terraform-plugin-go v0.24.0/go.mod h1:tUQ53lAsOyYSckFGEefGC5C8BAaO0ENqzFd3bQeuYQg= +github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 h1:RXMmu7JgpFjnI1a5QjMCBb11usrW2OtAG+iOTIj5c9Y= +github.com/hashicorp/terraform-plugin-framework-validators v0.15.0/go.mod h1:Bh89/hNmqsEWug4/XWKYBwtnw3tbz5BAy1L1OgvbIaY= +github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974rdTxjqEhXJjbAyks= +github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg= -github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:2+tmRNhvnfE4Bs8rB6v58S/VpqzGC6RCh9Y8ujdn+aw= -github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:iWRW3+loP33WMch2P/TEyCxxct/ZEcCGMquSLSCVsrc= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 h1:wyKCCtn6pBBL46c1uIIBNUOWlNfYXfXpVo16iDyLp8Y= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0/go.mod h1:B0Al8NyYVr8Mp/KLwssKXG1RqnTk7FySqSn4fRuLNgw= +github.com/hashicorp/terraform-plugin-testing v1.11.0 h1:MeDT5W3YHbONJt2aPQyaBsgQeAIckwPX41EUHXEn29A= +github.com/hashicorp/terraform-plugin-testing v1.11.0/go.mod h1:WNAHQ3DcgV/0J+B15WTE6hDvxcUdkPPpnB1FR3M910U= github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= @@ -155,10 +155,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/uptime-com/uptime-client-go/v2 v2.0.0-20241003114205-5aa12ad3c97a h1:bMIbbcsD3mnLzVD0fxnJk1sW+fRiT/yFw9gW2Rz1rsw= -github.com/uptime-com/uptime-client-go/v2 v2.0.0-20241003114205-5aa12ad3c97a/go.mod h1:jtWeB/tQ00fLX2r9OwKfTnxQ/PMR0YjmhTuc9RZH2h0= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/uptime-com/uptime-client-go/v2 v2.0.0-20241204093759-45854fa6a664 h1:IphsKMPkm6/EQryzDZtfYxImHyk/HnCkJs1MtkB7NGU= +github.com/uptime-com/uptime-client-go/v2 v2.0.0-20241204093759-45854fa6a664/go.mod h1:jtWeB/tQ00fLX2r9OwKfTnxQ/PMR0YjmhTuc9RZH2h0= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= @@ -175,22 +175,22 @@ github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6 github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -203,8 +203,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -212,8 +212,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -226,14 +226,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= -google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/provider/attr_address.go b/internal/provider/attr_address.go index 9d2f11e..d1c0133 100644 --- a/internal/provider/attr_address.go +++ b/internal/provider/attr_address.go @@ -14,6 +14,14 @@ func AddressHostnameSchemaAttribute() schema.StringAttribute { } } +func AddressHostnameOrIPSchemaAttribute() schema.StringAttribute { + // Use this attribute when the address can be either a hostname or an IP (v4 or v6) address + // Validation in that case done by API. + return schema.StringAttribute{ + Required: true, + } +} + func AddressHostnameSchemaAttributeDescription(desc string) schema.StringAttribute { return schema.StringAttribute{ Description: desc, diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 83bab4e..a29f281 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "runtime" + "strconv" "sync" "time" @@ -69,6 +70,9 @@ func (p *providerImpl) Configure(ctx context.Context, rq provider.ConfigureReque rs.Diagnostics.Append(diags...) return } + if cfg.Endpoint.IsNull() { + cfg.Endpoint = types.StringValue(os.Getenv("UPTIME_ENDPOINT")) + } if cfg.Token.IsNull() { cfg.Token = types.StringValue(os.Getenv("UPTIME_TOKEN")) } @@ -76,7 +80,13 @@ func (p *providerImpl) Configure(ctx context.Context, rq provider.ConfigureReque cfg.Trace = types.BoolValue(os.Getenv("UPTIME_TRACE") != "") } if cfg.RateLimit.IsNull() { - cfg.RateLimit = types.Float64Value(0.5) + rateLimit := 0.5 + if val := os.Getenv("UPTIME_RATE_LIMIT"); val != "" { + if parsedVal, err := strconv.ParseFloat(val, 64); err != nil { + rateLimit = parsedVal + } + } + cfg.RateLimit = types.Float64Value(rateLimit) } opts := []upapi.Option{ upapi.WithToken(cfg.Token.ValueString()), @@ -129,8 +139,17 @@ func (p *providerImpl) Resources(ctx context.Context) []func() resource.Resource func() resource.Resource { return NewCheckWebhookResource(ctx, p) }, func() resource.Resource { return NewCheckMaintenanceResource(ctx, p) }, + func() resource.Resource { return NewIntegrationOpsgenieResource(ctx, p) }, func() resource.Resource { return NewContactResource(ctx, p) }, + func() resource.Resource { return NewCredentialResource(ctx, p) }, func() resource.Resource { return NewStatusPageResource(ctx, p) }, + func() resource.Resource { return NewStatusPageComponentResource(ctx, p) }, + func() resource.Resource { return NewStatusPageIncidentResource(ctx, p) }, + func() resource.Resource { return NewStatusPageMetricResource(ctx, p) }, + func() resource.Resource { return NewStatusPageSubscriberResource(ctx, p) }, + func() resource.Resource { return NewStatusPageSubsDomainAllowResource(ctx, p) }, + func() resource.Resource { return NewStatusPageSubsDomainBlockResource(ctx, p) }, + func() resource.Resource { return NewStatusPageUserResource(ctx, p) }, func() resource.Resource { return NewSLAReportResource(ctx, p) }, func() resource.Resource { return NewDashboardResource(ctx, p) }, func() resource.Resource { return NewTagResource(ctx, p) }, diff --git a/internal/provider/resource_check_icmp.go b/internal/provider/resource_check_icmp.go index d1f8d7c..28d452e 100644 --- a/internal/provider/resource_check_icmp.go +++ b/internal/provider/resource_check_icmp.go @@ -24,7 +24,7 @@ func NewCheckICMPResource(_ context.Context, p *providerImpl) resource.Resource "id": IDSchemaAttribute(), "url": URLSchemaAttribute(), "name": NameSchemaAttribute(), - "address": AddressHostnameSchemaAttribute(), + "address": AddressHostnameOrIPSchemaAttribute(), "contact_groups": ContactGroupsSchemaAttribute(), "locations": LocationsSchemaAttribute(p), "tags": TagsSchemaAttribute(), diff --git a/internal/provider/resource_credentials.go b/internal/provider/resource_credentials.go new file mode 100644 index 0000000..54fcd23 --- /dev/null +++ b/internal/provider/resource_credentials.go @@ -0,0 +1,311 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/uptime-com/uptime-client-go/v2/pkg/upapi" +) + +func NewCredentialResource(_ context.Context, p *providerImpl) resource.Resource { + return APIResource[CredentialResourceModel, upapi.Credential, upapi.Credential]{ + api: CredentialResourceAPI{provider: p}, + mod: CredentialResourceModelAdapter{}, + meta: APIResourceMetadata{ + TypeNameSuffix: "credential", + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": IDSchemaAttribute(), + "display_name": schema.StringAttribute{ + Required: true, + }, + "description": schema.StringAttribute{ + Computed: true, + Optional: true, + }, + "credential_type": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + OneOfStringValidator([]string{"BASIC", "CERTIFICATE", "TOKEN"}), + }, + }, + "username": schema.StringAttribute{ + Computed: true, + Optional: true, + }, + "secret": schema.SingleNestedAttribute{ + Required: true, + Attributes: map[string]schema.Attribute{ + "certificate": schema.StringAttribute{ + Computed: true, + Optional: true, + Sensitive: true, + }, + "key": schema.StringAttribute{ + Computed: true, + Optional: true, + Sensitive: true, + }, + "password": schema.StringAttribute{ + Computed: true, + Optional: true, + Sensitive: true, + }, + "passphrase": schema.StringAttribute{ + Computed: true, + Optional: true, + Sensitive: true, + }, + "secret": schema.StringAttribute{ + Computed: true, + Optional: true, + Sensitive: true, + }, + }, + }, + }, + }, + ConfigValidators: func(context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{NewCredentialTypeValidator()} + }, + }, + } +} + +type CredentialResourceModel struct { + ID types.Int64 `tfsdk:"id" ref:"PK,opt"` + DisplayName types.String `tfsdk:"display_name"` + Description types.String `tfsdk:"description"` + CredentialType types.String `tfsdk:"credential_type"` + Username types.String `tfsdk:"username"` + Secret types.Object `tfsdk:"secret"` + + secret *CredentialSecretAttribute +} + +func (m CredentialResourceModel) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(m.ID.ValueInt64()) +} + +type CredentialSecretAttribute struct { + Certificate types.String `tfsdk:"certificate"` + Key types.String `tfsdk:"key"` + Password types.String `tfsdk:"password"` + Passphrase types.String `tfsdk:"passphrase"` + Secret types.String `tfsdk:"secret"` +} + +type CredentialResourceModelAdapter struct { + SetAttributeAdapter[string] +} + +func (a CredentialResourceModelAdapter) Get(ctx context.Context, sg StateGetter) (*CredentialResourceModel, diag.Diagnostics) { + model := *new(CredentialResourceModel) + diags := sg.Get(ctx, &model) + if diags.HasError() { + return nil, diags + } + model.secret, diags = a.SecretAttributeContext(ctx, model.Secret) + if diags.HasError() { + return nil, diags + } + + return &model, nil +} + +func (a CredentialResourceModelAdapter) ToAPIArgument(model CredentialResourceModel) (*upapi.Credential, error) { + api := upapi.Credential{ + PK: model.ID.ValueInt64(), + DisplayName: model.DisplayName.ValueString(), + Description: model.Description.ValueString(), + CredentialType: model.CredentialType.ValueString(), + Username: model.Username.ValueString(), + Secret: upapi.CredentialSecret{ + Certificate: model.secret.Certificate.ValueString(), + Key: model.secret.Key.ValueString(), + Password: model.secret.Password.ValueString(), + Passphrase: model.secret.Passphrase.ValueString(), + Secret: model.secret.Secret.ValueString(), + }, + } + return &api, nil +} + +func (a CredentialResourceModelAdapter) FromAPIResult(api upapi.Credential) (*CredentialResourceModel, error) { + model := CredentialResourceModel{ + ID: types.Int64Value(api.PK), + DisplayName: types.StringValue(api.DisplayName), + Description: types.StringValue(api.Description), + CredentialType: types.StringValue(api.CredentialType), + Username: types.StringValue(api.Username), + Secret: a.SecretAttributeValue(CredentialSecretAttribute{ + Certificate: types.StringValue(api.Secret.Certificate), + Key: types.StringValue(api.Secret.Key), + Passphrase: types.StringValue(api.Secret.Passphrase), + Password: types.StringValue(api.Secret.Password), + Secret: types.StringValue(api.Secret.Secret), + }), + } + return &model, nil +} + +func (a CredentialResourceModelAdapter) secretAttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "certificate": types.StringType, + "key": types.StringType, + "password": types.StringType, + "passphrase": types.StringType, + "secret": types.StringType, + } +} + +func (a CredentialResourceModelAdapter) secretAttributeValues(m CredentialSecretAttribute) map[string]attr.Value { + return map[string]attr.Value{ + "certificate": m.Certificate, + "key": m.Key, + "password": m.Password, + "passphrase": m.Passphrase, + "secret": m.Secret, + } +} + +func (a CredentialResourceModelAdapter) SecretAttributeContext(ctx context.Context, v types.Object) (*CredentialSecretAttribute, diag.Diagnostics) { + if v.IsNull() || v.IsUnknown() { + return nil, nil + } + m := new(CredentialSecretAttribute) + diags := v.As(ctx, m, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, diags + } + return m, nil +} + +func (a CredentialResourceModelAdapter) SecretAttributeValue(m CredentialSecretAttribute) types.Object { + return types.ObjectValueMust(a.secretAttributeTypes(), a.secretAttributeValues(m)) +} + +type CredentialResourceAPI struct { + provider *providerImpl +} + +func (c CredentialResourceAPI) Create(ctx context.Context, arg upapi.Credential) (*upapi.Credential, error) { + obj, err := c.provider.api.Credentials().Create(ctx, arg) + obj.Secret = arg.Secret + return obj, err +} + +func (c CredentialResourceAPI) Read(ctx context.Context, pk upapi.PrimaryKeyable) (*upapi.Credential, error) { + obj, err := c.provider.api.Credentials().Get(ctx, pk) + secret := pk.(CredentialResourceModel).secret + obj.Secret = upapi.CredentialSecret{ + Certificate: secret.Certificate.ValueString(), + Key: secret.Key.ValueString(), + Passphrase: secret.Passphrase.ValueString(), + Password: secret.Password.ValueString(), + Secret: secret.Secret.ValueString(), + } + return obj, err +} + +func (c CredentialResourceAPI) Update(ctx context.Context, pk upapi.PrimaryKeyable, arg upapi.Credential) (*upapi.Credential, error) { + if err := c.Delete(ctx, pk); err != nil { + return nil, err + } + return c.Create(ctx, arg) +} + +func (c CredentialResourceAPI) Delete(ctx context.Context, pk upapi.PrimaryKeyable) error { + return c.provider.api.Credentials().Delete(ctx, pk) +} + +type credentialTypeValidator struct{} + +func NewCredentialTypeValidator() resource.ConfigValidator { + return &credentialTypeValidator{} +} + +func (v *credentialTypeValidator) Description(ctx context.Context) string { + return "Validates that the credential_type field has valid values and corresponding secret fields are set correctly." +} + +func (v *credentialTypeValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v *credentialTypeValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + var model CredentialResourceModel + diags := req.Config.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if model.CredentialType.IsNull() || model.CredentialType.IsUnknown() { + return + } + + // var a attr.Value + var secretAttr CredentialSecretAttribute + p := path.Root("secret") + diags = req.Config.GetAttribute(ctx, p, &secretAttr) + if diags.HasError() { + resp.Diagnostics = diags + return + } + + switch model.CredentialType.ValueString() { + case "BASIC": + if secretAttr.Password.IsNull() { + resp.Diagnostics.AddError( + "Invalid Configuration", + "When credential_type is BASIC, the password field must be set.", + ) + } + if !secretAttr.Certificate.IsNull() || !secretAttr.Key.IsNull() || !secretAttr.Passphrase.IsNull() || !secretAttr.Secret.IsNull() { + resp.Diagnostics.AddError( + "Invalid Configuration", + "When credential_type is BASIC, only the password field should be set.", + ) + } + case "CERTIFICATE": + if secretAttr.Certificate.IsNull() || secretAttr.Key.IsNull() || secretAttr.Passphrase.IsNull() { + resp.Diagnostics.AddError( + "Invalid Configuration", + "When credential_type is CERTIFICATE, the certificate, key, and passphrase fields must be set.", + ) + } + if !secretAttr.Password.IsNull() || !secretAttr.Secret.IsNull() { + resp.Diagnostics.AddError( + "Invalid Configuration", + "When credential_type is CERTIFICATE, only the certificate, key, and passphrase fields should be set.", + ) + } + case "TOKEN": + if model.Secret.IsNull() { + resp.Diagnostics.AddError( + "Invalid Configuration", + "When credential_type is TOKEN, the secret field must be set.", + ) + } + if !secretAttr.Password.IsNull() || !secretAttr.Certificate.IsNull() || !secretAttr.Key.IsNull() || !secretAttr.Passphrase.IsNull() { + resp.Diagnostics.AddError( + "Invalid Configuration", + "When credential_type is TOKEN, only the secret field should be set.", + ) + } + default: + resp.Diagnostics.AddError( + "Invalid Configuration", + fmt.Sprintf("Invalid credential_type: %s", model.CredentialType.ValueString()), + ) + } +} diff --git a/internal/provider/resource_credentials_test.go b/internal/provider/resource_credentials_test.go new file mode 100644 index 0000000..384ae85 --- /dev/null +++ b/internal/provider/resource_credentials_test.go @@ -0,0 +1,84 @@ +package provider + +import ( + "regexp" + "testing" + + petname "github.com/dustinkirkland/golang-petname" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccCredentialResource(t *testing.T) { + names := [3]string{ + petname.Generate(3, "-"), + petname.Generate(3, "-"), + } + passwords := [3]string{ + petname.Generate(1, "-"), + petname.Generate(1, "-"), + } + resource.Test(t, testCaseFromSteps(t, []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/resource_credential/_basic"), + ConfigVariables: config.Variables{ + "display_name": config.StringVariable(names[0]), + "credential_type": config.StringVariable("BASIC"), + "password": config.StringVariable(passwords[0]), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_credential.test", "display_name", names[0]), + resource.TestCheckResourceAttr("uptime_credential.test", "credential_type", "BASIC"), + resource.TestCheckResourceAttr("uptime_credential.test", "secret.password", passwords[0]), + ), + }, + { + ConfigDirectory: config.StaticDirectory("testdata/resource_credential/_basic"), + ConfigVariables: config.Variables{ + "display_name": config.StringVariable(names[1]), + "credential_type": config.StringVariable("BASIC"), + "password": config.StringVariable(passwords[1]), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_credential.test", "display_name", names[1]), + resource.TestCheckResourceAttr("uptime_credential.test", "credential_type", "BASIC"), + resource.TestCheckResourceAttr("uptime_credential.test", "secret.password", passwords[1]), + ), + }, + })) +} + +func TestAccCredentialResource_Validation(t *testing.T) { + names := [3]string{ + petname.Generate(3, "-"), + petname.Generate(3, "-"), + } + passwords := [3]string{ + petname.Generate(1, "-"), + petname.Generate(1, "-"), + } + resource.Test(t, testCaseFromSteps(t, []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/resource_credential/validation"), + ConfigVariables: config.Variables{ + "display_name": config.StringVariable(names[0]), + "credential_type": config.StringVariable("BASIC"), + "password": config.StringVariable(passwords[0]), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_credential.test", "display_name", names[0]), + resource.TestCheckResourceAttr("uptime_credential.test", "credential_type", "BASIC"), + resource.TestCheckResourceAttr("uptime_credential.test", "secret.password", passwords[0]), + ), + }, + { + ConfigDirectory: config.StaticDirectory("testdata/resource_credential/validation"), + ConfigVariables: config.Variables{ + "display_name": config.StringVariable(names[2]), + "credential_type": config.StringVariable("TOKEN"), + "token": config.StringVariable(passwords[2]), + }, + ExpectError: regexp.MustCompile("When credential_type is TOKEN, only the secret field should be set."), + }, + })) +} diff --git a/internal/provider/resource_integration_opsgenie.go b/internal/provider/resource_integration_opsgenie.go new file mode 100644 index 0000000..97a7852 --- /dev/null +++ b/internal/provider/resource_integration_opsgenie.go @@ -0,0 +1,133 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/uptime-com/uptime-client-go/v2/pkg/upapi" +) + +func NewIntegrationOpsgenieResource(_ context.Context, p *providerImpl) resource.Resource { + return APIResource[IntegrationOpsgenieResourceModel, upapi.IntegrationOpsgenie, upapi.Integration]{ + api: IntegrationOpsgenieResourceAPI{provider: p}, + mod: IntegrationOpsgenieResourceModelAdapter{}, + meta: APIResourceMetadata{ + TypeNameSuffix: "integration_opsgenie", + Schema: schema.Schema{ + Description: "Opsgenie integration resource", + Attributes: map[string]schema.Attribute{ + "id": IDSchemaAttribute(), + "url": URLSchemaAttribute(), + "name": NameSchemaAttribute(), + "contact_groups": ContactGroupsSchemaAttribute(), + "api_endpoint": schema.StringAttribute{ + Required: true, + }, + "api_key": schema.StringAttribute{ + Required: true, + }, + "teams": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + Description: "A comma separated list of team names which will be responsible for the alert", + }, + "tags": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + Description: ("A comma separated list of labels attached to the alert. " + + "You may overwrite the quiet hours setting for urgent alerts by adding the OverwriteQuietHours tag. " + + "Leave blank to automatically pull the tags from the check instead."), + }, + "auto_resolve": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Automatically resolve incident once the check is back up.", + Default: booldefault.StaticBool(false), + }, + }, + }, + }, + } +} + +type IntegrationOpsgenieResourceModel struct { + ID types.Int64 `tfsdk:"id"` + URL types.String `tfsdk:"url"` + Name types.String `tfsdk:"name"` + ContactGroups types.Set `tfsdk:"contact_groups"` + APIEndpoint types.String `tfsdk:"api_endpoint"` + APIKey types.String `tfsdk:"api_key"` + Teams types.String `tfsdk:"teams"` + Tags types.String `tfsdk:"tags"` + AutoResolve types.Bool `tfsdk:"auto_resolve"` +} + +func (m IntegrationOpsgenieResourceModel) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(m.ID.ValueInt64()) +} + +type IntegrationOpsgenieResourceModelAdapter struct { + ContactGroupsAttributeAdapter +} + +func (a IntegrationOpsgenieResourceModelAdapter) Get(ctx context.Context, sg StateGetter) (*IntegrationOpsgenieResourceModel, diag.Diagnostics) { + model := *new(IntegrationOpsgenieResourceModel) + diags := sg.Get(ctx, &model) + if diags.HasError() { + return nil, diags + } + return &model, nil +} + +func (a IntegrationOpsgenieResourceModelAdapter) ToAPIArgument(model IntegrationOpsgenieResourceModel) (*upapi.IntegrationOpsgenie, error) { + return &upapi.IntegrationOpsgenie{ + Name: model.Name.ValueString(), + ContactGroups: a.ContactGroups(model.ContactGroups), + APIEndpoint: model.APIEndpoint.ValueString(), + APIKey: model.APIKey.ValueString(), + Teams: model.Teams.ValueString(), + Tags: model.Tags.ValueString(), + Autoresolve: model.AutoResolve.ValueBool(), + }, nil +} + +func (a IntegrationOpsgenieResourceModelAdapter) FromAPIResult(api upapi.Integration) (*IntegrationOpsgenieResourceModel, error) { + return &IntegrationOpsgenieResourceModel{ + ID: types.Int64Value(api.PK), + URL: types.StringValue(api.URL), + Name: types.StringValue(api.Name), + ContactGroups: a.ContactGroupsValue(api.ContactGroups), + APIEndpoint: types.StringValue(api.APIEndpoint), + APIKey: types.StringValue(api.APIKey), + Teams: types.StringValue(api.Teams), + Tags: types.StringValue(api.Tags), + AutoResolve: types.BoolValue(api.Autoresolve), + }, nil +} + +type IntegrationOpsgenieResourceAPI struct { + provider *providerImpl +} + +func (a IntegrationOpsgenieResourceAPI) Create(ctx context.Context, arg upapi.IntegrationOpsgenie) (*upapi.Integration, error) { + return a.provider.api.Integrations().CreateOpsgenie(ctx, arg) +} + +func (a IntegrationOpsgenieResourceAPI) Read(ctx context.Context, pk upapi.PrimaryKeyable) (*upapi.Integration, error) { + return a.provider.api.Integrations().Get(ctx, pk) +} + +func (a IntegrationOpsgenieResourceAPI) Update(ctx context.Context, pk upapi.PrimaryKeyable, arg upapi.IntegrationOpsgenie) (*upapi.Integration, error) { + return a.provider.api.Integrations().UpdateOpsgenie(ctx, pk, arg) +} + +func (a IntegrationOpsgenieResourceAPI) Delete(ctx context.Context, pk upapi.PrimaryKeyable) error { + return a.provider.api.Integrations().Delete(ctx, pk) +} diff --git a/internal/provider/resource_integration_opsgenie_test.go b/internal/provider/resource_integration_opsgenie_test.go new file mode 100644 index 0000000..f5e425b --- /dev/null +++ b/internal/provider/resource_integration_opsgenie_test.go @@ -0,0 +1,54 @@ +package provider + +import ( + "testing" + + petname "github.com/dustinkirkland/golang-petname" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccIntegrationOpsgenieResource(t *testing.T) { + names := [2]string{ + petname.Generate(3, "-"), + petname.Generate(3, "-"), + } + apiEndpoint := [2]string{ + "https://api.opsgenie.com/v1/json/uptime1", + "https://api.opsgenie.com/v1/json/uptime2", + } + apiKey := [2]string{ + "16c8bfe0-b219-11ef-a5da-4b9e62fe7439", + "274d1848-b219-11ef-a468-9f18e59bdc97", + } + resource.Test(t, testCaseFromSteps(t, []resource.TestStep{ + { + ConfigVariables: config.Variables{ + "name": config.StringVariable(names[0]), + "api_endpoint": config.StringVariable(apiEndpoint[0]), + "api_key": config.StringVariable(apiKey[0]), + }, + ConfigDirectory: config.StaticDirectory("testdata/resource_integration_opsgenie/_basic"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("uptime_integration_opsgenie.test", "id"), + resource.TestCheckResourceAttrSet("uptime_integration_opsgenie.test", "url"), + resource.TestCheckResourceAttr("uptime_integration_opsgenie.test", "name", names[0]), + resource.TestCheckResourceAttr("uptime_integration_opsgenie.test", "api_endpoint", apiEndpoint[0]), + resource.TestCheckResourceAttr("uptime_integration_opsgenie.test", "api_key", apiKey[0]), + ), + }, + { + ConfigVariables: config.Variables{ + "name": config.StringVariable(names[1]), + "api_endpoint": config.StringVariable(apiEndpoint[1]), + "api_key": config.StringVariable(apiKey[1]), + }, + ConfigDirectory: config.StaticDirectory("testdata/resource_integration_opsgenie/_basic"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_integration_opsgenie.test", "name", names[1]), + resource.TestCheckResourceAttr("uptime_integration_opsgenie.test", "api_endpoint", apiEndpoint[1]), + resource.TestCheckResourceAttr("uptime_integration_opsgenie.test", "api_key", apiKey[1]), + ), + }, + })) +} diff --git a/internal/provider/resource_statuspage.go b/internal/provider/resource_statuspage.go index a7b25cd..31732af 100644 --- a/internal/provider/resource_statuspage.go +++ b/internal/provider/resource_statuspage.go @@ -185,6 +185,51 @@ func NewStatusPageResource(_ context.Context, p *providerImpl) resource.Resource Computed: true, Default: stringdefault.StaticString("GMT"), }, + "allow_subscriptions_email": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "allow_subscriptions_rss": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "allow_subscriptions_slack": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "allow_subscriptions_sms": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "allow_subscriptions_webhook": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "hide_empty_tabs_history": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "theme": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("LEGACY"), + }, + "custom_header_bg_color_hex": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("#002E52"), + }, + "custom_header_text_color_hex": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("#FFFFFF"), + }, }, }, }, @@ -225,6 +270,15 @@ type StatusPageResourceModel struct { CustomCss types.String `tfsdk:"custom_css"` CompanyWebsiteUrl types.String `tfsdk:"company_website_url"` Timezone types.String `tfsdk:"timezone"` + AllowSubscriptionsEmail types.Bool `tfsdk:"allow_subscriptions_email"` + AllowSubscriptionsRss types.Bool `tfsdk:"allow_subscriptions_rss"` + AllowSubscriptionsSlack types.Bool `tfsdk:"allow_subscriptions_slack"` + AllowSubscriptionsSms types.Bool `tfsdk:"allow_subscriptions_sms"` + AllowSubscriptionsWebhook types.Bool `tfsdk:"allow_subscriptions_webhook"` + HideEmptyTabsHistory types.Bool `tfsdk:"hide_empty_tabs_history"` + Theme types.String `tfsdk:"theme"` + CustomHeaderBgColorHex types.String `tfsdk:"custom_header_bg_color_hex"` + CustomHeaderTextColorHex types.String `tfsdk:"custom_header_text_color_hex"` } func (m StatusPageResourceModel) PrimaryKey() upapi.PrimaryKey { @@ -275,6 +329,15 @@ func (c StatusPageResourceModelAdapter) ToAPIArgument(model StatusPageResourceMo CustomCss: model.CustomCss.ValueString(), CompanyWebsiteUrl: model.CompanyWebsiteUrl.ValueString(), Timezone: model.Timezone.ValueString(), + AllowSubscriptionsEmail: model.AllowSubscriptionsEmail.ValueBool(), + AllowSubscriptionsRss: model.AllowSubscriptionsRss.ValueBool(), + AllowSubscriptionsSlack: model.AllowSubscriptionsSlack.ValueBool(), + AllowSubscriptionsSms: model.AllowSubscriptionsSms.ValueBool(), + AllowSubscriptionsWebhook: model.AllowSubscriptionsWebhook.ValueBool(), + HideEmptyTabsHistory: model.HideEmptyTabsHistory.ValueBool(), + Theme: model.Theme.ValueString(), + CustomHeaderBgColorHex: model.CustomHeaderBgColorHex.ValueString(), + CustomHeaderTextColorHex: model.CustomHeaderTextColorHex.ValueString(), } return &api, nil } @@ -314,6 +377,15 @@ func (c StatusPageResourceModelAdapter) FromAPIResult(api upapi.StatusPage) (*St CustomCss: types.StringValue(api.CustomCss), CompanyWebsiteUrl: types.StringValue(api.CompanyWebsiteUrl), Timezone: types.StringValue(api.Timezone), + AllowSubscriptionsEmail: types.BoolValue(api.AllowSubscriptionsEmail), + AllowSubscriptionsRss: types.BoolValue(api.AllowSubscriptionsRss), + AllowSubscriptionsSlack: types.BoolValue(api.AllowSubscriptionsSlack), + AllowSubscriptionsSms: types.BoolValue(api.AllowSubscriptionsSms), + AllowSubscriptionsWebhook: types.BoolValue(api.AllowSubscriptionsWebhook), + HideEmptyTabsHistory: types.BoolValue(api.HideEmptyTabsHistory), + Theme: types.StringValue(api.Theme), + CustomHeaderBgColorHex: types.StringValue(api.CustomHeaderBgColorHex), + CustomHeaderTextColorHex: types.StringValue(api.CustomHeaderTextColorHex), } return &model, nil } diff --git a/internal/provider/resource_statuspage_component.go b/internal/provider/resource_statuspage_component.go new file mode 100644 index 0000000..035f12a --- /dev/null +++ b/internal/provider/resource_statuspage_component.go @@ -0,0 +1,200 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/uptime-com/uptime-client-go/v2/pkg/upapi" +) + +func NewStatusPageComponentResource(_ context.Context, p *providerImpl) resource.Resource { + return APIResource[StatusPageComponentResourceModel, StatusPageComponentWrapper, StatusPageComponentWrapper]{ + api: &StatusPageComponentResourceAPI{provider: p}, + mod: StatusPageComponentResourceModelAdapter{}, + meta: APIResourceMetadata{ + TypeNameSuffix: "statuspage_component", + Schema: schema.Schema{ + Description: "Status page component resource", + Attributes: map[string]schema.Attribute{ + "statuspage_id": schema.Int64Attribute{ + Required: true, + }, + "id": IDSchemaAttribute(), + "url": URLSchemaAttribute(), + "name": NameSchemaAttribute(), + "description": schema.StringAttribute{ + Computed: true, + Optional: true, + Default: stringdefault.StaticString(""), + }, + "group_id": schema.Int64Attribute{ + Optional: true, + Computed: true, + }, + "service_id": schema.Int64Attribute{ + Optional: true, + Computed: true, + }, + "is_group": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "status": schema.StringAttribute{ + Optional: true, + Computed: true, + Validators: []validator.String{ + OneOfStringValidator([]string{"operational", "major-outage", "partial-outage", "degraded-performance", "under-maintenance"}), + }, + }, + "auto_status_down": schema.StringAttribute{ + Optional: true, + Computed: true, + Validators: []validator.String{ + OneOfStringValidator([]string{"major-outage", "partial-outage", "degraded-performance", "under-maintenance"}), + }, + }, + "auto_status_up": schema.StringAttribute{ + Optional: true, + Computed: true, + Validators: []validator.String{ + OneOfStringValidator([]string{"operational", "major-outage", "partial-outage", "degraded-performance", "under-maintenance"}), + }, + }, + }, + }, + }, + } +} + +type StatusPageComponentWrapper struct { + upapi.StatusPageComponent + + StatusPageID int64 +} + +func (w StatusPageComponentWrapper) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(w.PK) +} + +type StatusPageComponentResourceModel struct { + StatusPageID types.Int64 `tfsdk:"statuspage_id"` + ID types.Int64 `tfsdk:"id"` + URL types.String `tfsdk:"url"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + GroupID types.Int64 `tfsdk:"group_id"` + ServiceID types.Int64 `tfsdk:"service_id"` + IsGroup types.Bool `tfsdk:"is_group"` + Status types.String `tfsdk:"status"` + AutoStatusDown types.String `tfsdk:"auto_status_down"` + AutoStatusUp types.String `tfsdk:"auto_status_up"` +} + +func (m StatusPageComponentResourceModel) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(m.ID.ValueInt64()) +} + +type StatusPageComponentResourceModelAdapter struct { + SetAttributeAdapter[int32] +} + +func (a StatusPageComponentResourceModelAdapter) Get( + ctx context.Context, sg StateGetter, +) (*StatusPageComponentResourceModel, diag.Diagnostics) { + var model StatusPageComponentResourceModel + if diags := sg.Get(ctx, &model); diags.HasError() { + return nil, diags + } + + return &model, nil +} + +func (a StatusPageComponentResourceModelAdapter) ToAPIArgument( + model StatusPageComponentResourceModel, +) (*StatusPageComponentWrapper, error) { + return &StatusPageComponentWrapper{ + StatusPageID: model.StatusPageID.ValueInt64(), + StatusPageComponent: upapi.StatusPageComponent{ + Name: model.Name.ValueString(), + Description: model.Description.ValueString(), + IsGroup: model.IsGroup.ValueBool(), + GroupID: model.GroupID.ValueInt64(), + ServiceID: model.ServiceID.ValueInt64(), + Status: model.Status.ValueString(), + AutoStatusDown: model.AutoStatusDown.ValueString(), + AutoStatusUp: model.AutoStatusUp.ValueString(), + }, + }, nil +} + +func (a StatusPageComponentResourceModelAdapter) FromAPIResult( + api StatusPageComponentWrapper, +) (*StatusPageComponentResourceModel, error) { + return &StatusPageComponentResourceModel{ + StatusPageID: types.Int64Value(api.StatusPageID), + ID: types.Int64Value(api.PK), + URL: types.StringValue(api.URL), + Name: types.StringValue(api.Name), + Description: types.StringValue(api.Description), + IsGroup: types.BoolValue(api.IsGroup), + GroupID: types.Int64Value(api.GroupID), + ServiceID: types.Int64Value(api.ServiceID), + Status: types.StringValue(api.Status), + AutoStatusDown: types.StringValue(api.AutoStatusDown), + AutoStatusUp: types.StringValue(api.AutoStatusUp), + }, nil +} + +type StatusPageComponentResourceAPI struct { + provider *providerImpl +} + +func (a StatusPageComponentResourceAPI) Create(ctx context.Context, arg StatusPageComponentWrapper) (*StatusPageComponentWrapper, error) { + obj, err := a.provider.api.StatusPages().Components(upapi.PrimaryKey(arg.StatusPageID)).Create(ctx, arg.StatusPageComponent) + if err != nil { + return nil, err + } + + return &StatusPageComponentWrapper{StatusPageComponent: *obj, StatusPageID: arg.StatusPageID}, nil +} + +func (a StatusPageComponentResourceAPI) Read(ctx context.Context, arg upapi.PrimaryKeyable) (*StatusPageComponentWrapper, error) { + model, ok := arg.(StatusPageComponentResourceModel) + if !ok { + return nil, fmt.Errorf("resource read failed:unexpected type %T", arg) + } + + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + obj, err := a.provider.api.StatusPages().Components(statusPageID).Get(ctx, arg) + if err != nil { + return nil, err + } + + return &StatusPageComponentWrapper{StatusPageComponent: *obj, StatusPageID: int64(statusPageID)}, nil +} + +func (a StatusPageComponentResourceAPI) Update(ctx context.Context, pk upapi.PrimaryKeyable, arg StatusPageComponentWrapper) (*StatusPageComponentWrapper, error) { + obj, err := a.provider.api.StatusPages().Components(upapi.PrimaryKey(arg.StatusPageID)).Update(ctx, pk, arg.StatusPageComponent) + if err != nil { + return nil, err + } + return &StatusPageComponentWrapper{StatusPageComponent: *obj, StatusPageID: arg.StatusPageID}, nil +} + +func (a StatusPageComponentResourceAPI) Delete(ctx context.Context, arg upapi.PrimaryKeyable) error { + model, ok := arg.(StatusPageComponentResourceModel) + if !ok { + return fmt.Errorf("resource delete failed: unexpected type %T", arg) + } + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + return a.provider.api.StatusPages().Components(statusPageID).Delete(ctx, arg) +} diff --git a/internal/provider/resource_statuspage_component_test.go b/internal/provider/resource_statuspage_component_test.go new file mode 100644 index 0000000..4dc28fc --- /dev/null +++ b/internal/provider/resource_statuspage_component_test.go @@ -0,0 +1,45 @@ +package provider + +import ( + "testing" + + petname "github.com/dustinkirkland/golang-petname" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccStatusPageComponentResource(t *testing.T) { + name := petname.Generate(3, "-") + componentNames := [2]string{ + petname.Generate(3, "-"), + petname.Generate(3, "-"), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { _ = testAccAPIClient(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_component/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "component_name": config.StringVariable(componentNames[0]), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", name), + resource.TestCheckResourceAttr("uptime_statuspage_component.test", "name", componentNames[0]), + ), + }, + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_component/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "component_name": config.StringVariable(componentNames[1]), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage_component.test", "name", componentNames[1]), + ), + }, + }, + }) +} diff --git a/internal/provider/resource_statuspage_incident.go b/internal/provider/resource_statuspage_incident.go new file mode 100644 index 0000000..da71f34 --- /dev/null +++ b/internal/provider/resource_statuspage_incident.go @@ -0,0 +1,444 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/uptime-com/uptime-client-go/v2/pkg/upapi" +) + +func NewStatusPageIncidentResource(_ context.Context, p *providerImpl) resource.Resource { + return APIResource[StatusPageIncidentResourceModel, StatusPageIncidentWrapper, StatusPageIncidentWrapper]{ + api: &StatusPageIncidentResourceAPI{provider: p}, + mod: StatusPageIncidentResourceModelAdapter{}, + meta: APIResourceMetadata{ + TypeNameSuffix: "statuspage_incident", + Schema: schema.Schema{ + Description: "Status page incident or maintenance window resource", + Attributes: map[string]schema.Attribute{ + "statuspage_id": schema.Int64Attribute{ + Required: true, + }, + "id": IDSchemaAttribute(), + "url": URLSchemaAttribute(), + "name": NameSchemaAttribute(), + "ends_at": schema.StringAttribute{ + Optional: true, + Computed: true, + CustomType: timetypes.RFC3339Type{}, + }, + "starts_at": schema.StringAttribute{ + Optional: true, + Computed: true, + CustomType: timetypes.RFC3339Type{}, + }, + "include_in_global_metrics": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "updates": schema.SetNestedAttribute{ + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": IDSchemaAttribute(), + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "incident_state": schema.StringAttribute{ + Optional: true, + Computed: true, + Validators: []validator.String{ + OneOfStringValidator([]string{"investigating", "identified", "monitoring", "resolved", "notification", "maintenance"}), + }, + }, + }, + }, + }, + "affected_components": schema.SetNestedAttribute{ + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": IDSchemaAttribute(), + "status": schema.StringAttribute{ + Optional: true, + Computed: true, + Validators: []validator.String{ + OneOfStringValidator([]string{"major-outage", "partial-outage", "degraded-performance", "under-maintenance"}), + }, + }, + "component_id": schema.Int64Attribute{ + Required: true, + }, + }, + }, + }, + "incident_type": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("major-outage"), + Validators: []validator.String{ + OneOfStringValidator([]string{"INCIDENT", "SCHEDULED_MAINTENANCE"}), + }, + }, + "update_component_status": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "notify_subscribers": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "send_maintenance_start_notification": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + }, + }, + }, + } +} + +type StatusPageIncidentWrapper struct { + upapi.StatusPageIncident + StatusPageID int64 +} + +func (w StatusPageIncidentWrapper) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(w.PK) +} + +type StatusPageIncidentResourceModel struct { + StatusPageID types.Int64 `tfsdk:"statuspage_id"` + ID types.Int64 `tfsdk:"id"` + URL types.String `tfsdk:"url"` + Name types.String `tfsdk:"name"` + EndsAt timetypes.RFC3339 `tfsdk:"ends_at"` + StartsAt timetypes.RFC3339 `tfsdk:"starts_at"` + IncludeInGlobalMetrics types.Bool `tfsdk:"include_in_global_metrics"` + Updates types.Set `tfsdk:"updates"` + AffectedComponents types.Set `tfsdk:"affected_components"` + IncidentType types.String `tfsdk:"incident_type"` + UpdateComponentStatus types.Bool `tfsdk:"update_component_status"` + NotifySubscribers types.Bool `tfsdk:"notify_subscribers"` + SendMaintenanceStartNotification types.Bool `tfsdk:"send_maintenance_start_notification"` + + updates []StatusPageIncidentUpdateAttribute + affectedComponents []StatusPageIncidentAffectedComponentAttribute +} + +func (m StatusPageIncidentResourceModel) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(m.ID.ValueInt64()) +} + +type StatusPageIncidentResourceModelAdapter struct { + SetAttributeAdapter[int32] +} + +func (a StatusPageIncidentResourceModelAdapter) Get( + ctx context.Context, sg StateGetter, +) (*StatusPageIncidentResourceModel, diag.Diagnostics) { + var model StatusPageIncidentResourceModel + diags := sg.Get(ctx, &model) + if diags.HasError() { + return nil, diags + } + + model.updates, diags = a.UpdatesContext(ctx, model.Updates) + if diags.HasError() { + return nil, diags + } + + model.affectedComponents, diags = a.AffectedComponentsContext(ctx, model.AffectedComponents) + if diags.HasError() { + return nil, diags + } + return &model, nil +} + +func (a StatusPageIncidentResourceModelAdapter) ToAPIArgument( + model StatusPageIncidentResourceModel, +) (*StatusPageIncidentWrapper, error) { + api := StatusPageIncidentWrapper{ + StatusPageID: model.StatusPageID.ValueInt64(), + StatusPageIncident: upapi.StatusPageIncident{ + Name: model.Name.ValueString(), + IncludeInGlobalMetrics: model.IncludeInGlobalMetrics.ValueBool(), + IncidentType: model.IncidentType.ValueString(), + UpdateComponentStatus: model.UpdateComponentStatus.ValueBool(), + NotifySubscribers: model.NotifySubscribers.ValueBool(), + SendMaintenanceStartNotification: model.SendMaintenanceStartNotification.ValueBool(), + }, + } + + if !model.EndsAt.IsNull() && !model.EndsAt.IsUnknown() { + api.EndsAt = model.EndsAt.ValueString() + } + + if !model.StartsAt.IsNull() && !model.StartsAt.IsUnknown() { + api.StartsAt = model.StartsAt.ValueString() + } + + if len(model.updates) != 0 { + updates := make([]upapi.IncidentUpdate, 0) + for _, v := range model.updates { + updates = append(updates, upapi.IncidentUpdate{ + Description: v.Description.ValueString(), + IncidentState: v.IncidentState.ValueString(), + }) + } + api.Updates = updates + } + + if len(model.affectedComponents) != 0 { + affectedComponents := make([]upapi.IncidentAffectedComponentEntity, 0) + for _, v := range model.affectedComponents { + affectedComponents = append(affectedComponents, upapi.IncidentAffectedComponentEntity{ + PK: v.ID.ValueInt64(), + Status: v.Status.ValueString(), + Component: upapi.IncidentAffectedComponent{ + PK: v.ComponentID.ValueInt64(), + }, + }) + } + api.AffectedComponents = affectedComponents + } + + return &api, nil +} + +func (a StatusPageIncidentResourceModelAdapter) FromAPIResult( + api StatusPageIncidentWrapper, +) (*StatusPageIncidentResourceModel, error) { + model := &StatusPageIncidentResourceModel{ + StatusPageID: types.Int64Value(api.StatusPageID), + ID: types.Int64Value(api.PK), + URL: types.StringValue(api.URL), + Name: types.StringValue(api.Name), + IncludeInGlobalMetrics: types.BoolValue(api.IncludeInGlobalMetrics), + IncidentType: types.StringValue(api.IncidentType), + UpdateComponentStatus: types.BoolValue(api.UpdateComponentStatus), + NotifySubscribers: types.BoolValue(api.NotifySubscribers), + SendMaintenanceStartNotification: types.BoolValue(api.SendMaintenanceStartNotification), + } + + var d diag.Diagnostics + if api.EndsAt != "" { + model.EndsAt, d = timetypes.NewRFC3339PointerValue(&api.EndsAt) + if d.HasError() { + return nil, fmt.Errorf("error parsing EndsAt: %v", d) + } + } + + if api.StartsAt != "" { + model.StartsAt, d = timetypes.NewRFC3339PointerValue(&api.StartsAt) + if d.HasError() { + return nil, fmt.Errorf("error parsing StartsAt: %v", d) + } + } + + updates := []StatusPageIncidentUpdateAttribute{} + for _, item := range api.Updates { + updates = append(updates, StatusPageIncidentUpdateAttribute{ + ID: types.Int64Value(item.PK), + Description: types.StringValue(item.Description), + IncidentState: types.StringValue(item.IncidentState), + }) + } + + var diags diag.Diagnostics + if model.Updates, diags = a.UpdatesValue(updates); diags.HasError() { + return nil, fmt.Errorf("failed to convert updates: %v", diags) + } + + affectedComponents := []StatusPageIncidentAffectedComponentAttribute{} + for _, item := range api.AffectedComponents { + affectedComponents = append(affectedComponents, StatusPageIncidentAffectedComponentAttribute{ + ID: types.Int64Value(item.PK), + Status: types.StringValue(item.Status), + ComponentID: types.Int64Value(item.Component.PK), + }) + } + + if model.AffectedComponents, diags = a.AffectedComponentsValue(affectedComponents); diags.HasError() { + return nil, fmt.Errorf("failed to convert affected components: %v", diags) + } + + return model, nil +} + +func (a StatusPageIncidentResourceModelAdapter) UpdatesContext( + ctx context.Context, v types.Set, +) ([]StatusPageIncidentUpdateAttribute, diag.Diagnostics) { + if v.IsNull() || v.IsUnknown() { + return nil, nil + } + + out := make([]StatusPageIncidentUpdateAttribute, 0) + if d := v.ElementsAs(ctx, &out, false); d.HasError() { + return nil, d + } + return out, nil +} + +func (a StatusPageIncidentResourceModelAdapter) UpdatesValue( + model []StatusPageIncidentUpdateAttribute, +) (types.Set, diag.Diagnostics) { + values, diags := a.updatesAttributeValues(model) + if diags.HasError() { + return types.Set{}, diags + } + return types.SetValueMust( + types.ObjectType{}.WithAttributeTypes(a.updatesAttributeTypes()), values), diags +} + +func (a StatusPageIncidentResourceModelAdapter) updatesAttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "id": types.Int64Type, + "description": types.StringType, + "incident_state": types.StringType, + } +} + +func (a StatusPageIncidentResourceModelAdapter) updatesAttributeValues( + model []StatusPageIncidentUpdateAttribute, +) (out []attr.Value, diags diag.Diagnostics) { + out = make([]attr.Value, len(model)) + for i := range model { + out[i], diags = types.ObjectValue(a.updatesAttributeTypes(), map[string]attr.Value{ + "id": model[i].ID, + "description": model[i].Description, + "incident_state": model[i].IncidentState, + }) + if diags.HasError() { + return + } + } + return +} + +func (a StatusPageIncidentResourceModelAdapter) AffectedComponentsContext( + ctx context.Context, v types.Set, +) ([]StatusPageIncidentAffectedComponentAttribute, diag.Diagnostics) { + if v.IsNull() || v.IsUnknown() { + return nil, nil + } + + out := make([]StatusPageIncidentAffectedComponentAttribute, 0) + if d := v.ElementsAs(ctx, &out, false); d.HasError() { + return nil, d + } + return out, nil +} + +func (a StatusPageIncidentResourceModelAdapter) AffectedComponentsValue( + model []StatusPageIncidentAffectedComponentAttribute, +) (types.Set, diag.Diagnostics) { + values, diags := a.affectedComponentsAttributeValues(model) + if diags.HasError() { + return types.Set{}, diags + } + return types.SetValueMust( + types.ObjectType{}.WithAttributeTypes(a.affectedComponentsAttributeTypes()), values), diags +} + +func (a StatusPageIncidentResourceModelAdapter) affectedComponentsAttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "id": types.Int64Type, + "status": types.StringType, + "component_id": types.Int64Type, + } +} + +func (a StatusPageIncidentResourceModelAdapter) affectedComponentsAttributeValues( + model []StatusPageIncidentAffectedComponentAttribute, +) (out []attr.Value, diags diag.Diagnostics) { + out = make([]attr.Value, len(model)) + for i := range model { + out[i], diags = types.ObjectValue(a.affectedComponentsAttributeTypes(), map[string]attr.Value{ + "id": model[i].ID, + "status": model[i].Status, + "component_id": model[i].ComponentID, + }) + if diags.HasError() { + return + } + } + return +} + +type StatusPageIncidentResourceAPI struct { + provider *providerImpl +} + +func (a StatusPageIncidentResourceAPI) Create(ctx context.Context, arg StatusPageIncidentWrapper) (*StatusPageIncidentWrapper, error) { + obj, err := a.provider.api.StatusPages().Incidents(upapi.PrimaryKey(arg.StatusPageID)).Create(ctx, arg.StatusPageIncident) + if err != nil { + return nil, err + } + + return &StatusPageIncidentWrapper{StatusPageIncident: *obj, StatusPageID: arg.StatusPageID}, nil +} + +func (a StatusPageIncidentResourceAPI) Read(ctx context.Context, arg upapi.PrimaryKeyable) (*StatusPageIncidentWrapper, error) { + model, ok := arg.(StatusPageIncidentResourceModel) + if !ok { + return nil, fmt.Errorf("resource read failed:unexpected type %T", arg) + } + + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + obj, err := a.provider.api.StatusPages().Incidents(statusPageID).Get(ctx, arg) + if err != nil { + return nil, err + } + + return &StatusPageIncidentWrapper{StatusPageIncident: *obj, StatusPageID: int64(statusPageID)}, nil +} + +func (a StatusPageIncidentResourceAPI) Update( + ctx context.Context, pk upapi.PrimaryKeyable, arg StatusPageIncidentWrapper, +) (*StatusPageIncidentWrapper, error) { + obj, err := a.provider.api.StatusPages(). + Incidents(upapi.PrimaryKey(arg.StatusPageID)). + Update(ctx, pk, arg.StatusPageIncident) + if err != nil { + return nil, err + } + return &StatusPageIncidentWrapper{StatusPageIncident: *obj, StatusPageID: arg.StatusPageID}, nil +} + +func (a StatusPageIncidentResourceAPI) Delete(ctx context.Context, arg upapi.PrimaryKeyable) error { + model, ok := arg.(StatusPageIncidentResourceModel) + if !ok { + return fmt.Errorf("resource delete failed: unexpected type %T", arg) + } + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + return a.provider.api.StatusPages().Incidents(statusPageID).Delete(ctx, arg) +} + +type StatusPageIncidentUpdateAttribute struct { + ID types.Int64 `tfsdk:"id"` + Description types.String `tfsdk:"description"` + IncidentState types.String `tfsdk:"incident_state"` +} + +type StatusPageIncidentAffectedComponentAttribute struct { + ID types.Int64 `tfsdk:"id"` + Status types.String `tfsdk:"status"` + ComponentID types.Int64 `tfsdk:"component_id"` +} diff --git a/internal/provider/resource_statuspage_incident_test.go b/internal/provider/resource_statuspage_incident_test.go new file mode 100644 index 0000000..9a4258f --- /dev/null +++ b/internal/provider/resource_statuspage_incident_test.go @@ -0,0 +1,78 @@ +package provider + +import ( + "testing" + + petname "github.com/dustinkirkland/golang-petname" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccStatusPageIncidentBasicResource(t *testing.T) { + name := petname.Generate(3, "-") + incidentName := petname.Generate(3, "-") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { _ = testAccAPIClient(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_incident/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "incident_name": config.StringVariable(incidentName), + "incident_state": config.StringVariable("investigating"), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", name), + resource.TestCheckResourceAttr("uptime_statuspage_incident.test", "name", incidentName), + resource.TestCheckResourceAttr("uptime_statuspage_incident.test", "incident_type", "INCIDENT"), + ), + }, + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_incident/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "incident_name": config.StringVariable(incidentName), + "incident_state": config.StringVariable("monitoring"), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage_incident.test", "updates.#", "1"), + resource.TestCheckResourceAttr("uptime_statuspage_incident.test", "updates.0.incident_state", "monitoring"), + ), + }, + }, + }) +} + +func TestAccStatusPageIncidentAffectedComponentsResource(t *testing.T) { + name := petname.Generate(3, "-") + incidentName := petname.Generate(3, "-") + checkName := petname.Generate(3, "-") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { _ = testAccAPIClient(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_incident/affected_components"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "incident_name": config.StringVariable(incidentName), + "check_name": config.StringVariable(checkName), + "incident_state": config.StringVariable("investigating"), + "incident_component_status": config.StringVariable("major-outage"), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", name), + resource.TestCheckResourceAttr("uptime_statuspage_incident.test", "name", incidentName), + resource.TestCheckResourceAttr("uptime_statuspage_incident.test", "incident_type", "INCIDENT"), + resource.TestCheckResourceAttr("uptime_statuspage_incident.test", "affected_components.#", "1"), + resource.TestCheckResourceAttr( + "uptime_statuspage_incident.test", "affected_components.0.status", "major-outage", + ), + ), + }, + }, + }) +} diff --git a/internal/provider/resource_statuspage_metric.go b/internal/provider/resource_statuspage_metric.go new file mode 100644 index 0000000..2406567 --- /dev/null +++ b/internal/provider/resource_statuspage_metric.go @@ -0,0 +1,153 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/uptime-com/uptime-client-go/v2/pkg/upapi" +) + +func NewStatusPageMetricResource(_ context.Context, p *providerImpl) resource.Resource { + return APIResource[StatusPageMetricResourceModel, StatusPageMetricWrapper, StatusPageMetricWrapper]{ + api: &StatusPageMetricResourceAPI{provider: p}, + mod: StatusPageMetricResourceModelAdapter{}, + meta: APIResourceMetadata{ + TypeNameSuffix: "statuspage_metric", + Schema: schema.Schema{ + Description: "Status page metric resource", + Attributes: map[string]schema.Attribute{ + "statuspage_id": schema.Int64Attribute{ + Required: true, + }, + "id": IDSchemaAttribute(), + "url": URLSchemaAttribute(), + "name": NameSchemaAttribute(), + "service_id": schema.Int64Attribute{ + Required: true, + }, + "is_visible": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + }, + }, + }, + } +} + +type StatusPageMetricWrapper struct { + upapi.StatusPageMetric + + StatusPageID int64 +} + +func (w StatusPageMetricWrapper) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(w.PK) +} + +type StatusPageMetricResourceModel struct { + StatusPageID types.Int64 `tfsdk:"statuspage_id"` + ID types.Int64 `tfsdk:"id"` + URL types.String `tfsdk:"url"` + Name types.String `tfsdk:"name"` + ServiceID types.Int64 `tfsdk:"service_id"` + IsVisible types.Bool `tfsdk:"is_visible"` +} + +func (m StatusPageMetricResourceModel) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(m.ID.ValueInt64()) +} + +type StatusPageMetricResourceModelAdapter struct { + SetAttributeAdapter[int32] +} + +func (a StatusPageMetricResourceModelAdapter) Get( + ctx context.Context, sg StateGetter, +) (*StatusPageMetricResourceModel, diag.Diagnostics) { + var model StatusPageMetricResourceModel + if diags := sg.Get(ctx, &model); diags.HasError() { + return nil, diags + } + + return &model, nil +} + +func (a StatusPageMetricResourceModelAdapter) ToAPIArgument( + model StatusPageMetricResourceModel, +) (*StatusPageMetricWrapper, error) { + return &StatusPageMetricWrapper{ + StatusPageID: model.StatusPageID.ValueInt64(), + StatusPageMetric: upapi.StatusPageMetric{ + PK: model.ID.ValueInt64(), + Name: model.Name.ValueString(), + ServiceID: model.ServiceID.ValueInt64(), + IsVisible: model.IsVisible.ValueBool(), + }, + }, nil +} + +func (a StatusPageMetricResourceModelAdapter) FromAPIResult( + api StatusPageMetricWrapper, +) (*StatusPageMetricResourceModel, error) { + return &StatusPageMetricResourceModel{ + StatusPageID: types.Int64Value(api.StatusPageID), + ID: types.Int64Value(api.PK), + URL: types.StringValue(api.URL), + Name: types.StringValue(api.Name), + ServiceID: types.Int64Value(api.ServiceID), + IsVisible: types.BoolValue(api.IsVisible), + }, nil +} + +type StatusPageMetricResourceAPI struct { + provider *providerImpl +} + +func (a StatusPageMetricResourceAPI) Create(ctx context.Context, arg StatusPageMetricWrapper) (*StatusPageMetricWrapper, error) { + obj, err := a.provider.api.StatusPages().Metrics(upapi.PrimaryKey(arg.StatusPageID)).Create(ctx, arg.StatusPageMetric) + if err != nil { + return nil, err + } + + return &StatusPageMetricWrapper{StatusPageMetric: *obj, StatusPageID: arg.StatusPageID}, nil +} + +func (a StatusPageMetricResourceAPI) Read(ctx context.Context, arg upapi.PrimaryKeyable) (*StatusPageMetricWrapper, error) { + model, ok := arg.(StatusPageMetricResourceModel) + if !ok { + return nil, fmt.Errorf("resource read failed:unexpected type %T", arg) + } + + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + obj, err := a.provider.api.StatusPages().Metrics(statusPageID).Get(ctx, arg) + if err != nil { + return nil, err + } + + return &StatusPageMetricWrapper{StatusPageMetric: *obj, StatusPageID: int64(statusPageID)}, nil +} + +func (a StatusPageMetricResourceAPI) Update(ctx context.Context, pk upapi.PrimaryKeyable, arg StatusPageMetricWrapper) (*StatusPageMetricWrapper, error) { + obj, err := a.provider.api.StatusPages().Metrics(upapi.PrimaryKey(arg.StatusPageID)).Update(ctx, pk, arg.StatusPageMetric) + if err != nil { + return nil, err + } + return &StatusPageMetricWrapper{StatusPageMetric: *obj, StatusPageID: arg.StatusPageID}, nil +} + +func (a StatusPageMetricResourceAPI) Delete(ctx context.Context, arg upapi.PrimaryKeyable) error { + model, ok := arg.(StatusPageMetricResourceModel) + if !ok { + return fmt.Errorf("resource delete failed: unexpected type %T", arg) + } + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + return a.provider.api.StatusPages().Metrics(statusPageID).Delete(ctx, arg) +} diff --git a/internal/provider/resource_statuspage_metric_test.go b/internal/provider/resource_statuspage_metric_test.go new file mode 100644 index 0000000..7916ce2 --- /dev/null +++ b/internal/provider/resource_statuspage_metric_test.go @@ -0,0 +1,49 @@ +package provider + +import ( + "testing" + + petname "github.com/dustinkirkland/golang-petname" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccStatusPageMetricResource(t *testing.T) { + name := petname.Generate(3, "-") + metricName := [2]string{ + petname.Generate(3, "-"), + petname.Generate(3, "-"), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { _ = testAccAPIClient(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_metric/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "metric_name": config.StringVariable(metricName[0]), + "is_visible": config.BoolVariable(false), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", name), + resource.TestCheckResourceAttr("uptime_statuspage_metric.test", "name", metricName[0]), + resource.TestCheckResourceAttr("uptime_statuspage_metric.test", "is_visible", "false"), + ), + }, + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_metric/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "metric_name": config.StringVariable(metricName[1]), + "is_visible": config.BoolVariable(true), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage_metric.test", "name", metricName[1]), + resource.TestCheckResourceAttr("uptime_statuspage_metric.test", "is_visible", "true"), + ), + }, + }, + }) +} diff --git a/internal/provider/resource_statuspage_subscriber.go b/internal/provider/resource_statuspage_subscriber.go new file mode 100644 index 0000000..9d94444 --- /dev/null +++ b/internal/provider/resource_statuspage_subscriber.go @@ -0,0 +1,162 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/uptime-com/uptime-client-go/v2/pkg/upapi" +) + +func NewStatusPageSubscriberResource(_ context.Context, p *providerImpl) resource.Resource { + return APIResource[StatusPageSubscriberResourceModel, StatusPageSubscriberWrapper, StatusPageSubscriberWrapper]{ + api: &StatusPageSubscriberResourceAPI{provider: p}, + mod: StatusPageSubscriberResourceModelAdapter{}, + meta: APIResourceMetadata{ + TypeNameSuffix: "statuspage_subscriber", + Schema: schema.Schema{ + Description: "Status page subscriber resource", + Attributes: map[string]schema.Attribute{ + "statuspage_id": schema.Int64Attribute{ + Required: true, + }, + "id": IDSchemaAttribute(), + "target": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "type": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + OneOfStringValidator([]string{"EMAIL", "SMS", "SLACK", "WEBHOOK"}), + }, + }, + "force_validation_sms": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + }, + }, + }, + } +} + +type StatusPageSubscriberWrapper struct { + upapi.StatusPageSubscriber + + StatusPageID int64 +} + +func (w StatusPageSubscriberWrapper) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(w.PK) +} + +type StatusPageSubscriberResourceModel struct { + StatusPageID types.Int64 `tfsdk:"statuspage_id"` + ID types.Int64 `tfsdk:"id"` + Target types.String `tfsdk:"target"` + Type types.String `tfsdk:"type"` + ForceValidationSMS types.Bool `tfsdk:"force_validation_sms"` +} + +func (m StatusPageSubscriberResourceModel) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(m.ID.ValueInt64()) +} + +type StatusPageSubscriberResourceModelAdapter struct { + SetAttributeAdapter[int32] +} + +func (a StatusPageSubscriberResourceModelAdapter) Get( + ctx context.Context, sg StateGetter, +) (*StatusPageSubscriberResourceModel, diag.Diagnostics) { + var model StatusPageSubscriberResourceModel + if diags := sg.Get(ctx, &model); diags.HasError() { + return nil, diags + } + + return &model, nil +} + +func (a StatusPageSubscriberResourceModelAdapter) ToAPIArgument( + model StatusPageSubscriberResourceModel, +) (*StatusPageSubscriberWrapper, error) { + return &StatusPageSubscriberWrapper{ + StatusPageID: model.StatusPageID.ValueInt64(), + StatusPageSubscriber: upapi.StatusPageSubscriber{ + PK: model.ID.ValueInt64(), + Target: model.Target.ValueString(), + Type: model.Type.ValueString(), + ForceValidationSMS: model.ForceValidationSMS.ValueBool(), + }, + }, nil +} + +func (a StatusPageSubscriberResourceModelAdapter) FromAPIResult( + api StatusPageSubscriberWrapper, +) (*StatusPageSubscriberResourceModel, error) { + return &StatusPageSubscriberResourceModel{ + StatusPageID: types.Int64Value(api.StatusPageID), + ID: types.Int64Value(api.PK), + Target: types.StringValue(api.Target), + Type: types.StringValue(api.Type), + ForceValidationSMS: types.BoolValue(api.ForceValidationSMS), + }, nil +} + +type StatusPageSubscriberResourceAPI struct { + provider *providerImpl +} + +func (a StatusPageSubscriberResourceAPI) Create(ctx context.Context, arg StatusPageSubscriberWrapper) (*StatusPageSubscriberWrapper, error) { + obj, err := a.provider.api.StatusPages().Subscribers(upapi.PrimaryKey(arg.StatusPageID)).Create(ctx, arg.StatusPageSubscriber) + if err != nil { + return nil, err + } + + return &StatusPageSubscriberWrapper{StatusPageSubscriber: *obj, StatusPageID: arg.StatusPageID}, nil +} + +func (a StatusPageSubscriberResourceAPI) Read(ctx context.Context, arg upapi.PrimaryKeyable) (*StatusPageSubscriberWrapper, error) { + model, ok := arg.(StatusPageSubscriberResourceModel) + if !ok { + return nil, fmt.Errorf("resource read failed:unexpected type %T", arg) + } + + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + obj, err := a.provider.api.StatusPages().Subscribers(statusPageID).Get(ctx, arg) + if err != nil { + return nil, err + } + + return &StatusPageSubscriberWrapper{StatusPageSubscriber: *obj, StatusPageID: int64(statusPageID)}, nil +} + +func (a StatusPageSubscriberResourceAPI) Update(ctx context.Context, pk upapi.PrimaryKeyable, arg StatusPageSubscriberWrapper) (*StatusPageSubscriberWrapper, error) { + err := a.provider.api.StatusPages().Subscribers(upapi.PrimaryKey(arg.StatusPageID)).Delete(ctx, pk) + if err != nil { + return nil, err + } + + obj, err := a.provider.api.StatusPages().Subscribers(upapi.PrimaryKey(arg.StatusPageID)).Create(ctx, arg.StatusPageSubscriber) + if err != nil { + return nil, err + } + return &StatusPageSubscriberWrapper{StatusPageSubscriber: *obj, StatusPageID: arg.StatusPageID}, nil +} + +func (a StatusPageSubscriberResourceAPI) Delete(ctx context.Context, arg upapi.PrimaryKeyable) error { + model, ok := arg.(StatusPageSubscriberResourceModel) + if !ok { + return fmt.Errorf("resource delete failed: unexpected type %T", arg) + } + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + return a.provider.api.StatusPages().Subscribers(statusPageID).Delete(ctx, arg) +} diff --git a/internal/provider/resource_statuspage_subscriber_test.go b/internal/provider/resource_statuspage_subscriber_test.go new file mode 100644 index 0000000..0b7c0b1 --- /dev/null +++ b/internal/provider/resource_statuspage_subscriber_test.go @@ -0,0 +1,54 @@ +package provider + +import ( + "testing" + + petname "github.com/dustinkirkland/golang-petname" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccStatusPageSubscriberResource(t *testing.T) { + name := petname.Generate(3, "-") + subscriberTargets := [2]string{ + "test@test.com", + "https://test.com/webhook", + } + subscriberTypes := [2]string{ + "EMAIL", + "WEBHOOK", + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { _ = testAccAPIClient(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_subscriber/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "subscriber_target": config.StringVariable(subscriberTargets[0]), + "subscriber_type": config.StringVariable(subscriberTypes[0]), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", name), + resource.TestCheckResourceAttr("uptime_statuspage_subscriber.test", "target", subscriberTargets[0]), + resource.TestCheckResourceAttr("uptime_statuspage_subscriber.test", "type", subscriberTypes[0]), + ), + }, + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_subscriber/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "subscriber_target": config.StringVariable(subscriberTargets[1]), + "subscriber_type": config.StringVariable(subscriberTypes[1]), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", name), + resource.TestCheckResourceAttr("uptime_statuspage_subscriber.test", "target", subscriberTargets[1]), + resource.TestCheckResourceAttr("uptime_statuspage_subscriber.test", "type", subscriberTypes[1]), + ), + }, + }, + }) +} diff --git a/internal/provider/resource_statuspage_subscription_domain_allow.go b/internal/provider/resource_statuspage_subscription_domain_allow.go new file mode 100644 index 0000000..7e7b8ba --- /dev/null +++ b/internal/provider/resource_statuspage_subscription_domain_allow.go @@ -0,0 +1,154 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/uptime-com/uptime-client-go/v2/pkg/upapi" +) + +func NewStatusPageSubsDomainAllowResource(_ context.Context, p *providerImpl) resource.Resource { + return APIResource[StatusPageSubsDomainAllowResourceModel, StatusPageSubsDomainAllowWrapper, StatusPageSubsDomainAllowWrapper]{ + api: &StatusPageSubsDomainAllowResourceAPI{provider: p}, + mod: StatusPageSubsDomainAllowResourceModelAdapter{}, + meta: APIResourceMetadata{ + TypeNameSuffix: "statuspage_subscription_domain_allow", + Schema: schema.Schema{ + Description: "Status page subscription domain allow resource", + Attributes: map[string]schema.Attribute{ + "statuspage_id": schema.Int64Attribute{ + Required: true, + }, + "id": IDSchemaAttribute(), + "domain": NameSchemaAttribute(), + }, + }, + }, + } +} + +type StatusPageSubsDomainAllowWrapper struct { + upapi.StatusPageSubsDomainAllowList + + StatusPageID int64 +} + +func (w StatusPageSubsDomainAllowWrapper) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(w.PK) +} + +type StatusPageSubsDomainAllowResourceModel struct { + StatusPageID types.Int64 `tfsdk:"statuspage_id"` + ID types.Int64 `tfsdk:"id"` + Domain types.String `tfsdk:"domain"` +} + +func (m StatusPageSubsDomainAllowResourceModel) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(m.ID.ValueInt64()) +} + +type StatusPageSubsDomainAllowResourceModelAdapter struct { + SetAttributeAdapter[int32] +} + +func (a StatusPageSubsDomainAllowResourceModelAdapter) Get( + ctx context.Context, sg StateGetter, +) (*StatusPageSubsDomainAllowResourceModel, diag.Diagnostics) { + var model StatusPageSubsDomainAllowResourceModel + if diags := sg.Get(ctx, &model); diags.HasError() { + return nil, diags + } + + return &model, nil +} + +func (a StatusPageSubsDomainAllowResourceModelAdapter) ToAPIArgument( + model StatusPageSubsDomainAllowResourceModel, +) (*StatusPageSubsDomainAllowWrapper, error) { + return &StatusPageSubsDomainAllowWrapper{ + StatusPageID: model.StatusPageID.ValueInt64(), + StatusPageSubsDomainAllowList: upapi.StatusPageSubsDomainAllowList{ + PK: model.ID.ValueInt64(), + Domain: model.Domain.ValueString(), + }, + }, nil +} + +func (a StatusPageSubsDomainAllowResourceModelAdapter) FromAPIResult( + api StatusPageSubsDomainAllowWrapper, +) (*StatusPageSubsDomainAllowResourceModel, error) { + return &StatusPageSubsDomainAllowResourceModel{ + StatusPageID: types.Int64Value(api.StatusPageID), + ID: types.Int64Value(api.PK), + Domain: types.StringValue(api.Domain), + }, nil +} + +type StatusPageSubsDomainAllowResourceAPI struct { + provider *providerImpl +} + +func (a StatusPageSubsDomainAllowResourceAPI) Create( + ctx context.Context, arg StatusPageSubsDomainAllowWrapper, +) (*StatusPageSubsDomainAllowWrapper, error) { + obj, err := a.provider.api.StatusPages(). + SubscriptionDomainAllowList(upapi.PrimaryKey(arg.StatusPageID)). + Create(ctx, arg.StatusPageSubsDomainAllowList) + if err != nil { + return nil, err + } + + return &StatusPageSubsDomainAllowWrapper{ + StatusPageSubsDomainAllowList: *obj, + StatusPageID: arg.StatusPageID, + }, nil +} + +func (a StatusPageSubsDomainAllowResourceAPI) Read( + ctx context.Context, arg upapi.PrimaryKeyable, +) (*StatusPageSubsDomainAllowWrapper, error) { + model, ok := arg.(StatusPageSubsDomainAllowResourceModel) + if !ok { + return nil, fmt.Errorf("resource read failed:unexpected type %T", arg) + } + + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + obj, err := a.provider.api.StatusPages().SubscriptionDomainAllowList(statusPageID).Get(ctx, arg) + if err != nil { + return nil, err + } + + return &StatusPageSubsDomainAllowWrapper{ + StatusPageSubsDomainAllowList: *obj, + StatusPageID: int64(statusPageID), + }, nil +} + +func (a StatusPageSubsDomainAllowResourceAPI) Update( + ctx context.Context, pk upapi.PrimaryKeyable, arg StatusPageSubsDomainAllowWrapper, +) (*StatusPageSubsDomainAllowWrapper, error) { + obj, err := a.provider.api.StatusPages(). + SubscriptionDomainAllowList(upapi.PrimaryKey(arg.StatusPageID)). + Update(ctx, pk, arg.StatusPageSubsDomainAllowList) + if err != nil { + return nil, err + } + return &StatusPageSubsDomainAllowWrapper{ + StatusPageSubsDomainAllowList: *obj, + StatusPageID: arg.StatusPageID, + }, nil +} + +func (a StatusPageSubsDomainAllowResourceAPI) Delete(ctx context.Context, arg upapi.PrimaryKeyable) error { + model, ok := arg.(StatusPageSubsDomainAllowResourceModel) + if !ok { + return fmt.Errorf("resource delete failed: unexpected type %T", arg) + } + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + return a.provider.api.StatusPages().SubscriptionDomainAllowList(statusPageID).Delete(ctx, arg) +} diff --git a/internal/provider/resource_statuspage_subscription_domain_allow_test.go b/internal/provider/resource_statuspage_subscription_domain_allow_test.go new file mode 100644 index 0000000..cb09aac --- /dev/null +++ b/internal/provider/resource_statuspage_subscription_domain_allow_test.go @@ -0,0 +1,46 @@ +package provider + +import ( + "testing" + + petname "github.com/dustinkirkland/golang-petname" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccStatusPageSubscriptionDomainAllowResource(t *testing.T) { + name := petname.Generate(3, "-") + domains := [2]string{ + "test1.com", + "test2.com", + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { _ = testAccAPIClient(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_subscription_domain_allow/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "domain": config.StringVariable(domains[0]), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", name), + resource.TestCheckResourceAttr("uptime_statuspage_subscription_domain_allow.test", "domain", domains[0]), + ), + }, + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_subscription_domain_allow/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "domain": config.StringVariable(domains[0]), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", name), + resource.TestCheckResourceAttr("uptime_statuspage_subscription_domain_allow.test", "domain", domains[0]), + ), + }, + }, + }) +} diff --git a/internal/provider/resource_statuspage_subscription_domain_block.go b/internal/provider/resource_statuspage_subscription_domain_block.go new file mode 100644 index 0000000..50e8c32 --- /dev/null +++ b/internal/provider/resource_statuspage_subscription_domain_block.go @@ -0,0 +1,154 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/uptime-com/uptime-client-go/v2/pkg/upapi" +) + +func NewStatusPageSubsDomainBlockResource(_ context.Context, p *providerImpl) resource.Resource { + return APIResource[StatusPageSubsDomainBlockResourceModel, StatusPageSubsDomainBlockWrapper, StatusPageSubsDomainBlockWrapper]{ + api: &StatusPageSubsDomainBlockResourceAPI{provider: p}, + mod: StatusPageSubsDomainBlockResourceModelAdapter{}, + meta: APIResourceMetadata{ + TypeNameSuffix: "statuspage_subscription_domain_block", + Schema: schema.Schema{ + Description: "Status page subscription domain block resource", + Attributes: map[string]schema.Attribute{ + "statuspage_id": schema.Int64Attribute{ + Required: true, + }, + "id": IDSchemaAttribute(), + "domain": NameSchemaAttribute(), + }, + }, + }, + } +} + +type StatusPageSubsDomainBlockWrapper struct { + upapi.StatusPageSubsDomainBlockList + + StatusPageID int64 +} + +func (w StatusPageSubsDomainBlockWrapper) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(w.PK) +} + +type StatusPageSubsDomainBlockResourceModel struct { + StatusPageID types.Int64 `tfsdk:"statuspage_id"` + ID types.Int64 `tfsdk:"id"` + Domain types.String `tfsdk:"domain"` +} + +func (m StatusPageSubsDomainBlockResourceModel) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(m.ID.ValueInt64()) +} + +type StatusPageSubsDomainBlockResourceModelAdapter struct { + SetAttributeAdapter[int32] +} + +func (a StatusPageSubsDomainBlockResourceModelAdapter) Get( + ctx context.Context, sg StateGetter, +) (*StatusPageSubsDomainBlockResourceModel, diag.Diagnostics) { + var model StatusPageSubsDomainBlockResourceModel + if diags := sg.Get(ctx, &model); diags.HasError() { + return nil, diags + } + + return &model, nil +} + +func (a StatusPageSubsDomainBlockResourceModelAdapter) ToAPIArgument( + model StatusPageSubsDomainBlockResourceModel, +) (*StatusPageSubsDomainBlockWrapper, error) { + return &StatusPageSubsDomainBlockWrapper{ + StatusPageID: model.StatusPageID.ValueInt64(), + StatusPageSubsDomainBlockList: upapi.StatusPageSubsDomainBlockList{ + PK: model.ID.ValueInt64(), + Domain: model.Domain.ValueString(), + }, + }, nil +} + +func (a StatusPageSubsDomainBlockResourceModelAdapter) FromAPIResult( + api StatusPageSubsDomainBlockWrapper, +) (*StatusPageSubsDomainBlockResourceModel, error) { + return &StatusPageSubsDomainBlockResourceModel{ + StatusPageID: types.Int64Value(api.StatusPageID), + ID: types.Int64Value(api.PK), + Domain: types.StringValue(api.Domain), + }, nil +} + +type StatusPageSubsDomainBlockResourceAPI struct { + provider *providerImpl +} + +func (a StatusPageSubsDomainBlockResourceAPI) Create( + ctx context.Context, arg StatusPageSubsDomainBlockWrapper, +) (*StatusPageSubsDomainBlockWrapper, error) { + obj, err := a.provider.api.StatusPages(). + SubscriptionDomainBlockList(upapi.PrimaryKey(arg.StatusPageID)). + Create(ctx, arg.StatusPageSubsDomainBlockList) + if err != nil { + return nil, err + } + + return &StatusPageSubsDomainBlockWrapper{ + StatusPageSubsDomainBlockList: *obj, + StatusPageID: arg.StatusPageID, + }, nil +} + +func (a StatusPageSubsDomainBlockResourceAPI) Read( + ctx context.Context, arg upapi.PrimaryKeyable, +) (*StatusPageSubsDomainBlockWrapper, error) { + model, ok := arg.(StatusPageSubsDomainBlockResourceModel) + if !ok { + return nil, fmt.Errorf("resource read failed:unexpected type %T", arg) + } + + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + obj, err := a.provider.api.StatusPages().SubscriptionDomainBlockList(statusPageID).Get(ctx, arg) + if err != nil { + return nil, err + } + + return &StatusPageSubsDomainBlockWrapper{ + StatusPageSubsDomainBlockList: *obj, + StatusPageID: int64(statusPageID), + }, nil +} + +func (a StatusPageSubsDomainBlockResourceAPI) Update( + ctx context.Context, pk upapi.PrimaryKeyable, arg StatusPageSubsDomainBlockWrapper, +) (*StatusPageSubsDomainBlockWrapper, error) { + obj, err := a.provider.api.StatusPages(). + SubscriptionDomainBlockList(upapi.PrimaryKey(arg.StatusPageID)). + Update(ctx, pk, arg.StatusPageSubsDomainBlockList) + if err != nil { + return nil, err + } + return &StatusPageSubsDomainBlockWrapper{ + StatusPageSubsDomainBlockList: *obj, + StatusPageID: arg.StatusPageID, + }, nil +} + +func (a StatusPageSubsDomainBlockResourceAPI) Delete(ctx context.Context, arg upapi.PrimaryKeyable) error { + model, ok := arg.(StatusPageSubsDomainBlockResourceModel) + if !ok { + return fmt.Errorf("resource delete failed: unexpected type %T", arg) + } + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + return a.provider.api.StatusPages().SubscriptionDomainBlockList(statusPageID).Delete(ctx, arg) +} diff --git a/internal/provider/resource_statuspage_subscription_domain_block_test.go b/internal/provider/resource_statuspage_subscription_domain_block_test.go new file mode 100644 index 0000000..9df2ba6 --- /dev/null +++ b/internal/provider/resource_statuspage_subscription_domain_block_test.go @@ -0,0 +1,46 @@ +package provider + +import ( + "testing" + + petname "github.com/dustinkirkland/golang-petname" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccStatusPageSubscriptionDomainBlockResource(t *testing.T) { + name := petname.Generate(3, "-") + domains := [2]string{ + "test1.com", + "test2.com", + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { _ = testAccAPIClient(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_subscription_domain_block/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "domain": config.StringVariable(domains[0]), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", name), + resource.TestCheckResourceAttr("uptime_statuspage_subscription_domain_block.test", "domain", domains[0]), + ), + }, + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_subscription_domain_block/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "domain": config.StringVariable(domains[0]), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", name), + resource.TestCheckResourceAttr("uptime_statuspage_subscription_domain_block.test", "domain", domains[0]), + ), + }, + }, + }) +} diff --git a/internal/provider/resource_statuspage_test.go b/internal/provider/resource_statuspage_test.go index d7bf6e8..ea2185c 100644 --- a/internal/provider/resource_statuspage_test.go +++ b/internal/provider/resource_statuspage_test.go @@ -40,4 +40,69 @@ func TestAccStatusPageResource(t *testing.T) { }) } -// TODO: Cover more fields +func TestAccStatusPageExtendedResource(t *testing.T) { + names := [2]string{ + petname.Generate(3, "-"), + petname.Generate(3, "-"), + } + resource.Test(t, resource.TestCase{ + PreCheck: func() { _ = testAccAPIClient(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage/extended"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(names[0]), + "allow_subscriptions_email": config.BoolVariable(true), + "allow_subscriptions_rss": config.BoolVariable(true), + "allow_subscriptions_slack": config.BoolVariable(true), + "allow_subscriptions_sms": config.BoolVariable(true), + "allow_subscriptions_webhook": config.BoolVariable(true), + "hide_empty_tabs_history": config.BoolVariable(true), + "theme": config.StringVariable("LEGACY"), + "custom_header_bg_color_hex": config.StringVariable("#000000"), + "custom_header_text_color_hex": config.StringVariable("#FFFFFF"), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", names[0]), + resource.TestCheckResourceAttr("uptime_statuspage.test", "allow_subscriptions_email", "true"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "allow_subscriptions_rss", "true"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "allow_subscriptions_slack", "true"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "allow_subscriptions_sms", "true"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "allow_subscriptions_webhook", "true"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "hide_empty_tabs_history", "true"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "theme", "LEGACY"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "custom_header_bg_color_hex", "#000000"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "custom_header_text_color_hex", "#FFFFFF"), + ), + }, + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage/extended"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(names[1]), + "allow_subscriptions_email": config.BoolVariable(false), + "allow_subscriptions_rss": config.BoolVariable(false), + "allow_subscriptions_slack": config.BoolVariable(false), + "allow_subscriptions_sms": config.BoolVariable(false), + "allow_subscriptions_webhook": config.BoolVariable(false), + "hide_empty_tabs_history": config.BoolVariable(false), + "theme": config.StringVariable("INSPIRE"), + "custom_header_bg_color_hex": config.StringVariable("#FFFFFF"), + "custom_header_text_color_hex": config.StringVariable("#000000"), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", names[1]), + resource.TestCheckResourceAttr("uptime_statuspage.test", "allow_subscriptions_email", "false"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "allow_subscriptions_rss", "false"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "allow_subscriptions_slack", "false"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "allow_subscriptions_sms", "false"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "allow_subscriptions_webhook", "false"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "hide_empty_tabs_history", "false"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "theme", "INSPIRE"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "custom_header_bg_color_hex", "#FFFFFF"), + resource.TestCheckResourceAttr("uptime_statuspage.test", "custom_header_text_color_hex", "#000000"), + ), + }, + }, + }) +} diff --git a/internal/provider/resource_statuspage_user.go b/internal/provider/resource_statuspage_user.go new file mode 100644 index 0000000..7a1ac91 --- /dev/null +++ b/internal/provider/resource_statuspage_user.go @@ -0,0 +1,159 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/uptime-com/uptime-client-go/v2/pkg/upapi" +) + +func NewStatusPageUserResource(_ context.Context, p *providerImpl) resource.Resource { + return APIResource[StatusPageUserResourceModel, StatusPageUserWrapper, StatusPageUserWrapper]{ + api: &StatusPageUserResourceAPI{provider: p}, + mod: StatusPageUserResourceModelAdapter{}, + meta: APIResourceMetadata{ + TypeNameSuffix: "statuspage_user", + Schema: schema.Schema{ + Description: "Status page user resource", + Attributes: map[string]schema.Attribute{ + "statuspage_id": schema.Int64Attribute{ + Required: true, + }, + "id": IDSchemaAttribute(), + "email": schema.StringAttribute{ + Required: true, + }, + "first_name": schema.StringAttribute{ + Required: true, + }, + "last_name": schema.StringAttribute{ + Required: true, + }, + "is_active": schema.BoolAttribute{ + Required: true, + }, + }, + }, + }, + } +} + +type StatusPageUserWrapper struct { + upapi.StatusPageUser + + StatusPageID int64 +} + +func (w StatusPageUserWrapper) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(w.PK) +} + +type StatusPageUserResourceModel struct { + StatusPageID types.Int64 `tfsdk:"statuspage_id"` + ID types.Int64 `tfsdk:"id"` + Email types.String `tfsdk:"email"` + FirstName types.String `tfsdk:"first_name"` + LastName types.String `tfsdk:"last_name"` + IsActive types.Bool `tfsdk:"is_active"` +} + +func (m StatusPageUserResourceModel) PrimaryKey() upapi.PrimaryKey { + return upapi.PrimaryKey(m.ID.ValueInt64()) +} + +type StatusPageUserResourceModelAdapter struct { + SetAttributeAdapter[int32] +} + +func (a StatusPageUserResourceModelAdapter) Get( + ctx context.Context, sg StateGetter, +) (*StatusPageUserResourceModel, diag.Diagnostics) { + var model StatusPageUserResourceModel + if diags := sg.Get(ctx, &model); diags.HasError() { + return nil, diags + } + + return &model, nil +} + +func (a StatusPageUserResourceModelAdapter) ToAPIArgument( + model StatusPageUserResourceModel, +) (*StatusPageUserWrapper, error) { + return &StatusPageUserWrapper{ + StatusPageID: model.StatusPageID.ValueInt64(), + StatusPageUser: upapi.StatusPageUser{ + PK: model.ID.ValueInt64(), + Email: model.Email.ValueString(), + FirstName: model.FirstName.ValueString(), + LastName: model.LastName.ValueString(), + IsActive: model.IsActive.ValueBool(), + }, + }, nil +} + +func (a StatusPageUserResourceModelAdapter) FromAPIResult( + api StatusPageUserWrapper, +) (*StatusPageUserResourceModel, error) { + return &StatusPageUserResourceModel{ + StatusPageID: types.Int64Value(api.StatusPageID), + ID: types.Int64Value(api.PK), + Email: types.StringValue(api.Email), + FirstName: types.StringValue(api.FirstName), + LastName: types.StringValue(api.LastName), + IsActive: types.BoolValue(api.IsActive), + }, nil +} + +type StatusPageUserResourceAPI struct { + provider *providerImpl +} + +func (a StatusPageUserResourceAPI) Create(ctx context.Context, arg StatusPageUserWrapper) (*StatusPageUserWrapper, error) { + obj, err := a.provider.api.StatusPages().Users(upapi.PrimaryKey(arg.StatusPageID)).Create(ctx, arg.StatusPageUser) + if err != nil { + return nil, err + } + + return &StatusPageUserWrapper{StatusPageUser: *obj, StatusPageID: arg.StatusPageID}, nil +} + +func (a StatusPageUserResourceAPI) Read(ctx context.Context, arg upapi.PrimaryKeyable) (*StatusPageUserWrapper, error) { + model, ok := arg.(StatusPageUserResourceModel) + if !ok { + return nil, fmt.Errorf("resource read failed:unexpected type %T", arg) + } + + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + obj, err := a.provider.api.StatusPages().Users(statusPageID).Get(ctx, arg) + if err != nil { + return nil, err + } + + return &StatusPageUserWrapper{StatusPageUser: *obj, StatusPageID: int64(statusPageID)}, nil +} + +func (a StatusPageUserResourceAPI) Update( + ctx context.Context, pk upapi.PrimaryKeyable, arg StatusPageUserWrapper, +) (*StatusPageUserWrapper, error) { + obj, err := a.provider.api.StatusPages(). + Users(upapi.PrimaryKey(arg.StatusPageID)). + Update(ctx, pk, arg.StatusPageUser) + if err != nil { + return nil, err + } + return &StatusPageUserWrapper{StatusPageUser: *obj, StatusPageID: arg.StatusPageID}, nil +} + +func (a StatusPageUserResourceAPI) Delete(ctx context.Context, arg upapi.PrimaryKeyable) error { + model, ok := arg.(StatusPageUserResourceModel) + if !ok { + return fmt.Errorf("resource delete failed: unexpected type %T", arg) + } + statusPageID := upapi.PrimaryKey(model.StatusPageID.ValueInt64()) + return a.provider.api.StatusPages().Users(statusPageID).Delete(ctx, arg) +} diff --git a/internal/provider/resource_statuspage_user_test.go b/internal/provider/resource_statuspage_user_test.go new file mode 100644 index 0000000..4a95076 --- /dev/null +++ b/internal/provider/resource_statuspage_user_test.go @@ -0,0 +1,67 @@ +package provider + +import ( + "testing" + + petname "github.com/dustinkirkland/golang-petname" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccStatusPageUserResource(t *testing.T) { + t.Skip("Skipping test as API is broken: GET/DELETE methods") + name := petname.Generate(3, "-") + emails := [2]string{ + petname.Generate(3, "-") + "@test.com", + petname.Generate(3, "-") + "@test.com", + } + firstNames := [2]string{ + petname.Generate(3, "-"), + petname.Generate(3, "-"), + } + lastNames := [2]string{ + petname.Generate(3, "-"), + petname.Generate(3, "-"), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { _ = testAccAPIClient(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_user/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "email": config.StringVariable(emails[0]), + "first_name": config.StringVariable(firstNames[0]), + "last_name": config.StringVariable(lastNames[0]), + "is_active": config.BoolVariable(true), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", name), + resource.TestCheckResourceAttr("uptime_statuspage_user.test", "email", emails[0]), + resource.TestCheckResourceAttr("uptime_statuspage_user.test", "first_name", firstNames[0]), + resource.TestCheckResourceAttr("uptime_statuspage_user.test", "last_name", lastNames[0]), + resource.TestCheckResourceAttr("uptime_statuspage_user.test", "is_active", "true"), + ), + }, + { + ConfigDirectory: config.StaticDirectory("testdata/resource_statuspage_user/_basic"), + ConfigVariables: config.Variables{ + "name": config.StringVariable(name), + "email": config.StringVariable(emails[1]), + "first_name": config.StringVariable(firstNames[1]), + "last_name": config.StringVariable(lastNames[1]), + "is_active": config.BoolVariable(false), + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uptime_statuspage.test", "name", name), + resource.TestCheckResourceAttr("uptime_statuspage_user.test", "email", emails[1]), + resource.TestCheckResourceAttr("uptime_statuspage_user.test", "first_name", firstNames[1]), + resource.TestCheckResourceAttr("uptime_statuspage_user.test", "last_name", lastNames[1]), + resource.TestCheckResourceAttr("uptime_statuspage_user.test", "is_active", "false"), + ), + }, + }, + }) +} diff --git a/internal/provider/testdata/resource_credential/_basic/main.tf b/internal/provider/testdata/resource_credential/_basic/main.tf new file mode 100644 index 0000000..b2303cd --- /dev/null +++ b/internal/provider/testdata/resource_credential/_basic/main.tf @@ -0,0 +1,21 @@ +variable display_name { + type = string +} + +variable credential_type { + type = string +} + +variable password { + type = string + default = "" + sensitive = true +} + +resource uptime_credential test { + display_name = var.display_name + credential_type = var.credential_type + secret = { + password = var.password + } +} diff --git a/internal/provider/testdata/resource_credential/validation/main.tf b/internal/provider/testdata/resource_credential/validation/main.tf new file mode 100644 index 0000000..74c409f --- /dev/null +++ b/internal/provider/testdata/resource_credential/validation/main.tf @@ -0,0 +1,28 @@ +variable display_name { + type = string +} + +variable credential_type { + type = string +} + +variable password { + type = string + default = "" + sensitive = true +} + +variable token { + type = string + default = "" + sensitive = true +} + +resource uptime_credential test { + display_name = var.display_name + credential_type = var.credential_type + secret = { + password = var.password + token = var.token + } +} diff --git a/internal/provider/testdata/resource_integration_opsgenie/_basic/main.tf b/internal/provider/testdata/resource_integration_opsgenie/_basic/main.tf new file mode 100644 index 0000000..e8ed9f1 --- /dev/null +++ b/internal/provider/testdata/resource_integration_opsgenie/_basic/main.tf @@ -0,0 +1,17 @@ +variable name { + type = string +} + +variable api_endpoint { + type = string +} + +variable api_key { + type = string +} + +resource uptime_integration_opsgenie test { + name = var.name + api_endpoint = var.api_endpoint + api_key = var.api_key +} diff --git a/internal/provider/testdata/resource_statuspage/extended/main.tf b/internal/provider/testdata/resource_statuspage/extended/main.tf new file mode 100644 index 0000000..48d99b2 --- /dev/null +++ b/internal/provider/testdata/resource_statuspage/extended/main.tf @@ -0,0 +1,52 @@ +variable name { + type = string +} + +variable allow_subscriptions_email { + type = bool +} + +variable allow_subscriptions_rss { + type = bool +} + +variable allow_subscriptions_slack { + type = bool +} + +variable allow_subscriptions_sms { + type = bool +} + +variable allow_subscriptions_webhook { + type = bool +} + +variable hide_empty_tabs_history { + type = bool +} + +variable theme { + type = string +} + +variable custom_header_bg_color_hex { + type = string +} + +variable custom_header_text_color_hex { + type = string +} + +resource "uptime_statuspage" "test" { + name = var.name + allow_subscriptions_email = var.allow_subscriptions_email + allow_subscriptions_rss = var.allow_subscriptions_rss + allow_subscriptions_slack = var.allow_subscriptions_slack + allow_subscriptions_sms = var.allow_subscriptions_sms + allow_subscriptions_webhook = var.allow_subscriptions_webhook + hide_empty_tabs_history = var.hide_empty_tabs_history + theme = var.theme + custom_header_bg_color_hex = var.custom_header_bg_color_hex + custom_header_text_color_hex = var.custom_header_text_color_hex +} diff --git a/internal/provider/testdata/resource_statuspage_component/_basic/main.tf b/internal/provider/testdata/resource_statuspage_component/_basic/main.tf new file mode 100644 index 0000000..0843d53 --- /dev/null +++ b/internal/provider/testdata/resource_statuspage_component/_basic/main.tf @@ -0,0 +1,31 @@ +variable "name" { + type = string +} + +variable "component_name" { + type = string +} + +resource "uptime_check_api" "test" { + name = "test" + script = <