From 5eda846b507c5d252d290d2ec05c387651384f56 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Wed, 20 Nov 2024 08:11:04 +0900 Subject: [PATCH 01/22] feat: switch from Terraform Plugin SDK v2 to Terraform Plugin Framework --- checkly/data_source_static_ips.go | 109 -- checkly/resource_alert_channel.go | 572 --------- checkly/resource_check.go | 1115 ----------------- checkly/resource_check_group.go | 621 --------- checkly/resource_dashboard.go | 275 ---- checkly/resource_environment_variable.go | 118 -- checkly/resource_heartbeat.go | 428 ------- checkly/resource_maintenance_window.go | 183 --- checkly/resource_private_locations.go | 123 -- checkly/resource_snippet.go | 129 -- checkly/resource_trigger_check.go | 124 -- checkly/resource_trigger_group.go | 123 -- go.mod | 66 +- go.sum | 216 ++-- internal/provider/alert_channel_resource.go | 731 +++++++++++ ...ck_alert_channel_subscription_attribute.go | 52 + .../check_alert_settings_attribute.go | 269 ++++ .../check_api_check_defaults_attribute.go | 66 + .../provider/check_assertion_attribute.go | 91 ++ .../provider/check_basic_auth_attribute.go | 48 + .../check_environment_variable_attribute.go | 96 ++ internal/provider/check_group_resource.go | 500 ++++++++ internal/provider/check_headers_attribute.go | 35 + .../provider/check_locations_attribute.go | 33 + .../check_private_locations_attribute.go | 33 + .../check_query_parameters_attribute.go | 35 + internal/provider/check_request_attribute.go | 145 +++ internal/provider/check_resource.go | 591 +++++++++ .../check_retry_strategy_attribute.go | 93 ++ internal/provider/dashboard_resource.go | 391 ++++++ .../provider/environment_variable_resource.go | 278 ++++ internal/provider/heartbeat_resource.go | 389 ++++++ internal/provider/last_updated_attribute.go | 17 + .../provider/maintenance_windows_resource.go | 319 +++++ .../provider/private_location_resource.go | 285 +++++ internal/provider/provider.go | 261 ++++ internal/provider/resource_model.go | 102 ++ internal/provider/sdk.go | 86 ++ internal/provider/snippet_resource.go | 279 +++++ internal/provider/static_ips_data_source.go | 155 +++ internal/provider/trigger_check_resource.go | 262 ++++ internal/provider/trigger_group_resource.go | 260 ++++ internal/provider/util.go | 32 + main.go | 28 +- 44 files changed, 6070 insertions(+), 4094 deletions(-) delete mode 100644 checkly/data_source_static_ips.go delete mode 100644 checkly/resource_alert_channel.go delete mode 100644 checkly/resource_check.go delete mode 100644 checkly/resource_check_group.go delete mode 100644 checkly/resource_dashboard.go delete mode 100644 checkly/resource_environment_variable.go delete mode 100644 checkly/resource_heartbeat.go delete mode 100644 checkly/resource_maintenance_window.go delete mode 100644 checkly/resource_private_locations.go delete mode 100644 checkly/resource_snippet.go delete mode 100644 checkly/resource_trigger_check.go delete mode 100644 checkly/resource_trigger_group.go create mode 100644 internal/provider/alert_channel_resource.go create mode 100644 internal/provider/check_alert_channel_subscription_attribute.go create mode 100644 internal/provider/check_alert_settings_attribute.go create mode 100644 internal/provider/check_api_check_defaults_attribute.go create mode 100644 internal/provider/check_assertion_attribute.go create mode 100644 internal/provider/check_basic_auth_attribute.go create mode 100644 internal/provider/check_environment_variable_attribute.go create mode 100644 internal/provider/check_group_resource.go create mode 100644 internal/provider/check_headers_attribute.go create mode 100644 internal/provider/check_locations_attribute.go create mode 100644 internal/provider/check_private_locations_attribute.go create mode 100644 internal/provider/check_query_parameters_attribute.go create mode 100644 internal/provider/check_request_attribute.go create mode 100644 internal/provider/check_resource.go create mode 100644 internal/provider/check_retry_strategy_attribute.go create mode 100644 internal/provider/dashboard_resource.go create mode 100644 internal/provider/environment_variable_resource.go create mode 100644 internal/provider/heartbeat_resource.go create mode 100644 internal/provider/last_updated_attribute.go create mode 100644 internal/provider/maintenance_windows_resource.go create mode 100644 internal/provider/private_location_resource.go create mode 100644 internal/provider/provider.go create mode 100644 internal/provider/resource_model.go create mode 100644 internal/provider/sdk.go create mode 100644 internal/provider/snippet_resource.go create mode 100644 internal/provider/static_ips_data_source.go create mode 100644 internal/provider/trigger_check_resource.go create mode 100644 internal/provider/trigger_group_resource.go create mode 100644 internal/provider/util.go diff --git a/checkly/data_source_static_ips.go b/checkly/data_source_static_ips.go deleted file mode 100644 index c5cbaa5..0000000 --- a/checkly/data_source_static_ips.go +++ /dev/null @@ -1,109 +0,0 @@ -package checkly - -import ( - "context" - "fmt" - "slices" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - checkly "github.com/checkly/checkly-go-sdk" -) - -func dataSourceStaticIPs() *schema.Resource { - return &schema.Resource{ - Read: dataSourceStaticIPsRead, - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Computed: true, - Description: "ID of the static IPs data source.", - }, - "addresses": { - Type: schema.TypeSet, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Computed: true, - Description: "Static IP addresses for Checkly's runner infrastructure.", - }, - "locations": { - Type: schema.TypeSet, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Optional: true, - Description: "Specify the locations you want to get static IPs for.", - }, - "ip_family": { - Type: schema.TypeString, - Optional: true, - Description: "Specify the IP families you want to get static IPs for. Only `IPv6` or `IPv4` are valid options.", - ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { - value := val.(string) - if !slices.Contains([]string{"IPv6", "IPv4"}, value) { - errs = append(errs, fmt.Errorf("%q must be either \"IPv6\" or \"IPv4\"", key)) - } - return - }, - }, - }, - } -} - -func dataSourceStaticIPsRead(d *schema.ResourceData, client interface{}) error { - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - staticIPs, err := client.(checkly.Client).GetStaticIPs(ctx) - defer cancel() - if err != nil { - return fmt.Errorf("dateSourceStaticIPsRead: API error: %w", err) - } - return dataSourceFromStaticIPs(staticIPs, d) -} - -func dataSourceFromStaticIPs(s []checkly.StaticIP, d *schema.ResourceData) error { - var staticIPs []checkly.StaticIP - var addresses []string - - locations := stringsFromSet(d.Get("locations").(*schema.Set)) - ip_family := d.Get("ip_family").(string) - - // only locations is set for filtering - if len(locations) > 0 && ip_family == "" { - for _, ip := range s { - if slices.Contains(locations, ip.Region) { - staticIPs = append(staticIPs, ip) - } - } - // only ip_family is set for filtering - } else if ip_family != "" && len(locations) == 0 { - for _, ip := range s { - if ip_family == "IPv4" && ip.Address.Addr().Is4() { - staticIPs = append(staticIPs, ip) - } else if ip_family == "IPv6" && ip.Address.Addr().Is6() { - staticIPs = append(staticIPs, ip) - } - } - // both region and ip_family are set for filtering - } else if len(locations) > 0 && ip_family != "" { - for _, ip := range s { - if ip_family == "IPv4" && ip.Address.Addr().Is4() && slices.Contains(locations, ip.Region) { - staticIPs = append(staticIPs, ip) - } else if ip_family == "IPv6" && ip.Address.Addr().Is6() && slices.Contains(locations, ip.Region) { - staticIPs = append(staticIPs, ip) - } - } - // no region nor ip_family filters set - } else { - staticIPs = s - } - - for _, ip := range staticIPs { - addresses = append(addresses, ip.Address.String()) - } - - d.Set("addresses", addresses) - d.SetId("checkly_static_ips_data_source_id") - - return nil -} diff --git a/checkly/resource_alert_channel.go b/checkly/resource_alert_channel.go deleted file mode 100644 index ebcfdec..0000000 --- a/checkly/resource_alert_channel.go +++ /dev/null @@ -1,572 +0,0 @@ -package checkly - -import ( - "context" - "encoding/json" - "fmt" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - "github.com/checkly/checkly-go-sdk" -) - -const ( - AcFieldEmail = "email" - AcFieldEmailAddress = "address" - AcFieldSlack = "slack" - AcFieldSlackURL = "url" - AcFieldSlackChannel = "channel" - AcFieldSMS = "sms" - AcFieldSMSName = "name" - AcFieldSMSNumber = "number" - AcFieldWebhook = "webhook" - AcFieldWebhookName = "name" - AcFieldWebhookMethod = "method" - AcFieldWebhookHeaders = "headers" - AcFieldWebhookQueryParams = "query_parameters" - AcFieldWebhookTemplate = "template" - AcFieldWebhookURL = "url" - AcFieldWebhookSecret = "webhook_secret" - AcFieldWebhookType = "webhook_type" - AcFieldOpsgenie = "opsgenie" - AcFieldOpsgenieName = "name" - AcFieldOpsgenieAPIKey = "api_key" - AcFieldOpsgenieRegion = "region" - AcFieldOpsgeniePriority = "priority" - AcFieldPagerduty = "pagerduty" - AcFieldPagerdutyAccount = "account" - AcFieldPagerdutyServiceKey = "service_key" - AcFieldPagerdutyServiceName = "service_name" - AcFieldSendRecovery = "send_recovery" - AcFieldSendFailure = "send_failure" - AcFieldSendDegraded = "send_degraded" - AcFieldSSLExpiry = "ssl_expiry" - AcFieldSSLExpiryThreshold = "ssl_expiry_threshold" - AcFieldCall = "call" - AcFieldCallName = "name" - AcFieldCallNumber = "number" -) - -func resourceAlertChannel() *schema.Resource { - return &schema.Resource{ - Description: "Allows you to define alerting channels for the checks and groups in your account", - Create: resourceAlertChannelCreate, - Read: resourceAlertChannelRead, - Update: resourceAlertChannelUpdate, - Delete: resourceAlertChannelDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Schema: map[string]*schema.Schema{ - AcFieldEmail: { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - AcFieldEmailAddress: { - Type: schema.TypeString, - Required: true, - Description: "The email address of this email alert channel.", - }, - }, - }, - }, - AcFieldSlack: { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - AcFieldSlackURL: { - Type: schema.TypeString, - Required: true, - Description: "The Slack webhook URL", - }, - AcFieldSlackChannel: { - Type: schema.TypeString, - Required: true, - Description: "The name of the alert's Slack channel", - }, - }, - }, - }, - AcFieldSMS: { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - AcFieldSMSName: { - Type: schema.TypeString, - Required: true, - Description: "The name of this alert channel", - }, - AcFieldSMSNumber: { - Type: schema.TypeString, - Required: true, - Description: "The mobile number to receive the alerts", - }, - }, - }, - }, - AcFieldCall: { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - AcFieldCallName: { - Type: schema.TypeString, - Required: true, - Description: "The name of this alert channel", - }, - AcFieldCallNumber: { - Type: schema.TypeString, - Required: true, - Description: "The mobile number to receive the alerts", - }, - }, - }, - }, - AcFieldWebhook: { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - AcFieldWebhookName: { - Type: schema.TypeString, - Required: true, - }, - AcFieldWebhookMethod: { - Type: schema.TypeString, - Optional: true, - Default: "POST", - Description: "(Default `POST`)", - }, - AcFieldWebhookHeaders: { - Type: schema.TypeMap, - Optional: true, - Computed: true, - DefaultFunc: func() (interface{}, error) { - return []tfMap{}, nil - }, - }, - AcFieldWebhookQueryParams: { - Type: schema.TypeMap, - Optional: true, - Computed: true, - DefaultFunc: func() (interface{}, error) { - return []tfMap{}, nil - }, - }, - AcFieldWebhookTemplate: { - Type: schema.TypeString, - Optional: true, - }, - AcFieldWebhookURL: { - Type: schema.TypeString, - Required: true, - }, - AcFieldWebhookSecret: { - Type: schema.TypeString, - Optional: true, - }, - AcFieldWebhookType: { - Type: schema.TypeString, - Optional: true, - Description: "Type of the webhook. Possible values are 'WEBHOOK_DISCORD', 'WEBHOOK_FIREHYDRANT', 'WEBHOOK_GITLAB_ALERT', 'WEBHOOK_SPIKESH', 'WEBHOOK_SPLUNK', 'WEBHOOK_MSTEAMS' and 'WEBHOOK_TELEGRAM'.", - ValidateFunc: func(value interface{}, key string) (warns []string, errs []error) { - v := value.(string) - isValid := false - options := []string{"WEBHOOK_DISCORD", "WEBHOOK_FIREHYDRANT", "WEBHOOK_GITLAB_ALERT", "WEBHOOK_SPIKESH", "WEBHOOK_SPLUNK", "WEBHOOK_MSTEAMS", "WEBHOOK_TELEGRAM"} - for _, option := range options { - if v == option { - isValid = true - } - } - if !isValid { - errs = append(errs, fmt.Errorf("%q must be one of %v, got %s", key, options, v)) - } - return warns, errs - }, - }, - }, - }, - }, - AcFieldOpsgenie: { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - AcFieldOpsgenieName: { - Type: schema.TypeString, - Required: true, - }, - AcFieldOpsgenieAPIKey: { - Type: schema.TypeString, - Required: true, - }, - AcFieldOpsgenieRegion: { - Type: schema.TypeString, - Required: true, - }, - AcFieldOpsgeniePriority: { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - AcFieldPagerduty: { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - AcFieldPagerdutyServiceKey: { - Type: schema.TypeString, - Required: true, - }, - AcFieldPagerdutyServiceName: { - Type: schema.TypeString, - Optional: true, - }, - AcFieldPagerdutyAccount: { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - AcFieldSendRecovery: { - Type: schema.TypeBool, - Optional: true, - Default: true, - Description: "(Default `true`)", - }, - AcFieldSendFailure: { - Type: schema.TypeBool, - Optional: true, - Default: true, - Description: "(Default `true`)", - }, - AcFieldSendDegraded: { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: "(Default `false`)", - }, - AcFieldSSLExpiry: { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: "(Default `false`)", - }, - AcFieldSSLExpiryThreshold: { - Type: schema.TypeInt, - Optional: true, - Default: 30, - ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { - min := 1 - max := 30 - v := val.(int) - if v < min || v > max { - errs = append(errs, fmt.Errorf("%q must be between %d and %d, got: %d", key, min, max, v)) - } - return warns, errs - }, - Description: "Value must be between 1 and 30 (Default `30`)", - }, - }, - } -} - -func resourceAlertChannelCreate(d *schema.ResourceData, client interface{}) error { - ac, err := alertChannelFromResourceData(d) - if err != nil { - return makeError("resourceAlertChannelCreate.1", &ErrorLog{"err": err.Error()}) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - resp, err := client.(checkly.Client).CreateAlertChannel(ctx, ac) - if err != nil { - cjson, _ := json.Marshal(ac.GetConfig()) - return makeError("resourceAlertChannelCreate.2", &ErrorLog{ - "err": err.Error(), - "type": ac.Type, - "config": string(cjson), - }) - } - d.SetId(fmt.Sprintf("%d", resp.ID)) - return resourceAlertChannelRead(d, client) -} - -func resourceAlertChannelRead(d *schema.ResourceData, client interface{}) error { - ID, err := resourceIDToInt(d.Id()) - if err != nil { - return makeError("resourceAlertChannelRead.1", &ErrorLog{"err": err.Error()}) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - ac, err := client.(checkly.Client).GetAlertChannel(ctx, ID) - if err != nil { - if strings.Contains(err.Error(), "404") { - //if resource is deleted remotely, then mark it as - //successfully gone by unsetting it's ID - d.SetId("") - return nil - } - return makeError("resourceAlertChannelRead.2", &ErrorLog{"err": err.Error()}) - } - return resourceDataFromAlertChannel(ac, d) -} - -func resourceAlertChannelUpdate(d *schema.ResourceData, client interface{}) error { - ac, err := alertChannelFromResourceData(d) - if err != nil { - return makeError("resourceAlertChannelUpdate.1", &ErrorLog{"err": err.Error()}) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - _, err = client.(checkly.Client).UpdateAlertChannel(ctx, ac.ID, ac) - if err != nil { - return makeError("resourceAlertChannelUpdate.2", &ErrorLog{"err": err.Error()}) - } - d.SetId(fmt.Sprintf("%d", ac.ID)) - return resourceAlertChannelRead(d, client) -} - -func resourceAlertChannelDelete(d *schema.ResourceData, client interface{}) error { - ID, err := resourceIDToInt(d.Id()) - if err != nil { - return makeError("resourceAlertChannelDelete.1", &ErrorLog{"err": err.Error()}) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - if err := client.(checkly.Client).DeleteAlertChannel(ctx, ID); err != nil { - return makeError("resourceAlertChannelDelete.2", &ErrorLog{"err": err.Error()}) - } - return nil -} - -func resourceDataFromAlertChannel(it *checkly.AlertChannel, d *schema.ResourceData) error { - d.Set(AcFieldEmail, setFromEmail(it.Email)) - d.Set(AcFieldSMS, setFromSMS(it.SMS)) - d.Set(AcFieldCall, setFromCall(it.CALL)) - d.Set(AcFieldSlack, setFromSlack(it.Slack)) - d.Set(AcFieldWebhook, setFromWebhook(it.Webhook)) - d.Set(AcFieldOpsgenie, setFromOpsgenie(it.Opsgenie)) - d.Set(AcFieldPagerduty, setFromPagerduty(it.Pagerduty)) - if it.SendRecovery != nil { - d.Set(AcFieldSendRecovery, *it.SendRecovery) - } - if it.SendFailure != nil { - d.Set(AcFieldSendFailure, *it.SendFailure) - } - if it.SendDegraded != nil { - d.Set(AcFieldSendDegraded, *it.SendDegraded) - } - if it.SSLExpiry != nil { - d.Set(AcFieldSSLExpiry, *it.SSLExpiry) - } - if it.SSLExpiryThreshold != nil { - d.Set(AcFieldSSLExpiryThreshold, *it.SSLExpiryThreshold) - } - return nil -} - -func alertChannelFromResourceData(d *schema.ResourceData) (checkly.AlertChannel, error) { - ac := checkly.AlertChannel{} - ID, err := resourceIDToInt(d.Id()) - if err != nil { - return ac, makeError("alertChannelFromResourceData.1", &ErrorLog{"err": err.Error()}) - } - if err == nil { - ac.ID = ID - } - - sendRecovery := d.Get(AcFieldSendRecovery).(bool) - ac.SendRecovery = &sendRecovery - - sendFailure := d.Get(AcFieldSendFailure).(bool) - ac.SendFailure = &sendFailure - - sndDegraded := d.Get(AcFieldSendDegraded).(bool) - ac.SendDegraded = &sndDegraded - - sslExpiry := d.Get(AcFieldSSLExpiry).(bool) - ac.SSLExpiry = &sslExpiry - - if v, ok := d.GetOk(AcFieldSSLExpiryThreshold); ok { - i := v.(int) - ac.SSLExpiryThreshold = &i - } - - fields := []string{AcFieldEmail, AcFieldSMS, AcFieldCall, AcFieldSlack, AcFieldWebhook, AcFieldOpsgenie, AcFieldPagerduty} - setCount := 0 - for _, field := range fields { - cfgSet := (d.Get(field)).(*schema.Set) - if cfgSet.Len() > 0 { - ac.Type = strings.ToUpper(field) - c, err := alertChannelConfigFromSet(ac.Type, cfgSet) - if err != nil { - return ac, makeError("alertChannelFromResourceData.2", &ErrorLog{"err": err.Error()}) - } - ac.SetConfig(c) - setCount++ - } - } - if setCount > 1 { - return ac, makeError("Alert-Channel config can't contain more than one Channel", nil) - } - return ac, nil -} - -func alertChannelConfigFromSet(channelType string, s *schema.Set) (interface{}, error) { - if s.Len() == 0 { - return nil, nil - } - cfg := s.List()[0].(map[string]interface{}) - switch channelType { - case checkly.AlertTypeEmail: - return &checkly.AlertChannelEmail{ - Address: cfg[AcFieldEmailAddress].(string), - }, nil - case checkly.AlertTypeSMS: - return &checkly.AlertChannelSMS{ - Name: cfg[AcFieldSMSName].(string), - Number: cfg[AcFieldSMSNumber].(string), - }, nil - case checkly.AlertTypeCall: - return &checkly.AlertChannelCall{ - Name: cfg[AcFieldCallName].(string), - Number: cfg[AcFieldCallNumber].(string), - }, nil - case checkly.AlertTypeSlack: - return &checkly.AlertChannelSlack{ - Channel: cfg[AcFieldSlackChannel].(string), - WebhookURL: cfg[AcFieldSlackURL].(string), - }, nil - case checkly.AlertTypeOpsgenie: - return &checkly.AlertChannelOpsgenie{ - Name: cfg[AcFieldOpsgenieName].(string), - APIKey: cfg[AcFieldOpsgenieAPIKey].(string), - Region: cfg[AcFieldOpsgenieRegion].(string), - Priority: cfg[AcFieldOpsgeniePriority].(string), - }, nil - case checkly.AlertTypePagerduty: - return &checkly.AlertChannelPagerduty{ - Account: cfg[AcFieldPagerdutyAccount].(string), - ServiceKey: cfg[AcFieldPagerdutyServiceKey].(string), - ServiceName: cfg[AcFieldPagerdutyServiceName].(string), - }, nil - case checkly.AlertTypeWebhook: - return &checkly.AlertChannelWebhook{ - Name: cfg[AcFieldWebhookName].(string), - Method: cfg[AcFieldWebhookMethod].(string), - Template: cfg[AcFieldWebhookTemplate].(string), - URL: cfg[AcFieldWebhookURL].(string), - WebhookSecret: cfg[AcFieldWebhookSecret].(string), - WebhookType: cfg[AcFieldWebhookType].(string), - Headers: keyValuesFromMap(cfg[AcFieldWebhookHeaders].(tfMap)), - QueryParameters: keyValuesFromMap(cfg[AcFieldWebhookQueryParams].(tfMap)), - }, nil - } - return nil, makeError("alertChannelConfigFromSet:unkownType", nil) -} - -func setFromEmail(cfg *checkly.AlertChannelEmail) []tfMap { - if cfg == nil { - return []tfMap{} - } - return []tfMap{ - { - AcFieldEmailAddress: cfg.Address, - }, - } -} - -func setFromSMS(cfg *checkly.AlertChannelSMS) []tfMap { - if cfg == nil { - return []tfMap{} - } - return []tfMap{ - { - AcFieldSMSName: cfg.Name, - AcFieldSMSNumber: cfg.Number, - }, - } -} - -func setFromCall(cfg *checkly.AlertChannelCall) []tfMap { - if cfg == nil { - return []tfMap{} - } - return []tfMap{ - { - AcFieldCallName: cfg.Name, - AcFieldCallNumber: cfg.Number, - }, - } -} - -func setFromSlack(cfg *checkly.AlertChannelSlack) []tfMap { - if cfg == nil { - return []tfMap{} - } - return []tfMap{ - { - AcFieldSlackChannel: cfg.Channel, - AcFieldSlackURL: cfg.WebhookURL, - }, - } -} - -func setFromWebhook(cfg *checkly.AlertChannelWebhook) []tfMap { - if cfg == nil { - return []tfMap{} - } - return []tfMap{ - { - AcFieldWebhookName: cfg.Name, - AcFieldWebhookMethod: cfg.Method, - AcFieldWebhookHeaders: mapFromKeyValues(cfg.Headers), - AcFieldWebhookQueryParams: mapFromKeyValues(cfg.QueryParameters), - AcFieldWebhookTemplate: cfg.Template, - AcFieldWebhookURL: cfg.URL, - AcFieldWebhookSecret: cfg.WebhookSecret, - AcFieldWebhookType: cfg.WebhookType, - }, - } -} - -func setFromOpsgenie(cfg *checkly.AlertChannelOpsgenie) []tfMap { - if cfg == nil { - return []tfMap{} - } - return []tfMap{ - { - AcFieldOpsgenieName: cfg.Name, - AcFieldOpsgenieAPIKey: cfg.APIKey, - AcFieldOpsgenieRegion: cfg.Region, - AcFieldOpsgeniePriority: cfg.Priority, - }, - } -} - -func setFromPagerduty(cfg *checkly.AlertChannelPagerduty) []tfMap { - if cfg == nil { - return []tfMap{} - } - return []tfMap{ - { - AcFieldPagerdutyAccount: cfg.Account, - AcFieldPagerdutyServiceKey: cfg.ServiceKey, - AcFieldPagerdutyServiceName: cfg.ServiceName, - }, - } -} diff --git a/checkly/resource_check.go b/checkly/resource_check.go deleted file mode 100644 index 7d390b8..0000000 --- a/checkly/resource_check.go +++ /dev/null @@ -1,1115 +0,0 @@ -package checkly - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "sort" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - "github.com/checkly/checkly-go-sdk" -) - -// tfMap is a shorthand alias for convenience; Terraform uses this type a *lot*. -type tfMap = map[string]interface{} - -func resourceCheck() *schema.Resource { - return &schema.Resource{ - Create: resourceCheckCreate, - Read: resourceCheckRead, - Update: resourceCheckUpdate, - Delete: resourceCheckDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Description: "Checks allows you to monitor key webapp flows, backend API's and set up alerting, so you get a notification when things break or slow down.", - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - Description: "The name of the check.", - }, - "type": { - Type: schema.TypeString, - Required: true, - Description: "The type of the check. Possible values are `API`, `BROWSER`, and `MULTI_STEP`.", - }, - "frequency": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { - v := val.(int) - valid := false - validFreqs := []int{0, 1, 2, 5, 10, 15, 30, 60, 120, 180, 360, 720, 1440} - for _, i := range validFreqs { - if v == i { - valid = true - } - } - if !valid { - errs = append(errs, fmt.Errorf("%q must be one of %v, got %d", key, validFreqs, v)) - } - return warns, errs - }, - Description: "The frequency in minutes to run the check. Possible values are `0`, `1`, `2`, `5`, `10`, `15`, `30`, `60`, `120`, `180`, `360`, `720`, and `1440`.", - }, - "frequency_offset": { - Type: schema.TypeInt, - Optional: true, - Description: "This property only valid for API high frequency checks. To create a hight frequency check, the property `frequency` must be `0` and `frequency_offset` could be `10`, `20` or `30`.", - }, - "activated": { - Type: schema.TypeBool, - Required: true, - Description: "Determines if the check is running or not. Possible values `true`, and `false`.", - }, - "muted": { - Type: schema.TypeBool, - Optional: true, - Description: "Determines if any notifications will be sent out when a check fails/degrades/recovers.", - }, - "should_fail": { - Type: schema.TypeBool, - Optional: true, - Description: "Allows to invert the behaviour of when a check is considered to fail. Allows for validating error status like 404.", - }, - "run_parallel": { - Type: schema.TypeBool, - Optional: true, - Description: "Determines if the check should run in all selected locations in parallel or round-robin.", - }, - "locations": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Description: "An array of one or more data center locations where to run the this check. (Default [\"us-east-1\"])", - }, - "script": { - Type: schema.TypeString, - Optional: true, - Default: "", - Description: "A valid piece of Node.js JavaScript code describing a browser interaction with the Puppeteer/Playwright framework or a reference to an external JavaScript file.", - }, - "degraded_response_time": { - Type: schema.TypeInt, - Optional: true, - Default: 15000, - ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { - // https://checklyhq.com/docs/api-checks/limits/ - v := val.(int) - if v < 0 || v > 30000 { - errs = append(errs, fmt.Errorf("%q must be 0-30000 ms, got %d", key, v)) - } - return warns, errs - }, - Description: "The response time in milliseconds starting from which a check should be considered degraded. Possible values are between 0 and 30000. (Default `15000`).", - }, - "max_response_time": { - Type: schema.TypeInt, - Optional: true, - Default: 30000, - ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { - v := val.(int) - // https://checklyhq.com/docs/api-checks/limits/ - if v < 0 || v > 30000 { - errs = append(errs, fmt.Errorf("%q must be 0-30000 ms, got: %d", key, v)) - } - return warns, errs - }, - Description: "The response time in milliseconds starting from which a check should be considered failing. Possible values are between 0 and 30000. (Default `30000`).", - }, - "environment_variables": { - Type: schema.TypeMap, - Optional: true, - Deprecated: "The property `environment_variables` is deprecated and will be removed in a future version. Consider using the new `environment_variable` list.", - Description: "Key/value pairs for setting environment variables during check execution. These are only relevant for browser checks. Use global environment variables whenever possible.", - }, - "environment_variable": { - Type: schema.TypeList, - Optional: true, - Description: "Key/value pairs for setting environment variables during check execution, add locked = true to keep value hidden, add secret = true to create a secret variable. These are only relevant for browser checks. Use global environment variables whenever possible.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - }, - "value": { - Type: schema.TypeString, - Required: true, - }, - "locked": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "secret": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - }, - }, - }, - "double_check": { - Type: schema.TypeBool, - Optional: true, - Description: "Setting this to `true` will trigger a retry when a check fails from the failing region and another, randomly selected region before marking the check as failed.", - Deprecated: "The property `double_check` is deprecated and will be removed in a future version. To enable retries for failed check runs, use the `retry_strategy` property instead.", - }, - "tags": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Description: "A list of tags for organizing and filtering checks.", - }, - "ssl_check": { - Type: schema.TypeBool, - Optional: true, - Deprecated: "The property `ssl_check` is deprecated and it's ignored by the Checkly Public API. It will be removed in a future version.", - Description: "Determines if the SSL certificate should be validated for expiry.", - }, - "ssl_check_domain": { - Type: schema.TypeString, - Optional: true, - Description: "A valid fully qualified domain name (FQDN) to check its SSL certificate.", - }, - "setup_snippet_id": { - Type: schema.TypeInt, - Optional: true, - Description: "An ID reference to a snippet to use in the setup phase of an API check.", - }, - "teardown_snippet_id": { - Type: schema.TypeInt, - Optional: true, - Description: "An ID reference to a snippet to use in the teardown phase of an API check.", - }, - "local_setup_script": { - Type: schema.TypeString, - Optional: true, - Description: "A valid piece of Node.js code to run in the setup phase.", - }, - "local_teardown_script": { - Type: schema.TypeString, - Optional: true, - Description: "A valid piece of Node.js code to run in the teardown phase.", - }, - "runtime_id": { - Type: schema.TypeString, - Optional: true, - Default: nil, - Description: "The id of the runtime to use for this check.", - }, - "alert_channel_subscription": { - Type: schema.TypeList, - Optional: true, - Description: "An array of channel IDs and whether they're activated or not. If you don't set at least one alert subscription for your check, we won't be able to alert you in case something goes wrong with it.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "channel_id": { - Type: schema.TypeInt, - Required: true, - }, - "activated": { - Type: schema.TypeBool, - Required: true, - }, - }, - }, - }, - "private_locations": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - DefaultFunc: func() (interface{}, error) { - return []tfMap{}, nil - }, - Description: "An array of one or more private locations slugs.", - }, - "alert_settings": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "escalation_type": { - Type: schema.TypeString, - Optional: true, - Description: "Determines what type of escalation to use. Possible values are `RUN_BASED` or `TIME_BASED`.", - }, - "run_based_escalation": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "failed_run_threshold": { - Type: schema.TypeInt, - Optional: true, - Description: "After how many failed consecutive check runs an alert notification should be sent. Possible values are between 1 and 5. (Default `1`).", - }, - }, - }, - }, - "time_based_escalation": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "minutes_failing_threshold": { - Type: schema.TypeInt, - Optional: true, - Description: "After how many minutes after a check starts failing an alert should be sent. Possible values are `5`, `10`, `15`, and `30`. (Default `5`).", - }, - }, - }, - }, - "reminders": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "amount": { - Type: schema.TypeInt, - Optional: true, - Description: "How many reminders to send out after the initial alert notification. Possible values are `0`, `1`, `2`, `3`, `4`, `5`, and `100000`", - }, - "interval": { - Type: schema.TypeInt, - Optional: true, - Default: 5, - Description: "Possible values are `5`, `10`, `15`, and `30`. (Default `5`).", - }, - }, - }, - }, - "parallel_run_failure_threshold": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: "Applicable only for checks scheduled in parallel in multiple locations.", - }, - "percentage": { - Type: schema.TypeInt, - Optional: true, - Default: 10, - Description: "Possible values are `10`, `20`, `30`, `40`, `50`, `60`, `70`, `80`, `100`, and `100`. (Default `10`).", - }, - }, - }, - }, - "ssl_certificates": { - Type: schema.TypeSet, - Optional: true, - Deprecated: "This property is deprecated and it's ignored by the Checkly Public API. It will be removed in a future version.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "enabled": { - Type: schema.TypeBool, - Optional: true, - Description: "Determines if alert notifications should be sent for expiring SSL certificates. Possible values `true`, and `false`. (Default `false`).", - }, - "alert_threshold": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { - v := val.(int) - valid := false - validFreqs := []int{3, 7, 14, 30} - for _, i := range validFreqs { - if v == i { - valid = true - } - } - if !valid { - errs = append(errs, fmt.Errorf("%q must be one of %v, got: %d", key, validFreqs, v)) - } - return warns, errs - }, - Description: "How long before SSL certificate expiry to send alerts. Possible values `3`, `7`, `14`, `30`. (Default `3`).", - }, - }, - Description: "At what interval the reminders should be sent.", - }, - }, - }, - }, - }, - "use_global_alert_settings": { - Type: schema.TypeBool, - Optional: true, - Description: "When true, the account level alert settings will be used, not the alert setting defined on this check.", - }, - "request": { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "method": { - Type: schema.TypeString, - Optional: true, - Default: "GET", - Description: "The HTTP method to use for this API check. Possible values are `GET`, `POST`, `PUT`, `HEAD`, `DELETE`, `PATCH`. (Default `GET`).", - }, - "url": { - Type: schema.TypeString, - Required: true, - }, - "follow_redirects": { - Type: schema.TypeBool, - Optional: true, - }, - "skip_ssl": { - Type: schema.TypeBool, - Optional: true, - }, - "headers": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - DefaultFunc: func() (interface{}, error) { - return []tfMap{}, nil - }, - }, - "query_parameters": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - DefaultFunc: func() (interface{}, error) { - return []tfMap{}, nil - }, - }, - "body": { - Type: schema.TypeString, - Optional: true, - Description: "The body of the request.", - }, - "body_type": { - Type: schema.TypeString, - Optional: true, - Default: "NONE", - Description: "The `Content-Type` header of the request. Possible values `NONE`, `JSON`, `FORM`, `RAW`, and `GRAPHQL`.", - ValidateFunc: func(value interface{}, key string) (warns []string, errs []error) { - v := value.(string) - isValid := false - options := []string{"NONE", "JSON", "FORM", "RAW", "GRAPHQL"} - for _, option := range options { - if v == option { - isValid = true - } - } - if !isValid { - errs = append(errs, fmt.Errorf("%q must be one of %v, got %s", key, options, v)) - } - return warns, errs - }, - }, - "assertion": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "source": { - Type: schema.TypeString, - Required: true, - Description: "The source of the asserted value. Possible values `STATUS_CODE`, `JSON_BODY`, `HEADERS`, `TEXT_BODY`, and `RESPONSE_TIME`.", - }, - "property": { - Type: schema.TypeString, - Optional: true, - }, - "comparison": { - Type: schema.TypeString, - Required: true, - Description: "The type of comparison to be executed between expected and actual value of the assertion. Possible values `EQUALS`, `NOT_EQUALS`, `HAS_KEY`, `NOT_HAS_KEY`, `HAS_VALUE`, `NOT_HAS_VALUE`, `IS_EMPTY`, `NOT_EMPTY`, `GREATER_THAN`, `LESS_THAN`, `CONTAINS`, `NOT_CONTAINS`, `IS_NULL`, and `NOT_NULL`.", - }, - "target": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - Description: "A request can have multiple assertions.", - }, - "basic_auth": { - Type: schema.TypeSet, - MaxItems: 1, - Optional: true, - Computed: true, - DefaultFunc: func() (interface{}, error) { - return []tfMap{}, nil - }, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "username": { - Type: schema.TypeString, - Required: true, - }, - "password": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - Description: "Set up HTTP basic authentication (username & password).", - }, - "ip_family": { - Type: schema.TypeString, - Optional: true, - Default: "IPv4", - Description: "IP Family to be used when executing the api check. The value can be either IPv4 or IPv6.", - ValidateFunc: func(value interface{}, key string) (warns []string, errs []error) { - v := value.(string) - isValid := false - options := []string{"IPv4", "IPv6"} - for _, option := range options { - if v == option { - isValid = true - } - } - if !isValid { - errs = append(errs, fmt.Errorf("%q must be one of %v, got %s", key, options, v)) - } - return warns, errs - }, - }, - }, - }, - Description: "An API check might have one request config.", - }, - "group_id": { - Type: schema.TypeInt, - Optional: true, - Description: "The id of the check group this check is part of.", - }, - "group_order": { - Type: schema.TypeInt, - Optional: true, - Description: "The position of this check in a check group. It determines in what order checks are run when a group is triggered from the API or from CI/CD.", - }, - "retry_strategy": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - MaxItems: 1, - DefaultFunc: func() (interface{}, error) { - return []tfMap{}, nil - }, - Description: "A strategy for retrying failed check runs.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - Description: "Determines which type of retry strategy to use. Possible values are `FIXED`, `LINEAR`, or `EXPONENTIAL`.", - }, - "base_backoff_seconds": { - Type: schema.TypeInt, - Optional: true, - Default: 60, - Description: "The number of seconds to wait before the first retry attempt.", - }, - "max_retries": { - Type: schema.TypeInt, - Optional: true, - Default: 2, - Description: "The maximum number of times to retry the check. Value must be between 1 and 10.", - }, - "max_duration_seconds": { - Type: schema.TypeInt, - Optional: true, - Default: 600, - Description: "The total amount of time to continue retrying the check (maximum 600 seconds).", - }, - "same_region": { - Type: schema.TypeBool, - Optional: true, - Default: true, - Description: "Whether retries should be run in the same region as the initial check run.", - }, - }, - }, - }, - }, - } -} - -func resourceCheckCreate(d *schema.ResourceData, client interface{}) error { - check, err := checkFromResourceData(d) - if err != nil { - return fmt.Errorf("translation error: %w", err) - } - - validationErr := validateRuntimeSupport(check, client) - if validationErr != nil { - return validationErr - } - - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - newCheck, err := client.(checkly.Client).CreateCheck(ctx, check) - - if err != nil { - checkJSON, _ := json.Marshal(check) - return fmt.Errorf("API error 1: %w, Check: %s", err, string(checkJSON)) - } - d.SetId(newCheck.ID) - return resourceCheckRead(d, client) -} - -func resourceCheckRead(d *schema.ResourceData, client interface{}) error { - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - check, err := client.(checkly.Client).GetCheck(ctx, d.Id()) - if err != nil { - if strings.Contains(err.Error(), "404") { - //if resource is deleted remotely, then mark it as - //successfully gone by unsetting it's ID - d.SetId("") - return nil - } - return fmt.Errorf("API error 2: %w", err) - } - return resourceDataFromCheck(check, d) -} - -func resourceCheckUpdate(d *schema.ResourceData, client interface{}) error { - check, err := checkFromResourceData(d) - - if err != nil { - return fmt.Errorf("translation error: %w", err) - } - - validationErr := validateRuntimeSupport(check, client) - if validationErr != nil { - return validationErr - } - - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - _, err = client.(checkly.Client).UpdateCheck(ctx, check.ID, check) - if err != nil { - checkJSON, _ := json.Marshal(check) - return fmt.Errorf("API error 3: Couldn't update check, Error: %w, \nCheck: %s", err, checkJSON) - } - d.SetId(check.ID) - return resourceCheckRead(d, client) -} - -func resourceCheckDelete(d *schema.ResourceData, client interface{}) error { - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - if err := client.(checkly.Client).DeleteCheck(ctx, d.Id()); err != nil { - return fmt.Errorf("API error 4: Couldn't delete Check %s, Error: %w", d.Id(), err) - } - return nil -} - -func resourceDataFromCheck(c *checkly.Check, d *schema.ResourceData) error { - d.Set("name", c.Name) - d.Set("type", c.Type) - d.Set("activated", c.Activated) - d.Set("muted", c.Muted) - d.Set("should_fail", c.ShouldFail) - d.Set("run_parallel", c.RunParallel) - d.Set("locations", c.Locations) - d.Set("script", c.Script) - d.Set("degraded_response_time", c.DegradedResponseTime) - d.Set("max_response_time", c.MaxResponseTime) - d.Set("double_check", c.DoubleCheck) - d.Set("setup_snippet_id", c.SetupSnippetID) - d.Set("teardown_snippet_id", c.TearDownSnippetID) - d.Set("local_setup_script", c.LocalSetupScript) - d.Set("local_teardown_script", c.LocalTearDownScript) - - sort.Strings(c.Tags) - d.Set("tags", c.Tags) - - d.Set("frequency", c.Frequency) - if c.Frequency == 0 { - d.Set("frequency_offset", c.FrequencyOffset) - } - - if c.RuntimeID != nil { - d.Set("runtime_id", *c.RuntimeID) - } - - // ssl_check_domain is only supported for Browser checks - if c.Type == "BROWSER" && c.SSLCheckDomain != "" { - d.Set("ssl_check_domain", c.SSLCheckDomain) - } - - environmentVariables := environmentVariablesFromSet(d.Get("environment_variable").([]interface{})) - if len(environmentVariables) > 0 { - d.Set("environment_variable", c.EnvironmentVariables) - } else if err := d.Set("environment_variables", setFromEnvVars(c.EnvironmentVariables)); err != nil { - return fmt.Errorf("error setting environment variables for resource %s: %s", d.Id(), err) - } - - if err := d.Set("alert_settings", setFromAlertSettings(c.AlertSettings)); err != nil { - return fmt.Errorf("error setting alert settings for resource %s: %w", d.Id(), err) - } - d.Set("use_global_alert_settings", c.UseGlobalAlertSettings) - - if c.Type == checkly.TypeAPI { - err := d.Set("request", setFromRequest(c.Request)) - if err != nil { - return fmt.Errorf("error setting request for resource %s: %w", d.Id(), err) - } - } - d.Set("group_id", c.GroupID) - d.Set("group_order", c.GroupOrder) - d.Set("private_locations", c.PrivateLocations) - d.Set("alert_channel_subscription", c.AlertChannelSubscriptions) - d.Set("retry_strategy", setFromRetryStrategy(c.RetryStrategy)) - d.SetId(d.Id()) - return nil -} - -func setFromEnvVars(evs []checkly.EnvironmentVariable) tfMap { - var s = tfMap{} - for _, ev := range evs { - s[ev.Key] = ev.Value - } - return s -} - -func setFromAlertSettings(as checkly.AlertSettings) []tfMap { - if as.EscalationType == checkly.RunBased { - return []tfMap{ - { - "escalation_type": as.EscalationType, - "run_based_escalation": []tfMap{ - { - "failed_run_threshold": as.RunBasedEscalation.FailedRunThreshold, - }, - }, - "reminders": []tfMap{ - { - "amount": as.Reminders.Amount, - "interval": as.Reminders.Interval, - }, - }, - "parallel_run_failure_threshold": []tfMap{ - { - "enabled": as.ParallelRunFailureThreshold.Enabled, - "percentage": as.ParallelRunFailureThreshold.Percentage, - }, - }, - }, - } - } else { - return []tfMap{ - { - "escalation_type": as.EscalationType, - "time_based_escalation": []tfMap{ - { - "minutes_failing_threshold": as.TimeBasedEscalation.MinutesFailingThreshold, - }, - }, - "reminders": []tfMap{ - { - "amount": as.Reminders.Amount, - "interval": as.Reminders.Interval, - }, - }, - "parallel_run_failure_threshold": []tfMap{ - { - "enabled": as.ParallelRunFailureThreshold.Enabled, - "percentage": as.ParallelRunFailureThreshold.Percentage, - }, - }, - }, - } - } -} - -func setFromRequest(r checkly.Request) []tfMap { - s := tfMap{} - s["method"] = r.Method - s["url"] = r.URL - s["follow_redirects"] = r.FollowRedirects - s["skip_ssl"] = r.SkipSSL - s["body"] = r.Body - s["body_type"] = r.BodyType - s["headers"] = mapFromKeyValues(r.Headers) - s["query_parameters"] = mapFromKeyValues(r.QueryParameters) - s["assertion"] = setFromAssertions(r.Assertions) - s["basic_auth"] = setFromBasicAuth(r.BasicAuth) - s["ip_family"] = r.IPFamily - return []tfMap{s} -} - -func setFromAssertions(assertions []checkly.Assertion) []tfMap { - s := make([]tfMap, len(assertions)) - for i, a := range assertions { - as := tfMap{} - as["source"] = a.Source - as["property"] = a.Property - as["comparison"] = a.Comparison - as["target"] = a.Target - s[i] = as - } - return s -} - -func mapFromKeyValues(kvs []checkly.KeyValue) tfMap { - var s = tfMap{} - for _, item := range kvs { - s[item.Key] = item.Value - } - return s -} - -func setFromBasicAuth(b *checkly.BasicAuth) []tfMap { - if b == nil { - return []tfMap{} - } - return []tfMap{ - { - "username": b.Username, - "password": b.Password, - }, - } -} - -func setFromRetryStrategy(rs *checkly.RetryStrategy) []tfMap { - if rs == nil { - return []tfMap{} - } - return []tfMap{ - { - "type": rs.Type, - "base_backoff_seconds": rs.BaseBackoffSeconds, - "max_retries": rs.MaxRetries, - "max_duration_seconds": rs.MaxDurationSeconds, - "same_region": rs.SameRegion, - }, - } -} - -func checkFromResourceData(d *schema.ResourceData) (checkly.Check, error) { - check := checkly.Check{ - ID: d.Id(), - Name: d.Get("name").(string), - Type: d.Get("type").(string), - Frequency: d.Get("frequency").(int), - Activated: d.Get("activated").(bool), - Muted: d.Get("muted").(bool), - ShouldFail: d.Get("should_fail").(bool), - RunParallel: d.Get("run_parallel").(bool), - Locations: stringsFromSet(d.Get("locations").(*schema.Set)), - Script: d.Get("script").(string), - DegradedResponseTime: d.Get("degraded_response_time").(int), - MaxResponseTime: d.Get("max_response_time").(int), - DoubleCheck: d.Get("double_check").(bool), - Tags: stringsFromSet(d.Get("tags").(*schema.Set)), - SSLCheck: d.Get("ssl_check").(bool), - SSLCheckDomain: d.Get("ssl_check_domain").(string), - SetupSnippetID: int64(d.Get("setup_snippet_id").(int)), - TearDownSnippetID: int64(d.Get("teardown_snippet_id").(int)), - LocalSetupScript: d.Get("local_setup_script").(string), - LocalTearDownScript: d.Get("local_teardown_script").(string), - AlertSettings: alertSettingsFromSet(d.Get("alert_settings").([]interface{})), - UseGlobalAlertSettings: d.Get("use_global_alert_settings").(bool), - GroupID: int64(d.Get("group_id").(int)), - GroupOrder: d.Get("group_order").(int), - AlertChannelSubscriptions: alertChannelSubscriptionsFromSet(d.Get("alert_channel_subscription").([]interface{})), - RetryStrategy: retryStrategyFromSet(d.Get("retry_strategy").(*schema.Set)), - } - - runtimeId := d.Get("runtime_id").(string) - if runtimeId == "" { - check.RuntimeID = nil - } else { - check.RuntimeID = &runtimeId - } - - environmentVariables, err := getResourceEnvironmentVariables(d) - if err != nil { - return checkly.Check{}, err - } - check.EnvironmentVariables = environmentVariables - - privateLocations := stringsFromSet(d.Get("private_locations").(*schema.Set)) - check.PrivateLocations = &privateLocations - - if check.Type == checkly.TypeAPI { - // this will prevent subsequent apply from causing a tf config change in browser checks - check.Request = requestFromSet(d.Get("request").(*schema.Set)) - check.FrequencyOffset = d.Get("frequency_offset").(int) - - if check.Frequency == 0 && (check.FrequencyOffset != 10 && check.FrequencyOffset != 20 && check.FrequencyOffset != 30) { - return check, errors.New("when property frequency is 0, frequency_offset must be 10, 20 or 30") - } - - if check.SSLCheckDomain != "" { - return check, errors.New("ssl_check_domain is allowed only for Browser checks") - } - } - - if check.Type == checkly.TypeBrowser && check.Frequency == 0 { - return check, errors.New("property frequency could only be 0 for API checks") - } - - return check, nil -} - -func stringsFromSet(s *schema.Set) []string { - r := make([]string, s.Len()) - for i, item := range s.List() { - r[i] = item.(string) - } - return r -} - -func assertionsFromSet(s *schema.Set) []checkly.Assertion { - r := make([]checkly.Assertion, s.Len()) - for i, item := range s.List() { - res := item.(tfMap) - r[i] = checkly.Assertion{ - Source: res["source"].(string), - Property: res["property"].(string), - Comparison: res["comparison"].(string), - Target: res["target"].(string), - } - } - return r -} - -func basicAuthFromSet(s *schema.Set) *checkly.BasicAuth { - if s.Len() == 0 { - return nil - } - res := s.List()[0].(tfMap) - return &checkly.BasicAuth{ - Username: res["username"].(string), - Password: res["password"].(string), - } -} - -func alertSettingsFromSet(s []interface{}) checkly.AlertSettings { - if len(s) == 0 { - return checkly.AlertSettings{ - EscalationType: checkly.RunBased, - RunBasedEscalation: checkly.RunBasedEscalation{ - FailedRunThreshold: 1, - }, - } - } - res := s[0].(tfMap) - alertSettings := checkly.AlertSettings{ - EscalationType: res["escalation_type"].(string), - Reminders: remindersFromSet(res["reminders"].([]interface{})), - ParallelRunFailureThreshold: parallelRunFailureThresholdFromSet(res["parallel_run_failure_threshold"].([]interface{})), - } - - if alertSettings.EscalationType == checkly.RunBased { - alertSettings.RunBasedEscalation = runBasedEscalationFromSet(res["run_based_escalation"].([]interface{})) - } else { - alertSettings.TimeBasedEscalation = timeBasedEscalationFromSet(res["time_based_escalation"].([]interface{})) - } - - return alertSettings -} - -func retryStrategyFromSet(s *schema.Set) *checkly.RetryStrategy { - if s.Len() == 0 { - return nil - } - res := s.List()[0].(tfMap) - return &checkly.RetryStrategy{ - Type: res["type"].(string), - BaseBackoffSeconds: res["base_backoff_seconds"].(int), - MaxRetries: res["max_retries"].(int), - MaxDurationSeconds: res["max_duration_seconds"].(int), - SameRegion: res["same_region"].(bool), - } -} - -func alertChannelSubscriptionsFromSet(s []interface{}) []checkly.AlertChannelSubscription { - res := []checkly.AlertChannelSubscription{} - if len(s) == 0 { - return res - } - for _, it := range s { - tm := it.(tfMap) - chid := tm["channel_id"].(int) - activated := tm["activated"].(bool) - res = append(res, checkly.AlertChannelSubscription{ - Activated: activated, - ChannelID: int64(chid), - }) - } - return res -} - -func environmentVariablesFromSet(s []interface{}) []checkly.EnvironmentVariable { - res := []checkly.EnvironmentVariable{} - if len(s) == 0 { - return res - } - for _, it := range s { - tm := it.(tfMap) - key := tm["key"].(string) - value := tm["value"].(string) - locked := tm["locked"].(bool) - secret := tm["secret"].(bool) - res = append(res, checkly.EnvironmentVariable{ - Key: key, - Value: value, - Locked: locked, - Secret: secret, - }) - } - - return res -} - -func runBasedEscalationFromSet(s []interface{}) checkly.RunBasedEscalation { - if len(s) == 0 { - return checkly.RunBasedEscalation{} - } - res := s[0].(tfMap) - return checkly.RunBasedEscalation{ - FailedRunThreshold: res["failed_run_threshold"].(int), - } -} - -func timeBasedEscalationFromSet(s []interface{}) checkly.TimeBasedEscalation { - if len(s) == 0 { - return checkly.TimeBasedEscalation{} - } - res := s[0].(tfMap) - return checkly.TimeBasedEscalation{ - MinutesFailingThreshold: res["minutes_failing_threshold"].(int), - } -} - -func remindersFromSet(s []interface{}) checkly.Reminders { - if len(s) == 0 { - return checkly.Reminders{} - } - res := s[0].(tfMap) - return checkly.Reminders{ - Amount: res["amount"].(int), - Interval: res["interval"].(int), - } -} - -func parallelRunFailureThresholdFromSet(s []interface{}) checkly.ParallelRunFailureThreshold { - if len(s) == 0 { - return checkly.ParallelRunFailureThreshold{} - } - res := s[0].(tfMap) - return checkly.ParallelRunFailureThreshold{ - Enabled: res["enabled"].(bool), - Percentage: res["percentage"].(int), - } -} - -func requestFromSet(s *schema.Set) checkly.Request { - if s.Len() == 0 { - return checkly.Request{} - } - res := s.List()[0].(tfMap) - return checkly.Request{ - Method: res["method"].(string), - URL: res["url"].(string), - FollowRedirects: res["follow_redirects"].(bool), - SkipSSL: res["skip_ssl"].(bool), - Body: res["body"].(string), - BodyType: res["body_type"].(string), - Headers: keyValuesFromMap(res["headers"].(tfMap)), - QueryParameters: keyValuesFromMap(res["query_parameters"].(tfMap)), - Assertions: assertionsFromSet(res["assertion"].(*schema.Set)), - BasicAuth: basicAuthFromSet(res["basic_auth"].(*schema.Set)), - IPFamily: res["ip_family"].(string), - } -} - -func envVarsFromMap(m map[string]interface{}) []checkly.EnvironmentVariable { - r := make([]checkly.EnvironmentVariable, 0, len(m)) - for k, v := range m { - s, ok := v.(string) - if !ok { - panic(fmt.Errorf("could not convert environment variable value %v to string", v)) - } - - r = append(r, checkly.EnvironmentVariable{ - Key: k, - Value: s, - }) - - } - return r -} - -func keyValuesFromMap(m map[string]interface{}) []checkly.KeyValue { - r := make([]checkly.KeyValue, 0, len(m)) - for k, v := range m { - s, ok := v.(string) - if !ok { - panic(fmt.Errorf("could not convert environment variable value %v to string", v)) - } - r = append(r, checkly.KeyValue{ - Key: k, - Value: s, - }) - } - return r -} - -func getResourceEnvironmentVariables(d *schema.ResourceData) ([]checkly.EnvironmentVariable, error) { - deprecatedEnvironmentVariables := envVarsFromMap(d.Get("environment_variables").(tfMap)) - environmentVariables := environmentVariablesFromSet(d.Get("environment_variable").([]interface{})) - - if len(environmentVariables) > 0 && len(deprecatedEnvironmentVariables) > 0 { - return nil, errors.New("can't use both \"environment_variables\" and \"environment_variable\" on checkly_check_group resource") - } - - if len(environmentVariables) > 0 { - return environmentVariables, nil - } - - return deprecatedEnvironmentVariables, nil -} - -func validateRuntimeSupport(check checkly.Check, client interface{}) error { - // If the check has a runtime ID set, then validate that the runtime supports multistep. - // Note that if the runtime ID is coming from the account defaults or group, then we don't validate it. - // Adding validation there as well would be a nice improvement, though. - if check.Type == "MULTI_STEP" && check.RuntimeID != nil { - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - checkRuntime, err := client.(checkly.Client).GetRuntime(ctx, *check.RuntimeID) - - if err != nil { - return fmt.Errorf("API error while fetching runtimes: %w", err) - } - - if !checkRuntime.MultiStepSupport { - return fmt.Errorf("runtime %s does not support MULTI_STEP checks", *check.RuntimeID) - } - } - return nil -} diff --git a/checkly/resource_check_group.go b/checkly/resource_check_group.go deleted file mode 100644 index e43df9c..0000000 --- a/checkly/resource_check_group.go +++ /dev/null @@ -1,621 +0,0 @@ -package checkly - -import ( - "context" - "fmt" - "sort" - "strconv" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - checkly "github.com/checkly/checkly-go-sdk" -) - -func resourceCheckGroup() *schema.Resource { - return &schema.Resource{ - Create: resourceCheckGroupCreate, - Read: resourceCheckGroupRead, - Update: resourceCheckGroupUpdate, - Delete: resourceCheckGroupDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Description: "Check groups allow you to group together a set of related checks, which can also share default settings for various attributes.", - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - Description: "The name of the check group.", - }, - "concurrency": { - Type: schema.TypeInt, - Required: true, - Description: "Determines how many checks are run concurrently when triggering a check group from CI/CD or through the API.", - }, - "activated": { - Type: schema.TypeBool, - Required: true, - Description: "Determines if the checks in the group are running or not.", - }, - "muted": { - Type: schema.TypeBool, - Optional: true, - Description: "Determines if any notifications will be sent out when a check in this group fails and/or recovers.", - }, - "run_parallel": { - Type: schema.TypeBool, - Optional: true, - Description: "Determines if the checks in the group should run in all selected locations in parallel or round-robin.", - }, - "locations": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Description: "An array of one or more data center locations where to run the checks.", - }, - "private_locations": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - DefaultFunc: func() (interface{}, error) { - return []tfMap{}, nil - }, - Description: "An array of one or more private locations slugs.", - }, - "environment_variables": { - Type: schema.TypeMap, - Optional: true, - Deprecated: "The property `environment_variables` is deprecated and will be removed in a future version. Consider using the new `environment_variable` list.", - Description: "Key/value pairs for setting environment variables during check execution. These are only relevant for browser checks. Use global environment variables whenever possible.", - }, - "environment_variable": { - Type: schema.TypeList, - Optional: true, - Description: "Key/value pairs for setting environment variables during check execution, add locked = true to keep value hidden, add secret = true to create a secret variable. These are only relevant for browser checks. Use global environment variables whenever possible.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - }, - "value": { - Type: schema.TypeString, - Required: true, - }, - "locked": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "secret": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - }, - }, - }, - "double_check": { - Type: schema.TypeBool, - Optional: true, - Description: "Setting this to `true` will trigger a retry when a check fails from the failing region and another, randomly selected region before marking the check as failed.", - Deprecated: "The property `double_check` is deprecated and will be removed in a future version. To enable retries for failed check runs, use the `retry_strategy` property instead.", - }, - "tags": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Description: "Tags for organizing and filtering checks.", - }, - "setup_snippet_id": { - Type: schema.TypeInt, - Optional: true, - Description: "An ID reference to a snippet to use in the setup phase of an API check.", - }, - "teardown_snippet_id": { - Type: schema.TypeInt, - Optional: true, - Description: "An ID reference to a snippet to use in the teardown phase of an API check.", - }, - "local_setup_script": { - Type: schema.TypeString, - Optional: true, - Description: "A valid piece of Node.js code to run in the setup phase of an API check in this group.", - }, - "local_teardown_script": { - Type: schema.TypeString, - Optional: true, - Description: "A valid piece of Node.js code to run in the teardown phase of an API check in this group.", - }, - "runtime_id": { - Type: schema.TypeString, - Optional: true, - Default: nil, - Description: "The id of the runtime to use for this group.", - }, - "alert_channel_subscription": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "channel_id": { - Type: schema.TypeInt, - Required: true, - }, - "activated": { - Type: schema.TypeBool, - Required: true, - }, - }, - }, - }, - "alert_settings": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "escalation_type": { - Type: schema.TypeString, - Optional: true, - Default: checkly.RunBased, - Description: "Determines what type of escalation to use. Possible values are `RUN_BASED` or `TIME_BASED`.", - }, - "run_based_escalation": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "failed_run_threshold": { - Type: schema.TypeInt, - Optional: true, - Description: "After how many failed consecutive check runs an alert notification should be sent. Possible values are between 1 and 5. (Default `1`).", - }, - }, - }, - }, - "time_based_escalation": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "minutes_failing_threshold": { - Type: schema.TypeInt, - Optional: true, - Description: "After how many minutes after a check starts failing an alert should be sent. Possible values are `5`, `10`, `15`, and `30`. (Default `5`).", - }, - }, - }, - }, - "reminders": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "amount": { - Type: schema.TypeInt, - Optional: true, - Description: "How many reminders to send out after the initial alert notification. Possible values are `0`, `1`, `2`, `3`, `4`, `5`, and `100000`", - }, - "interval": { - Type: schema.TypeInt, - Optional: true, - Default: 5, - Description: "Possible values are `5`, `10`, `15`, and `30`. (Default `5`).", - }, - }, - }, - }, - "parallel_run_failure_threshold": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: "Applicable only for checks scheduled in parallel in multiple locations.", - }, - "percentage": { - Type: schema.TypeInt, - Optional: true, - Default: 10, - Description: "Possible values are `10`, `20`, `30`, `40`, `50`, `60`, `70`, `80`, `100`, and `100`. (Default `10`).", - }, - }, - }, - }, - "ssl_certificates": { - Type: schema.TypeSet, - Optional: true, - Deprecated: "This property is deprecated and it's ignored by the Checkly Public API. It will be removed in a future version.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "enabled": { - Type: schema.TypeBool, - Optional: true, - Description: "Determines if alert notifications should be sent for expiring SSL certificates.", - }, - "alert_threshold": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { - v := val.(int) - valid := false - validFreqs := []int{3, 7, 14, 30} - for _, i := range validFreqs { - if v == i { - valid = true - } - } - if !valid { - errs = append(errs, fmt.Errorf("%q must be one of %v, got: %d", key, validFreqs, v)) - } - return warns, errs - }, - Description: "At what moment in time to start alerting on SSL certificates. Possible values `3`, `7`, `14`, `30`. (Default `3`).", - }, - }, - }, - }, - }, - }, - }, - "use_global_alert_settings": { - Type: schema.TypeBool, - Optional: true, - Description: "When true, the account level alert settings will be used, not the alert setting defined on this check group.", - }, - "api_check_defaults": { - Type: schema.TypeSet, - MaxItems: 1, - Optional: true, - Computed: true, - DefaultFunc: func() (interface{}, error) { - return []tfMap{ - tfMap{ - "url": "", - "headers": []tfMap{}, - "query_parameters": []tfMap{}, - "basic_auth": tfMap{}, - }}, nil - }, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "url": { - Type: schema.TypeString, - Required: true, - Description: "The base url for this group which you can reference with the `GROUP_BASE_URL` variable in all group checks.", - }, - "headers": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - DefaultFunc: func() (interface{}, error) { - return []tfMap{}, nil - }, - }, - "query_parameters": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - DefaultFunc: func() (interface{}, error) { - return []tfMap{}, nil - }, - }, - "assertion": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "source": { - Type: schema.TypeString, - Required: true, - Description: "The source of the asserted value. Possible values `STATUS_CODE`, `JSON_BODY`, `HEADERS`, `TEXT_BODY`, and `RESPONSE_TIME`.", - }, - "property": { - Type: schema.TypeString, - Optional: true, - }, - "comparison": { - Type: schema.TypeString, - Required: true, - Description: "The type of comparison to be executed between expected and actual value of the assertion. Possible values `EQUALS`, `NOT_EQUALS`, `HAS_KEY`, `NOT_HAS_KEY`, `HAS_VALUE`, `NOT_HAS_VALUE`, `IS_EMPTY`, `NOT_EMPTY`, `GREATER_THAN`, `LESS_THAN`, `CONTAINS`, `NOT_CONTAINS`, `IS_NULL`, and `NOT_NULL`.", - }, - "target": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - "basic_auth": { - Type: schema.TypeSet, - MaxItems: 1, - Optional: true, - Computed: true, - DefaultFunc: func() (interface{}, error) { - return []tfMap{}, nil - }, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "username": { - Type: schema.TypeString, - Required: true, - }, - "password": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - }, - }, - }, - "retry_strategy": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - MaxItems: 1, - DefaultFunc: func() (interface{}, error) { - return []tfMap{}, nil - }, - Description: "A strategy for retrying failed check runs.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - Description: "Determines which type of retry strategy to use. Possible values are `FIXED`, `LINEAR`, or `EXPONENTIAL`.", - }, - "base_backoff_seconds": { - Type: schema.TypeInt, - Optional: true, - Default: 60, - Description: "The number of seconds to wait before the first retry attempt.", - }, - "max_retries": { - Type: schema.TypeInt, - Optional: true, - Default: 2, - Description: "The maximum number of times to retry the check. Value must be between 1 and 10.", - }, - "max_duration_seconds": { - Type: schema.TypeInt, - Optional: true, - Default: 600, - Description: "The total amount of time to continue retrying the check (maximum 600 seconds).", - }, - "same_region": { - Type: schema.TypeBool, - Optional: true, - Default: true, - Description: "Whether retries should be run in the same region as the initial check run.", - }, - }, - }, - }, - }, - } -} - -func resourceCheckGroupCreate(d *schema.ResourceData, client interface{}) error { - group, err := checkGroupFromResourceData(d) - if err != nil { - return fmt.Errorf("translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - gotGroup, err := client.(checkly.Client).CreateGroup(ctx, group) - if err != nil { - return fmt.Errorf("API error11: %w", err) - } - d.SetId(fmt.Sprintf("%d", gotGroup.ID)) - return resourceCheckGroupRead(d, client) -} - -func resourceCheckGroupRead(d *schema.ResourceData, client interface{}) error { - ID, err := strconv.ParseInt(d.Id(), 10, 64) - if err != nil { - return fmt.Errorf("ID %s is not numeric: %w", d.Id(), err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - group, err := client.(checkly.Client).GetGroup(ctx, ID) - if err != nil { - if strings.Contains(err.Error(), "404") { - //if resource is deleted remotely, then mark it as - //successfully gone by unsetting it's ID - d.SetId("") - return nil - } - return fmt.Errorf("API error12: %w", err) - } - return resourceDataFromCheckGroup(group, d) -} - -func resourceCheckGroupUpdate(d *schema.ResourceData, client interface{}) error { - group, err := checkGroupFromResourceData(d) - if err != nil { - return fmt.Errorf("translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - _, err = client.(checkly.Client).UpdateGroup(ctx, group.ID, group) - if err != nil { - return fmt.Errorf("API error13: %w", err) - } - d.SetId(fmt.Sprintf("%d", group.ID)) - return resourceCheckGroupRead(d, client) -} - -func resourceCheckGroupDelete(d *schema.ResourceData, client interface{}) error { - ID, err := strconv.ParseInt(d.Id(), 10, 64) - if err != nil { - return fmt.Errorf("ID %s is not numeric: %w", d.Id(), err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - if err := client.(checkly.Client).DeleteGroup(ctx, ID); err != nil { - return fmt.Errorf("API error14: %w", err) - } - return nil -} - -func resourceDataFromCheckGroup(g *checkly.Group, d *schema.ResourceData) error { - d.Set("name", g.Name) - d.Set("concurrency", g.Concurrency) - d.Set("activated", g.Activated) - d.Set("muted", g.Muted) - d.Set("run_parallel", g.RunParallel) - d.Set("locations", g.Locations) - d.Set("double_check", g.DoubleCheck) - d.Set("setup_snippet_id", g.SetupSnippetID) - d.Set("teardown_snippet_id", g.TearDownSnippetID) - d.Set("local_setup_script", g.LocalSetupScript) - d.Set("local_teardown_script", g.LocalTearDownScript) - d.Set("alert_channel_subscription", g.AlertChannelSubscriptions) - d.Set("private_locations", g.PrivateLocations) - - sort.Strings(g.Tags) - d.Set("tags", g.Tags) - - environmentVariables := environmentVariablesFromSet(d.Get("environment_variable").([]interface{})) - if len(environmentVariables) > 0 { - d.Set("environment_variable", g.EnvironmentVariables) - } else if err := d.Set("environment_variables", setFromEnvVars(g.EnvironmentVariables)); err != nil { - return fmt.Errorf("error setting environment variables for resource %s: %s", d.Id(), err) - } - - if g.RuntimeID != nil { - d.Set("runtime_id", *g.RuntimeID) - } - - if err := d.Set("alert_settings", setFromAlertSettings(g.AlertSettings)); err != nil { - return fmt.Errorf("error setting alert settings for resource %s: %s", d.Id(), err) - } - d.Set("use_global_alert_settings", g.UseGlobalAlertSettings) - - if err := d.Set("api_check_defaults", setFromAPICheckDefaults(g.APICheckDefaults)); err != nil { - return fmt.Errorf("error setting request for resource %s: %s", d.Id(), err) - } - - d.Set("retry_strategy", setFromRetryStrategy(g.RetryStrategy)) - - d.SetId(d.Id()) - return nil -} - -func checkGroupFromResourceData(d *schema.ResourceData) (checkly.Group, error) { - ID, err := strconv.ParseInt(d.Id(), 10, 64) - if err != nil { - if d.Id() != "" { - return checkly.Group{}, err - } - ID = 0 - } - - group := checkly.Group{ - ID: ID, - Name: d.Get("name").(string), - Concurrency: d.Get("concurrency").(int), - Activated: d.Get("activated").(bool), - Muted: d.Get("muted").(bool), - RunParallel: d.Get("run_parallel").(bool), - Locations: stringsFromSet(d.Get("locations").(*schema.Set)), - DoubleCheck: d.Get("double_check").(bool), - Tags: stringsFromSet(d.Get("tags").(*schema.Set)), - SetupSnippetID: int64(d.Get("setup_snippet_id").(int)), - TearDownSnippetID: int64(d.Get("teardown_snippet_id").(int)), - LocalSetupScript: d.Get("local_setup_script").(string), - LocalTearDownScript: d.Get("local_teardown_script").(string), - AlertSettings: alertSettingsFromSet(d.Get("alert_settings").([]interface{})), - UseGlobalAlertSettings: d.Get("use_global_alert_settings").(bool), - APICheckDefaults: apiCheckDefaultsFromSet(d.Get("api_check_defaults").(*schema.Set)), - AlertChannelSubscriptions: alertChannelSubscriptionsFromSet(d.Get("alert_channel_subscription").([]interface{})), - RetryStrategy: retryStrategyFromSet(d.Get("retry_strategy").(*schema.Set)), - } - - runtimeId := d.Get("runtime_id").(string) - if runtimeId == "" { - group.RuntimeID = nil - } else { - group.RuntimeID = &runtimeId - } - - environmentVariables, err := getResourceEnvironmentVariables(d) - if err != nil { - return checkly.Group{}, err - } - group.EnvironmentVariables = environmentVariables - - privateLocations := stringsFromSet(d.Get("private_locations").(*schema.Set)) - group.PrivateLocations = &privateLocations - - return group, nil -} - -func setFromAPICheckDefaults(a checkly.APICheckDefaults) []tfMap { - s := tfMap{} - s["url"] = a.BaseURL - s["headers"] = mapFromKeyValues(a.Headers) - s["query_parameters"] = mapFromKeyValues(a.QueryParameters) - s["assertion"] = setFromAssertions(a.Assertions) - s["basic_auth"] = checkGroupSetFromBasicAuth(a.BasicAuth) - return []tfMap{s} -} - -func apiCheckDefaultsFromSet(s *schema.Set) checkly.APICheckDefaults { - if s.Len() == 0 { - return checkly.APICheckDefaults{} - } - res := s.List()[0].(tfMap) - - return checkly.APICheckDefaults{ - BaseURL: res["url"].(string), - Headers: keyValuesFromMap(res["headers"].(tfMap)), - QueryParameters: keyValuesFromMap(res["query_parameters"].(tfMap)), - Assertions: assertionsFromSet(res["assertion"].(*schema.Set)), - BasicAuth: checkGroupBasicAuthFromSet(res["basic_auth"].(*schema.Set)), - } -} - -func checkGroupSetFromBasicAuth(b checkly.BasicAuth) []tfMap { - if b.Username == "" && b.Password == "" { - return []tfMap{} - } - return []tfMap{ - { - "username": b.Username, - "password": b.Password, - }, - } -} - -func checkGroupBasicAuthFromSet(s *schema.Set) checkly.BasicAuth { - if s.Len() == 0 { - return checkly.BasicAuth{ - Username: "", - Password: "", - } - } - res := s.List()[0].(tfMap) - return checkly.BasicAuth{ - Username: res["username"].(string), - Password: res["password"].(string), - } -} diff --git a/checkly/resource_dashboard.go b/checkly/resource_dashboard.go deleted file mode 100644 index 68679c2..0000000 --- a/checkly/resource_dashboard.go +++ /dev/null @@ -1,275 +0,0 @@ -package checkly - -import ( - "context" - "fmt" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - checkly "github.com/checkly/checkly-go-sdk" -) - -func validateOptions(options []int) func(val interface{}, key string) (warns []string, errs []error) { - return func(val interface{}, key string) (warns []string, errs []error) { - v := val.(int) - valid := false - for _, i := range options { - if v == i { - valid = true - } - } - if !valid { - errs = append(errs, fmt.Errorf("%q must be one of %v, got: %d", key, options, v)) - } - return warns, errs - } -} - -func resourceDashboard() *schema.Resource { - return &schema.Resource{ - Create: resourceDashboardCreate, - Read: resourceDashboardRead, - Update: resourceDashboardUpdate, - Delete: resourceDashboardDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Schema: map[string]*schema.Schema{ - "custom_url": { - Type: schema.TypeString, - Required: true, - Description: "A subdomain name under 'checklyhq.com'. Needs to be unique across all users.", - }, - "custom_domain": { - Type: schema.TypeString, - Optional: true, - Default: nil, - Description: "A custom user domain, e.g. 'status.example.com'. See the docs on updating your DNS and SSL usage.", - }, - "logo": { - Type: schema.TypeString, - Optional: true, - Default: "", - Description: "A URL pointing to an image file to use for the dashboard logo.", - }, - "favicon": { - Type: schema.TypeString, - Optional: true, - Default: "", - Description: "A URL pointing to an image file to use as browser favicon.", - }, - "link": { - Type: schema.TypeString, - Optional: true, - Default: "", - Description: "A link to for the dashboard logo.", - }, - "description": { - Type: schema.TypeString, - Optional: true, - Default: "", - Description: "HTML description for the dashboard.", - }, - "header": { - Type: schema.TypeString, - Optional: true, - Default: "", - Description: "A piece of text displayed at the top of your dashboard.", - }, - "width": { - Type: schema.TypeString, - Optional: true, - Default: "FULL", - ValidateFunc: func(value interface{}, key string) (warns []string, errs []error) { - full := "FULL" - px960 := "960PX" - v := value.(string) - if v != full && v != px960 { - errs = append(errs, fmt.Errorf("%q must %s and %s, got: %s", key, full, px960, v)) - } - return warns, errs - }, - Description: "Determines whether to use the full screen or focus in the center. Possible values `FULL` and `960PX`.", - }, - "refresh_rate": { - Type: schema.TypeInt, - Optional: true, - Default: 60, - ValidateFunc: validateOptions([]int{60, 300, 600}), - Description: "How often to refresh the dashboard in seconds. Possible values `60`, '300' and `600`.", - }, - "paginate": { - Type: schema.TypeBool, - Optional: true, - Default: true, - Description: "Determines if pagination is on or off.", - }, - "checks_per_page": { - Type: schema.TypeInt, - Optional: true, - Default: 15, - Description: "Determines how many checks to show per page.", - }, - "pagination_rate": { - Type: schema.TypeInt, - Optional: true, - Default: 60, - ValidateFunc: validateOptions([]int{30, 60, 300}), - Description: "How often to trigger pagination in seconds. Possible values `30`, `60` and `300`.", - }, - "tags": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Description: "A list of one or more tags that filter which checks to display on the dashboard.", - }, - "hide_tags": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: "Show or hide the tags on the dashboard.", - }, - "use_tags_and_operator": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: "Set when to use AND operator for fetching dashboard tags.", - }, - "is_private": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: "Set your dashboard as private and generate key.", - }, - // moving to TypeString here https://github.com/hashicorp/terraform-plugin-sdk/issues/792 - "key": { - Type: schema.TypeString, - Computed: true, - Sensitive: true, - Description: "The access key when the dashboard is private.", - }, - }, - } -} - -func dashboardFromResourceData(d *schema.ResourceData) (checkly.Dashboard, error) { - a := checkly.Dashboard{ - CustomDomain: d.Get("custom_domain").(string), - CustomUrl: d.Get("custom_url").(string), - Logo: d.Get("logo").(string), - Favicon: d.Get("favicon").(string), - Link: d.Get("link").(string), - Description: d.Get("description").(string), - Header: d.Get("header").(string), - RefreshRate: d.Get("refresh_rate").(int), - Paginate: d.Get("paginate").(bool), - ChecksPerPage: d.Get("checks_per_page").(int), - PaginationRate: d.Get("pagination_rate").(int), - HideTags: d.Get("hide_tags").(bool), - Width: d.Get("width").(string), - UseTagsAndOperator: d.Get("use_tags_and_operator").(bool), - IsPrivate: d.Get("is_private").(bool), - Tags: stringsFromSet(d.Get("tags").(*schema.Set)), - } - - fmt.Printf("%v", a) - - return a, nil -} - -func resourceDataFromDashboard(s *checkly.Dashboard, d *schema.ResourceData) error { - d.Set("custom_domain", s.CustomDomain) - d.Set("custom_url", s.CustomUrl) - d.Set("logo", s.Logo) - d.Set("favicon", s.Favicon) - d.Set("link", s.Link) - d.Set("description", s.Description) - d.Set("header", s.Header) - d.Set("refresh_rate", s.RefreshRate) - d.Set("paginate", s.Paginate) - d.Set("checks_per_page", s.ChecksPerPage) - d.Set("pagination_rate", s.PaginationRate) - d.Set("hide_tags", s.HideTags) - d.Set("tags", s.Tags) - d.Set("width", s.Width) - d.Set("use_tags_and_operator", s.UseTagsAndOperator) - d.Set("is_private", s.IsPrivate) - - // if the dashboard is private, we either do nothing - // or set the key to a new value if there is any - if s.IsPrivate { - if len(s.Keys) > 0 { - d.Set("key", s.Keys[0].RawKey) - } - } else { - // if the dashboard is public, remove the key - d.Set("key", nil) - } - - return nil -} - -func resourceDashboardCreate(d *schema.ResourceData, client interface{}) error { - dashboard, err := dashboardFromResourceData(d) - if err != nil { - return fmt.Errorf("resourceDashboardCreate: translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - result, err := client.(checkly.Client).CreateDashboard(ctx, dashboard) - - if err != nil { - return fmt.Errorf("CreateDashboard: API error: %w", err) - } - - d.SetId(result.DashboardID) - - // we cannot take the detour through resourceDashboardRead since - // we would not get the keys back from an additional GET call - return resourceDataFromDashboard(result, d) -} - -func resourceDashboardUpdate(d *schema.ResourceData, client interface{}) error { - dashboard, err := dashboardFromResourceData(d) - if err != nil { - return fmt.Errorf("resourceDashboardUpdate: translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - result, err := client.(checkly.Client).UpdateDashboard(ctx, d.Id(), dashboard) - if err != nil { - return fmt.Errorf("resourceDashboardUpdate: API error: %w", err) - } - d.SetId(result.DashboardID) - - // we cannot take the detour through resourceDashboardRead since - // we would not get the keys back from an additional GET call - return resourceDataFromDashboard(result, d) -} - -func resourceDashboardDelete(d *schema.ResourceData, client interface{}) error { - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - err := client.(checkly.Client).DeleteDashboard(ctx, d.Id()) - if err != nil { - return fmt.Errorf("resourceDashboardDelete: API error: %w", err) - } - return nil -} - -func resourceDashboardRead(d *schema.ResourceData, client interface{}) error { - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - dashboard, err := client.(checkly.Client).GetDashboard(ctx, d.Id()) - defer cancel() - if err != nil { - if strings.Contains(err.Error(), "404") { - d.SetId("") - return nil - } - return fmt.Errorf("resourceDashboardRead: API error: %w", err) - } - return resourceDataFromDashboard(dashboard, d) -} diff --git a/checkly/resource_environment_variable.go b/checkly/resource_environment_variable.go deleted file mode 100644 index fb55897..0000000 --- a/checkly/resource_environment_variable.go +++ /dev/null @@ -1,118 +0,0 @@ -package checkly - -import ( - "context" - "fmt" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - checkly "github.com/checkly/checkly-go-sdk" -) - -func resourceEnvironmentVariable() *schema.Resource { - return &schema.Resource{ - Create: resourceEnvironmentVariableCreate, - Read: resourceEnvironmentVariableRead, - Update: resourceEnvironmentVariableUpdate, - Delete: resourceEnvironmentVariableDelete, - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - }, - "value": { - Type: schema.TypeString, - Required: true, - }, - "locked": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "secret": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - }, - } -} - -func resourceEnvironmentVariableCreate(d *schema.ResourceData, client interface{}) error { - envVar, err := environmentVariableFromResourceData(d) - if err != nil { - return fmt.Errorf("resourceEnvironmentVariableCreate: translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - _, err = client.(checkly.Client).CreateEnvironmentVariable(ctx, envVar) - if err != nil { - return fmt.Errorf("CreateEnvironmentVariable: API error: %w", err) - } - d.SetId(envVar.Key) - return resourceEnvironmentVariableRead(d, client) -} - -func environmentVariableFromResourceData(d *schema.ResourceData) (checkly.EnvironmentVariable, error) { - return checkly.EnvironmentVariable{ - Key: d.Get("key").(string), - Value: d.Get("value").(string), - Locked: d.Get("locked").(bool), - Secret: d.Get("secret").(bool), - }, nil -} - -func resourceDataFromEnvironmentVariable(s *checkly.EnvironmentVariable, d *schema.ResourceData) error { - d.Set("key", s.Key) - if !s.Secret { - d.Set("value", s.Value) - } - d.Set("locked", s.Locked) - d.Set("secret", s.Secret) - return nil -} - -func resourceEnvironmentVariableRead(d *schema.ResourceData, client interface{}) error { - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - envVar, err := client.(checkly.Client).GetEnvironmentVariable(ctx, d.Id()) - if err != nil { - if strings.Contains(err.Error(), "404") { - //if resource is deleted remotely, then mark it as - //successfully gone by unsetting it's ID - d.SetId("") - return nil - } - return fmt.Errorf("resourceEnvironmentVariableRead: API error: %w", err) - } - return resourceDataFromEnvironmentVariable(envVar, d) -} - -func resourceEnvironmentVariableUpdate(d *schema.ResourceData, client interface{}) error { - envVar, err := environmentVariableFromResourceData(d) - if err != nil { - return fmt.Errorf("resourceEnvironmentVariableUpdate: translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - _, err = client.(checkly.Client).UpdateEnvironmentVariable(ctx, d.Id(), envVar) - if err != nil { - return fmt.Errorf("resourceEnvironmentVariableUpdate: API error: %w", err) - } - - return resourceEnvironmentVariableRead(d, client) -} - -func resourceEnvironmentVariableDelete(d *schema.ResourceData, client interface{}) error { - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - err := client.(checkly.Client).DeleteEnvironmentVariable(ctx, d.Id()) - if err != nil { - return fmt.Errorf("resourceEnvironmentVariableDelete: API error: %w", err) - } - return nil -} diff --git a/checkly/resource_heartbeat.go b/checkly/resource_heartbeat.go deleted file mode 100644 index 3b2fa0b..0000000 --- a/checkly/resource_heartbeat.go +++ /dev/null @@ -1,428 +0,0 @@ -package checkly - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "sort" - "strings" - "time" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - "github.com/checkly/checkly-go-sdk" -) - -func resourceHeartbeat() *schema.Resource { - return &schema.Resource{ - Create: resourceHeartbeatCreate, - Read: resourceHeartbeatRead, - Update: resourceHeartbeatUpdate, - Delete: resourceHeartbeatDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Description: "Heartbeats allows you to monitor your cron jobs and set up alerting, so you get a notification when things break or slow down.", - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - Description: "The name of the check.", - }, - "activated": { - Type: schema.TypeBool, - Required: true, - Description: "Determines if the check is running or not. Possible values `true`, and `false`.", - }, - "muted": { - Type: schema.TypeBool, - Optional: true, - Description: "Determines if any notifications will be sent out when a check fails/degrades/recovers.", - }, - "tags": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Description: "A list of tags for organizing and filtering checks.", - }, - "alert_settings": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "escalation_type": { - Type: schema.TypeString, - Optional: true, - Description: "Determines what type of escalation to use. Possible values are `RUN_BASED` or `TIME_BASED`.", - }, - "run_based_escalation": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "failed_run_threshold": { - Type: schema.TypeInt, - Optional: true, - Description: "After how many failed consecutive check runs an alert notification should be sent. Possible values are between 1 and 5. (Default `1`).", - }, - }, - }, - }, - "time_based_escalation": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "minutes_failing_threshold": { - Type: schema.TypeInt, - Optional: true, - Description: "After how many minutes after a check starts failing an alert should be sent. Possible values are `5`, `10`, `15`, and `30`. (Default `5`).", - }, - }, - }, - }, - "reminders": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "amount": { - Type: schema.TypeInt, - Optional: true, - Description: "How many reminders to send out after the initial alert notification. Possible values are `0`, `1`, `2`, `3`, `4`, `5`, and `100000`", - }, - "interval": { - Type: schema.TypeInt, - Optional: true, - Default: 5, - Description: "Possible values are `5`, `10`, `15`, and `30`. (Default `5`).", - }, - }, - }, - }, - "parallel_run_failure_threshold": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: "Applicable only for checks scheduled in parallel in multiple locations.", - }, - "percentage": { - Type: schema.TypeInt, - Optional: true, - Default: 10, - Description: "Possible values are `10`, `20`, `30`, `40`, `50`, `60`, `70`, `80`, `100`, and `100`. (Default `10`).", - }, - }, - }, - }, - "ssl_certificates": { - Type: schema.TypeSet, - Optional: true, - Deprecated: "This property is deprecated and it's ignored by the Checkly Public API. It will be removed in a future version.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "enabled": { - Type: schema.TypeBool, - Optional: true, - Description: "Determines if alert notifications should be sent for expiring SSL certificates. Possible values `true`, and `false`. (Default `false`).", - }, - "alert_threshold": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { - v := val.(int) - valid := false - validFreqs := []int{3, 7, 14, 30} - for _, i := range validFreqs { - if v == i { - valid = true - } - } - if !valid { - errs = append(errs, fmt.Errorf("%q must be one of %v, got: %d", key, validFreqs, v)) - } - return warns, errs - }, - Description: "How long before SSL certificate expiry to send alerts. Possible values `3`, `7`, `14`, `30`. (Default `3`).", - }, - }, - Description: "At what interval the reminders should be sent.", - }, - }, - }, - }, - }, - "use_global_alert_settings": { - Type: schema.TypeBool, - Optional: true, - Description: "When true, the account level alert settings will be used, not the alert setting defined on this check.", - }, - "heartbeat": { - Type: schema.TypeSet, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "period": { - Type: schema.TypeInt, - Required: true, - Description: "How often you expect a ping to the ping URL.", - }, - "period_unit": { - Type: schema.TypeString, - Required: true, - ValidateFunc: func(value interface{}, key string) (warns []string, errs []error) { - v := value.(string) - isValid := false - options := []string{"seconds", "minutes", "hours", "days"} - for _, option := range options { - if v == option { - isValid = true - } - } - if !isValid { - errs = append(errs, fmt.Errorf("%q must be one of %v, got %s", key, options, v)) - } - return warns, errs - }, - Description: "Possible values `seconds`, `minutes`, `hours` and `days`.", - }, - "grace": { - Type: schema.TypeInt, - Required: true, - Description: "How long Checkly should wait before triggering any alerts when a ping does not arrive within the set period.", - }, - "grace_unit": { - Type: schema.TypeString, - Required: true, - ValidateFunc: func(value interface{}, key string) (warns []string, errs []error) { - v := value.(string) - isValid := false - options := []string{"seconds", "minutes", "hours", "days"} - for _, option := range options { - if v == option { - isValid = true - } - } - if !isValid { - errs = append(errs, fmt.Errorf("%q must be one of %v, got %s", key, options, v)) - } - return warns, errs - }, - Description: "Possible values `seconds`, `minutes`, `hours` and `days`.", - }, - "ping_token": { - Type: schema.TypeString, - Optional: true, - Computed: true, - Description: "Custom token to generate your ping URL. Checkly will expect a ping to `https://ping.checklyhq.com/[PING_TOKEN]`.", - }, - }, - }, - }, - "alert_channel_subscription": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "channel_id": { - Type: schema.TypeInt, - Required: true, - }, - "activated": { - Type: schema.TypeBool, - Required: true, - }, - }, - }, - }, - }, - } -} - -func resourceHeartbeatCreate(d *schema.ResourceData, client interface{}) error { - check, err := heartbeatCheckFromResourceData(d) - - if err != nil { - return fmt.Errorf("translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - newCheck, err := client.(checkly.Client).CreateHeartbeat(ctx, check) - - if err != nil { - checkJSON, _ := json.Marshal(check) - return fmt.Errorf("API error 1: %w, Check: %s", err, string(checkJSON)) - } - d.SetId(newCheck.ID) - return resourceHeartbeatRead(d, client) -} - -func resourceHeartbeatRead(d *schema.ResourceData, client interface{}) error { - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - check, err := client.(checkly.Client).GetHeartbeatCheck(ctx, d.Id()) - if err != nil { - if strings.Contains(err.Error(), "404") { - //if resource is deleted remotely, then mark it as - //successfully gone by unsetting it's ID - d.SetId("") - return nil - } - return fmt.Errorf("API error 2: %w", err) - } - return resourceDataFromHeartbeat(check, d) -} - -func resourceHeartbeatUpdate(d *schema.ResourceData, client interface{}) error { - check, err := heartbeatCheckFromResourceData(d) - - if err != nil { - return fmt.Errorf("translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - _, err = client.(checkly.Client).UpdateHeartbeat(ctx, check.ID, check) - if err != nil { - checkJSON, _ := json.Marshal(check) - return fmt.Errorf("API error 3: Couldn't update check, Error: %w, \nCheck: %s", err, checkJSON) - } - d.SetId(check.ID) - return resourceHeartbeatRead(d, client) -} - -func resourceHeartbeatDelete(d *schema.ResourceData, client interface{}) error { - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - if err := client.(checkly.Client).Delete(ctx, d.Id()); err != nil { - return fmt.Errorf("API error 4: Couldn't delete Check %s, Error: %w", d.Id(), err) - } - return nil -} - -func heartbeatCheckFromResourceData(d *schema.ResourceData) (checkly.HeartbeatCheck, error) { - check := checkly.HeartbeatCheck{ - ID: d.Id(), - Name: d.Get("name").(string), - Activated: d.Get("activated").(bool), - Muted: d.Get("muted").(bool), - Tags: stringsFromSet(d.Get("tags").(*schema.Set)), - AlertSettings: alertSettingsFromSet(d.Get("alert_settings").([]interface{})), - UseGlobalAlertSettings: d.Get("use_global_alert_settings").(bool), - AlertChannelSubscriptions: alertChannelSubscriptionsFromSet(d.Get("alert_channel_subscription").([]interface{})), - } - - // this will prevent subsequent apply from causing a tf config change in browser checks - check.Heartbeat = heartbeatFromSet(d.Get("heartbeat").(*schema.Set)) - - // Period / Grace validation - periodDaysInHours := 0 - periodHours := 0 - periodMinutes := 0 - periodSseconds := 0 - graceDaysInHours := 0 - graceHours := 0 - graceMinutes := 0 - graceSseconds := 0 - - if check.Heartbeat.PeriodUnit == "days" { - periodDaysInHours = check.Heartbeat.Period * 24 - } else if check.Heartbeat.PeriodUnit == "hours" { - periodHours = check.Heartbeat.Period - } else if check.Heartbeat.PeriodUnit == "minutes" { - periodMinutes = check.Heartbeat.Period - } else { - periodSseconds = check.Heartbeat.Period - } - - if check.Heartbeat.GraceUnit == "days" { - graceDaysInHours = check.Heartbeat.Grace * 24 - } else if check.Heartbeat.GraceUnit == "hours" { - graceHours = check.Heartbeat.Grace - } else if check.Heartbeat.GraceUnit == "minutes" { - graceMinutes = check.Heartbeat.Grace - } else { - graceSseconds = check.Heartbeat.Grace - } - - now := time.Now().Local() - addedTimePeriod := time.Now().Local().Add( - time.Hour*time.Duration(periodDaysInHours+periodHours) + - time.Minute*time.Duration(periodMinutes) + - time.Second*time.Duration(periodSseconds)) - addedTimeGrace := time.Now().Local().Add( - time.Hour*time.Duration(graceDaysInHours+graceHours) + - time.Minute*time.Duration(graceMinutes) + - time.Second*time.Duration(graceSseconds)) - - if addedTimePeriod.Sub(now).Hours()/float64(24) > 365 || addedTimePeriod.Sub(now).Seconds() < 30 { - return check, errors.New(fmt.Sprintf("period must be between 30 seconds and 365 days")) - } - - if addedTimeGrace.Sub(now).Hours()/float64(24) > 365 { - return check, errors.New("grace must be less than 365 days") - } - - return check, nil -} - -func resourceDataFromHeartbeat(c *checkly.HeartbeatCheck, d *schema.ResourceData) error { - d.Set("name", c.Name) - d.Set("activated", c.Activated) - d.Set("muted", c.Muted) - - sort.Strings(c.Tags) - d.Set("tags", c.Tags) - if err := d.Set("alert_settings", setFromAlertSettings(c.AlertSettings)); err != nil { - return fmt.Errorf("error setting alert settings for resource %s: %w", d.Id(), err) - } - d.Set("use_global_alert_settings", c.UseGlobalAlertSettings) - - err := d.Set("heartbeat", setFromHeartbeat(c.Heartbeat)) - if err != nil { - return fmt.Errorf("error setting heartbeat for resource %s: %w %v", d.Id(), err, c.Heartbeat) - } - - d.Set("alert_channel_subscription", c.AlertChannelSubscriptions) - d.SetId(d.Id()) - - return nil -} - -func setFromHeartbeat(r checkly.Heartbeat) []tfMap { - s := tfMap{} - s["period"] = r.Period - s["period_unit"] = r.PeriodUnit - s["grace_unit"] = r.GraceUnit - s["grace"] = r.Grace - s["ping_token"] = r.PingToken - return []tfMap{s} -} - -func heartbeatFromSet(s *schema.Set) checkly.Heartbeat { - if s.Len() == 0 { - return checkly.Heartbeat{} - } - res := s.List()[0].(tfMap) - return checkly.Heartbeat{ - Period: res["period"].(int), - PeriodUnit: res["period_unit"].(string), - Grace: res["grace"].(int), - GraceUnit: res["grace_unit"].(string), - PingToken: res["ping_token"].(string), - } -} diff --git a/checkly/resource_maintenance_window.go b/checkly/resource_maintenance_window.go deleted file mode 100644 index b8f5a32..0000000 --- a/checkly/resource_maintenance_window.go +++ /dev/null @@ -1,183 +0,0 @@ -package checkly - -import ( - "context" - "fmt" - "strconv" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - checkly "github.com/checkly/checkly-go-sdk" -) - -func resourceMaintenanceWindow() *schema.Resource { - return &schema.Resource{ - Create: resourceMaintenanceWindowCreate, - Read: resourceMaintenanceWindowRead, - Update: resourceMaintenanceWindowUpdate, - Delete: resourceMaintenanceWindowDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - Description: "The maintenance window name.", - }, - "starts_at": { - Type: schema.TypeString, - Required: true, - Description: "The start date of the maintenance window.", - }, - "ends_at": { - Type: schema.TypeString, - Required: true, - Description: "The end date of the maintenance window.", - }, - "repeat_unit": { - Type: schema.TypeString, - Optional: true, - Default: nil, - ValidateFunc: func(value interface{}, key string) (warns []string, errs []error) { - v := value.(string) - isValid := false - options := []string{"DAY", "WEEK", "MONTH"} - for _, option := range options { - if v == option { - isValid = true - } - } - if !isValid { - errs = append(errs, fmt.Errorf("%q must be one of %v, got %s", key, options, v)) - } - return warns, errs - }, - Description: "The repeat cadence for the maintenance window. Possible values `DAY`, `WEEK` and `MONTH`.", - }, - "repeat_interval": { - Type: schema.TypeInt, - Optional: true, - Default: nil, - Description: "The repeat interval of the maintenance window from the first occurrence.", - }, - "repeat_ends_at": { - Type: schema.TypeString, - Optional: true, - Default: nil, - Description: "The date on which the maintenance window should stop repeating.", - }, - "tags": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - DefaultFunc: func() (interface{}, error) { - return []tfMap{}, nil - }, - Description: "The names of the checks and groups maintenance window should apply to.", - }, - }, - } -} - -func maintenanceWindowsFromResourceData(d *schema.ResourceData) (checkly.MaintenanceWindow, error) { - ID, err := strconv.ParseInt(d.Id(), 10, 64) - if err != nil { - if d.Id() != "" { - return checkly.MaintenanceWindow{}, err - } - ID = 0 - } - a := checkly.MaintenanceWindow{ - ID: ID, - Name: d.Get("name").(string), - StartsAt: d.Get("starts_at").(string), - EndsAt: d.Get("ends_at").(string), - RepeatUnit: d.Get("repeat_unit").(string), - RepeatEndsAt: d.Get("repeat_ends_at").(string), - RepeatInterval: d.Get("repeat_interval").(int), - Tags: stringsFromSet(d.Get("tags").(*schema.Set)), - } - - fmt.Printf("%v", a) - - return a, nil -} - -func resourceDataFromMaintenanceWindows(s *checkly.MaintenanceWindow, d *schema.ResourceData) error { - d.Set("name", s.Name) - d.Set("starts_at", s.StartsAt) - d.Set("ends_at", s.EndsAt) - d.Set("repeat_unit", s.RepeatUnit) - d.Set("repeat_ends_at", s.RepeatEndsAt) - d.Set("repeat_interval", s.RepeatInterval) - d.Set("tags", s.Tags) - return nil -} - -func resourceMaintenanceWindowCreate(d *schema.ResourceData, client interface{}) error { - mw, err := maintenanceWindowsFromResourceData(d) - if err != nil { - return fmt.Errorf("resourceMaintenanceWindowCreate: translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - result, err := client.(checkly.Client).CreateMaintenanceWindow(ctx, mw) - - if err != nil { - return fmt.Errorf("CreateMaintenanceWindows: API error: %w", err) - } - - d.SetId(fmt.Sprintf("%d", result.ID)) - return resourceMaintenanceWindowRead(d, client) -} - -func resourceMaintenanceWindowUpdate(d *schema.ResourceData, client interface{}) error { - mw, err := maintenanceWindowsFromResourceData(d) - if err != nil { - return fmt.Errorf("resourceMaintenanceWindowUpdate: translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - _, err = client.(checkly.Client).UpdateMaintenanceWindow(ctx, mw.ID, mw) - if err != nil { - return fmt.Errorf("resourceMaintenanceWindowUpdate: API error: %w", err) - } - d.SetId(fmt.Sprintf("%d", mw.ID)) - return resourceMaintenanceWindowRead(d, client) -} - -func resourceMaintenanceWindowDelete(d *schema.ResourceData, client interface{}) error { - ID, err := strconv.ParseInt(d.Id(), 10, 64) - if err != nil { - return fmt.Errorf("resourceMaintenanceWindowDelete: ID %s is not numeric: %w", d.Id(), err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - err = client.(checkly.Client).DeleteMaintenanceWindow(ctx, ID) - if err != nil { - return fmt.Errorf("resourceMaintenanceWindowDelete: API error: %w", err) - } - return nil -} - -func resourceMaintenanceWindowRead(d *schema.ResourceData, client interface{}) error { - ID, err := strconv.ParseInt(d.Id(), 10, 64) - if err != nil { - return fmt.Errorf("resourceMaintenanceWindowRead: ID %s is not numeric: %w", d.Id(), err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - mw, err := client.(checkly.Client).GetMaintenanceWindow(ctx, ID) - defer cancel() - if err != nil { - if strings.Contains(err.Error(), "404") { - d.SetId("") - return nil - } - return fmt.Errorf("resourceMaintenanceWindowRead: API error: %w", err) - } - return resourceDataFromMaintenanceWindows(mw, d) -} diff --git a/checkly/resource_private_locations.go b/checkly/resource_private_locations.go deleted file mode 100644 index d0e3f37..0000000 --- a/checkly/resource_private_locations.go +++ /dev/null @@ -1,123 +0,0 @@ -package checkly - -import ( - "context" - "fmt" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - checkly "github.com/checkly/checkly-go-sdk" -) - -func resourcePrivateLocation() *schema.Resource { - return &schema.Resource{ - Create: resourcePrivateLocationCreate, - Read: resourcePrivateLocationRead, - Update: resourcePrivateLocationUpdate, - Delete: resourcePrivateLocationDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - Description: "The private location name.", - }, - "slug_name": { - Type: schema.TypeString, - Required: true, - Description: "Valid slug name.", - }, - "icon": { - Type: schema.TypeString, - Optional: true, - Default: "location", - Description: "Icon assigned to the private location.", - }, - "keys": { - Type: schema.TypeSet, - Computed: true, - Sensitive: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Description: "Private location API keys.", - }, - }, - } -} - -func privateLocationFromResourceData(d *schema.ResourceData) (checkly.PrivateLocation, error) { - return checkly.PrivateLocation{ - Name: d.Get("name").(string), - SlugName: d.Get("slug_name").(string), - Icon: d.Get("icon").(string), - }, nil -} - -func resourcePrivateLocationCreate(d *schema.ResourceData, client interface{}) error { - pl, err := privateLocationFromResourceData(d) - if err != nil { - return fmt.Errorf("resourcePrivateLocationCreate: translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - result, err := client.(checkly.Client).CreatePrivateLocation(ctx, pl) - if err != nil { - return fmt.Errorf("CreatePrivateLocation: API error: %w", err) - } - d.SetId(result.ID) - - var keys = []string{result.Keys[0].RawKey} - d.Set("keys", keys) - return resourcePrivateLocationRead(d, client) -} - -func resourceDataFromPrivateLocation(pl *checkly.PrivateLocation, d *schema.ResourceData) error { - d.Set("name", pl.Name) - d.Set("slug_name", pl.SlugName) - d.Set("icon", pl.Icon) - return nil -} - -func resourcePrivateLocationUpdate(d *schema.ResourceData, client interface{}) error { - pl, err := privateLocationFromResourceData(d) - if err != nil { - return fmt.Errorf("resourcePrivateLocationUpdate: translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - _, err = client.(checkly.Client).UpdatePrivateLocation(ctx, d.Id(), pl) - if err != nil { - return fmt.Errorf("resourcePrivateLocationUpdate: API error: %w", err) - } - return resourcePrivateLocationRead(d, client) -} - -func resourcePrivateLocationRead(d *schema.ResourceData, client interface{}) error { - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - pl, err := client.(checkly.Client).GetPrivateLocation(ctx, d.Id()) - if err != nil { - if strings.Contains(err.Error(), "404") { - //if resource is deleted remotely, then mark it as - //successfully gone by unsetting it's ID - d.SetId("") - return nil - } - return fmt.Errorf("resourcePrivateLocationRead: %w", err) - } - return resourceDataFromPrivateLocation(pl, d) -} - -func resourcePrivateLocationDelete(d *schema.ResourceData, client interface{}) error { - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - err := client.(checkly.Client).DeletePrivateLocation(ctx, d.Id()) - if err != nil { - return fmt.Errorf("resourcePrivateLocationDelete: API error: %w", err) - } - return nil -} diff --git a/checkly/resource_snippet.go b/checkly/resource_snippet.go deleted file mode 100644 index 3ea903a..0000000 --- a/checkly/resource_snippet.go +++ /dev/null @@ -1,129 +0,0 @@ -package checkly - -import ( - "context" - "fmt" - "strconv" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - checkly "github.com/checkly/checkly-go-sdk" -) - -func resourceSnippet() *schema.Resource { - return &schema.Resource{ - Create: resourceSnippetCreate, - Read: resourceSnippetRead, - Update: resourceSnippetUpdate, - Delete: resourceSnippetDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - Description: "The name of the snippet", - }, - "script": { - Type: schema.TypeString, - Required: true, - Description: "Your Node.js code that interacts with the API check lifecycle, or functions as a partial for browser checks.", - }, - }, - } -} - -func resourceSnippetCreate(d *schema.ResourceData, client interface{}) error { - snippet, err := snippetFromResourceData(d) - if err != nil { - return fmt.Errorf("resourceSnippetCreate: translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - result, err := client.(checkly.Client).CreateSnippet(ctx, snippet) - if err != nil { - return fmt.Errorf("CreateSnippet: API error: %w", err) - } - d.SetId(fmt.Sprintf("%d", result.ID)) - return resourceSnippetRead(d, client) -} - -func snippetFromResourceData(d *schema.ResourceData) (checkly.Snippet, error) { - id, err := resourceIDToInt(d.Id()) - if err != nil { - return checkly.Snippet{}, err - } - return checkly.Snippet{ - ID: id, - Name: d.Get("name").(string), - Script: d.Get("script").(string), - }, nil -} - -func resourceDataFromSnippet(s *checkly.Snippet, d *schema.ResourceData) error { - d.Set("name", s.Name) - d.Set("script", s.Script) - return nil -} - -func resourceIDToInt(id string) (int64, error) { - if id == "" { - return 0, nil - } - res, err := strconv.ParseInt(id, 10, 64) - if err != nil { - return 0, err - } - return res, nil -} - -func resourceSnippetRead(d *schema.ResourceData, client interface{}) error { - ID, err := strconv.ParseInt(d.Id(), 10, 64) - if err != nil { - return fmt.Errorf("resourceSnippetRead: ID %s is not numeric: %w", d.Id(), err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - snippet, err := client.(checkly.Client).GetSnippet(ctx, ID) - if err != nil { - if strings.Contains(err.Error(), "404") { - //if resource is deleted remotely, then mark it as - //successfully gone by unsetting it's ID - d.SetId("") - return nil - } - return fmt.Errorf("resourceSnippetRead: API error: %w", err) - } - return resourceDataFromSnippet(snippet, d) -} - -func resourceSnippetUpdate(d *schema.ResourceData, client interface{}) error { - snippet, err := snippetFromResourceData(d) - if err != nil { - return fmt.Errorf("resourceSnippetUpdate: translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - _, err = client.(checkly.Client).UpdateSnippet(ctx, snippet.ID, snippet) - if err != nil { - return fmt.Errorf("resourceSnippetUpdate: API error: %w", err) - } - d.SetId(fmt.Sprintf("%d", snippet.ID)) - return resourceSnippetRead(d, client) -} - -func resourceSnippetDelete(d *schema.ResourceData, client interface{}) error { - ID, err := strconv.ParseInt(d.Id(), 10, 64) - if err != nil { - return fmt.Errorf("resourceSnippetDelete: ID %s is not numeric: %w", d.Id(), err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - err = client.(checkly.Client).DeleteSnippet(ctx, ID) - if err != nil { - return fmt.Errorf("resourceSnippetDelete: API error: %w", err) - } - return nil -} diff --git a/checkly/resource_trigger_check.go b/checkly/resource_trigger_check.go deleted file mode 100644 index 18d60ee..0000000 --- a/checkly/resource_trigger_check.go +++ /dev/null @@ -1,124 +0,0 @@ -package checkly - -import ( - "context" - "fmt" - "strconv" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - checkly "github.com/checkly/checkly-go-sdk" -) - -func resourceTriggerCheck() *schema.Resource { - return &schema.Resource{ - Create: resourceTriggerCheckCreate, - Read: resourceTriggerCheckRead, - Delete: resourceTriggerCheckDelete, - Update: resourceTriggerCheckUpdate, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Schema: map[string]*schema.Schema{ - "check_id": { - Type: schema.TypeString, - Required: true, - Description: "The id of the check that you want to attach the trigger to.", - }, - "token": { - Type: schema.TypeString, - Optional: true, - Computed: true, - Description: "The token value created to trigger the check", - }, - "url": { - Type: schema.TypeString, - Optional: true, - Computed: true, - Description: "The request URL to trigger the check run.", - }, - }, - } -} - -func triggerCheckFromResourceData(data *schema.ResourceData) (checkly.TriggerCheck, error) { - ID, err := strconv.ParseInt(data.Id(), 10, 64) - if err != nil { - if data.Id() != "" { - return checkly.TriggerCheck{}, err - } - ID = 0 - } - return checkly.TriggerCheck{ - ID: ID, - CheckId: data.Get("check_id").(string), - Token: data.Get("token").(string), - URL: data.Get("url").(string), - }, nil -} - -func resourceDataFromTriggerCheck(trigger *checkly.TriggerCheck, data *schema.ResourceData) error { - data.Set("check_id", trigger.CheckId) - data.Set("token", trigger.Token) - data.Set("url", trigger.URL) - return nil -} - -func resourceTriggerCheckCreate(data *schema.ResourceData, client interface{}) error { - trigger, err := triggerCheckFromResourceData(data) - if err != nil { - return fmt.Errorf("resourceTriggerCheckCreate: translation error: %w", err) - } - - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - result, err := client.(checkly.Client).CreateTriggerCheck(ctx, trigger.CheckId) - if err != nil { - return fmt.Errorf("CreateTriggerCheck: API error: %w", err) - } - - data.SetId(fmt.Sprintf("%d", result.ID)) - - return resourceTriggerCheckRead(data, client) -} - -func resourceTriggerCheckDelete(data *schema.ResourceData, client interface{}) error { - trigger, err := triggerCheckFromResourceData(data) - if err != nil { - return fmt.Errorf("resourceTriggerCheckDelete: translation error: %w", err) - } - - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - err = client.(checkly.Client).DeleteTriggerCheck(ctx, trigger.CheckId) - if err != nil { - return fmt.Errorf("DeleteTriggerCheck: API error: %w", err) - } - - return nil -} - -func resourceTriggerCheckRead(data *schema.ResourceData, client interface{}) error { - trigger, err := triggerCheckFromResourceData(data) - if err != nil { - return fmt.Errorf("resourceTriggerCheckRead: ID %s is not numeric: %w", data.Id(), err) - } - - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - result, err := client.(checkly.Client).GetTriggerCheck(ctx, trigger.CheckId) - defer cancel() - if err != nil { - if strings.Contains(err.Error(), "404") { - data.SetId("") - return nil - } - return fmt.Errorf("GetTriggerCheck: API error: %w", err) - } - - return resourceDataFromTriggerCheck(result, data) -} - -func resourceTriggerCheckUpdate(data *schema.ResourceData, client interface{}) error { - return resourceTriggerCheckRead(data, client) -} diff --git a/checkly/resource_trigger_group.go b/checkly/resource_trigger_group.go deleted file mode 100644 index 1b344df..0000000 --- a/checkly/resource_trigger_group.go +++ /dev/null @@ -1,123 +0,0 @@ -package checkly - -import ( - "context" - "fmt" - "strconv" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - checkly "github.com/checkly/checkly-go-sdk" -) - -func resourceTriggerGroup() *schema.Resource { - return &schema.Resource{ - Create: resourceTriggerGroupCreate, - Read: resourceTriggerGroupRead, - Delete: resourceTriggerGroupDelete, - Update: resourceTriggerGroupUpdate, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Schema: map[string]*schema.Schema{ - "group_id": { - Type: schema.TypeInt, - Required: true, - Description: "The id of the group that you want to attach the trigger to.", - }, - "token": { - Type: schema.TypeString, - Optional: true, - Computed: true, - Description: "The token value created to trigger the group", - }, - "url": { - Type: schema.TypeString, - Optional: true, - Computed: true, - Description: "The request URL to trigger the group run.", - }, - }, - } -} - -func triggerGroupFromResourceData(data *schema.ResourceData) (checkly.TriggerGroup, error) { - ID, err := strconv.ParseInt(data.Id(), 10, 64) - if err != nil { - if data.Id() != "" { - return checkly.TriggerGroup{}, err - } - ID = 0 - } - - return checkly.TriggerGroup{ - ID: ID, - GroupId: int64(data.Get("group_id").(int)), - Token: data.Get("token").(string), - }, nil -} - -func resourceDataFromTriggerGroup(trigger *checkly.TriggerGroup, data *schema.ResourceData) error { - data.Set("group_id", trigger.GroupId) - data.Set("token", trigger.Token) - data.Set("url", trigger.URL) - return nil -} - -func resourceTriggerGroupCreate(data *schema.ResourceData, client interface{}) error { - tc, err := triggerGroupFromResourceData(data) - if err != nil { - return fmt.Errorf("resourceTriggerGroupCreate: translation error: %w", err) - } - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - result, err := client.(checkly.Client).CreateTriggerGroup(ctx, tc.GroupId) - - if err != nil { - return fmt.Errorf("CreateTriggerGroup: API error: %w", err) - } - - data.SetId(fmt.Sprintf("%d", result.ID)) - - return resourceTriggerGroupRead(data, client) -} - -func resourceTriggerGroupDelete(data *schema.ResourceData, client interface{}) error { - tc, err := triggerGroupFromResourceData(data) - if err != nil { - return fmt.Errorf("resourceTriggerGroupDelete: translation error: %w", err) - } - - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - defer cancel() - err = client.(checkly.Client).DeleteTriggerGroup(ctx, tc.GroupId) - if err != nil { - return fmt.Errorf("DeleteTriggerGroup: API error: %w", err) - } - - return nil -} - -func resourceTriggerGroupRead(data *schema.ResourceData, client interface{}) error { - trigger, err := triggerGroupFromResourceData(data) - if err != nil { - return fmt.Errorf("resourceTriggerGroupRead: ID %s is not numeric: %w", data.Id(), err) - } - - ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout()) - result, err := client.(checkly.Client).GetTriggerGroup(ctx, trigger.GroupId) - defer cancel() - if err != nil { - if strings.Contains(err.Error(), "404") { - data.SetId("") - return nil - } - return fmt.Errorf("GetTriggerGroup: API error: %w", err) - } - return resourceDataFromTriggerGroup(result, data) -} - -func resourceTriggerGroupUpdate(data *schema.ResourceData, client interface{}) error { - return resourceTriggerCheckRead(data, client) -} diff --git a/go.mod b/go.mod index d37322a..b9b4e5f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,13 @@ module github.com/checkly/terraform-provider-checkly -go 1.22 +go 1.23.3 + +require ( + github.com/hashicorp/terraform-plugin-framework v1.13.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-log v0.9.0 +) require ( github.com/aws/aws-sdk-go v1.44.122 // indirect @@ -9,13 +16,12 @@ require ( github.com/google/go-cmp v0.6.0 github.com/gruntwork-io/terratest v0.41.16 github.com/hashicorp/terraform-plugin-docs v0.19.4 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.12.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 ) require ( cloud.google.com/go v0.105.0 // indirect - cloud.google.com/go/compute v1.12.1 // indirect - cloud.google.com/go/compute/metadata v0.2.1 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect cloud.google.com/go/iam v0.7.0 // indirect cloud.google.com/go/storage v1.27.0 // indirect github.com/BurntSushi/toml v1.2.1 // indirect @@ -25,7 +31,6 @@ require ( github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect github.com/agext/levenshtein v1.2.3 // indirect - github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect @@ -34,8 +39,8 @@ require ( github.com/cloudflare/circl v1.3.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.16.0 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/hashicorp/cli v1.1.6 // indirect @@ -44,22 +49,21 @@ 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-getter v1.7.1 // indirect - github.com/hashicorp/go-hclog v1.2.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.4.3 // indirect + github.com/hashicorp/go-plugin v1.6.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-safetemp v1.0.0 // 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.7.0 // indirect - github.com/hashicorp/hcl/v2 v2.11.1 // indirect + github.com/hashicorp/hc-install v0.8.0 // indirect + github.com/hashicorp/hcl/v2 v2.21.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-plugin-go v0.8.0 // indirect - github.com/hashicorp/terraform-plugin-log v0.3.0 // indirect - github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 // indirect - github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect - github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d // 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 github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a // indirect @@ -73,37 +77,39 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/posener/complete v1.2.3 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/spf13/cast v1.5.0 // indirect - github.com/stretchr/testify v1.8.2 // indirect + github.com/stretchr/testify v1.8.3 // indirect github.com/tmccombs/hcl2json v0.3.3 // indirect github.com/ulikunitz/xz v0.5.10 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect - github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect - github.com/vmihailenco/tagparser v0.1.1 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/yuin/goldmark v1.7.1 // indirect github.com/yuin/goldmark-meta v1.1.0 // indirect - github.com/zclconf/go-cty v1.14.4 // indirect + github.com/zclconf/go-cty v1.15.0 // indirect go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.21.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/oauth2 v0.1.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/mod v0.19.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.103.0 // indirect - google.golang.org/appengine v1.6.7 // indirect + google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c // indirect - google.golang.org/grpc v1.51.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 81364d0..64d6913 100644 --- a/go.sum +++ b/go.sum @@ -68,10 +68,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= @@ -200,35 +198,23 @@ github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7Y github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f/go.mod h1:k8feO4+kXDxro6ErPXBRTJ/ro2mf0SsFG8s7doP9kJE= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= -github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= -github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= -github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= @@ -237,6 +223,8 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -264,7 +252,6 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -278,22 +265,16 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= -github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -334,8 +315,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -379,8 +361,9 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= @@ -408,78 +391,71 @@ github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= github.com/hashicorp/go-getter v1.7.1 h1:SWiSWN/42qdpR0MdhaOc/bLR48PLuP1ZQtYLRlM69uY= github.com/hashicorp/go-getter v1.7.1/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= -github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 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.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= -github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +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-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 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/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hc-install v0.3.1/go.mod h1:3LCdWcCDS1gaHC9mhHCGbkYfoY6vdsKohGjugbZdZak= -github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk= -github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA= +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.9.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= -github.com/hashicorp/hcl/v2 v2.11.1 h1:yTyWcXcm9XB0TEkyU/JCRU6rYy4K+mgLtzn2wlrJbcc= -github.com/hashicorp/hcl/v2 v2.11.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= +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/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.16.0/go.mod h1:wB5JHmjxZ/YVNZuv9npAXKmz5pGyxy8PSi0GRR0+YjA= 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.13.0/go.mod h1:y5OdLBCT+rxbwnpxZs9kGL7R9ExU76+cpdY8zHwoazk= 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-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c= github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA= -github.com/hashicorp/terraform-plugin-go v0.8.0 h1:MvY43PcDj9VlBjYifBWCO/6j1wf106xU8d5Tob/WRs0= -github.com/hashicorp/terraform-plugin-go v0.8.0/go.mod h1:E3GuvfX0Pz2Azcl6BegD6t51StXsVZMOYQoGO8mkHM0= -github.com/hashicorp/terraform-plugin-log v0.3.0 h1:NPENNOjaJSVX0f7JJTl4f/2JKRPQ7S2ZN9B4NSqq5kA= -github.com/hashicorp/terraform-plugin-log v0.3.0/go.mod h1:EjueSP/HjlyFAsDqt+okpCPjkT4NDynAe32AeDC4vps= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.12.0 h1:rjJxyLUVA180BG0ZXTOree4x2RVvo2jigdYoT2rw5j0= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.12.0/go.mod h1:TPjMXvpPNWagHzYOmVPzzRRIBTuaLVukR+esL08tgzg= -github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 h1:1FGtlkJw87UsTMg5s8jrekrHmUPUJaMcu6ELiVhQrNw= -github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896/go.mod h1:bzBPnUIkI0RxauU8Dqo+2KrZZ28Cf48s8V6IHt3p4co= -github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= -github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d h1:W+SIwDdl3+jXWeidYySAgzytE3piq6GumXeBjFBG67c= -github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +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-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-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= +github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -488,13 +464,11 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -504,16 +478,15 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -527,27 +500,20 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= -github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= -github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -558,16 +524,12 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -576,20 +538,19 @@ github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tmccombs/hcl2json v0.3.3 h1:+DLNYqpWE0CsOQiEZu+OZm5ZBImake3wtITYxQ8uLFQ= github.com/tmccombs/hcl2json v0.3.3/go.mod h1:Y2chtz2x9bAeRTvSibVRVgbLJhLJXKlUeIvjeVdnm4w= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= @@ -597,11 +558,12 @@ github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o 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= -github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -614,15 +576,14 @@ github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= -github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.8.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty v1.9.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= -github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= +github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -635,20 +596,16 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -687,9 +644,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -703,7 +659,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -725,7 +680,6 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -741,8 +695,8 @@ golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -767,8 +721,9 @@ golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7Lm golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -783,10 +738,10 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/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.0.0-20220929204114-8fcdb60fdcc0/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/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -795,12 +750,11 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -823,10 +777,8 @@ golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -836,6 +788,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -856,14 +809,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.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/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -873,9 +826,10 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -916,7 +870,6 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -932,8 +885,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -999,9 +952,9 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +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 v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1029,7 +982,6 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1106,7 +1058,6 @@ google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c h1:S34D59DS2GWOEwWNt4fYmTcFrtlOgukG2k9WsomZ7tg= google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1142,8 +1093,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1159,21 +1110,18 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/internal/provider/alert_channel_resource.go b/internal/provider/alert_channel_resource.go new file mode 100644 index 0000000..5c8c573 --- /dev/null +++ b/internal/provider/alert_channel_resource.go @@ -0,0 +1,731 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" + "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ resource.Resource = (*AlertChannelResource)(nil) + _ resource.ResourceWithConfigure = (*AlertChannelResource)(nil) + _ resource.ResourceWithImportState = (*AlertChannelResource)(nil) + _ resource.ResourceWithConfigValidators = (*AlertChannelResource)(nil) +) + +type AlertChannelResource struct { + client checkly.Client +} + +func NewAlertChannelResource() resource.Resource { + return &AlertChannelResource{} +} + +func (r *AlertChannelResource) Metadata( + ctx context.Context, + req resource.MetadataRequest, + resp *resource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_alert_channel" +} + +func (r *AlertChannelResource) Schema( + ctx context.Context, + req resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + // TODO: Investigate UpgradeState's potential ability to allow prior + // 1-length Sets and Lists to be seamlessly converted to + // SingleNestedAttributes. + resp.Schema = schema.Schema{ + Description: "Allows you to define alerting channels for the checks and groups in your account.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "", // TODO + }, + "last_updated": LastUpdatedAttributeSchema, + "email": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "address": schema.StringAttribute{ + Required: true, + Description: "The email address of this email alert channel.", + }, + }, + }, + "slack": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "url": schema.StringAttribute{ + Required: true, + Description: "The Slack webhook URL", + }, + "channel": schema.StringAttribute{ + Required: true, + Description: "The name of the alert's Slack channel", + }, + }, + }, + "sms": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + Description: "The name of this alert channel", + }, + "number": schema.StringAttribute{ + Required: true, + Description: "The mobile number to receive the alerts", + }, + }, + }, + "call": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + Description: "The name of this alert channel", + }, + "number": schema.StringAttribute{ + Required: true, + Description: "The mobile number to receive the alerts", + }, + }, + }, + "webhook": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + }, + "method": schema.StringAttribute{ + Optional: true, + Default: stringdefault.StaticString("POST"), + Description: "(Default `POST`)", + }, + "headers": schema.MapAttribute{ + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "query_parameters": schema.MapAttribute{ + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "template": schema.StringAttribute{ + Optional: true, + }, + "url": schema.StringAttribute{ + Required: true, + }, + "webhook_secret": schema.StringAttribute{ + Optional: true, + }, + "webhook_type": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "WEBHOOK_DISCORD", + "WEBHOOK_FIREHYDRANT", + "WEBHOOK_GITLAB_ALERT", + "WEBHOOK_SPIKESH", + "WEBHOOK_SPLUNK", + "WEBHOOK_MSTEAMS", + "WEBHOOK_TELEGRAM", + ), + }, + Description: "Type of the webhook. Possible values are 'WEBHOOK_DISCORD', 'WEBHOOK_FIREHYDRANT', 'WEBHOOK_GITLAB_ALERT', 'WEBHOOK_SPIKESH', 'WEBHOOK_SPLUNK', 'WEBHOOK_MSTEAMS' and 'WEBHOOK_TELEGRAM'.", + }, + }, + }, + "opsgenie": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + }, + "api_key": schema.StringAttribute{ + Required: true, + }, + "region": schema.StringAttribute{ + Required: true, + }, + "priority": schema.StringAttribute{ + Required: true, + }, + }, + }, + "pagerduty": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "service_key": schema.StringAttribute{ + Required: true, + }, + "service_name": schema.StringAttribute{ + Optional: true, + }, + "account": schema.StringAttribute{ + Optional: true, + }, + }, + }, + "send_recovery": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(true), + Description: "(Default `true`)", + }, + "send_failure": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(true), + Description: "(Default `true`)", + }, + "send_degraded": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + Description: "(Default `false`)", + }, + "ssl_expiry": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + Description: "(Default `false`)", + }, + "ssl_expiry_threshold": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(30), + Validators: []validator.Int32{ + int32validator.Between(1, 30), + }, + Description: "Value must be between 1 and 30 (Default `30`)", + }, + }, + } +} + +func (r *AlertChannelResource) ConfigValidators( + ctx context.Context, +) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.ExactlyOneOf( + path.MatchRoot("email"), + path.MatchRoot("slack"), + path.MatchRoot("sms"), + path.MatchRoot("call"), + path.MatchRoot("webhook"), + path.MatchRoot("opsgenie"), + path.MatchRoot("pagerduty"), + ), + } +} + +func (r *AlertChannelResource) Configure( + ctx context.Context, + req resource.ConfigureRequest, + resp *resource.ConfigureResponse, +) { + client, diags := ClientFromProviderData(req.ProviderData) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + r.client = client +} + +func (r *AlertChannelResource) ImportState( + ctx context.Context, + req resource.ImportStateRequest, + resp *resource.ImportStateResponse, +) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *AlertChannelResource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan AlertChannelResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.AlertChannel + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.CreateAlertChannel(ctx, desiredModel) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Checkly Alert Channel", + fmt.Sprintf("Could not create alert channel, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *AlertChannelResource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state AlertChannelResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + id, diags := AlertChannelID.FromString(state.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + err := r.client.DeleteAlertChannel(ctx, id) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting Checkly Alert Channel", + fmt.Sprintf("Could not delete alert channel, unexpected error: %s", err), + ) + + return + } +} + +func (r *AlertChannelResource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state AlertChannelResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + id, diags := AlertChannelID.FromString(state.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + // TODO: Check if we really have to do the weird 404 handling + realizedModel, err := r.client.GetAlertChannel(ctx, id) + if err != nil { + resp.Diagnostics.AddError( + "Error Reading Checkly Alert Channel", + fmt.Sprintf("Could not retrieve alert channel, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *AlertChannelResource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan AlertChannelResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + id, diags := AlertChannelID.FromString(plan.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + var desiredModel checkly.AlertChannel + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.UpdateAlertChannel( + ctx, + id, + desiredModel, + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Checkly Alert Channel", + fmt.Sprintf("Could not update alert channel, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +var AlertChannelID = SDKIdentifier{ + Path: path.Root("id"), + Title: "Checkly Alert Channel ID", +} + +var ( + _ ResourceModel[checkly.AlertChannel] = (*AlertChannelResourceModel)(nil) + _ ResourceModel[checkly.AlertChannelEmail] = (*EmailAttributeModel)(nil) + _ ResourceModel[checkly.AlertChannelSlack] = (*SlackAttributeModel)(nil) + _ ResourceModel[checkly.AlertChannelSMS] = (*SMSAttributeModel)(nil) + _ ResourceModel[checkly.AlertChannelCall] = (*CallAttributeModel)(nil) + _ ResourceModel[checkly.AlertChannelWebhook] = (*WebhookAttributeModel)(nil) + _ ResourceModel[checkly.AlertChannelOpsgenie] = (*OpsgenieAttributeModel)(nil) + _ ResourceModel[checkly.AlertChannelPagerduty] = (*PagerdutyAttributeModel)(nil) +) + +type AlertChannelResourceModel struct { + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + Email *EmailAttributeModel `tfsdk:"email"` + Slack *SlackAttributeModel `tfsdk:"slack"` + SMS *SMSAttributeModel `tfsdk:"sms"` + Call *CallAttributeModel `tfsdk:"call"` + Webhook *WebhookAttributeModel `tfsdk:"webhook"` + Opsgenie *OpsgenieAttributeModel `tfsdk:"opsgenie"` + Pagerduty *PagerdutyAttributeModel `tfsdk:"pagerduty"` + SendRecovery types.Bool `tfsdk:"send_recovery"` + SendFailure types.Bool `tfsdk:"send_failure"` + SendDegraded types.Bool `tfsdk:"send_degraded"` + SSLExpiry types.Bool `tfsdk:"ssl_expiry"` + SSLExpiryThreshold types.Int32 `tfsdk:"ssl_expiry_threshold"` +} + +func (m *AlertChannelResourceModel) Refresh(ctx context.Context, from *checkly.AlertChannel, flags RefreshFlags) diag.Diagnostics { + var diags diag.Diagnostics + + if flags.Created() { + m.ID = AlertChannelID.IntoString(from.ID) + } + + if flags.Created() || flags.Updated() { + m.LastUpdated = LastUpdatedNow() + } + + m.Email = nil + m.Slack = nil + m.SMS = nil + m.Call = nil + m.Webhook = nil + m.Opsgenie = nil + m.Pagerduty = nil + + switch from.Type { + case checkly.AlertTypeEmail: + m.Email = new(EmailAttributeModel) + diags.Append(m.Email.Refresh(ctx, from.Email, flags)...) + case checkly.AlertTypeSlack: + m.Slack = new(SlackAttributeModel) + diags.Append(m.Slack.Refresh(ctx, from.Slack, flags)...) + case checkly.AlertTypeSMS: + m.SMS = new(SMSAttributeModel) + diags.Append(m.SMS.Refresh(ctx, from.SMS, flags)...) + case checkly.AlertTypeCall: + m.Call = new(CallAttributeModel) + diags.Append(m.Call.Refresh(ctx, from.CALL, flags)...) + case checkly.AlertTypeWebhook: + m.Webhook = new(WebhookAttributeModel) + diags.Append(m.Webhook.Refresh(ctx, from.Webhook, flags)...) + case checkly.AlertTypeOpsgenie: + m.Opsgenie = new(OpsgenieAttributeModel) + diags.Append(m.Opsgenie.Refresh(ctx, from.Opsgenie, flags)...) + case checkly.AlertTypePagerduty: + m.Pagerduty = new(PagerdutyAttributeModel) + diags.Append(m.Pagerduty.Refresh(ctx, from.Pagerduty, flags)...) + default: + // TODO diags + } + + if from.SendRecovery != nil { + m.SendRecovery = types.BoolValue(*from.SendRecovery) + } else { + m.SendRecovery = types.BoolNull() + } + + if from.SendFailure != nil { + m.SendFailure = types.BoolValue(*from.SendFailure) + } else { + m.SendFailure = types.BoolNull() + } + + if from.SendDegraded != nil { + m.SendDegraded = types.BoolValue(*from.SendDegraded) + } else { + m.SendDegraded = types.BoolNull() + } + + if from.SSLExpiry != nil { + m.SSLExpiry = types.BoolValue(*from.SSLExpiry) + } else { + m.SSLExpiry = types.BoolNull() + } + + if from.SSLExpiryThreshold != nil { + m.SSLExpiryThreshold = types.Int32Value(int32(*from.SSLExpiryThreshold)) + } else { + m.SSLExpiryThreshold = types.Int32Null() + } + + return nil +} + +func (m *AlertChannelResourceModel) Render(ctx context.Context, into *checkly.AlertChannel) diag.Diagnostics { + var diags diag.Diagnostics + + switch { + case m.Email != nil: + into.Type = checkly.AlertTypeEmail + into.Email = new(checkly.AlertChannelEmail) + diags.Append(m.Email.Render(ctx, into.Email)...) + case m.Slack != nil: + into.Type = checkly.AlertTypeSlack + into.Slack = new(checkly.AlertChannelSlack) + diags.Append(m.Slack.Render(ctx, into.Slack)...) + case m.SMS != nil: + into.Type = checkly.AlertTypeSMS + into.SMS = new(checkly.AlertChannelSMS) + diags.Append(m.SMS.Render(ctx, into.SMS)...) + case m.Call != nil: + into.Type = checkly.AlertTypeCall + into.CALL = new(checkly.AlertChannelCall) + diags.Append(m.Call.Render(ctx, into.CALL)...) + case m.Opsgenie != nil: + into.Type = checkly.AlertTypeOpsgenie + into.Opsgenie = new(checkly.AlertChannelOpsgenie) + diags.Append(m.Opsgenie.Render(ctx, into.Opsgenie)...) + case m.Webhook != nil: + into.Type = checkly.AlertTypeWebhook + into.Webhook = new(checkly.AlertChannelWebhook) + diags.Append(m.Webhook.Render(ctx, into.Webhook)...) + case m.Pagerduty != nil: + into.Type = checkly.AlertTypePagerduty + into.Pagerduty = new(checkly.AlertChannelPagerduty) + diags.Append(m.Pagerduty.Render(ctx, into.Pagerduty)...) + default: + // TODO: Use diags instead + panic("bug: impossible AlertChannelResourceModel state: no type set") + } + + into.SendRecovery = m.SendRecovery.ValueBoolPointer() + into.SendFailure = m.SendFailure.ValueBoolPointer() + into.SendDegraded = m.SendDegraded.ValueBoolPointer() + into.SSLExpiry = m.SSLExpiry.ValueBoolPointer() + + if !m.SSLExpiryThreshold.IsNull() { + value := int(m.SSLExpiryThreshold.ValueInt32()) + into.SSLExpiryThreshold = &value + } + + return diags +} + +type EmailAttributeModel struct { + Address types.String `tfsdk:"address"` +} + +func (m *EmailAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelEmail, flags RefreshFlags) diag.Diagnostics { + m.Address = types.StringValue(from.Address) + + return nil +} + +func (m *EmailAttributeModel) Render(ctx context.Context, into *checkly.AlertChannelEmail) diag.Diagnostics { + into.Address = m.Address.ValueString() + + return nil +} + +type SlackAttributeModel struct { + URL types.String `tfsdk:"url"` + Channel types.String `tfsdk:"channel"` +} + +func (m *SlackAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelSlack, flags RefreshFlags) diag.Diagnostics { + m.URL = types.StringValue(from.WebhookURL) + m.Channel = types.StringValue(from.Channel) + + return nil +} + +func (m *SlackAttributeModel) Render(ctx context.Context, into *checkly.AlertChannelSlack) diag.Diagnostics { + into.WebhookURL = m.URL.ValueString() + into.Channel = m.Channel.ValueString() + + return nil +} + +type SMSAttributeModel struct { + Name types.String `tfsdk:"name"` + Number types.String `tfsdk:"number"` +} + +func (m *SMSAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelSMS, flags RefreshFlags) diag.Diagnostics { + m.Name = types.StringValue(from.Name) + m.Number = types.StringValue(from.Number) + + return nil +} + +func (m *SMSAttributeModel) Render(ctx context.Context, into *checkly.AlertChannelSMS) diag.Diagnostics { + into.Name = m.Name.ValueString() + into.Number = m.Number.ValueString() + + return nil +} + +type CallAttributeModel struct { + Name types.String `tfsdk:"name"` + Number types.String `tfsdk:"number"` +} + +func (m *CallAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelCall, flags RefreshFlags) diag.Diagnostics { + m.Name = types.StringValue(from.Name) + m.Number = types.StringValue(from.Number) + + return nil +} + +func (m *CallAttributeModel) Render(ctx context.Context, into *checkly.AlertChannelCall) diag.Diagnostics { + into.Name = m.Name.ValueString() + into.Number = m.Number.ValueString() + + return nil +} + +type WebhookAttributeModel struct { + Name types.String `tfsdk:"name"` + Method types.String `tfsdk:"method"` + Headers types.Map `tfsdk:"headers"` + QueryParameters types.Map `tfsdk:"query_parameters"` + Template types.String `tfsdk:"template"` + URL types.String `tfsdk:"url"` + WebhookSecret types.String `tfsdk:"webhook_secret"` + WebhookType types.String `tfsdk:"webhook_type"` +} + +func (m *WebhookAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelWebhook, flags RefreshFlags) diag.Diagnostics { + m.Name = types.StringValue(from.Name) + m.Method = types.StringValue(from.Method) + m.Headers = SDKKeyValuesIntoMap(&from.Headers) + m.QueryParameters = SDKKeyValuesIntoMap(&from.QueryParameters) + m.Template = types.StringValue(from.Template) + m.URL = types.StringValue(from.URL) + m.WebhookSecret = types.StringValue(from.WebhookSecret) + m.WebhookType = types.StringValue(from.WebhookType) + + return nil +} + +func (m *WebhookAttributeModel) Render(ctx context.Context, into *checkly.AlertChannelWebhook) diag.Diagnostics { + into.Name = m.Name.ValueString() + into.Method = m.Method.ValueString() + into.Headers = SDKKeyValuesFromMap(m.Headers) + into.QueryParameters = SDKKeyValuesFromMap(m.QueryParameters) + into.Template = m.Template.ValueString() + into.URL = m.URL.ValueString() + into.WebhookSecret = m.WebhookSecret.ValueString() + into.WebhookType = m.WebhookType.ValueString() + + return nil +} + +type OpsgenieAttributeModel struct { + Name types.String `tfsdk:"name"` + APIKey types.String `tfsdk:"api_key"` + Region types.String `tfsdk:"region"` + Priority types.String `tfsdk:"priority"` +} + +func (m *OpsgenieAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelOpsgenie, flags RefreshFlags) diag.Diagnostics { + m.Name = types.StringValue(from.Name) + m.APIKey = types.StringValue(from.APIKey) + m.Region = types.StringValue(from.Region) + m.Priority = types.StringValue(from.Priority) + + return nil +} + +func (m *OpsgenieAttributeModel) Render(ctx context.Context, into *checkly.AlertChannelOpsgenie) diag.Diagnostics { + into.Name = m.Name.ValueString() + into.APIKey = m.APIKey.ValueString() + into.Region = m.Region.ValueString() + into.Priority = m.Priority.ValueString() + + return nil +} + +type PagerdutyAttributeModel struct { + ServiceKey types.String `tfsdk:"service_key"` + ServiceName types.String `tfsdk:"service_name"` + Account types.String `tfsdk:"account"` +} + +func (m *PagerdutyAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelPagerduty, flags RefreshFlags) diag.Diagnostics { + m.ServiceKey = types.StringValue(from.ServiceKey) + m.ServiceName = types.StringValue(from.ServiceName) + m.Account = types.StringValue(from.Account) + + return nil +} + +func (m *PagerdutyAttributeModel) Render(ctx context.Context, into *checkly.AlertChannelPagerduty) diag.Diagnostics { + into.ServiceKey = m.ServiceKey.ValueString() + into.ServiceName = m.ServiceName.ValueString() + into.Account = m.Account.ValueString() + + return nil +} diff --git a/internal/provider/check_alert_channel_subscription_attribute.go b/internal/provider/check_alert_channel_subscription_attribute.go new file mode 100644 index 0000000..980bdb9 --- /dev/null +++ b/internal/provider/check_alert_channel_subscription_attribute.go @@ -0,0 +1,52 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ ResourceModel[checkly.AlertChannelSubscription] = (*CheckAlertChannelSubscriptionAttributeModel)(nil) +) + +var CheckAlertChannelSubscriptionAttributeSchema = schema.ListNestedAttribute{ + Description: "An array of channel IDs and whether they're activated or " + + "not. If you don't set at least one alert subscription for your " + + "check, we won't be able to alert you in case something goes wrong " + + "with it.", + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "channel_id": schema.Int64Attribute{ + Required: true, + }, + "activated": schema.BoolAttribute{ + Required: true, + }, + }, + }, +} + +type CheckAlertChannelSubscriptionAttributeModel struct { + ChannelID types.Int64 `tfsdk:"channel_id"` + Activated types.Bool `tfsdk:"activated"` +} + +func (m *CheckAlertChannelSubscriptionAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelSubscription, flags RefreshFlags) diag.Diagnostics { + m.ChannelID = types.Int64Value(from.ChannelID) + m.Activated = types.BoolValue(from.Activated) + + return nil +} + +func (m *CheckAlertChannelSubscriptionAttributeModel) Render(ctx context.Context, into *checkly.AlertChannelSubscription) diag.Diagnostics { + into.ChannelID = m.ChannelID.ValueInt64() + into.Activated = m.Activated.ValueBool() + + return nil +} diff --git a/internal/provider/check_alert_settings_attribute.go b/internal/provider/check_alert_settings_attribute.go new file mode 100644 index 0000000..b32234a --- /dev/null +++ b/internal/provider/check_alert_settings_attribute.go @@ -0,0 +1,269 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "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/int32default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ ResourceModel[checkly.AlertSettings] = (*CheckAlertSettingsAttributeModel)(nil) + _ ResourceModel[checkly.RunBasedEscalation] = (*CheckRunBasedEscalationAttributeModel)(nil) + _ ResourceModel[checkly.TimeBasedEscalation] = (*CheckTimeBasedEscalationAttributeModel)(nil) + _ ResourceModel[checkly.Reminders] = (*CheckRemindersAttributeModel)(nil) + _ ResourceModel[checkly.ParallelRunFailureThreshold] = (*CheckParallelRunFailureThresholdAttributeModel)(nil) + _ ResourceModel[checkly.SSLCertificates] = (*CheckSSLCertificatesAttributeModel)(nil) +) + +var CheckAlertSettingsAttributeSchema = schema.SingleNestedAttribute{ + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "escalation_type": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("RUN_BASED", "TIME_BASED"), + }, + Default: stringdefault.StaticString("RUN_BASED"), + Description: "Determines what type of escalation to use. Possible values are `RUN_BASED` or `TIME_BASED`.", + }, + "run_based_escalation": schema.SingleNestedAttribute{ + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "failed_run_threshold": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(1), + Validators: []validator.Int32{ + int32validator.Between(1, 5), + }, + Description: "After how many failed consecutive check runs an alert notification should be sent. Possible values are between 1 and 5. (Default `1`).", + }, + }, + }, + "time_based_escalation": schema.SingleNestedAttribute{ + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "minutes_failing_threshold": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(5), + Validators: []validator.Int32{ + int32validator.OneOf(5, 10, 15, 30), + }, + Description: "After how many minutes after a check starts failing an alert should be sent. Possible values are `5`, `10`, `15`, and `30`. (Default `5`).", + }, + }, + }, + "reminders": schema.SingleNestedAttribute{ + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "amount": schema.Int32Attribute{ + Optional: true, + Validators: []validator.Int32{ + int32validator.OneOf(0, 1, 2, 3, 4, 5, 100000), + }, + Description: "How many reminders to send out after the initial alert notification. Possible values are `0`, `1`, `2`, `3`, `4`, `5`, and `100000`", + }, + "interval": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(5), + Validators: []validator.Int32{ + int32validator.OneOf(5, 10, 15, 30), + }, + Description: "Possible values are `5`, `10`, `15`, and `30`. (Default `5`).", + }, + }, + }, + "parallel_run_failure_threshold": schema.SingleNestedAttribute{ + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + Description: "Applicable only for checks scheduled in parallel in multiple locations.", + }, + "percentage": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(10), + Validators: []validator.Int32{ + int32validator.OneOf(10, 20, 30, 40, 50, 60, 70, 80, 90, 100), + }, + Description: "Possible values are `10`, `20`, `30`, `40`, `50`, `60`, `70`, `80`, `90`, and `100`. (Default `10`).", + }, + }, + }, + "ssl_certificates": schema.SingleNestedAttribute{ + Optional: true, + DeprecationMessage: "This property is deprecated and it's ignored by the Checkly Public API. It will be removed in a future version.", + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + Description: "Determines if alert notifications should be sent for expiring SSL certificates. Possible values `true`, and `false`. (Default `false`).", + }, + "alert_threshold": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(3), + Validators: []validator.Int32{ + int32validator.OneOf(3, 7, 14, 30), + }, + Description: "How long before SSL certificate expiry to send alerts. Possible values `3`, `7`, `14`, `30`. (Default `3`).", + }, + }, + Description: "At what interval the reminders should be sent.", + }, + }, +} + +type CheckAlertSettingsAttributeModel struct { + EscalationType types.String `tfsdk:"escalation_type"` + RunBasedEscalations CheckRunBasedEscalationAttributeModel `tfsdk:"run_based_escalation"` + TimeBasedEscalations CheckTimeBasedEscalationAttributeModel `tfsdk:"time_based_escalation"` + Reminders CheckRemindersAttributeModel `tfsdk:"reminders"` + ParallelRunFailureThreshold CheckParallelRunFailureThresholdAttributeModel `tfsdk:"parallel_run_failure_threshold"` + SSLCertificates CheckSSLCertificatesAttributeModel `tfsdk:"ssl_certificates"` +} + +func (m *CheckAlertSettingsAttributeModel) Refresh(ctx context.Context, from *checkly.AlertSettings, flags RefreshFlags) diag.Diagnostics { + var diags diag.Diagnostics + + m.EscalationType = types.StringValue(from.EscalationType) + + switch from.EscalationType { + case checkly.RunBased: + diags.Append(m.RunBasedEscalations.Refresh(ctx, &from.RunBasedEscalation, flags)...) + case checkly.TimeBased: + diags.Append(m.TimeBasedEscalations.Refresh(ctx, &from.TimeBasedEscalation, flags)...) + default: + // TODO diags + } + + diags.Append(m.Reminders.Refresh(ctx, &from.Reminders, flags)...) + diags.Append(m.ParallelRunFailureThreshold.Refresh(ctx, &from.ParallelRunFailureThreshold, flags)...) + diags.Append(m.SSLCertificates.Refresh(ctx, &from.SSLCertificates, flags)...) + + return diags +} + +func (m *CheckAlertSettingsAttributeModel) Render(ctx context.Context, into *checkly.AlertSettings) diag.Diagnostics { + var diags diag.Diagnostics + + switch m.EscalationType.ValueString() { + case checkly.RunBased: + into.EscalationType = checkly.RunBased + diags.Append(m.RunBasedEscalations.Render(ctx, &into.RunBasedEscalation)...) + case checkly.TimeBased: + into.EscalationType = checkly.TimeBased + diags.Append(m.TimeBasedEscalations.Render(ctx, &into.TimeBasedEscalation)...) + default: + // TODO diags + } + + diags.Append(m.Reminders.Render(ctx, &into.Reminders)...) + diags.Append(m.ParallelRunFailureThreshold.Render(ctx, &into.ParallelRunFailureThreshold)...) + diags.Append(m.SSLCertificates.Render(ctx, &into.SSLCertificates)...) + + return diags +} + +type CheckRunBasedEscalationAttributeModel struct { + FailedRunThreshold types.Int32 `tfsdk:"failed_run_threshold"` +} + +func (m *CheckRunBasedEscalationAttributeModel) Refresh(ctx context.Context, from *checkly.RunBasedEscalation, flags RefreshFlags) diag.Diagnostics { + m.FailedRunThreshold = types.Int32Value(int32(from.FailedRunThreshold)) + + return nil +} + +func (m *CheckRunBasedEscalationAttributeModel) Render(ctx context.Context, into *checkly.RunBasedEscalation) diag.Diagnostics { + into.FailedRunThreshold = int(m.FailedRunThreshold.ValueInt32()) + + return nil +} + +type CheckTimeBasedEscalationAttributeModel struct { + MinutesFailingThreshold types.Int32 `tfsdk:"minutes_failing_threshold"` +} + +func (m *CheckTimeBasedEscalationAttributeModel) Refresh(ctx context.Context, from *checkly.TimeBasedEscalation, flags RefreshFlags) diag.Diagnostics { + m.MinutesFailingThreshold = types.Int32Value(int32(from.MinutesFailingThreshold)) + + return nil +} + +func (m *CheckTimeBasedEscalationAttributeModel) Render(ctx context.Context, into *checkly.TimeBasedEscalation) diag.Diagnostics { + into.MinutesFailingThreshold = int(m.MinutesFailingThreshold.ValueInt32()) + + return nil +} + +type CheckRemindersAttributeModel struct { + Amount types.Int32 `tfsdk:"amount"` + Interval types.Int32 `tfsdk:"interval"` +} + +func (m *CheckRemindersAttributeModel) Refresh(ctx context.Context, from *checkly.Reminders, flags RefreshFlags) diag.Diagnostics { + m.Amount = types.Int32Value(int32(from.Amount)) + m.Interval = types.Int32Value(int32(from.Interval)) + + return nil +} + +func (m *CheckRemindersAttributeModel) Render(ctx context.Context, into *checkly.Reminders) diag.Diagnostics { + into.Amount = int(m.Amount.ValueInt32()) + into.Interval = int(m.Interval.ValueInt32()) + + return nil +} + +type CheckParallelRunFailureThresholdAttributeModel struct { + Enabled types.Bool `tfsdk:"enabled"` + Percentage types.Int32 `tfsdk:"percentage"` +} + +func (m *CheckParallelRunFailureThresholdAttributeModel) Refresh(ctx context.Context, from *checkly.ParallelRunFailureThreshold, flags RefreshFlags) diag.Diagnostics { + m.Enabled = types.BoolValue(from.Enabled) + m.Percentage = types.Int32Value(int32(from.Percentage)) + + return nil +} + +func (m *CheckParallelRunFailureThresholdAttributeModel) Render(ctx context.Context, into *checkly.ParallelRunFailureThreshold) diag.Diagnostics { + into.Enabled = m.Enabled.ValueBool() + into.Percentage = int(m.Percentage.ValueInt32()) + + return nil +} + +type CheckSSLCertificatesAttributeModel struct { + Enabled types.Bool `tfsdk:"enabled"` + AlertThreshold types.Int32 `tfsdk:"alert_threshold"` +} + +func (m *CheckSSLCertificatesAttributeModel) Refresh(ctx context.Context, from *checkly.SSLCertificates, flags RefreshFlags) diag.Diagnostics { + m.Enabled = types.BoolValue(from.Enabled) + m.AlertThreshold = types.Int32Value(int32(from.AlertThreshold)) + + return nil +} + +func (m *CheckSSLCertificatesAttributeModel) Render(ctx context.Context, into *checkly.SSLCertificates) diag.Diagnostics { + into.Enabled = m.Enabled.ValueBool() + into.AlertThreshold = int(m.AlertThreshold.ValueInt32()) + + return nil +} diff --git a/internal/provider/check_api_check_defaults_attribute.go b/internal/provider/check_api_check_defaults_attribute.go new file mode 100644 index 0000000..c0bc5e9 --- /dev/null +++ b/internal/provider/check_api_check_defaults_attribute.go @@ -0,0 +1,66 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ ResourceModel[checkly.APICheckDefaults] = (*CheckAPICheckDefaultsAttributeModel)(nil) +) + +var CheckAPICheckDefaultsAttributeSchema = schema.SingleNestedAttribute{ + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "url": schema.StringAttribute{ + Required: true, + Description: "The base url for this group which you can reference with the `GROUP_BASE_URL` variable in all group checks.", + }, + "headers": CheckHeadersAttributeSchema, + "query_parameters": CheckQueryParametersAttributeSchema, + "assertion": CheckAssertionAttributeSchema, + "basic_auth": CheckBasicAuthAttributeSchema, + }, +} + +type CheckAPICheckDefaultsAttributeModel struct { + URL types.String `tfsdk:"url"` + Headers types.Map `tfsdk:"headers"` + QueryParameters types.Map `tfsdk:"query_parameters"` + Assertions []CheckAssertionAttributeModel `tfsdk:"assertion"` + BasicAuth CheckBasicAuthAttributeModel `tfsdk:"basic_auth"` +} + +func (m *CheckAPICheckDefaultsAttributeModel) Refresh(ctx context.Context, from *checkly.APICheckDefaults, flags RefreshFlags) diag.Diagnostics { + var diags diag.Diagnostics + + m.URL = types.StringValue(from.BaseURL) + m.Headers = SDKKeyValuesIntoMap(&from.Headers) + m.QueryParameters = SDKKeyValuesIntoMap(&from.QueryParameters) + + diags.Append(RefreshMany(ctx, from.Assertions, m.Assertions, flags)...) + + diags.Append(m.BasicAuth.Refresh(ctx, &from.BasicAuth, flags)...) + + return diags +} + +func (m *CheckAPICheckDefaultsAttributeModel) Render(ctx context.Context, into *checkly.APICheckDefaults) diag.Diagnostics { + var diags diag.Diagnostics + + into.BaseURL = m.URL.ValueString() + into.Headers = SDKKeyValuesFromMap(m.Headers) + into.QueryParameters = SDKKeyValuesFromMap(m.QueryParameters) + + diags.Append(RenderMany(ctx, m.Assertions, into.Assertions)...) + + diags.Append(m.BasicAuth.Render(ctx, &into.BasicAuth)...) + + return diags +} diff --git a/internal/provider/check_assertion_attribute.go b/internal/provider/check_assertion_attribute.go new file mode 100644 index 0000000..250ff60 --- /dev/null +++ b/internal/provider/check_assertion_attribute.go @@ -0,0 +1,91 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ ResourceModel[checkly.Assertion] = (*CheckAssertionAttributeModel)(nil) +) + +var CheckAssertionAttributeSchema = schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "source": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "STATUS_CODE", + "JSON_BODY", + "HEADERS", + "TEXT_BODY", + "RESPONSE_TIME", + ), + }, + Description: "The source of the asserted value. Possible values `STATUS_CODE`, `JSON_BODY`, `HEADERS`, `TEXT_BODY`, and `RESPONSE_TIME`.", + }, + "property": schema.StringAttribute{ + Optional: true, + }, + "comparison": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "CONTAINS", + "EQUALS", + "GREATER_THAN", + "HAS_KEY", + "HAS_VALUE", + "IS_EMPTY", + "IS_NULL", + "LESS_THAN", + "NOT_CONTAINS", + "NOT_EMPTY", + "NOT_EQUALS", + "NOT_HAS_KEY", + "NOT_HAS_VALUE", + "NOT_NULL", + ), + }, + Description: "The type of comparison to be executed between expected and actual value of the assertion. Possible values `EQUALS`, `NOT_EQUALS`, `HAS_KEY`, `NOT_HAS_KEY`, `HAS_VALUE`, `NOT_HAS_VALUE`, `IS_EMPTY`, `NOT_EMPTY`, `GREATER_THAN`, `LESS_THAN`, `CONTAINS`, `NOT_CONTAINS`, `IS_NULL`, and `NOT_NULL`.", + }, + "target": schema.StringAttribute{ + Required: true, + }, + }, + }, +} + +type CheckAssertionAttributeModel struct { + Source types.String `tfsdk:"source"` + Property types.String `tfsdk:"property"` + Comparison types.String `tfsdk:"comparison"` + Target types.String `tfsdk:"target"` +} + +func (m *CheckAssertionAttributeModel) Refresh(ctx context.Context, from *checkly.Assertion, flags RefreshFlags) diag.Diagnostics { + m.Source = types.StringValue(from.Source) + m.Property = types.StringValue(from.Property) + m.Comparison = types.StringValue(from.Comparison) + m.Target = types.StringValue(from.Target) + + return nil +} + +func (m *CheckAssertionAttributeModel) Render(ctx context.Context, into *checkly.Assertion) diag.Diagnostics { + into.Source = m.Source.ValueString() + into.Property = m.Property.ValueString() + into.Comparison = m.Comparison.ValueString() + into.Target = m.Target.ValueString() + + return nil +} diff --git a/internal/provider/check_basic_auth_attribute.go b/internal/provider/check_basic_auth_attribute.go new file mode 100644 index 0000000..3c52434 --- /dev/null +++ b/internal/provider/check_basic_auth_attribute.go @@ -0,0 +1,48 @@ +package provider + +import ( + "context" + + "github.com/checkly/checkly-go-sdk" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ ResourceModel[checkly.BasicAuth] = (*CheckBasicAuthAttributeModel)(nil) +) + +var CheckBasicAuthAttributeSchema = schema.SingleNestedAttribute{ + Description: "Credentials for Basic HTTP authentication.", + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "username": schema.StringAttribute{ + Required: true, + }, + "password": schema.StringAttribute{ + Required: true, + Sensitive: true, + }, + }, +} + +type CheckBasicAuthAttributeModel struct { + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` +} + +func (m *CheckBasicAuthAttributeModel) Refresh(ctx context.Context, from *checkly.BasicAuth, flags RefreshFlags) diag.Diagnostics { + m.Username = types.StringValue(from.Username) + m.Password = types.StringValue(from.Password) + + return nil +} + +func (m *CheckBasicAuthAttributeModel) Render(ctx context.Context, into *checkly.BasicAuth) diag.Diagnostics { + into.Username = m.Username.ValueString() + into.Password = m.Password.ValueString() + + return nil +} diff --git a/internal/provider/check_environment_variable_attribute.go b/internal/provider/check_environment_variable_attribute.go new file mode 100644 index 0000000..b941b1e --- /dev/null +++ b/internal/provider/check_environment_variable_attribute.go @@ -0,0 +1,96 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "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" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ ResourceModel[checkly.EnvironmentVariable] = (*CheckEnvironmentVariableAttributeModel)(nil) +) + +var CheckEnvironmentVariableAttributeSchema = schema.ListNestedAttribute{ + Optional: true, + Description: "Introduce additional environment variables to the check " + + "execution environment. Only relevant for browser checks. Prefer " + + "global environment variables when possible.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "key": schema.StringAttribute{ + Description: "The name of the environment variable.", + Required: true, + }, + "value": schema.StringAttribute{ + Description: "The value of the environment variable. By " + + "default the value is plain text and can be seen by any " + + "team member. It will also be present in check results " + + "and logs.", + Required: true, + // We cannot make the value conditionally sensitive, so it's + // better to assume everything's sensitive. + Sensitive: true, + }, + "locked": schema.BoolAttribute{ + Description: "Locked environment variables are encrypted at " + + "rest and in flight on the Checkly backend and are only " + + "decrypted when needed. Their value is hidden by " + + "default, but can be accessed by team members with the " + + "appropriate permissions.", + Optional: true, + Default: booldefault.StaticBool(false), + Validators: []validator.Bool{ + boolvalidator.ConflictsWith( + path.MatchRelative().AtParent().AtName("secret"), + ), + }, + }, + "secret": schema.BoolAttribute{ + Description: "Secret environment variables are always " + + "encrypted and their value is never shown to any user. " + + "However, keep in mind that your Terraform state will " + + "still contain the value.", + Optional: true, + Default: booldefault.StaticBool(false), + Validators: []validator.Bool{ + boolvalidator.ConflictsWith( + path.MatchRelative().AtParent().AtName("locked"), + ), + }, + }, + }, + }, +} + +type CheckEnvironmentVariableAttributeModel struct { + Key types.String `tfsdk:"key"` + Value types.String `tfsdk:"value"` + Locked types.Bool `tfsdk:"locked"` + Secret types.Bool `tfsdk:"secret"` +} + +func (m *CheckEnvironmentVariableAttributeModel) Refresh(ctx context.Context, from *checkly.EnvironmentVariable, flags RefreshFlags) diag.Diagnostics { + m.Key = types.StringValue(from.Key) + m.Value = types.StringValue(from.Value) + m.Locked = types.BoolValue(from.Locked) + m.Secret = types.BoolValue(from.Secret) + + return nil +} + +func (m *CheckEnvironmentVariableAttributeModel) Render(ctx context.Context, into *checkly.EnvironmentVariable) diag.Diagnostics { + into.Key = m.Key.ValueString() + into.Value = m.Value.ValueString() + into.Locked = m.Locked.ValueBool() + into.Secret = m.Secret.ValueBool() + + return nil +} diff --git a/internal/provider/check_group_resource.go b/internal/provider/check_group_resource.go new file mode 100644 index 0000000..f806340 --- /dev/null +++ b/internal/provider/check_group_resource.go @@ -0,0 +1,500 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" + "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" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ resource.Resource = (*CheckGroupResource)(nil) + _ resource.ResourceWithConfigure = (*CheckGroupResource)(nil) + _ resource.ResourceWithImportState = (*CheckGroupResource)(nil) +) + +type CheckGroupResource struct { + client checkly.Client +} + +func NewCheckGroupResource() resource.Resource { + return &CheckGroupResource{} +} + +func (r *CheckGroupResource) Metadata( + ctx context.Context, + req resource.MetadataRequest, + resp *resource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_check_group" +} + +func (r *CheckGroupResource) Schema( + ctx context.Context, + req resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Description: "Check groups allow you to group together a set of related checks, which can also share default settings for various attributes.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "", // TODO + }, + "last_updated": LastUpdatedAttributeSchema, + "name": schema.StringAttribute{ + Required: true, + Description: "The name of the check group.", + }, + "concurrency": schema.Int32Attribute{ + Required: true, + Description: "Determines how many checks are run concurrently when triggering a check group from CI/CD or through the API.", + }, + "activated": schema.BoolAttribute{ + Required: true, + Description: "Determines if the checks in the group are running or not.", + }, + "muted": schema.BoolAttribute{ + Optional: true, + Description: "Determines if any notifications will be sent out when a check in this group fails and/or recovers.", + }, + "run_parallel": schema.BoolAttribute{ + Optional: true, + Description: "Determines if the checks in the group should run in all selected locations in parallel or round-robin.", + }, + "locations": CheckLocationsAttributeSchema, + "private_locations": CheckPrivateLocationsAttributeSchema, + "environment_variables": schema.MapAttribute{ + ElementType: types.StringType, + Validators: []validator.Map{ + mapvalidator.ConflictsWith( + path.MatchRelative().AtParent().AtName("environment_variable"), + ), + }, + Optional: true, + Description: "Key/value pairs for setting environment variables during check execution. These are only relevant for browser checks. Use global environment variables whenever possible.", + DeprecationMessage: "The property `environment_variables` is deprecated and will be removed in a future version. Consider using the new `environment_variable` list.", + }, + "environment_variable": CheckEnvironmentVariableAttributeSchema, + "double_check": schema.BoolAttribute{ + Optional: true, + Description: "Setting this to `true` will trigger a retry when a check fails from the failing region and another, randomly selected region before marking the check as failed.", + DeprecationMessage: "The property `double_check` is deprecated and will be removed in a future version. To enable retries for failed check runs, use the `retry_strategy` property instead.", + }, + "tags": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Tags for organizing and filtering checks.", + }, + "setup_snippet_id": schema.Int64Attribute{ + Optional: true, + Description: "An ID reference to a snippet to use in the setup phase of an API check.", + }, + "teardown_snippet_id": schema.Int64Attribute{ + Optional: true, + Description: "An ID reference to a snippet to use in the teardown phase of an API check.", + }, + "local_setup_script": schema.StringAttribute{ + Optional: true, + Description: "A valid piece of Node.js code to run in the setup phase of an API check in this group.", + }, + "local_teardown_script": schema.StringAttribute{ + Optional: true, + Description: "A valid piece of Node.js code to run in the teardown phase of an API check in this group.", + }, + "runtime_id": schema.StringAttribute{ + Optional: true, + Description: "The id of the runtime to use for this group.", + }, + "alert_channel_subscription": CheckAlertChannelSubscriptionAttributeSchema, + "alert_settings": CheckAlertSettingsAttributeSchema, + "use_global_alert_settings": schema.BoolAttribute{ + Optional: true, + Description: "When true, the account level alert settings will be used, not the alert setting defined on this check group.", + }, + "api_check_defaults": CheckAPICheckDefaultsAttributeSchema, + "retry_strategy": CheckRetryStrategyAttributeSchema, + }, + } +} + +func (r *CheckGroupResource) Configure( + ctx context.Context, + req resource.ConfigureRequest, + resp *resource.ConfigureResponse, +) { + client, diags := ClientFromProviderData(req.ProviderData) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + r.client = client +} + +func (r *CheckGroupResource) ImportState( + ctx context.Context, + req resource.ImportStateRequest, + resp *resource.ImportStateResponse, +) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *CheckGroupResource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan CheckGroupResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.Group + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.CreateGroup(ctx, desiredModel) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Checkly Check Group", + fmt.Sprintf("Could not create check group, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *CheckGroupResource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state CheckGroupResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + id, diags := CheckGroupID.FromString(state.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + err := r.client.DeleteGroup(ctx, id) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting Checkly Check Group", + fmt.Sprintf("Could not delete check group, unexpected error: %s", err), + ) + + return + } +} + +func (r *CheckGroupResource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state CheckGroupResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + id, diags := CheckGroupID.FromString(state.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + // TODO: Check if we really have to do the weird 404 handling + realizedModel, err := r.client.GetGroup(ctx, id) + if err != nil { + resp.Diagnostics.AddError( + "Error Reading Checkly Check Group", + fmt.Sprintf("Could not retrieve check group, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *CheckGroupResource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan CheckGroupResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + id, diags := CheckGroupID.FromString(plan.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + var desiredModel checkly.Group + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.UpdateGroup( + ctx, + id, + desiredModel, + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Checkly Check Group", + fmt.Sprintf("Could not update check group, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +var CheckGroupID = SDKIdentifier{ + Path: path.Root("id"), + Title: "Checkly Check Group ID", +} + +var ( + _ ResourceModel[checkly.Group] = (*CheckGroupResourceModel)(nil) +) + +type CheckGroupResourceModel struct { + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + Name types.String `tfsdk:"name"` + Concurrency types.Int32 `tfsdk:"concurrency"` + Activated types.Bool `tfsdk:"activated"` + Muted types.Bool `tfsdk:"muted"` + RunParallel types.Bool `tfsdk:"run_parallel"` + Locations CheckLocationsAttributeModel `tfsdk:"locations"` + PrivateLocations CheckPrivateLocationsAttributeModel `tfsdk:"private_locations"` + EnvironmentVariables types.Map `tfsdk:"environment_variables"` + EnvironmentVariable []CheckEnvironmentVariableAttributeModel `tfsdk:"environment_variable"` + DoubleCheck types.Bool `tfsdk:"double_check"` + Tags types.Set `tfsdk:"tags"` + SetupSnippetID types.Int64 `tfsdk:"setup_snippet_id"` + TearDownSnippetID types.Int64 `tfsdk:"teardown_snippet_id"` + LocalSetupScript types.String `tfsdk:"local_setup_script"` + LocalTearDownScript types.String `tfsdk:"local_teardown_script"` + RuntimeID types.String `tfsdk:"runtime_id"` + AlertChannelSubscriptions []CheckAlertChannelSubscriptionAttributeModel `tfsdk:"alert_channel_subscription"` + AlertSettings CheckAlertSettingsAttributeModel `tfsdk:"alert_settings"` + UseGlobalAlertSettings types.Bool `tfsdk:"use_global_alert_settings"` + APICheckDefaults CheckAPICheckDefaultsAttributeModel `tfsdk:"api_check_defaults"` + RetryStrategy *CheckRetryStrategyAttributeModel `tfsdk:"retry_strategy"` +} + +func (m *CheckGroupResourceModel) Refresh(ctx context.Context, from *checkly.Group, flags RefreshFlags) diag.Diagnostics { + if flags.Created() { + m.ID = CheckGroupID.IntoString(from.ID) + } + + if flags.Created() || flags.Updated() { + m.LastUpdated = LastUpdatedNow() + } + + m.Name = types.StringValue(from.Name) + m.Concurrency = types.Int32Value(int32(from.Concurrency)) + m.Activated = types.BoolValue(from.Activated) + m.Muted = types.BoolValue(from.Muted) + m.RunParallel = types.BoolValue(from.RunParallel) + + diags := m.Locations.Refresh(ctx, &from.Locations, flags) + if diags != nil { + return diags + } + + diags = m.PrivateLocations.Refresh(ctx, from.PrivateLocations, flags) + if diags != nil { + return diags + } + + if !m.EnvironmentVariables.IsNull() { + // Deprecated mode. + m.EnvironmentVariables = types.MapNull(types.StringType) + + // TODO either implement backwards compat or remove. + } else { + m.EnvironmentVariable = nil + + diags := RefreshMany(ctx, from.EnvironmentVariables, m.EnvironmentVariable, flags) + if diags.HasError() { + return diags + } + } + + m.DoubleCheck = types.BoolValue(from.DoubleCheck) + + m.Tags = IntoUntypedStringSet(&from.Tags) + + m.SetupSnippetID = types.Int64Value(from.SetupSnippetID) + m.TearDownSnippetID = types.Int64Value(from.TearDownSnippetID) + m.LocalSetupScript = types.StringValue(from.LocalSetupScript) + m.LocalTearDownScript = types.StringValue(from.LocalTearDownScript) + + if from.RuntimeID != nil { + m.RuntimeID = types.StringValue(*from.RuntimeID) + } else { + m.RuntimeID = types.StringNull() + } + + diags = RefreshMany(ctx, from.AlertChannelSubscriptions, m.AlertChannelSubscriptions, flags) + if diags.HasError() { + return diags + } + + diags = m.AlertSettings.Refresh(ctx, &from.AlertSettings, flags) + if diags.HasError() { + return diags + } + + m.UseGlobalAlertSettings = types.BoolValue(from.UseGlobalAlertSettings) + + diags = m.APICheckDefaults.Refresh(ctx, &from.APICheckDefaults, flags) + if diags.HasError() { + return diags + } + + if from.RetryStrategy != nil { + diags = m.RetryStrategy.Refresh(ctx, from.RetryStrategy, flags) + if diags.HasError() { + return diags + } + } else { + m.RetryStrategy = nil + } + + return nil +} + +func (m *CheckGroupResourceModel) Render(ctx context.Context, into *checkly.Group) diag.Diagnostics { + into.Name = m.Name.ValueString() + into.Concurrency = int(m.Concurrency.ValueInt32()) + into.Activated = m.Activated.ValueBool() + into.Muted = m.Muted.ValueBool() + into.RunParallel = m.RunParallel.ValueBool() + + diags := m.Locations.Render(ctx, &into.Locations) + if diags.HasError() { + return diags + } + + diags = m.PrivateLocations.Render(ctx, into.PrivateLocations) + if diags.HasError() { + return diags + } + + if !m.EnvironmentVariables.IsNull() { + // Deprecated mode. + into.EnvironmentVariables = nil + + // TODO either implement backwards compat or remove. + } else { + into.EnvironmentVariables = nil + + diags := RenderMany(ctx, m.EnvironmentVariable, into.EnvironmentVariables) + if diags.HasError() { + return diags + } + } + + into.DoubleCheck = m.DoubleCheck.ValueBool() + + into.Tags = FromUntypedStringSet(m.Tags) + + into.SetupSnippetID = m.SetupSnippetID.ValueInt64() + into.TearDownSnippetID = m.TearDownSnippetID.ValueInt64() + into.LocalSetupScript = m.LocalSetupScript.ValueString() + into.LocalTearDownScript = m.LocalTearDownScript.ValueString() + + if !m.RuntimeID.IsNull() { + value := m.RuntimeID.ValueString() + into.RuntimeID = &value + } else { + into.RuntimeID = nil + } + + diags = RenderMany(ctx, m.AlertChannelSubscriptions, into.AlertChannelSubscriptions) + if diags.HasError() { + return diags + } + + diags = m.AlertSettings.Render(ctx, &into.AlertSettings) + if diags.HasError() { + return diags + } + + into.UseGlobalAlertSettings = m.UseGlobalAlertSettings.ValueBool() + + diags = m.APICheckDefaults.Render(ctx, &into.APICheckDefaults) + if diags.HasError() { + return diags + } + + if m.RetryStrategy != nil { + diags = m.RetryStrategy.Render(ctx, into.RetryStrategy) + if diags.HasError() { + return diags + } + } else { + into.RetryStrategy = nil + } + + return nil +} diff --git a/internal/provider/check_headers_attribute.go b/internal/provider/check_headers_attribute.go new file mode 100644 index 0000000..1fb1e23 --- /dev/null +++ b/internal/provider/check_headers_attribute.go @@ -0,0 +1,35 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ ResourceModel[[]checkly.KeyValue] = (*CheckHeadersAttributeModel)(nil) +) + +var CheckHeadersAttributeSchema = schema.MapAttribute{ + ElementType: types.StringType, + Optional: true, + Computed: true, // TODO: Really? +} + +type CheckHeadersAttributeModel types.Map + +func (m *CheckHeadersAttributeModel) Refresh(ctx context.Context, from *[]checkly.KeyValue, flags RefreshFlags) diag.Diagnostics { + *m = CheckHeadersAttributeModel(SDKKeyValuesIntoMap(from)) + + return nil +} + +func (m *CheckHeadersAttributeModel) Render(ctx context.Context, into *[]checkly.KeyValue) diag.Diagnostics { + *into = SDKKeyValuesFromMap(types.Map(*m)) + + return nil +} diff --git a/internal/provider/check_locations_attribute.go b/internal/provider/check_locations_attribute.go new file mode 100644 index 0000000..628a4e9 --- /dev/null +++ b/internal/provider/check_locations_attribute.go @@ -0,0 +1,33 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ ResourceModel[[]string] = (*CheckLocationsAttributeModel)(nil) +) + +var CheckLocationsAttributeSchema = schema.SetAttribute{ + Description: "An array of one or more data center locations where to run the checks.", + ElementType: types.StringType, + Optional: true, +} + +type CheckLocationsAttributeModel types.Set + +func (m *CheckLocationsAttributeModel) Refresh(ctx context.Context, from *[]string, flags RefreshFlags) diag.Diagnostics { + *m = CheckLocationsAttributeModel(IntoUntypedStringSet(from)) + + return nil +} + +func (m *CheckLocationsAttributeModel) Render(ctx context.Context, into *[]string) diag.Diagnostics { + *into = FromUntypedStringSet(types.Set(*m)) + + return nil +} diff --git a/internal/provider/check_private_locations_attribute.go b/internal/provider/check_private_locations_attribute.go new file mode 100644 index 0000000..2108937 --- /dev/null +++ b/internal/provider/check_private_locations_attribute.go @@ -0,0 +1,33 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ ResourceModel[[]string] = (*CheckPrivateLocationsAttributeModel)(nil) +) + +var CheckPrivateLocationsAttributeSchema = schema.SetAttribute{ + Description: "An array of one or more private locations slugs.", + ElementType: types.StringType, + Optional: true, +} + +type CheckPrivateLocationsAttributeModel types.Set + +func (m *CheckPrivateLocationsAttributeModel) Refresh(ctx context.Context, from *[]string, flags RefreshFlags) diag.Diagnostics { + *m = CheckPrivateLocationsAttributeModel(IntoUntypedStringSet(from)) + + return nil +} + +func (m *CheckPrivateLocationsAttributeModel) Render(ctx context.Context, into *[]string) diag.Diagnostics { + *into = FromUntypedStringSet(types.Set(*m)) + + return nil +} diff --git a/internal/provider/check_query_parameters_attribute.go b/internal/provider/check_query_parameters_attribute.go new file mode 100644 index 0000000..68aa293 --- /dev/null +++ b/internal/provider/check_query_parameters_attribute.go @@ -0,0 +1,35 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ ResourceModel[[]checkly.KeyValue] = (*CheckQueryParametersAttributeModel)(nil) +) + +var CheckQueryParametersAttributeSchema = schema.MapAttribute{ + ElementType: types.StringType, + Optional: true, + Computed: true, // TODO: Really? +} + +type CheckQueryParametersAttributeModel types.Map + +func (m *CheckQueryParametersAttributeModel) Refresh(ctx context.Context, from *[]checkly.KeyValue, flags RefreshFlags) diag.Diagnostics { + *m = CheckQueryParametersAttributeModel(SDKKeyValuesIntoMap(from)) + + return nil +} + +func (m *CheckQueryParametersAttributeModel) Render(ctx context.Context, into *[]checkly.KeyValue) diag.Diagnostics { + *into = SDKKeyValuesFromMap(types.Map(*m)) + + return nil +} diff --git a/internal/provider/check_request_attribute.go b/internal/provider/check_request_attribute.go new file mode 100644 index 0000000..2fee886 --- /dev/null +++ b/internal/provider/check_request_attribute.go @@ -0,0 +1,145 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ ResourceModel[checkly.Request] = (*CheckRequestAttributeModel)(nil) +) + +var CheckRequestAttributeSchema = schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "method": schema.StringAttribute{ + Optional: true, + Default: stringdefault.StaticString("GET"), + Validators: []validator.String{ + stringvalidator.OneOf( + "GET", + "POST", + "PUT", + "HEAD", + "DELETE", + "PATCH", + ), + }, + Description: "The HTTP method to use for this API check. Possible values are `GET`, `POST`, `PUT`, `HEAD`, `DELETE`, `PATCH`. (Default `GET`).", + }, + "url": schema.StringAttribute{ + Required: true, + }, + "follow_redirects": schema.BoolAttribute{ + Optional: true, + }, + "skip_ssl": schema.BoolAttribute{ + Optional: true, + }, + "headers": CheckHeadersAttributeSchema, + "query_parameters": CheckQueryParametersAttributeSchema, + "body": schema.StringAttribute{ + Optional: true, + Description: "The body of the request.", + }, + "body_type": schema.StringAttribute{ + Optional: true, + Default: stringdefault.StaticString("NONE"), + Validators: []validator.String{ + stringvalidator.OneOf( + "NONE", + "JSON", + "FORM", + "RAW", + "GRAPHQL", + ), + }, + Description: "The `Content-Type` header of the request. Possible values `NONE`, `JSON`, `FORM`, `RAW`, and `GRAPHQL`.", + }, + "assertion": CheckAssertionAttributeSchema, + "basic_auth": CheckBasicAuthAttributeSchema, + "ip_family": schema.StringAttribute{ + Optional: true, + Default: stringdefault.StaticString("IPv4"), + Validators: []validator.String{ + stringvalidator.OneOf( + "IPv4", + "IPv6", + ), + }, + Description: "IP Family to be used when executing the api check. The value can be either IPv4 or IPv6.", + }, + }, +} + +type CheckRequestAttributeModel struct { + Method types.String `tfsdk:"method"` + URL types.String `tfsdk:"url"` + FollowRedirects types.Bool `tfsdk:"follow_redirects"` + SkipSSL types.Bool `tfsdk:"skip_ssl"` + Headers types.Map `tfsdk:"headers"` + QueryParameters types.Map `tfsdk:"query_parameters"` + Body types.String `tfsdk:"body"` + BodyType types.String `tfsdk:"body_type"` + Assertions []CheckAssertionAttributeModel `tfsdk:"assertion"` + BasicAuth CheckBasicAuthAttributeModel `tfsdk:"basic_auth"` + IPFamily types.String `tfsdk:"ip_family"` +} + +func (m *CheckRequestAttributeModel) Refresh(ctx context.Context, from *checkly.Request, flags RefreshFlags) diag.Diagnostics { + m.Method = types.StringValue(from.Method) + m.URL = types.StringValue(from.URL) + m.FollowRedirects = types.BoolValue(from.FollowRedirects) + m.SkipSSL = types.BoolValue(from.SkipSSL) + m.Headers = SDKKeyValuesIntoMap(&from.Headers) + m.QueryParameters = SDKKeyValuesIntoMap(&from.QueryParameters) + m.Body = types.StringValue(from.Body) + m.BodyType = types.StringValue(from.BodyType) + + diags := RefreshMany(ctx, from.Assertions, m.Assertions, flags) + if diags.HasError() { + return diags + } + + diags = m.BasicAuth.Refresh(ctx, from.BasicAuth, flags) + if diags.HasError() { + return diags + } + + m.IPFamily = types.StringValue(from.IPFamily) + + return nil +} + +func (m *CheckRequestAttributeModel) Render(ctx context.Context, into *checkly.Request) diag.Diagnostics { + into.Method = m.Method.ValueString() + into.URL = m.URL.ValueString() + into.FollowRedirects = m.FollowRedirects.ValueBool() + into.SkipSSL = m.SkipSSL.ValueBool() + into.Headers = SDKKeyValuesFromMap(m.Headers) + into.QueryParameters = SDKKeyValuesFromMap(m.QueryParameters) + into.Body = m.Body.ValueString() + into.BodyType = m.Body.ValueString() + + diags := RenderMany(ctx, m.Assertions, into.Assertions) + if diags.HasError() { + return diags + } + + diags = m.BasicAuth.Render(ctx, into.BasicAuth) + if diags.HasError() { + return diags + } + + into.IPFamily = m.IPFamily.ValueString() + + return nil +} diff --git a/internal/provider/check_resource.go b/internal/provider/check_resource.go new file mode 100644 index 0000000..8ef3a37 --- /dev/null +++ b/internal/provider/check_resource.go @@ -0,0 +1,591 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" + "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/resource/schema/int32default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ resource.Resource = (*CheckResource)(nil) + _ resource.ResourceWithConfigure = (*CheckResource)(nil) + _ resource.ResourceWithImportState = (*CheckResource)(nil) +) + +type CheckResource struct { + client checkly.Client +} + +func NewCheckResource() resource.Resource { + return &CheckResource{} +} + +func (r *CheckResource) Metadata( + ctx context.Context, + req resource.MetadataRequest, + resp *resource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_check" +} + +func (r *CheckResource) Schema( + ctx context.Context, + req resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Description: "Check groups allow you to group together a set of related checks, which can also share default settings for various attributes.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "", // TODO + }, + "last_updated": LastUpdatedAttributeSchema, + "name": schema.StringAttribute{ + Required: true, + Description: "The name of the check.", + }, + "type": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "API", + "BROWSER", + "MULTI_STEP", + ), + }, + Description: "The type of the check. Possible values are `API`, `BROWSER`, and `MULTI_STEP`.", + }, + "frequency": schema.Int32Attribute{ + Required: true, + Validators: []validator.Int32{ + int32validator.OneOf(0, 1, 2, 5, 10, 15, 30, 60, 120, 180, 360, 720, 1440), + // TODO: can only be 0 if type == API + }, + Description: "The frequency in minutes to run the check. Possible values are `0`, `1`, `2`, `5`, `10`, `15`, `30`, `60`, `120`, `180`, `360`, `720`, and `1440`.", + }, + "frequency_offset": schema.Int32Attribute{ + Optional: true, + Validators: []validator.Int32{ + int32validator.OneOf(10, 20, 30), // TODO: Are these the only values? + // TODO: can only be set if frequency == 0 + }, + Description: "This property is only valid for high frequency API checks. To create a high frequency check, the property `frequency` must be `0` and `frequency_offset` could be `10`, `20` or `30`.", + }, + "activated": schema.BoolAttribute{ + Required: true, + Description: "Determines if the check is running or not. Possible values `true`, and `false`.", + }, + "muted": schema.BoolAttribute{ + Optional: true, + Description: "Determines if any notifications will be sent out when a check fails/degrades/recovers.", + }, + "should_fail": schema.BoolAttribute{ + Optional: true, + Description: "Allows to invert the behaviour of when a check is considered to fail. Allows for validating error status like 404.", + }, + "run_parallel": schema.BoolAttribute{ + Optional: true, + Description: "Determines if the check should run in all selected locations in parallel or round-robin.", + }, + "locations": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "An array of one or more data center locations where to run the checks.", + }, + "private_locations": CheckPrivateLocationsAttributeSchema, + "script": schema.StringAttribute{ + Optional: true, + Default: stringdefault.StaticString(""), + Description: "A valid piece of Node.js JavaScript code describing a browser interaction with the Puppeteer/Playwright framework or a reference to an external JavaScript file.", + }, + "degraded_response_time": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(15000), + Validators: []validator.Int32{ + int32validator.Between(0, 30000), + }, + Description: "The response time in milliseconds starting from which a check should be considered degraded. Possible values are between 0 and 30000. (Default `15000`).", + }, + "max_response_time": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(30000), + Validators: []validator.Int32{ + int32validator.Between(0, 30000), + }, + Description: "The response time in milliseconds starting from which a check should be considered failing. Possible values are between 0 and 30000. (Default `30000`).", + }, + "environment_variables": schema.MapAttribute{ + ElementType: types.StringType, + Validators: []validator.Map{ + mapvalidator.ConflictsWith( + path.MatchRelative().AtParent().AtName("environment_variable"), + ), + }, + Optional: true, + Description: "Key/value pairs for setting environment variables during check execution. These are only relevant for browser checks. Use global environment variables whenever possible.", + DeprecationMessage: "The property `environment_variables` is deprecated and will be removed in a future version. Consider using the new `environment_variable` list.", + }, + "environment_variable": CheckEnvironmentVariableAttributeSchema, + "double_check": schema.BoolAttribute{ + Optional: true, + Description: "Setting this to `true` will trigger a retry when a check fails from the failing region and another, randomly selected region before marking the check as failed.", + DeprecationMessage: "The property `double_check` is deprecated and will be removed in a future version. To enable retries for failed check runs, use the `retry_strategy` property instead.", + }, + "tags": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Tags for organizing and filtering checks.", + }, + "ssl_check": schema.BoolAttribute{ + Optional: true, + Description: "Determines if the SSL certificate should be validated for expiry.", + DeprecationMessage: "The property `ssl_check` is deprecated and it's ignored by the Checkly Public API. It will be removed in a future version.", + }, + "ssl_check_domain": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + // TODO: can only be set if type == BROWSER + }, + Description: "A valid fully qualified domain name (FQDN) to check its SSL certificate.", + }, + "setup_snippet_id": schema.Int64Attribute{ + Optional: true, + Description: "An ID reference to a snippet to use in the setup phase of an API check.", + }, + "teardown_snippet_id": schema.Int64Attribute{ + Optional: true, + Description: "An ID reference to a snippet to use in the teardown phase of an API check.", + }, + "local_setup_script": schema.StringAttribute{ + Optional: true, + Description: "A valid piece of Node.js code to run in the setup phase.", + }, + "local_teardown_script": schema.StringAttribute{ + Optional: true, + Description: "A valid piece of Node.js code to run in the teardown phase.", + }, + "runtime_id": schema.StringAttribute{ + Optional: true, + Description: "The id of the runtime to use for this check.", + // TODO: If type == MULTI_STEP, use GetRuntime to check whether + // the runtime supports MULTI_STEP + }, + "alert_channel_subscription": CheckAlertChannelSubscriptionAttributeSchema, + "alert_settings": CheckAlertSettingsAttributeSchema, + "use_global_alert_settings": schema.BoolAttribute{ + Optional: true, + Description: "When true, the account level alert settings will be used, not the alert setting defined on this check.", + }, + "request": CheckRequestAttributeSchema, // TODO: can only be set if type == API + "group_id": schema.Int64Attribute{ + Optional: true, + Description: "The id of the check group this check is part of.", + }, + "group_order": schema.Int32Attribute{ + Optional: true, + Description: "The position of this check in a check group. It determines in what order checks are run when a group is triggered from the API or from CI/CD.", + }, + "retry_strategy": CheckRetryStrategyAttributeSchema, + }, + } +} + +func (r *CheckResource) Configure( + ctx context.Context, + req resource.ConfigureRequest, + resp *resource.ConfigureResponse, +) { + client, diags := ClientFromProviderData(req.ProviderData) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + r.client = client +} + +func (r *CheckResource) ImportState( + ctx context.Context, + req resource.ImportStateRequest, + resp *resource.ImportStateResponse, +) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *CheckResource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan CheckResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.Check + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.CreateCheck(ctx, desiredModel) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Checkly Check", + fmt.Sprintf("Could not create check, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *CheckResource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state CheckResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + err := r.client.DeleteCheck(ctx, state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting Checkly Check", + fmt.Sprintf("Could not delete check, unexpected error: %s", err), + ) + + return + } +} + +func (r *CheckResource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state CheckResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // TODO: Check if we really have to do the weird 404 handling + realizedModel, err := r.client.GetCheck(ctx, state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Reading Checkly Check", + fmt.Sprintf("Could not retrieve check, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *CheckResource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan CheckResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.Check + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.UpdateCheck( + ctx, + plan.ID.ValueString(), + desiredModel, + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Checkly Check", + fmt.Sprintf("Could not update check, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +var ( + _ ResourceModel[checkly.Check] = (*CheckResourceModel)(nil) +) + +type CheckResourceModel struct { + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + Name types.String `tfsdk:"name"` + Type types.String `tfsdk:"type"` + Frequency types.Int32 `tfsdk:"frequency"` + FrequencyOffset types.Int32 `tfsdk:"frequency_offset"` + Activated types.Bool `tfsdk:"activated"` + Muted types.Bool `tfsdk:"muted"` + ShouldFail types.Bool `tfsdk:"should_fail"` + RunParallel types.Bool `tfsdk:"run_parallel"` + Locations CheckLocationsAttributeModel `tfsdk:"locations"` + PrivateLocations CheckPrivateLocationsAttributeModel `tfsdk:"private_locations"` + Script types.String `tfsdk:"script"` + DegradedResponseTime types.Int32 `tfsdk:"degraded_response_time"` + MaxResponseTime types.Int32 `tfsdk:"max_response_time"` + EnvironmentVariables types.Map `tfsdk:"environment_variables"` + EnvironmentVariable []CheckEnvironmentVariableAttributeModel `tfsdk:"environment_variable"` + DoubleCheck types.Bool `tfsdk:"double_check"` + Tags types.Set `tfsdk:"tags"` + SSLCheck types.Bool `tfsdk:"ssl_check"` + SSLCheckDomain types.String `tfsdk:"ssl_check_domain"` + SetupSnippetID types.Int64 `tfsdk:"setup_snippet_id"` + TearDownSnippetID types.Int64 `tfsdk:"teardown_snippet_id"` + LocalSetupScript types.String `tfsdk:"local_setup_script"` + LocalTearDownScript types.String `tfsdk:"local_teardown_script"` + RuntimeID types.String `tfsdk:"runtime_id"` + AlertChannelSubscriptions []CheckAlertChannelSubscriptionAttributeModel `tfsdk:"alert_channel_subscription"` + AlertSettings CheckAlertSettingsAttributeModel `tfsdk:"alert_settings"` + UseGlobalAlertSettings types.Bool `tfsdk:"use_global_alert_settings"` + Request CheckRequestAttributeModel `tfsdk:"request"` + GroupID types.Int64 `tfsdk:"group_id"` + GroupOrder types.Int32 `tfsdk:"group_order"` + RetryStrategy *CheckRetryStrategyAttributeModel `tfsdk:"retry_strategy"` +} + +func (m *CheckResourceModel) Refresh(ctx context.Context, from *checkly.Check, flags RefreshFlags) diag.Diagnostics { + if flags.Created() { + m.ID = types.StringValue(from.ID) + } + + if flags.Created() || flags.Updated() { + m.LastUpdated = LastUpdatedNow() + } + + m.Name = types.StringValue(from.Name) + m.Type = types.StringValue(from.Type) + m.Frequency = types.Int32Value(int32(from.Frequency)) + m.FrequencyOffset = types.Int32Value(int32(from.FrequencyOffset)) + m.Activated = types.BoolValue(from.Activated) + m.Muted = types.BoolValue(from.Muted) + m.ShouldFail = types.BoolValue(from.ShouldFail) + m.RunParallel = types.BoolValue(from.RunParallel) + + diags := m.Locations.Refresh(ctx, &from.Locations, flags) + if diags.HasError() { + return diags + } + + diags = m.PrivateLocations.Refresh(ctx, from.PrivateLocations, flags) + if diags.HasError() { + return diags + } + + m.Script = types.StringValue(from.Script) + m.DegradedResponseTime = types.Int32Value(int32(from.DegradedResponseTime)) + m.MaxResponseTime = types.Int32Value(int32(from.MaxResponseTime)) + + if !m.EnvironmentVariables.IsNull() { + // Deprecated mode. + m.EnvironmentVariables = types.MapNull(types.StringType) + + // TODO either implement backwards compat or remove. + } else { + m.EnvironmentVariable = nil + + diags := RefreshMany(ctx, from.EnvironmentVariables, m.EnvironmentVariable, flags) + if diags.HasError() { + return diags + } + } + + m.DoubleCheck = types.BoolValue(from.DoubleCheck) + + m.Tags = IntoUntypedStringSet(&from.Tags) + + m.SSLCheck = types.BoolValue(from.SSLCheck) + m.SSLCheckDomain = types.StringValue(from.SSLCheckDomain) + + m.SetupSnippetID = types.Int64Value(from.SetupSnippetID) + m.TearDownSnippetID = types.Int64Value(from.TearDownSnippetID) + m.LocalSetupScript = types.StringValue(from.LocalSetupScript) + m.LocalTearDownScript = types.StringValue(from.LocalTearDownScript) + + if from.RuntimeID != nil { + m.RuntimeID = types.StringValue(*from.RuntimeID) + } else { + m.RuntimeID = types.StringNull() + } + + diags = RefreshMany(ctx, from.AlertChannelSubscriptions, m.AlertChannelSubscriptions, flags) + if diags.HasError() { + return diags + } + + diags = m.AlertSettings.Refresh(ctx, &from.AlertSettings, flags) + if diags.HasError() { + return diags + } + + m.UseGlobalAlertSettings = types.BoolValue(from.UseGlobalAlertSettings) + + diags = m.Request.Refresh(ctx, &from.Request, flags) + if diags.HasError() { + return diags + } + + m.GroupID = types.Int64Value(from.GroupID) + m.GroupOrder = types.Int32Value(int32(from.GroupOrder)) + + if from.RetryStrategy != nil { + diags = m.RetryStrategy.Refresh(ctx, from.RetryStrategy, flags) + if diags.HasError() { + return diags + } + } else { + m.RetryStrategy = nil + } + + return nil +} + +func (m *CheckResourceModel) Render(ctx context.Context, into *checkly.Check) diag.Diagnostics { + into.Name = m.Name.ValueString() + into.Type = m.Type.ValueString() + into.Frequency = int(m.Frequency.ValueInt32()) + into.FrequencyOffset = int(m.FrequencyOffset.ValueInt32()) + into.Activated = m.Activated.ValueBool() + into.Muted = m.Muted.ValueBool() + into.ShouldFail = m.ShouldFail.ValueBool() + into.RunParallel = m.RunParallel.ValueBool() + + diags := m.Locations.Render(ctx, &into.Locations) + if diags.HasError() { + return diags + } + + diags = m.PrivateLocations.Render(ctx, into.PrivateLocations) + if diags.HasError() { + return diags + } + + into.Script = m.Script.ValueString() + into.DegradedResponseTime = int(m.DegradedResponseTime.ValueInt32()) + into.MaxResponseTime = int(m.MaxResponseTime.ValueInt32()) + + if !m.EnvironmentVariables.IsNull() { + // Deprecated mode. + into.EnvironmentVariables = nil + + // TODO either implement backwards compat or remove. + } else { + into.EnvironmentVariables = nil + + diags := RenderMany(ctx, m.EnvironmentVariable, into.EnvironmentVariables) + if diags.HasError() { + return diags + } + } + + into.DoubleCheck = m.DoubleCheck.ValueBool() + + into.Tags = FromUntypedStringSet(m.Tags) + + into.SSLCheck = m.SSLCheck.ValueBool() + into.SSLCheckDomain = m.SSLCheckDomain.ValueString() + + into.SetupSnippetID = m.SetupSnippetID.ValueInt64() + into.TearDownSnippetID = m.TearDownSnippetID.ValueInt64() + into.LocalSetupScript = m.LocalSetupScript.ValueString() + into.LocalTearDownScript = m.LocalTearDownScript.ValueString() + + if !m.RuntimeID.IsNull() { + value := m.RuntimeID.ValueString() + into.RuntimeID = &value + } else { + into.RuntimeID = nil + } + + diags = RenderMany(ctx, m.AlertChannelSubscriptions, into.AlertChannelSubscriptions) + if diags.HasError() { + return diags + } + + diags = m.AlertSettings.Render(ctx, &into.AlertSettings) + if diags.HasError() { + return diags + } + + into.UseGlobalAlertSettings = m.UseGlobalAlertSettings.ValueBool() + + diags = m.Request.Render(ctx, &into.Request) + if diags.HasError() { + return diags + } + + into.GroupID = m.GroupID.ValueInt64() + into.GroupOrder = int(m.GroupOrder.ValueInt32()) + + if m.RetryStrategy != nil { + diags = m.RetryStrategy.Render(ctx, into.RetryStrategy) + if diags.HasError() { + return diags + } + } else { + into.RetryStrategy = nil + } + + return nil +} diff --git a/internal/provider/check_retry_strategy_attribute.go b/internal/provider/check_retry_strategy_attribute.go new file mode 100644 index 0000000..10ce0a5 --- /dev/null +++ b/internal/provider/check_retry_strategy_attribute.go @@ -0,0 +1,93 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "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/int32default" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ ResourceModel[checkly.RetryStrategy] = (*CheckRetryStrategyAttributeModel)(nil) +) + +var CheckRetryStrategyAttributeSchema = schema.SingleNestedAttribute{ + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "FIXED", + "LINEAR", + "EXPONENTIAL", + ), + }, + Description: "Determines which type of retry strategy to use. Possible values are `FIXED`, `LINEAR`, or `EXPONENTIAL`.", + }, + "base_backoff_seconds": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(60), + Description: "The number of seconds to wait before the first retry attempt.", + }, + "max_retries": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(2), + Validators: []validator.Int32{ + int32validator.Between(1, 10), + }, + Description: "The maximum number of times to retry the check. Value must be between 1 and 10.", + }, + "max_duration_seconds": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(600), + Validators: []validator.Int32{ + int32validator.AtMost(600), + }, + Description: "The total amount of time to continue retrying the check (maximum 600 seconds).", + }, + "same_region": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(true), + Description: "Whether retries should be run in the same region as the initial check run.", + }, + }, + Description: "A strategy for retrying failed check runs.", +} + +type CheckRetryStrategyAttributeModel struct { + Type types.String `tfsdk:"type"` + BaseBackoffSeconds types.Int32 `tfsdk:"base_backoff_seconds"` + MaxRetries types.Int32 `tfsdk:"max_retries"` + MaxDurationSeconds types.Int32 `tfsdk:"max_duration_seconds"` + SameRegion types.Bool `tfsdk:"same_region"` +} + +func (m *CheckRetryStrategyAttributeModel) Refresh(ctx context.Context, from *checkly.RetryStrategy, flags RefreshFlags) diag.Diagnostics { + m.Type = types.StringValue(from.Type) + m.BaseBackoffSeconds = types.Int32Value(int32(from.BaseBackoffSeconds)) + m.MaxRetries = types.Int32Value(int32(from.MaxRetries)) + m.MaxDurationSeconds = types.Int32Value(int32(from.MaxDurationSeconds)) + m.SameRegion = types.BoolValue(from.SameRegion) + + return nil +} + +func (m *CheckRetryStrategyAttributeModel) Render(ctx context.Context, into *checkly.RetryStrategy) diag.Diagnostics { + into.Type = m.Type.ValueString() + into.BaseBackoffSeconds = int(m.BaseBackoffSeconds.ValueInt32()) + into.MaxRetries = int(m.MaxRetries.ValueInt32()) + into.MaxDurationSeconds = int(m.MaxDurationSeconds.ValueInt32()) + into.SameRegion = m.SameRegion.ValueBool() + + return nil +} diff --git a/internal/provider/dashboard_resource.go b/internal/provider/dashboard_resource.go new file mode 100644 index 0000000..b4c1b65 --- /dev/null +++ b/internal/provider/dashboard_resource.go @@ -0,0 +1,391 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ resource.Resource = (*DashboardResource)(nil) + _ resource.ResourceWithConfigure = (*DashboardResource)(nil) + _ resource.ResourceWithImportState = (*DashboardResource)(nil) +) + +type DashboardResource struct { + client checkly.Client +} + +func NewDashboardResource() resource.Resource { + return &DashboardResource{} +} + +func (r *DashboardResource) Metadata( + ctx context.Context, + req resource.MetadataRequest, + resp *resource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_dashboard" +} + +func (r *DashboardResource) Schema( + ctx context.Context, + req resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "", // TODO + }, + "last_updated": LastUpdatedAttributeSchema, + "custom_url": schema.StringAttribute{ + Required: true, + Description: "A subdomain name under 'checklyhq.com'. Needs to be unique across all users.", + }, + "custom_domain": schema.StringAttribute{ + Optional: true, + Default: nil, // TODO + Description: "A custom user domain, e.g. 'status.example.com'. See the docs on updating your DNS and SSL usage.", + }, + "logo": schema.StringAttribute{ + Optional: true, + Description: "A URL pointing to an image file to use for the dashboard logo.", + }, + "favicon": schema.StringAttribute{ + Optional: true, + Description: "A URL pointing to an image file to use as browser favicon.", + }, + "link": schema.StringAttribute{ + Optional: true, + Description: "A link to for the dashboard logo.", + }, + "description": schema.StringAttribute{ + Optional: true, + Description: "HTML description for the dashboard.", + }, + "header": schema.StringAttribute{ + Optional: true, + Description: "A piece of text displayed at the top of your dashboard.", + }, + "width": schema.StringAttribute{ + Optional: true, + Default: stringdefault.StaticString("FULL"), + Validators: []validator.String{ + stringvalidator.OneOf("FULL", "960PX"), + }, + Description: "Determines whether to use the full screen or focus in the center. Possible values `FULL` and `960PX`.", + }, + "refresh_rate": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(60), + Validators: []validator.Int32{ + int32validator.OneOf(60, 300, 600), + }, + Description: "How often to refresh the dashboard in seconds. Possible values `60`, '300' and `600`.", + }, + "paginate": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(true), + Description: "Determines if pagination is on or off.", + }, + "checks_per_page": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(15), + Description: "Determines how many checks to show per page.", + }, + "pagination_rate": schema.Int32Attribute{ + Optional: true, + Default: int32default.StaticInt32(60), + Validators: []validator.Int32{ + int32validator.OneOf(30, 60, 300), + }, + Description: "How often to trigger pagination in seconds. Possible values `30`, `60` and `300`.", + }, + "tags": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "A list of one or more tags that filter which checks to display on the dashboard.", + }, + "hide_tags": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + Description: "Show or hide the tags on the dashboard.", + }, + "use_tags_and_operator": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + Description: "Set when to use AND operator for fetching dashboard tags.", + }, + "is_private": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + Description: "Set your dashboard as private and generate key.", + }, + "key": schema.StringAttribute{ + Computed: true, + Sensitive: true, + Description: "The access key when the dashboard is private.", + }, + }, + } +} + +func (r *DashboardResource) Configure( + ctx context.Context, + req resource.ConfigureRequest, + resp *resource.ConfigureResponse, +) { + client, diags := ClientFromProviderData(req.ProviderData) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + r.client = client +} + +func (r *DashboardResource) ImportState( + ctx context.Context, + req resource.ImportStateRequest, + resp *resource.ImportStateResponse, +) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *DashboardResource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan DashboardResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.Dashboard + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.CreateDashboard(ctx, desiredModel) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Checkly Dashboard", + fmt.Sprintf("Could not create dashboard, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *DashboardResource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state DashboardResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + err := r.client.DeleteDashboard(ctx, state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting Checkly Dashboard", + fmt.Sprintf("Could not delete dashboard, unexpected error: %s", err), + ) + + return + } +} + +func (r *DashboardResource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state DashboardResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // TODO: Check if we really have to do the weird 404 handling + realizedModel, err := r.client.GetDashboard(ctx, state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Reading Checkly Dashboard", + fmt.Sprintf("Could not retrieve dashboard, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *DashboardResource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan DashboardResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.Dashboard + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.UpdateDashboard( + ctx, + plan.ID.ValueString(), + desiredModel, + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Checkly Dashboard", + fmt.Sprintf("Could not update dashboard, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +var ( + _ ResourceModel[checkly.Dashboard] = (*DashboardResourceModel)(nil) +) + +type DashboardResourceModel struct { + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + CustomURL types.String `tfsdk:"custom_url"` + CustomDomain types.String `tfsdk:"custom_domain"` + Logo types.String `tfsdk:"logo"` + Favicon types.String `tfsdk:"favicon"` + Link types.String `tfsdk:"link"` + Description types.String `tfsdk:"description"` + Header types.String `tfsdk:"header"` + Width types.String `tfsdk:"width"` + RefreshRate types.Int32 `tfsdk:"refresh_rate"` + Paginate types.Bool `tfsdk:"paginate"` + ChecksPerPage types.Int32 `tfsdk:"checks_per_page"` + PaginationRate types.Int32 `tfsdk:"pagination_rate"` + Tags types.Set `tfsdk:"tags"` + HideTags types.Bool `tfsdk:"hide_tags"` + UseTagsAndOperator types.Bool `tfsdk:"use_tags_and_operator"` + IsPrivate types.Bool `tfsdk:"is_private"` + Key types.String `tfsdk:"key"` +} + +func (m *DashboardResourceModel) Refresh(ctx context.Context, from *checkly.Dashboard, flags RefreshFlags) diag.Diagnostics { + if flags.Created() { + m.ID = types.StringValue(from.DashboardID) + } + + if flags.Created() || flags.Updated() { + m.LastUpdated = LastUpdatedNow() + } + + m.CustomURL = types.StringValue(from.CustomUrl) + m.CustomDomain = types.StringValue(from.CustomDomain) + m.Logo = types.StringValue(from.Logo) + m.Favicon = types.StringValue(from.Favicon) + m.Link = types.StringValue(from.Link) + m.Description = types.StringValue(from.Description) + m.Header = types.StringValue(from.Header) + m.Width = types.StringValue(from.Width) + m.RefreshRate = types.Int32Value(int32(from.RefreshRate)) + m.Paginate = types.BoolValue(from.Paginate) + m.ChecksPerPage = types.Int32Value(int32(from.ChecksPerPage)) + m.PaginationRate = types.Int32Value(int32(from.PaginationRate)) + m.HideTags = types.BoolValue(from.HideTags) + m.UseTagsAndOperator = types.BoolValue(from.UseTagsAndOperator) + m.IsPrivate = types.BoolValue(from.IsPrivate) + m.Tags = IntoUntypedStringSet(&from.Tags) + + if from.IsPrivate { + if len(from.Keys) > 0 { + m.Key = types.StringValue(from.Keys[0].RawKey) + } + } else { + m.Key = types.StringNull() + } + + return nil +} + +func (m *DashboardResourceModel) Render(ctx context.Context, into *checkly.Dashboard) diag.Diagnostics { + into.CustomUrl = m.CustomURL.ValueString() + into.CustomDomain = m.CustomDomain.ValueString() + into.IsPrivate = m.IsPrivate.ValueBool() + into.Logo = m.Logo.ValueString() + into.Link = m.Link.ValueString() + into.Description = m.Description.ValueString() + into.Favicon = m.Favicon.ValueString() + into.Header = m.Header.ValueString() + into.Width = m.Width.ValueString() + into.RefreshRate = int(m.RefreshRate.ValueInt32()) + into.ChecksPerPage = int(m.ChecksPerPage.ValueInt32()) + into.PaginationRate = int(m.PaginationRate.ValueInt32()) + into.Paginate = m.Paginate.ValueBool() + into.Tags = FromUntypedStringSet(m.Tags) + into.HideTags = m.HideTags.ValueBool() + into.UseTagsAndOperator = m.UseTagsAndOperator.ValueBool() + + return nil +} diff --git a/internal/provider/environment_variable_resource.go b/internal/provider/environment_variable_resource.go new file mode 100644 index 0000000..1b5ebf0 --- /dev/null +++ b/internal/provider/environment_variable_resource.go @@ -0,0 +1,278 @@ +package provider + +import ( + "context" + "fmt" + + "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/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ resource.Resource = (*EnvironmentVariableResource)(nil) + _ resource.ResourceWithConfigure = (*EnvironmentVariableResource)(nil) + _ resource.ResourceWithImportState = (*EnvironmentVariableResource)(nil) +) + +type EnvironmentVariableResource struct { + client checkly.Client +} + +func NewEnvironmentVariableResource() resource.Resource { + return &EnvironmentVariableResource{} +} + +func (r *EnvironmentVariableResource) Metadata( + ctx context.Context, + req resource.MetadataRequest, + resp *resource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_environment_variable" +} + +func (r *EnvironmentVariableResource) Schema( + ctx context.Context, + req resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "", // TODO + }, + "last_updated": LastUpdatedAttributeSchema, + "key": schema.StringAttribute{ + Required: true, + Description: "", // TODO + }, + "value": schema.StringAttribute{ + Required: true, + Sensitive: true, // FIXME: Keep sensitive? Old code did not set it. + Description: "", // TODO + }, + "locked": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + Description: "", // TODO + }, + "secret": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + Description: "", // TODO + }, + }, + } +} + +func (r *EnvironmentVariableResource) Configure( + ctx context.Context, + req resource.ConfigureRequest, + resp *resource.ConfigureResponse, +) { + client, diags := ClientFromProviderData(req.ProviderData) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + r.client = client +} + +func (r *EnvironmentVariableResource) ImportState( + ctx context.Context, + req resource.ImportStateRequest, + resp *resource.ImportStateResponse, +) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *EnvironmentVariableResource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan EnvironmentVariableResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.EnvironmentVariable + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.CreateEnvironmentVariable( + ctx, + desiredModel, + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Checkly Environment Variable", + fmt.Sprintf("Could not create environment variable, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *EnvironmentVariableResource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state EnvironmentVariableResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + err := r.client.DeleteEnvironmentVariable(ctx, state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting Checkly Environment Variable", + fmt.Sprintf("Could not delete environment variable, unexpected error: %s", err), + ) + + return + } +} + +func (r *EnvironmentVariableResource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state EnvironmentVariableResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // TODO: Check if we really have to do the weird 404 handling + realizedModel, err := r.client.GetEnvironmentVariable( + ctx, + state.ID.ValueString(), + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Reading Checkly Environment Variable", + fmt.Sprintf("Could not retrieve environment variable, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *EnvironmentVariableResource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan EnvironmentVariableResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.EnvironmentVariable + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.UpdateEnvironmentVariable( + ctx, + plan.ID.ValueString(), + desiredModel, + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Checkly Environment Variable", + fmt.Sprintf("Could not update environment variable, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +var ( + _ ResourceModel[checkly.EnvironmentVariable] = (*EnvironmentVariableResourceModel)(nil) +) + +type EnvironmentVariableResourceModel struct { + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + Key types.String `tfsdk:"key"` + Value types.String `tfsdk:"value"` + Locked types.Bool `tfsdk:"locked"` + Secret types.Bool `tfsdk:"secret"` +} + +func (m *EnvironmentVariableResourceModel) Refresh(ctx context.Context, from *checkly.EnvironmentVariable, flags RefreshFlags) diag.Diagnostics { + if flags.Created() { + m.ID = types.StringValue(from.Key) + } + + if flags.Created() || flags.Updated() { + m.LastUpdated = LastUpdatedNow() + } + + m.Key = types.StringValue(from.Key) + m.Value = types.StringValue(from.Value) + m.Locked = types.BoolValue(from.Locked) + m.Secret = types.BoolValue(from.Secret) + + return nil +} + +func (m *EnvironmentVariableResourceModel) Render(ctx context.Context, into *checkly.EnvironmentVariable) diag.Diagnostics { + into.Key = m.Key.ValueString() + into.Value = m.Value.ValueString() + into.Locked = m.Locked.ValueBool() + into.Secret = m.Secret.ValueBool() + + return nil +} diff --git a/internal/provider/heartbeat_resource.go b/internal/provider/heartbeat_resource.go new file mode 100644 index 0000000..a500254 --- /dev/null +++ b/internal/provider/heartbeat_resource.go @@ -0,0 +1,389 @@ +package provider + +import ( + "context" + "fmt" + "slices" + + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ resource.Resource = (*HeartbeatResource)(nil) + _ resource.ResourceWithConfigure = (*HeartbeatResource)(nil) + _ resource.ResourceWithImportState = (*HeartbeatResource)(nil) +) + +type HeartbeatResource struct { + client checkly.Client +} + +func NewHeartbeatResource() resource.Resource { + return &HeartbeatResource{} +} + +func (r *HeartbeatResource) Metadata( + ctx context.Context, + req resource.MetadataRequest, + resp *resource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_heartbeat" +} + +func (r *HeartbeatResource) Schema( + ctx context.Context, + req resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Description: "Heartbeats allows you to monitor your cron jobs and set up alerting, so you get a notification when things break or slow down.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "", // TODO + }, + "last_updated": LastUpdatedAttributeSchema, + "name": schema.StringAttribute{ + Required: true, + Description: "The name of the check.", + }, + "activated": schema.BoolAttribute{ + Required: true, + Description: "Determines if the check is running or not. Possible values `true`, and `false`.", + }, + "muted": schema.BoolAttribute{ + Optional: true, + Description: "Determines if any notifications will be sent out when a check fails/degrades/recovers.", + }, + "tags": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "A list of tags for organizing and filtering checks.", + }, + "alert_settings": CheckAlertSettingsAttributeSchema, + "use_global_alert_settings": schema.BoolAttribute{ + Optional: true, + Description: "When true, the account level alert settings will be used, not the alert setting defined on this check.", + }, + "heartbeat": schema.SetNestedAttribute{ // TODO: Change from set to single object. + Required: true, + Validators: []validator.Set{ + setvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedAttributeObject{ + Validators: []validator.Object{ + // TODO: period * period_unit must be between 30s and 365 days + // TODO: grace * grace_unit must be less than 365 days + }, + Attributes: map[string]schema.Attribute{ + "period": schema.Int32Attribute{ + Required: true, + Description: "How often you expect a ping to the ping URL.", + }, + "period_unit": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("seconds", "minutes", "hours", "days"), + }, + Description: "Possible values `seconds`, `minutes`, `hours` and `days`.", + }, + "grace": schema.Int32Attribute{ + Required: true, + Description: "How long Checkly should wait before triggering any alerts when a ping does not arrive within the set period.", + }, + "grace_unit": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("seconds", "minutes", "hours", "days"), + }, + Description: "Possible values `seconds`, `minutes`, `hours` and `days`.", + }, + "ping_token": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Custom token to generate your ping URL. Checkly will expect a ping to `https://ping.checklyhq.com/[PING_TOKEN]`.", + }, + }, + }, + }, + "alert_channel_subscription": CheckAlertChannelSubscriptionAttributeSchema, + }, + } +} + +func (r *HeartbeatResource) Configure( + ctx context.Context, + req resource.ConfigureRequest, + resp *resource.ConfigureResponse, +) { + client, diags := ClientFromProviderData(req.ProviderData) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + r.client = client +} + +func (r *HeartbeatResource) ImportState( + ctx context.Context, + req resource.ImportStateRequest, + resp *resource.ImportStateResponse, +) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *HeartbeatResource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan HeartbeatResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.HeartbeatCheck + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.CreateHeartbeat(ctx, desiredModel) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Checkly Heartbeat", + fmt.Sprintf("Could not create heartbeat, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *HeartbeatResource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state HeartbeatResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + err := r.client.DeleteCheck(ctx, state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting Checkly Heartbeat", + fmt.Sprintf("Could not delete heartbeat, unexpected error: %s", err), + ) + + return + } +} + +func (r *HeartbeatResource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state HeartbeatResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // TODO: Check if we really have to do the weird 404 handling + realizedModel, err := r.client.GetHeartbeatCheck(ctx, state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Reading Checkly Heartbeat", + fmt.Sprintf("Could not retrieve heartbeat, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *HeartbeatResource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan HeartbeatResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.HeartbeatCheck + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.UpdateHeartbeat( + ctx, + plan.ID.ValueString(), + desiredModel, + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Checkly Heartbeat", + fmt.Sprintf("Could not update heartbeat, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +var ( + _ ResourceModel[checkly.HeartbeatCheck] = (*HeartbeatResourceModel)(nil) + _ ResourceModel[checkly.Heartbeat] = (*HeartbeatAttributeModel)(nil) +) + +type HeartbeatResourceModel struct { + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + Name types.String `tfsdk:"name"` + Activated types.Bool `tfsdk:"activated"` + Muted types.Bool `tfsdk:"muted"` + Tags types.Set `tfsdk:"tags"` + AlertSettings CheckAlertSettingsAttributeModel `tfsdk:"alert_settings"` + UseGlobalAlertSettings types.Bool `tfsdk:"use_global_alert_settings"` + Heartbeat HeartbeatAttributeModel `tfsdk:"heartbeat"` + AlertChannelSubscriptions []CheckAlertChannelSubscriptionAttributeModel `tfsdk:"alert_channel_subscription"` +} + +func (m *HeartbeatResourceModel) Refresh(ctx context.Context, from *checkly.HeartbeatCheck, flags RefreshFlags) diag.Diagnostics { + var diags diag.Diagnostics + + if flags.Created() { + m.ID = types.StringValue(from.ID) + } + + if flags.Created() || flags.Updated() { + m.LastUpdated = LastUpdatedNow() + } + + m.Name = types.StringValue(from.Name) + m.Activated = types.BoolValue(from.Activated) + m.Muted = types.BoolValue(from.Muted) + + slices.Sort(from.Tags) + m.Tags = IntoUntypedStringSet(&from.Tags) + + diags.Append(m.AlertSettings.Refresh(ctx, &from.AlertSettings, flags)...) + if diags.HasError() { + return diags + } + + m.UseGlobalAlertSettings = types.BoolValue(from.UseGlobalAlertSettings) + + diags.Append(m.Heartbeat.Refresh(ctx, &from.Heartbeat, flags)...) + if diags.HasError() { + return diags + } + + m.AlertChannelSubscriptions = nil + for _, sub := range from.AlertChannelSubscriptions { + var subModel CheckAlertChannelSubscriptionAttributeModel + diags.Append(subModel.Refresh(ctx, &sub, flags)...) + if diags.HasError() { + return diags + } + + m.AlertChannelSubscriptions = append(m.AlertChannelSubscriptions, subModel) + } + + return diags +} + +func (m *HeartbeatResourceModel) Render(ctx context.Context, into *checkly.HeartbeatCheck) diag.Diagnostics { + var diags diag.Diagnostics + + into.Name = m.Name.ValueString() + into.Activated = m.Activated.ValueBool() + into.Muted = m.Muted.ValueBool() + into.Tags = FromUntypedStringSet(m.Tags) + + diags.Append(m.AlertSettings.Render(ctx, &into.AlertSettings)...) + + into.UseGlobalAlertSettings = m.UseGlobalAlertSettings.ValueBool() + + diags.Append(m.Heartbeat.Render(ctx, &into.Heartbeat)...) + + return diags +} + +type HeartbeatAttributeModel struct { + Period types.Int32 `tfsdk:"period"` + PeriodUnit types.String `tfsdk:"period_unit"` + Grace types.Int32 `tfsdk:"grace"` + GraceUnit types.String `tfsdk:"grace_unit"` + PingToken types.String `tfsdk:"ping_token"` +} + +func (m *HeartbeatAttributeModel) Refresh(ctx context.Context, from *checkly.Heartbeat, flags RefreshFlags) diag.Diagnostics { + m.Period = types.Int32Value(int32(from.Period)) + m.PeriodUnit = types.StringValue(from.PeriodUnit) + m.Grace = types.Int32Value(int32(from.Grace)) + m.GraceUnit = types.StringValue(from.GraceUnit) + m.PingToken = types.StringValue(from.PingToken) + + return nil +} + +func (m *HeartbeatAttributeModel) Render(ctx context.Context, into *checkly.Heartbeat) diag.Diagnostics { + into.Period = int(m.Period.ValueInt32()) + into.PeriodUnit = m.PeriodUnit.ValueString() + into.Grace = int(m.Grace.ValueInt32()) + into.GraceUnit = m.GraceUnit.ValueString() + into.PingToken = m.PingToken.ValueString() + + return nil +} diff --git a/internal/provider/last_updated_attribute.go b/internal/provider/last_updated_attribute.go new file mode 100644 index 0000000..d711bdf --- /dev/null +++ b/internal/provider/last_updated_attribute.go @@ -0,0 +1,17 @@ +package provider + +import ( + "time" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var LastUpdatedAttributeSchema = schema.StringAttribute{ + Computed: true, + Description: "When the resource was last updated by the provider.", +} + +func LastUpdatedNow() types.String { + return types.StringValue(time.Now().Format(time.RFC850)) +} diff --git a/internal/provider/maintenance_windows_resource.go b/internal/provider/maintenance_windows_resource.go new file mode 100644 index 0000000..74847a3 --- /dev/null +++ b/internal/provider/maintenance_windows_resource.go @@ -0,0 +1,319 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ resource.Resource = (*MaintenanceWindowsResource)(nil) + _ resource.ResourceWithConfigure = (*MaintenanceWindowsResource)(nil) + _ resource.ResourceWithImportState = (*MaintenanceWindowsResource)(nil) +) + +type MaintenanceWindowsResource struct { + client checkly.Client +} + +func NewMaintenanceWindowsResource() resource.Resource { + return &MaintenanceWindowsResource{} +} + +func (r *MaintenanceWindowsResource) Metadata( + ctx context.Context, + req resource.MetadataRequest, + resp *resource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_maintenance_windows" +} + +func (r *MaintenanceWindowsResource) Schema( + ctx context.Context, + req resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "", // TODO + }, + "last_updated": LastUpdatedAttributeSchema, + "name": schema.StringAttribute{ + Required: true, + Description: "The maintenance window name.", + }, + "starts_at": schema.StringAttribute{ + Required: true, + Description: "The start date of the maintenance window.", + }, + "ends_at": schema.StringAttribute{ + Required: true, + Description: "The end date of the maintenance window.", + }, + "repeat_unit": schema.StringAttribute{ + Optional: true, + Default: nil, + Validators: []validator.String{ + stringvalidator.OneOf("DAY", "WEEK", "MONTH"), + }, + Description: "The repeat cadence for the maintenance window. Possible values `DAY`, `WEEK` and `MONTH`.", + }, + "repeat_interval": schema.Int32Attribute{ + Optional: true, + Description: "The repeat interval of the maintenance window from the first occurrence.", + }, + "repeat_ends_at": schema.StringAttribute{ + Optional: true, + Description: "The date on which the maintenance window should stop repeating.", + }, + "tags": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "The names of the checks and groups maintenance window should apply to.", + }, + }, + } +} + +func (r *MaintenanceWindowsResource) Configure( + ctx context.Context, + req resource.ConfigureRequest, + resp *resource.ConfigureResponse, +) { + client, diags := ClientFromProviderData(req.ProviderData) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + r.client = client +} + +func (r *MaintenanceWindowsResource) ImportState( + ctx context.Context, + req resource.ImportStateRequest, + resp *resource.ImportStateResponse, +) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *MaintenanceWindowsResource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan MaintenanceWindowsResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.MaintenanceWindow + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.CreateMaintenanceWindow(ctx, desiredModel) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Checkly Maintenance Window", + fmt.Sprintf("Could not create maintenance window, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *MaintenanceWindowsResource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state MaintenanceWindowsResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + id, diags := MaintenanceWindowID.FromString(state.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + err := r.client.DeleteMaintenanceWindow(ctx, id) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting Checkly Maintenance Window", + fmt.Sprintf("Could not delete maintenance window, unexpected error: %s", err), + ) + + return + } +} + +func (r *MaintenanceWindowsResource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state MaintenanceWindowsResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + id, diags := MaintenanceWindowID.FromString(state.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + // TODO: Check if we really have to do the weird 404 handling + realizedModel, err := r.client.GetMaintenanceWindow(ctx, id) + if err != nil { + resp.Diagnostics.AddError( + "Error Reading Checkly Maintenance Window", + fmt.Sprintf("Could not retrieve maintenance window, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *MaintenanceWindowsResource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan MaintenanceWindowsResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + id, diags := MaintenanceWindowID.FromString(plan.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + var desiredModel checkly.MaintenanceWindow + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.UpdateMaintenanceWindow( + ctx, + id, + desiredModel, + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Checkly Maintenance Window", + fmt.Sprintf("Could not update maintenance window, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +var MaintenanceWindowID = SDKIdentifier{ + Path: path.Root("id"), + Title: "Checkly Maintenance Window ID", +} + +var ( + _ ResourceModel[checkly.MaintenanceWindow] = (*MaintenanceWindowsResourceModel)(nil) +) + +type MaintenanceWindowsResourceModel struct { + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + Name types.String `tfsdk:"name"` + StartsAt types.String `tfsdk:"starts_at"` + EndsAt types.String `tfsdk:"ends_at"` + RepeatUnit types.String `tfsdk:"repeat_unit"` + RepeatInterval types.Int32 `tfsdk:"repeat_interval"` + RepeatEndsAt types.String `tfsdk:"repeat_ends_at"` + Tags types.Set `tfsdk:"tags"` +} + +func (m *MaintenanceWindowsResourceModel) Refresh(ctx context.Context, from *checkly.MaintenanceWindow, flags RefreshFlags) diag.Diagnostics { + if flags.Created() { + m.ID = MaintenanceWindowID.IntoString(from.ID) + } + + if flags.Created() || flags.Updated() { + m.LastUpdated = LastUpdatedNow() + } + + m.Name = types.StringValue(from.Name) + m.StartsAt = types.StringValue(from.StartsAt) + m.EndsAt = types.StringValue(from.EndsAt) + m.RepeatUnit = types.StringValue(from.RepeatUnit) + m.RepeatEndsAt = types.StringValue(from.RepeatEndsAt) + m.RepeatInterval = types.Int32Value(int32(from.RepeatInterval)) + m.Tags = IntoUntypedStringSet(&from.Tags) + + return nil +} + +func (m *MaintenanceWindowsResourceModel) Render(ctx context.Context, into *checkly.MaintenanceWindow) diag.Diagnostics { + into.Name = m.Name.ValueString() + into.StartsAt = m.StartsAt.ValueString() + into.EndsAt = m.EndsAt.ValueString() + into.RepeatUnit = m.RepeatUnit.ValueString() + into.RepeatEndsAt = m.RepeatEndsAt.ValueString() + into.RepeatInterval = int(m.RepeatInterval.ValueInt32()) + into.Tags = FromUntypedStringSet(m.Tags) + + return nil +} diff --git a/internal/provider/private_location_resource.go b/internal/provider/private_location_resource.go new file mode 100644 index 0000000..fd4c83a --- /dev/null +++ b/internal/provider/private_location_resource.go @@ -0,0 +1,285 @@ +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/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ resource.Resource = (*PrivateLocationResource)(nil) + _ resource.ResourceWithConfigure = (*PrivateLocationResource)(nil) + _ resource.ResourceWithImportState = (*PrivateLocationResource)(nil) +) + +type PrivateLocationResource struct { + client checkly.Client +} + +func NewPrivateLocationResource() resource.Resource { + return &PrivateLocationResource{} +} + +func (r *PrivateLocationResource) Metadata( + ctx context.Context, + req resource.MetadataRequest, + resp *resource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_private_location" +} + +func (r *PrivateLocationResource) Schema( + ctx context.Context, + req resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "", // TODO + }, + "last_updated": LastUpdatedAttributeSchema, + "name": schema.StringAttribute{ + Required: true, + Description: "The private location name.", + }, + "slug_name": schema.StringAttribute{ + Required: true, + Description: "Valid slug name.", + }, + "icon": schema.StringAttribute{ + Optional: true, + Default: stringdefault.StaticString("location"), + Description: "Icon assigned to the private location.", + }, + "keys": schema.SetAttribute{ + ElementType: types.StringType, + Computed: true, + Sensitive: true, + Description: "Private location API keys.", + }, + }, + } +} + +func (r *PrivateLocationResource) Configure( + ctx context.Context, + req resource.ConfigureRequest, + resp *resource.ConfigureResponse, +) { + client, diags := ClientFromProviderData(req.ProviderData) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + r.client = client +} + +func (r *PrivateLocationResource) ImportState( + ctx context.Context, + req resource.ImportStateRequest, + resp *resource.ImportStateResponse, +) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *PrivateLocationResource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan PrivateLocationResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.PrivateLocation + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.CreatePrivateLocation(ctx, desiredModel) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Checkly Private Location", + fmt.Sprintf("Could not create private location, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *PrivateLocationResource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state PrivateLocationResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + err := r.client.DeletePrivateLocation(ctx, state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting Checkly Private Location", + fmt.Sprintf("Could not delete private location, unexpected error: %s", err), + ) + + return + } +} + +func (r *PrivateLocationResource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state PrivateLocationResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // TODO: Check if we really have to do the weird 404 handling + realizedModel, err := r.client.GetPrivateLocation(ctx, state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Reading Checkly Private Location", + fmt.Sprintf("Could not retrieve private location, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *PrivateLocationResource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan PrivateLocationResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.PrivateLocation + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.UpdatePrivateLocation( + ctx, + plan.ID.ValueString(), + desiredModel, + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Checkly Private Location", + fmt.Sprintf("Could not update private location, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +var ( + _ ResourceModel[checkly.PrivateLocation] = (*PrivateLocationResourceModel)(nil) +) + +type PrivateLocationResourceModel struct { + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + Name types.String `tfsdk:"name"` + SlugName types.String `tfsdk:"slug_name"` + Icon types.String `tfsdk:"icon"` + Keys types.Set `tfsdk:"keys"` +} + +func (m *PrivateLocationResourceModel) Refresh(ctx context.Context, from *checkly.PrivateLocation, flags RefreshFlags) diag.Diagnostics { + if flags.Created() { + m.ID = types.StringValue(from.ID) + } + + if flags.Created() || flags.Updated() { + m.LastUpdated = LastUpdatedNow() + } + + m.Name = types.StringValue(from.Name) + m.SlugName = types.StringValue(from.SlugName) + m.Icon = types.StringValue(from.Icon) + + var keyValues []attr.Value + for _, key := range from.Keys { + keyValues = append(keyValues, types.StringValue(key.RawKey)) + } + + keys, diags := types.SetValue(types.StringType, keyValues) + if diags.HasError() { + return diags + } + + m.Keys = keys + + return nil +} + +func (m *PrivateLocationResourceModel) Render(ctx context.Context, into *checkly.PrivateLocation) diag.Diagnostics { + into.Name = m.Name.ValueString() + into.SlugName = m.SlugName.ValueString() + into.Icon = m.Icon.ValueString() + + // Keys are intentionally not included. + + return nil +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go new file mode 100644 index 0000000..6715070 --- /dev/null +++ b/internal/provider/provider.go @@ -0,0 +1,261 @@ +package provider + +import ( + "context" + "fmt" + "io" + "os" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + + checkly "github.com/checkly/checkly-go-sdk" +) + +const ( + defaultAPIURL = "https://api.checklyhq.com" +) + +var ( + _ provider.Provider = (*ChecklyProvider)(nil) + _ provider.ProviderWithFunctions = (*ChecklyProvider)(nil) +) + +type ChecklyProvider struct { + version string +} + +type ChecklyProviderModel struct { + APIKey types.String `tfsdk:"api_key"` + APIURL types.String `tfsdk:"api_url"` + AccountID types.String `tfsdk:"account_id"` +} + +func (p *ChecklyProvider) Metadata( + ctx context.Context, + req provider.MetadataRequest, + resp *provider.MetadataResponse, +) { + resp.TypeName = "checkly" + resp.Version = p.version +} + +func (p *ChecklyProvider) Schema( + ctx context.Context, + req provider.SchemaRequest, + resp *provider.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "api_key": schema.StringAttribute{ + Optional: true, + Sensitive: true, // FIXME: Keep this? Old code did not set it. + }, + "api_url": schema.StringAttribute{ + Optional: true, + }, + "account_id": schema.StringAttribute{ + Optional: true, + }, + }, + } +} + +func (p *ChecklyProvider) Configure( + ctx context.Context, + req provider.ConfigureRequest, + resp *provider.ConfigureResponse, +) { + var config ChecklyProviderModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if config.APIKey.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("api_key"), + "Unknown Checkly API Key", + "The provider cannot create the Checkly API client as there is "+ + "an unknown configuration value for the Checkly API Key. "+ + "Either target apply the source of the value first, set the "+ + "value statically in the configuration, or use the "+ + "CHECKLY_API_KEY environment variable.", + ) + } + + if config.APIURL.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("api_url"), + "Unknown Checkly API URL", + "The provider cannot create the Checkly API client as there is "+ + "an unknown configuration value for the Checkly API URL. "+ + "Either target apply the source of the value first, set the "+ + "value statically in the configuration, or use the "+ + "CHECKLY_API_URL environment variable.", + ) + } + + if config.AccountID.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("account_id"), + "Unknown Checkly Account ID", + "The provider cannot create the Checkly API client as there is "+ + "an unknown configuration value for the Checkly Account ID. "+ + "Either target apply the source of the value first, set the "+ + "value statically in the configuration, or use the "+ + "CHECKLY_API_URL environment variable.", + ) + } + + if resp.Diagnostics.HasError() { + return + } + + apiKey := os.Getenv("CHECKLY_API_KEY") + apiURL := os.Getenv("CHECKLY_API_URL") + accountID := os.Getenv("CHECKLY_ACCOUNT_ID") + + if !config.APIKey.IsNull() { + apiKey = config.APIKey.ValueString() + } + + if !config.APIURL.IsNull() { + apiURL = config.APIURL.ValueString() + } + + if !config.AccountID.IsNull() { + accountID = config.AccountID.ValueString() + } + + if apiKey == "" { + resp.Diagnostics.AddAttributeError( + path.Root("api_key"), + "Missing Checkly API Key", + "The provider cannot create the Checkly API client as there is "+ + "a missing or empty value for the Checkly API Key. "+ + "Set the value in the configuration or use the "+ + "CHECKLY_API_KEY environment variable. If either is already "+ + "set, ensure the value is not empty.", + ) + } + + if apiURL == "" { + apiURL = defaultAPIURL + } + + if resp.Diagnostics.HasError() { + return + } + + ctx = tflog.SetField(ctx, "checkly_api_key", apiKey) + ctx = tflog.MaskFieldValuesWithFieldKeys(ctx, "checkly_api_key") + ctx = tflog.SetField(ctx, "checkly_api_url", apiURL) + ctx = tflog.SetField(ctx, "checkly_account_id", accountID) + + tflog.Debug(ctx, "Creating Checkly client") + + debugLog := os.Getenv("CHECKLY_DEBUG_LOG") + var debugOutput io.Writer + if debugLog != "" { + debugFile, err := os.OpenFile(debugLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + resp.Diagnostics.AddError( + "Error Accessing Checkly Debug Log File", + fmt.Sprintf("Could not open debug log file, unexpected error: %s", err), + ) + + return + } + + debugOutput = debugFile + } + + client := checkly.NewClient( + apiURL, + apiKey, + nil, + debugOutput, + ) + + if accountID != "" { + client.SetAccountId(accountID) + } + + apiSource := os.Getenv("CHECKLY_API_SOURCE") + if apiSource != "" { + client.SetChecklySource(apiSource) + } else { + client.SetChecklySource("TF") + } + + resp.DataSourceData = client + resp.ResourceData = client +} + +func ClientFromProviderData(providerData any) (checkly.Client, diag.Diagnostics) { + if providerData == nil { + return nil, diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Missing Configure Type", + "Expected checkly.Client, got nil. Please report this issue "+ + "to the provider developers.", + ), + } + } + + client, ok := providerData.(checkly.Client) + if !ok { + return nil, diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Unexpected Configure Type", + fmt.Sprintf("Expected checkly.Client, got: %T. Please report "+ + "this issue to the provider developers.", providerData), + ), + } + } + + return client, nil +} + +func (p *ChecklyProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + NewAlertChannelResource, + NewCheckGroupResource, + NewCheckResource, + NewDashboardResource, + NewEnvironmentVariableResource, + NewHeartbeatResource, + NewMaintenanceWindowsResource, + NewPrivateLocationResource, + NewSnippetResource, + NewTriggerCheckResource, + NewTriggerGroupResource, + } +} + +func (p *ChecklyProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewStaticIPsDataSource, + } +} + +func (p *ChecklyProvider) Functions(ctx context.Context) []func() function.Function { + return []func() function.Function{} +} + +func New(version string) func() provider.Provider { + return func() provider.Provider { + return &ChecklyProvider{ + version: version, + } + } +} diff --git a/internal/provider/resource_model.go b/internal/provider/resource_model.go new file mode 100644 index 0000000..9491160 --- /dev/null +++ b/internal/provider/resource_model.go @@ -0,0 +1,102 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" +) + +type RefreshFlags int + +const ( + ModelCreated RefreshFlags = 1 << iota + ModelUpdated + + ModelLoaded RefreshFlags = 0 +) + +func (f RefreshFlags) Contains(other RefreshFlags) bool { + return f&other == other +} + +func (f RefreshFlags) Updated() bool { + return f.Contains(ModelUpdated) +} + +func (f RefreshFlags) Created() bool { + return f.Contains(ModelCreated) +} + +type Render[SDKModel any] interface { + Render(ctx context.Context, into *SDKModel) diag.Diagnostics +} + +func RenderMany[ + SDKModel any, + R any, + RPtr interface { + Render[SDKModel] + *R + }, + Sources ~[]R, + Results ~[]SDKModel, +]( + ctx context.Context, + sources Sources, + results Results, +) ( + diags diag.Diagnostics, +) { + for _, source := range sources { + var result SDKModel + + diags.Append(RPtr(&source).Render(ctx, &result)...) + if diags.HasError() { + return diags + } + + results = append(results, result) + } + + return diags +} + +type Refresh[SDKModel any] interface { + Refresh(ctx context.Context, from *SDKModel, flags RefreshFlags) diag.Diagnostics +} + +func RefreshMany[ + SDKModel any, + R any, + RPtr interface { + Refresh[SDKModel] + *R + }, + Sources ~[]SDKModel, + Results ~[]R, +]( + ctx context.Context, + sources Sources, + results Results, + flags RefreshFlags, +) ( + diags diag.Diagnostics, +) { + for _, source := range sources { + var r R + + diags.Append(RPtr(&r).Refresh(ctx, &source, flags)...) + if diags.HasError() { + return diags + } + + results = append(results, r) + } + + return diags +} + +type ResourceModel[SDKModel any] interface { + Render[SDKModel] + Refresh[SDKModel] +} diff --git a/internal/provider/sdk.go b/internal/provider/sdk.go new file mode 100644 index 0000000..50df790 --- /dev/null +++ b/internal/provider/sdk.go @@ -0,0 +1,86 @@ +package provider + +import ( + "fmt" + "strconv" + + "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/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +type SDKIdentifier struct { + Path path.Path + Title string +} + +func (i *SDKIdentifier) FromString(id types.String) (int64, diag.Diagnostics) { + if id.IsUnknown() { + return 0, diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + i.Path, + "Unknown "+i.Title, + "", // TODO + ), + } + } + + if id.IsNull() { + return 0, diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + i.Path, + "Missing "+i.Title, + "", // TODO + ), + } + } + + val, err := strconv.ParseInt(id.ValueString(), 10, 64) + if err != nil { + return 0, diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + i.Path, + "Invalid "+i.Title, + "Value must be numeric, but was not: "+err.Error(), + ), + } + } + + return val, nil +} + +func (i *SDKIdentifier) IntoString(id int64) types.String { + return types.StringValue(fmt.Sprintf("%d", id)) +} + +func SDKKeyValuesFromMap(m types.Map) []checkly.KeyValue { + if m.IsNull() { + return nil + } + + var values []checkly.KeyValue + for key, val := range m.Elements() { + values = append(values, checkly.KeyValue{ + Key: key, + Value: val.(types.String).ValueString(), + }) + } + + return values +} + +func SDKKeyValuesIntoMap(values *[]checkly.KeyValue) types.Map { + if values == nil { + return types.MapNull(types.StringType) + } + + mapValues := make(map[string]attr.Value, len(*values)) + for _, kv := range *values { + mapValues[kv.Key] = types.StringValue(kv.Value) + } + + return types.MapValueMust(types.StringType, mapValues) +} diff --git a/internal/provider/snippet_resource.go b/internal/provider/snippet_resource.go new file mode 100644 index 0000000..82ec4ad --- /dev/null +++ b/internal/provider/snippet_resource.go @@ -0,0 +1,279 @@ +package provider + +import ( + "context" + "fmt" + + "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/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ resource.Resource = (*SnippetResource)(nil) + _ resource.ResourceWithConfigure = (*SnippetResource)(nil) + _ resource.ResourceWithImportState = (*SnippetResource)(nil) +) + +type SnippetResource struct { + client checkly.Client +} + +func NewSnippetResource() resource.Resource { + return &SnippetResource{} +} + +func (r *SnippetResource) Metadata( + ctx context.Context, + req resource.MetadataRequest, + resp *resource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_snippet" +} + +func (r *SnippetResource) Schema( + ctx context.Context, + req resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "", // TODO + }, + "last_updated": LastUpdatedAttributeSchema, + "name": schema.StringAttribute{ + Required: true, + Description: "The name of the snippet.", + }, + "script": schema.StringAttribute{ + Required: true, + Description: "Your Node.js code that interacts with the API " + + "check lifecycle, or functions as a partial for browser " + + "checks.", + }, + }, + } +} + +func (r *SnippetResource) Configure( + ctx context.Context, + req resource.ConfigureRequest, + resp *resource.ConfigureResponse, +) { + client, diags := ClientFromProviderData(req.ProviderData) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + r.client = client +} + +func (r *SnippetResource) ImportState( + ctx context.Context, + req resource.ImportStateRequest, + resp *resource.ImportStateResponse, +) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *SnippetResource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan SnippetResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var desiredModel checkly.Snippet + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.CreateSnippet(ctx, desiredModel) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Checkly Snippet", + fmt.Sprintf("Could not create snippet, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *SnippetResource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state SnippetResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + id, diags := SnippetID.FromString(state.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + err := r.client.DeleteSnippet(ctx, id) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting Checkly Snippet", + fmt.Sprintf("Could not delete snippet, unexpected error: %s", err), + ) + + return + } +} + +func (r *SnippetResource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state SnippetResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + id, diags := SnippetID.FromString(state.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + // TODO: Check if we really have to do the weird 404 handling + realizedModel, err := r.client.GetSnippet(ctx, id) + if err != nil { + resp.Diagnostics.AddError( + "Error Reading Checkly Snippet", + fmt.Sprintf("Could not retrieve snippet, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *SnippetResource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan SnippetResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + id, diags := SnippetID.FromString(plan.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + var desiredModel checkly.Snippet + resp.Diagnostics.Append(plan.Render(ctx, &desiredModel)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.UpdateSnippet( + ctx, + id, + desiredModel, + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Checkly Snippet", + fmt.Sprintf("Could not update snippet, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +var SnippetID = SDKIdentifier{ + Path: path.Root("id"), + Title: "Checkly Snippet ID", +} + +var ( + _ ResourceModel[checkly.Snippet] = (*SnippetResourceModel)(nil) +) + +type SnippetResourceModel struct { + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + Name types.String `tfsdk:"name"` + Script types.String `tfsdk:"script"` +} + +func (m *SnippetResourceModel) Refresh(ctx context.Context, from *checkly.Snippet, flags RefreshFlags) diag.Diagnostics { + if flags.Created() { + m.ID = SnippetID.IntoString(from.ID) + } + + if flags.Created() || flags.Updated() { + m.LastUpdated = LastUpdatedNow() + } + + m.Name = types.StringValue(from.Name) + m.Script = types.StringValue(from.Script) + + return nil +} + +func (m *SnippetResourceModel) Render(ctx context.Context, into *checkly.Snippet) diag.Diagnostics { + into.Name = m.Name.ValueString() + into.Script = m.Script.ValueString() + + return nil +} diff --git a/internal/provider/static_ips_data_source.go b/internal/provider/static_ips_data_source.go new file mode 100644 index 0000000..c13a08a --- /dev/null +++ b/internal/provider/static_ips_data_source.go @@ -0,0 +1,155 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ datasource.DataSource = (*StaticIPsDataSource)(nil) + _ datasource.DataSourceWithConfigure = (*StaticIPsDataSource)(nil) +) + +type StaticIPsDataSource struct { + client checkly.Client +} + +func NewStaticIPsDataSource() datasource.DataSource { + return &StaticIPsDataSource{} +} + +type StaticIPsDataSourceModel struct { + ID types.String `tfsdk:"id"` + Addresses types.Set `tfsdk:"addresses"` + Locations types.Set `tfsdk:"locations"` + IPFamily types.String `tfsdk:"ip_family"` +} + +func (d *StaticIPsDataSource) Metadata( + ctx context.Context, + req datasource.MetadataRequest, + resp *datasource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_static_ips" +} + +func (d *StaticIPsDataSource) Schema( + ctx context.Context, + req datasource.SchemaRequest, + resp *datasource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Description: "", // TODO + MarkdownDescription: "", // TODO + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "ID of the static IPs data source.", + }, + "addresses": schema.SetAttribute{ + ElementType: types.StringType, + Computed: true, + Description: "Static IP addresses for Checkly's runner infrastructure.", + }, + "locations": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Specify the locations you want to get static IPs for.", + }, + "ip_family": schema.StringAttribute{ + Optional: true, + Description: "Specify the IP families you want to get static " + + "IPs for. Only `IPv6` or `IPv4` are valid options.", + Validators: []validator.String{ + stringvalidator.OneOf("IPv6", "IPv4"), + }, + }, + }, + } +} + +func (d *StaticIPsDataSource) Configure( + ctx context.Context, + req datasource.ConfigureRequest, + resp *datasource.ConfigureResponse, +) { + client, diags := ClientFromProviderData(req.ProviderData) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + d.client = client +} + +func (d *StaticIPsDataSource) Read( + ctx context.Context, + req datasource.ReadRequest, + resp *datasource.ReadResponse, +) { + var data StaticIPsDataSourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + staticIPs, err := d.client.GetStaticIPs(ctx) + if err != nil { + resp.Diagnostics.AddError( + "Error Reading Checkly Static IPs", + fmt.Sprintf("Could not retrieve static IPs, unexpected error: %s", err), + ) + + return + } + + var haveLocationFilters bool + includeLocation := make(map[string]bool) + for _, el := range data.Locations.Elements() { + haveLocationFilters = true + includeLocation[el.(types.String).ValueString()] = true + } + + ipFamilyFilter := data.IPFamily.ValueString() + + only4 := ipFamilyFilter == "IPv4" + only6 := ipFamilyFilter == "IPv6" + + var addressValues []attr.Value + for _, ip := range staticIPs { + switch { + case only4 && !ip.Address.Addr().Is4(): + continue + case only6 && !ip.Address.Addr().Is6(): + continue + case haveLocationFilters && !includeLocation[ip.Region]: + continue + } + + addressValues = append(addressValues, types.StringValue(ip.Address.String())) + } + + addresses, diags := types.SetValue(types.StringType, addressValues) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + data.ID = types.StringValue("checkly_static_ips_data_source_id") + data.Addresses = addresses + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/internal/provider/trigger_check_resource.go b/internal/provider/trigger_check_resource.go new file mode 100644 index 0000000..06d83a2 --- /dev/null +++ b/internal/provider/trigger_check_resource.go @@ -0,0 +1,262 @@ +package provider + +import ( + "context" + "fmt" + + "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/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ resource.Resource = (*TriggerCheckResource)(nil) + _ resource.ResourceWithConfigure = (*TriggerCheckResource)(nil) + _ resource.ResourceWithImportState = (*TriggerCheckResource)(nil) +) + +type TriggerCheckResource struct { + client checkly.Client +} + +func NewTriggerCheckResource() resource.Resource { + return &TriggerCheckResource{} +} + +func (r *TriggerCheckResource) Metadata( + ctx context.Context, + req resource.MetadataRequest, + resp *resource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_trigger_check" +} + +func (r *TriggerCheckResource) Schema( + ctx context.Context, + req resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "", // TODO + }, + "last_updated": LastUpdatedAttributeSchema, + "check_id": schema.StringAttribute{ + Required: true, + Description: "The ID of the check that you want to attach the trigger to.", + }, + "token": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The token value created to trigger the check.", + }, + "url": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The request URL to trigger the check run.", + }, + }, + } +} + +func (r *TriggerCheckResource) Configure( + ctx context.Context, + req resource.ConfigureRequest, + resp *resource.ConfigureResponse, +) { + client, diags := ClientFromProviderData(req.ProviderData) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + r.client = client +} + +func (r *TriggerCheckResource) ImportState( + ctx context.Context, + req resource.ImportStateRequest, + resp *resource.ImportStateResponse, +) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *TriggerCheckResource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan TriggerCheckResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.CreateTriggerCheck( + ctx, + plan.CheckID.ValueString(), + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Checkly Trigger Check", + fmt.Sprintf("Could not create trigger check, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *TriggerCheckResource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state TriggerCheckResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + err := r.client.DeleteTriggerCheck(ctx, state.CheckID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting Checkly Trigger Check", + fmt.Sprintf("Could not delete trigger check, unexpected error: %s", err), + ) + + return + } +} + +func (r *TriggerCheckResource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state TriggerCheckResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // TODO: Check if we really have to do the weird 404 handling + realizedModel, err := r.client.GetTriggerCheck( + ctx, + state.CheckID.ValueString(), + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Reading Checkly Trigger Check", + fmt.Sprintf("Could not retrieve trigger check, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *TriggerCheckResource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan TriggerCheckResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.GetTriggerCheck( + ctx, + plan.CheckID.ValueString(), + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Checkly Trigger Check", + fmt.Sprintf("Could not update trigger check, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +var TriggerCheckID = SDKIdentifier{ + Path: path.Root("id"), + Title: "Checkly Trigger Check ID", +} + +var ( + _ ResourceModel[checkly.TriggerCheck] = (*TriggerCheckResourceModel)(nil) +) + +type TriggerCheckResourceModel struct { + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + CheckID types.String `tfsdk:"check_id"` + Token types.String `tfsdk:"token"` + URL types.String `tfsdk:"url"` +} + +func (m *TriggerCheckResourceModel) Refresh(ctx context.Context, from *checkly.TriggerCheck, flags RefreshFlags) diag.Diagnostics { + // TODO: Always update ID? CheckID, which is used for lookup, is user-modifiable, + // and we could receive back a complete different ID. + if flags.Created() { + m.ID = TriggerCheckID.IntoString(from.ID) + } + + if flags.Created() || flags.Updated() { + m.LastUpdated = LastUpdatedNow() + } + + m.CheckID = types.StringValue(from.CheckId) + m.Token = types.StringValue(from.Token) + m.URL = types.StringValue(from.URL) + + return nil +} + +func (m *TriggerCheckResourceModel) Render(ctx context.Context, into *checkly.TriggerCheck) diag.Diagnostics { + into.Token = m.Token.ValueString() + into.URL = m.URL.ValueString() + + return nil +} diff --git a/internal/provider/trigger_group_resource.go b/internal/provider/trigger_group_resource.go new file mode 100644 index 0000000..2c6e535 --- /dev/null +++ b/internal/provider/trigger_group_resource.go @@ -0,0 +1,260 @@ +package provider + +import ( + "context" + "fmt" + + "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/types" + + checkly "github.com/checkly/checkly-go-sdk" +) + +var ( + _ resource.Resource = (*TriggerGroupResource)(nil) + _ resource.ResourceWithConfigure = (*TriggerGroupResource)(nil) + _ resource.ResourceWithImportState = (*TriggerGroupResource)(nil) +) + +type TriggerGroupResource struct { + client checkly.Client +} + +func NewTriggerGroupResource() resource.Resource { + return &TriggerGroupResource{} +} + +func (r *TriggerGroupResource) Metadata( + ctx context.Context, + req resource.MetadataRequest, + resp *resource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_trigger_group" +} + +func (r *TriggerGroupResource) Schema( + ctx context.Context, + req resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "", // TODO + }, + "last_updated": LastUpdatedAttributeSchema, + "group_id": schema.Int64Attribute{ + Required: true, + Description: "The ID of the group that you want to attach the trigger to.", + }, + "token": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The token value created to trigger the group.", + }, + "url": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The request URL to trigger the group run.", + }, + }, + } +} + +func (r *TriggerGroupResource) Configure( + ctx context.Context, + req resource.ConfigureRequest, + resp *resource.ConfigureResponse, +) { + client, diags := ClientFromProviderData(req.ProviderData) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + r.client = client +} + +func (r *TriggerGroupResource) ImportState( + ctx context.Context, + req resource.ImportStateRequest, + resp *resource.ImportStateResponse, +) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *TriggerGroupResource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan TriggerGroupResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.CreateTriggerGroup( + ctx, + plan.GroupID.ValueInt64(), + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Checkly Trigger Group", + fmt.Sprintf("Could not create trigger group, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *TriggerGroupResource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state TriggerGroupResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + err := r.client.DeleteTriggerGroup(ctx, state.GroupID.ValueInt64()) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting Checkly Trigger Group", + fmt.Sprintf("Could not delete trigger group, unexpected error: %s", err), + ) + + return + } +} + +func (r *TriggerGroupResource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state TriggerGroupResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // TODO: Check if we really have to do the weird 404 handling + realizedModel, err := r.client.GetTriggerGroup( + ctx, + state.GroupID.ValueInt64(), + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Reading Checkly Trigger Group", + fmt.Sprintf("Could not retrieve trigger group, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *TriggerGroupResource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan TriggerGroupResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + realizedModel, err := r.client.GetTriggerGroup( + ctx, + plan.GroupID.ValueInt64(), + ) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Checkly Trigger Group", + fmt.Sprintf("Could not update trigger group, unexpected error: %s", err), + ) + + return + } + + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +var TriggerGroupID = SDKIdentifier{ + Path: path.Root("id"), + Title: "Checkly Trigger Group ID", +} + +var ( + _ ResourceModel[checkly.TriggerGroup] = (*TriggerGroupResourceModel)(nil) +) + +type TriggerGroupResourceModel struct { + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + GroupID types.Int64 `tfsdk:"group_id"` + Token types.String `tfsdk:"token"` + URL types.String `tfsdk:"url"` +} + +func (m *TriggerGroupResourceModel) Refresh(ctx context.Context, from *checkly.TriggerGroup, flags RefreshFlags) diag.Diagnostics { + if flags.Created() { + m.ID = TriggerGroupID.IntoString(from.ID) + } + + if flags.Created() || flags.Updated() { + m.LastUpdated = LastUpdatedNow() + } + + m.GroupID = types.Int64Value(from.GroupId) + m.Token = types.StringValue(from.Token) + m.URL = types.StringValue(from.URL) + + return nil +} + +func (m *TriggerGroupResourceModel) Render(ctx context.Context, into *checkly.TriggerGroup) diag.Diagnostics { + into.Token = m.Token.ValueString() + into.URL = m.URL.ValueString() + + return nil +} diff --git a/internal/provider/util.go b/internal/provider/util.go new file mode 100644 index 0000000..e221f3a --- /dev/null +++ b/internal/provider/util.go @@ -0,0 +1,32 @@ +package provider + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func IntoUntypedStringSet(slice *[]string) types.Set { + if slice == nil { + return types.SetNull(types.StringType) + } + + var values []attr.Value + for _, value := range *slice { + values = append(values, types.StringValue(value)) + } + + return types.SetValueMust(types.StringType, values) +} + +func FromUntypedStringSet(set types.Set) []string { + if set.IsNull() { + return nil + } + + var slice []string + for _, el := range set.Elements() { + slice = append(slice, el.(types.String).ValueString()) + } + + return slice +} diff --git a/main.go b/main.go index fb402ba..ebc129e 100644 --- a/main.go +++ b/main.go @@ -1,25 +1,33 @@ package main import ( + "context" "flag" + "log" - "github.com/checkly/terraform-provider-checkly/checkly" - "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + + "github.com/checkly/terraform-provider-checkly/internal/provider" ) -//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs +var ( + version = "dev" +) func main() { - var debugMode bool + var debug bool - flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") flag.Parse() - opts := &plugin.ServeOpts{ - Debug: debugMode, - ProviderAddr: "registry.terraform.io/checkly/checkly", - ProviderFunc: checkly.Provider, + opts := providerserver.ServeOpts{ + Address: "registry.terraform.io/checkly/checkly", + Debug: debug, } - plugin.Serve(opts) + err := providerserver.Serve(context.Background(), provider.New(version), opts) + + if err != nil { + log.Fatal(err.Error()) + } } From 20de37a95f34e648470c33afa2774c722a7a1030 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Tue, 26 Nov 2024 04:24:23 +0900 Subject: [PATCH 02/22] fix: check 404 in all resource Read handlers and remove resource if needed --- internal/provider/alert_channel_resource.go | 6 +++++- internal/provider/check_group_resource.go | 6 +++++- internal/provider/check_resource.go | 6 +++++- internal/provider/dashboard_resource.go | 6 +++++- .../provider/environment_variable_resource.go | 6 +++++- internal/provider/heartbeat_resource.go | 6 +++++- .../provider/maintenance_windows_resource.go | 6 +++++- internal/provider/private_location_resource.go | 6 +++++- internal/provider/sdk.go | 17 +++++++++++++++++ internal/provider/snippet_resource.go | 6 +++++- internal/provider/trigger_check_resource.go | 6 +++++- internal/provider/trigger_group_resource.go | 6 +++++- 12 files changed, 72 insertions(+), 11 deletions(-) diff --git a/internal/provider/alert_channel_resource.go b/internal/provider/alert_channel_resource.go index 5c8c573..da0da06 100644 --- a/internal/provider/alert_channel_resource.go +++ b/internal/provider/alert_channel_resource.go @@ -341,9 +341,13 @@ func (r *AlertChannelResource) Read( return } - // TODO: Check if we really have to do the weird 404 handling realizedModel, err := r.client.GetAlertChannel(ctx, id) if err != nil { + if SDKIsHTTPNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( "Error Reading Checkly Alert Channel", fmt.Sprintf("Could not retrieve alert channel, unexpected error: %s", err), diff --git a/internal/provider/check_group_resource.go b/internal/provider/check_group_resource.go index f806340..75cd990 100644 --- a/internal/provider/check_group_resource.go +++ b/internal/provider/check_group_resource.go @@ -234,9 +234,13 @@ func (r *CheckGroupResource) Read( return } - // TODO: Check if we really have to do the weird 404 handling realizedModel, err := r.client.GetGroup(ctx, id) if err != nil { + if SDKIsHTTPNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( "Error Reading Checkly Check Group", fmt.Sprintf("Could not retrieve check group, unexpected error: %s", err), diff --git a/internal/provider/check_resource.go b/internal/provider/check_resource.go index 8ef3a37..6591822 100644 --- a/internal/provider/check_resource.go +++ b/internal/provider/check_resource.go @@ -300,9 +300,13 @@ func (r *CheckResource) Read( return } - // TODO: Check if we really have to do the weird 404 handling realizedModel, err := r.client.GetCheck(ctx, state.ID.ValueString()) if err != nil { + if SDKIsHTTPNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( "Error Reading Checkly Check", fmt.Sprintf("Could not retrieve check, unexpected error: %s", err), diff --git a/internal/provider/dashboard_resource.go b/internal/provider/dashboard_resource.go index b4c1b65..0be1203 100644 --- a/internal/provider/dashboard_resource.go +++ b/internal/provider/dashboard_resource.go @@ -241,9 +241,13 @@ func (r *DashboardResource) Read( return } - // TODO: Check if we really have to do the weird 404 handling realizedModel, err := r.client.GetDashboard(ctx, state.ID.ValueString()) if err != nil { + if SDKIsHTTPNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( "Error Reading Checkly Dashboard", fmt.Sprintf("Could not retrieve dashboard, unexpected error: %s", err), diff --git a/internal/provider/environment_variable_resource.go b/internal/provider/environment_variable_resource.go index 1b5ebf0..3df3573 100644 --- a/internal/provider/environment_variable_resource.go +++ b/internal/provider/environment_variable_resource.go @@ -170,12 +170,16 @@ func (r *EnvironmentVariableResource) Read( return } - // TODO: Check if we really have to do the weird 404 handling realizedModel, err := r.client.GetEnvironmentVariable( ctx, state.ID.ValueString(), ) if err != nil { + if SDKIsHTTPNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( "Error Reading Checkly Environment Variable", fmt.Sprintf("Could not retrieve environment variable, unexpected error: %s", err), diff --git a/internal/provider/heartbeat_resource.go b/internal/provider/heartbeat_resource.go index a500254..035d1a8 100644 --- a/internal/provider/heartbeat_resource.go +++ b/internal/provider/heartbeat_resource.go @@ -216,9 +216,13 @@ func (r *HeartbeatResource) Read( return } - // TODO: Check if we really have to do the weird 404 handling realizedModel, err := r.client.GetHeartbeatCheck(ctx, state.ID.ValueString()) if err != nil { + if SDKIsHTTPNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( "Error Reading Checkly Heartbeat", fmt.Sprintf("Could not retrieve heartbeat, unexpected error: %s", err), diff --git a/internal/provider/maintenance_windows_resource.go b/internal/provider/maintenance_windows_resource.go index 74847a3..5871790 100644 --- a/internal/provider/maintenance_windows_resource.go +++ b/internal/provider/maintenance_windows_resource.go @@ -194,9 +194,13 @@ func (r *MaintenanceWindowsResource) Read( return } - // TODO: Check if we really have to do the weird 404 handling realizedModel, err := r.client.GetMaintenanceWindow(ctx, id) if err != nil { + if SDKIsHTTPNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( "Error Reading Checkly Maintenance Window", fmt.Sprintf("Could not retrieve maintenance window, unexpected error: %s", err), diff --git a/internal/provider/private_location_resource.go b/internal/provider/private_location_resource.go index fd4c83a..2f9e095 100644 --- a/internal/provider/private_location_resource.go +++ b/internal/provider/private_location_resource.go @@ -168,9 +168,13 @@ func (r *PrivateLocationResource) Read( return } - // TODO: Check if we really have to do the weird 404 handling realizedModel, err := r.client.GetPrivateLocation(ctx, state.ID.ValueString()) if err != nil { + if SDKIsHTTPNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( "Error Reading Checkly Private Location", fmt.Sprintf("Could not retrieve private location, unexpected error: %s", err), diff --git a/internal/provider/sdk.go b/internal/provider/sdk.go index 50df790..9d35eb3 100644 --- a/internal/provider/sdk.go +++ b/internal/provider/sdk.go @@ -3,6 +3,7 @@ package provider import ( "fmt" "strconv" + "strings" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -84,3 +85,19 @@ func SDKKeyValuesIntoMap(values *[]checkly.KeyValue) types.Map { return types.MapValueMust(types.StringType, mapValues) } + +func SDKIsHTTPNotFoundError(err error) bool { + // Unfortunately the SDK presents HTTP errors in a completely unusable way, + // forcing us to match against string values. + msg := err.Error() + + switch { + case strings.Contains(msg, "unexpected response status: 404"): + return true + // Unfortunate inconsistency. + case strings.Contains(msg, "unexpected response status 404"): + return true + } + + return false +} diff --git a/internal/provider/snippet_resource.go b/internal/provider/snippet_resource.go index 82ec4ad..35ba695 100644 --- a/internal/provider/snippet_resource.go +++ b/internal/provider/snippet_resource.go @@ -169,9 +169,13 @@ func (r *SnippetResource) Read( return } - // TODO: Check if we really have to do the weird 404 handling realizedModel, err := r.client.GetSnippet(ctx, id) if err != nil { + if SDKIsHTTPNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( "Error Reading Checkly Snippet", fmt.Sprintf("Could not retrieve snippet, unexpected error: %s", err), diff --git a/internal/provider/trigger_check_resource.go b/internal/provider/trigger_check_resource.go index 06d83a2..1080654 100644 --- a/internal/provider/trigger_check_resource.go +++ b/internal/provider/trigger_check_resource.go @@ -158,12 +158,16 @@ func (r *TriggerCheckResource) Read( return } - // TODO: Check if we really have to do the weird 404 handling realizedModel, err := r.client.GetTriggerCheck( ctx, state.CheckID.ValueString(), ) if err != nil { + if SDKIsHTTPNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( "Error Reading Checkly Trigger Check", fmt.Sprintf("Could not retrieve trigger check, unexpected error: %s", err), diff --git a/internal/provider/trigger_group_resource.go b/internal/provider/trigger_group_resource.go index 2c6e535..1d0421e 100644 --- a/internal/provider/trigger_group_resource.go +++ b/internal/provider/trigger_group_resource.go @@ -158,12 +158,16 @@ func (r *TriggerGroupResource) Read( return } - // TODO: Check if we really have to do the weird 404 handling realizedModel, err := r.client.GetTriggerGroup( ctx, state.GroupID.ValueInt64(), ) if err != nil { + if SDKIsHTTPNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( "Error Reading Checkly Trigger Group", fmt.Sprintf("Could not retrieve trigger group, unexpected error: %s", err), From cb41030d36ad18727fd20315a211e2af62e067c6 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Tue, 26 Nov 2024 04:46:49 +0900 Subject: [PATCH 03/22] feat: pump protocol version to 6.0 --- terraform-registry-manifest.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/terraform-registry-manifest.json b/terraform-registry-manifest.json index 5a294ba..8f42373 100644 --- a/terraform-registry-manifest.json +++ b/terraform-registry-manifest.json @@ -1,7 +1,8 @@ { "version": 1, "metadata": { - "protocol_versions": ["5.0"] + "protocol_versions": [ + "6.0" + ] } -} - +} \ No newline at end of file From d9c85325c4b3abf88e5b0fc61d8d018485fb1644 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Wed, 4 Dec 2024 01:11:27 +0900 Subject: [PATCH 04/22] fix: tfplugindocs says that attributes with Default values must be Computed --- internal/provider/alert_channel_resource.go | 6 ++++++ internal/provider/check_alert_settings_attribute.go | 10 +++++++++- .../provider/check_environment_variable_attribute.go | 2 ++ internal/provider/check_request_attribute.go | 3 +++ internal/provider/check_resource.go | 3 +++ internal/provider/check_retry_strategy_attribute.go | 4 ++++ internal/provider/dashboard_resource.go | 9 +++++++++ internal/provider/environment_variable_resource.go | 2 ++ internal/provider/maintenance_windows_resource.go | 1 - internal/provider/private_location_resource.go | 1 + 10 files changed, 39 insertions(+), 2 deletions(-) diff --git a/internal/provider/alert_channel_resource.go b/internal/provider/alert_channel_resource.go index da0da06..16e0c2f 100644 --- a/internal/provider/alert_channel_resource.go +++ b/internal/provider/alert_channel_resource.go @@ -115,6 +115,7 @@ func (r *AlertChannelResource) Schema( }, "method": schema.StringAttribute{ Optional: true, + Computed: true, Default: stringdefault.StaticString("POST"), Description: "(Default `POST`)", }, @@ -187,26 +188,31 @@ func (r *AlertChannelResource) Schema( }, "send_recovery": schema.BoolAttribute{ Optional: true, + Computed: true, Default: booldefault.StaticBool(true), Description: "(Default `true`)", }, "send_failure": schema.BoolAttribute{ Optional: true, + Computed: true, Default: booldefault.StaticBool(true), Description: "(Default `true`)", }, "send_degraded": schema.BoolAttribute{ Optional: true, + Computed: true, Default: booldefault.StaticBool(false), Description: "(Default `false`)", }, "ssl_expiry": schema.BoolAttribute{ Optional: true, + Computed: true, Default: booldefault.StaticBool(false), Description: "(Default `false`)", }, "ssl_expiry_threshold": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(30), Validators: []validator.Int32{ int32validator.Between(1, 30), diff --git a/internal/provider/check_alert_settings_attribute.go b/internal/provider/check_alert_settings_attribute.go index b32234a..a7fc6ab 100644 --- a/internal/provider/check_alert_settings_attribute.go +++ b/internal/provider/check_alert_settings_attribute.go @@ -31,10 +31,11 @@ var CheckAlertSettingsAttributeSchema = schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ "escalation_type": schema.StringAttribute{ Optional: true, + Computed: true, + Default: stringdefault.StaticString("RUN_BASED"), Validators: []validator.String{ stringvalidator.OneOf("RUN_BASED", "TIME_BASED"), }, - Default: stringdefault.StaticString("RUN_BASED"), Description: "Determines what type of escalation to use. Possible values are `RUN_BASED` or `TIME_BASED`.", }, "run_based_escalation": schema.SingleNestedAttribute{ @@ -43,6 +44,7 @@ var CheckAlertSettingsAttributeSchema = schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ "failed_run_threshold": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(1), Validators: []validator.Int32{ int32validator.Between(1, 5), @@ -57,6 +59,7 @@ var CheckAlertSettingsAttributeSchema = schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ "minutes_failing_threshold": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(5), Validators: []validator.Int32{ int32validator.OneOf(5, 10, 15, 30), @@ -78,6 +81,7 @@ var CheckAlertSettingsAttributeSchema = schema.SingleNestedAttribute{ }, "interval": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(5), Validators: []validator.Int32{ int32validator.OneOf(5, 10, 15, 30), @@ -92,11 +96,13 @@ var CheckAlertSettingsAttributeSchema = schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ "enabled": schema.BoolAttribute{ Optional: true, + Computed: true, Default: booldefault.StaticBool(false), Description: "Applicable only for checks scheduled in parallel in multiple locations.", }, "percentage": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(10), Validators: []validator.Int32{ int32validator.OneOf(10, 20, 30, 40, 50, 60, 70, 80, 90, 100), @@ -111,11 +117,13 @@ var CheckAlertSettingsAttributeSchema = schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ "enabled": schema.BoolAttribute{ Optional: true, + Computed: true, Default: booldefault.StaticBool(false), Description: "Determines if alert notifications should be sent for expiring SSL certificates. Possible values `true`, and `false`. (Default `false`).", }, "alert_threshold": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(3), Validators: []validator.Int32{ int32validator.OneOf(3, 7, 14, 30), diff --git a/internal/provider/check_environment_variable_attribute.go b/internal/provider/check_environment_variable_attribute.go index b941b1e..be32ed5 100644 --- a/internal/provider/check_environment_variable_attribute.go +++ b/internal/provider/check_environment_variable_attribute.go @@ -46,6 +46,7 @@ var CheckEnvironmentVariableAttributeSchema = schema.ListNestedAttribute{ "default, but can be accessed by team members with the " + "appropriate permissions.", Optional: true, + Computed: true, Default: booldefault.StaticBool(false), Validators: []validator.Bool{ boolvalidator.ConflictsWith( @@ -59,6 +60,7 @@ var CheckEnvironmentVariableAttributeSchema = schema.ListNestedAttribute{ "However, keep in mind that your Terraform state will " + "still contain the value.", Optional: true, + Computed: true, Default: booldefault.StaticBool(false), Validators: []validator.Bool{ boolvalidator.ConflictsWith( diff --git a/internal/provider/check_request_attribute.go b/internal/provider/check_request_attribute.go index 2fee886..c9d9357 100644 --- a/internal/provider/check_request_attribute.go +++ b/internal/provider/check_request_attribute.go @@ -22,6 +22,7 @@ var CheckRequestAttributeSchema = schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ "method": schema.StringAttribute{ Optional: true, + Computed: true, Default: stringdefault.StaticString("GET"), Validators: []validator.String{ stringvalidator.OneOf( @@ -52,6 +53,7 @@ var CheckRequestAttributeSchema = schema.SingleNestedAttribute{ }, "body_type": schema.StringAttribute{ Optional: true, + Computed: true, Default: stringdefault.StaticString("NONE"), Validators: []validator.String{ stringvalidator.OneOf( @@ -68,6 +70,7 @@ var CheckRequestAttributeSchema = schema.SingleNestedAttribute{ "basic_auth": CheckBasicAuthAttributeSchema, "ip_family": schema.StringAttribute{ Optional: true, + Computed: true, Default: stringdefault.StaticString("IPv4"), Validators: []validator.String{ stringvalidator.OneOf( diff --git a/internal/provider/check_resource.go b/internal/provider/check_resource.go index 6591822..f39dc34 100644 --- a/internal/provider/check_resource.go +++ b/internal/provider/check_resource.go @@ -109,11 +109,13 @@ func (r *CheckResource) Schema( "private_locations": CheckPrivateLocationsAttributeSchema, "script": schema.StringAttribute{ Optional: true, + Computed: true, Default: stringdefault.StaticString(""), Description: "A valid piece of Node.js JavaScript code describing a browser interaction with the Puppeteer/Playwright framework or a reference to an external JavaScript file.", }, "degraded_response_time": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(15000), Validators: []validator.Int32{ int32validator.Between(0, 30000), @@ -122,6 +124,7 @@ func (r *CheckResource) Schema( }, "max_response_time": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(30000), Validators: []validator.Int32{ int32validator.Between(0, 30000), diff --git a/internal/provider/check_retry_strategy_attribute.go b/internal/provider/check_retry_strategy_attribute.go index 10ce0a5..0680b39 100644 --- a/internal/provider/check_retry_strategy_attribute.go +++ b/internal/provider/check_retry_strategy_attribute.go @@ -36,11 +36,13 @@ var CheckRetryStrategyAttributeSchema = schema.SingleNestedAttribute{ }, "base_backoff_seconds": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(60), Description: "The number of seconds to wait before the first retry attempt.", }, "max_retries": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(2), Validators: []validator.Int32{ int32validator.Between(1, 10), @@ -49,6 +51,7 @@ var CheckRetryStrategyAttributeSchema = schema.SingleNestedAttribute{ }, "max_duration_seconds": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(600), Validators: []validator.Int32{ int32validator.AtMost(600), @@ -57,6 +60,7 @@ var CheckRetryStrategyAttributeSchema = schema.SingleNestedAttribute{ }, "same_region": schema.BoolAttribute{ Optional: true, + Computed: true, Default: booldefault.StaticBool(true), Description: "Whether retries should be run in the same region as the initial check run.", }, diff --git a/internal/provider/dashboard_resource.go b/internal/provider/dashboard_resource.go index 0be1203..ad39992 100644 --- a/internal/provider/dashboard_resource.go +++ b/internal/provider/dashboard_resource.go @@ -59,6 +59,7 @@ func (r *DashboardResource) Schema( }, "custom_domain": schema.StringAttribute{ Optional: true, + Computed: true, Default: nil, // TODO Description: "A custom user domain, e.g. 'status.example.com'. See the docs on updating your DNS and SSL usage.", }, @@ -84,6 +85,7 @@ func (r *DashboardResource) Schema( }, "width": schema.StringAttribute{ Optional: true, + Computed: true, Default: stringdefault.StaticString("FULL"), Validators: []validator.String{ stringvalidator.OneOf("FULL", "960PX"), @@ -92,6 +94,7 @@ func (r *DashboardResource) Schema( }, "refresh_rate": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(60), Validators: []validator.Int32{ int32validator.OneOf(60, 300, 600), @@ -100,16 +103,19 @@ func (r *DashboardResource) Schema( }, "paginate": schema.BoolAttribute{ Optional: true, + Computed: true, Default: booldefault.StaticBool(true), Description: "Determines if pagination is on or off.", }, "checks_per_page": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(15), Description: "Determines how many checks to show per page.", }, "pagination_rate": schema.Int32Attribute{ Optional: true, + Computed: true, Default: int32default.StaticInt32(60), Validators: []validator.Int32{ int32validator.OneOf(30, 60, 300), @@ -123,16 +129,19 @@ func (r *DashboardResource) Schema( }, "hide_tags": schema.BoolAttribute{ Optional: true, + Computed: true, Default: booldefault.StaticBool(false), Description: "Show or hide the tags on the dashboard.", }, "use_tags_and_operator": schema.BoolAttribute{ Optional: true, + Computed: true, Default: booldefault.StaticBool(false), Description: "Set when to use AND operator for fetching dashboard tags.", }, "is_private": schema.BoolAttribute{ Optional: true, + Computed: true, Default: booldefault.StaticBool(false), Description: "Set your dashboard as private and generate key.", }, diff --git a/internal/provider/environment_variable_resource.go b/internal/provider/environment_variable_resource.go index 3df3573..c7ce8ef 100644 --- a/internal/provider/environment_variable_resource.go +++ b/internal/provider/environment_variable_resource.go @@ -59,11 +59,13 @@ func (r *EnvironmentVariableResource) Schema( }, "locked": schema.BoolAttribute{ Optional: true, + Computed: true, Default: booldefault.StaticBool(false), Description: "", // TODO }, "secret": schema.BoolAttribute{ Optional: true, + Computed: true, Default: booldefault.StaticBool(false), Description: "", // TODO }, diff --git a/internal/provider/maintenance_windows_resource.go b/internal/provider/maintenance_windows_resource.go index 5871790..47bff8a 100644 --- a/internal/provider/maintenance_windows_resource.go +++ b/internal/provider/maintenance_windows_resource.go @@ -63,7 +63,6 @@ func (r *MaintenanceWindowsResource) Schema( }, "repeat_unit": schema.StringAttribute{ Optional: true, - Default: nil, Validators: []validator.String{ stringvalidator.OneOf("DAY", "WEEK", "MONTH"), }, diff --git a/internal/provider/private_location_resource.go b/internal/provider/private_location_resource.go index 2f9e095..34dd85b 100644 --- a/internal/provider/private_location_resource.go +++ b/internal/provider/private_location_resource.go @@ -59,6 +59,7 @@ func (r *PrivateLocationResource) Schema( }, "icon": schema.StringAttribute{ Optional: true, + Computed: true, Default: stringdefault.StaticString("location"), Description: "Icon assigned to the private location.", }, From 98bcc82ee8bee923113b924f3542c1fc36bf8e92 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Wed, 4 Dec 2024 01:13:08 +0900 Subject: [PATCH 05/22] feat: update doc generation script and update docs --- GNUmakefile | 6 +- docs/index.md | 5 +- docs/resources/alert_channel.md | 33 ++-- docs/resources/check.md | 73 ++++----- docs/resources/check_group.md | 69 ++++----- docs/resources/dashboard.md | 1 + docs/resources/environment_variable.md | 3 +- docs/resources/heartbeat.md | 35 ++--- docs/resources/maintenance_windows.md | 1 + docs/resources/private_location.md | 1 + docs/resources/snippet.md | 3 +- docs/resources/trigger_check.md | 5 +- docs/resources/trigger_group.md | 5 +- tools/go.mod | 57 +++++++ tools/go.sum | 203 +++++++++++++++++++++++++ tools/tools.go | 11 +- 16 files changed, 392 insertions(+), 119 deletions(-) create mode 100644 tools/go.mod create mode 100644 tools/go.sum diff --git a/GNUmakefile b/GNUmakefile index 920c116..ed9d3ac 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -36,8 +36,6 @@ fmt: go fmt ./checkly terraform fmt -doc: - ./tools/tfplugindocs - +# Generate docs generate: - go generate ./... + cd tools; go generate ./... diff --git a/docs/index.md b/docs/index.md index e487bc6..2b31e62 100644 --- a/docs/index.md +++ b/docs/index.md @@ -76,13 +76,10 @@ resource "checkly_check" "example_check" { ## Schema -### Required - -- `api_key` (String) - ### Optional - `account_id` (String) +- `api_key` (String, Sensitive) - `api_url` (String) > For additional documentation and examples, check the Resources sections. \ No newline at end of file diff --git a/docs/resources/alert_channel.md b/docs/resources/alert_channel.md index 2672f4f..01f36fc 100644 --- a/docs/resources/alert_channel.md +++ b/docs/resources/alert_channel.md @@ -3,12 +3,12 @@ page_title: "checkly_alert_channel Resource - terraform-provider-checkly" subcategory: "" description: |- - Allows you to define alerting channels for the checks and groups in your account + Allows you to define alerting channels for the checks and groups in your account. --- # checkly_alert_channel (Resource) -Allows you to define alerting channels for the checks and groups in your account +Allows you to define alerting channels for the checks and groups in your account. ## Example Usage @@ -115,24 +115,25 @@ resource "checkly_check" "example_check" { ### Optional -- `call` (Block Set, Max: 1) (see [below for nested schema](#nestedblock--call)) -- `email` (Block Set, Max: 1) (see [below for nested schema](#nestedblock--email)) -- `opsgenie` (Block Set, Max: 1) (see [below for nested schema](#nestedblock--opsgenie)) -- `pagerduty` (Block Set, Max: 1) (see [below for nested schema](#nestedblock--pagerduty)) +- `call` (Attributes) (see [below for nested schema](#nestedatt--call)) +- `email` (Attributes) (see [below for nested schema](#nestedatt--email)) +- `opsgenie` (Attributes) (see [below for nested schema](#nestedatt--opsgenie)) +- `pagerduty` (Attributes) (see [below for nested schema](#nestedatt--pagerduty)) - `send_degraded` (Boolean) (Default `false`) - `send_failure` (Boolean) (Default `true`) - `send_recovery` (Boolean) (Default `true`) -- `slack` (Block Set, Max: 1) (see [below for nested schema](#nestedblock--slack)) -- `sms` (Block Set, Max: 1) (see [below for nested schema](#nestedblock--sms)) +- `slack` (Attributes) (see [below for nested schema](#nestedatt--slack)) +- `sms` (Attributes) (see [below for nested schema](#nestedatt--sms)) - `ssl_expiry` (Boolean) (Default `false`) - `ssl_expiry_threshold` (Number) Value must be between 1 and 30 (Default `30`) -- `webhook` (Block Set, Max: 1) (see [below for nested schema](#nestedblock--webhook)) +- `webhook` (Attributes) (see [below for nested schema](#nestedatt--webhook)) ### Read-Only - `id` (String) The ID of this resource. +- `last_updated` (String) When the resource was last updated by the provider. - + ### Nested Schema for `call` Required: @@ -141,7 +142,7 @@ Required: - `number` (String) The mobile number to receive the alerts - + ### Nested Schema for `email` Required: @@ -149,7 +150,7 @@ Required: - `address` (String) The email address of this email alert channel. - + ### Nested Schema for `opsgenie` Required: @@ -160,7 +161,7 @@ Required: - `region` (String) - + ### Nested Schema for `pagerduty` Required: @@ -173,7 +174,7 @@ Optional: - `service_name` (String) - + ### Nested Schema for `slack` Required: @@ -182,7 +183,7 @@ Required: - `url` (String) The Slack webhook URL - + ### Nested Schema for `sms` Required: @@ -191,7 +192,7 @@ Required: - `number` (String) The mobile number to receive the alerts - + ### Nested Schema for `webhook` Required: diff --git a/docs/resources/check.md b/docs/resources/check.md index bfbddbe..17bb7bb 100644 --- a/docs/resources/check.md +++ b/docs/resources/check.md @@ -3,12 +3,12 @@ page_title: "checkly_check Resource - terraform-provider-checkly" subcategory: "" description: |- - Checks allows you to monitor key webapp flows, backend API's and set up alerting, so you get a notification when things break or slow down. + Check groups allow you to group together a set of related checks, which can also share default settings for various attributes. --- # checkly_check (Resource) -Checks allows you to monitor key webapp flows, backend API's and set up alerting, so you get a notification when things break or slow down. +Check groups allow you to group together a set of related checks, which can also share default settings for various attributes. ## Example Usage @@ -196,23 +196,23 @@ resource "checkly_check" "example_check" { ### Optional -- `alert_channel_subscription` (Block List) An array of channel IDs and whether they're activated or not. If you don't set at least one alert subscription for your check, we won't be able to alert you in case something goes wrong with it. (see [below for nested schema](#nestedblock--alert_channel_subscription)) -- `alert_settings` (Block List, Max: 1) (see [below for nested schema](#nestedblock--alert_settings)) +- `alert_channel_subscription` (Attributes List) An array of channel IDs and whether they're activated or not. If you don't set at least one alert subscription for your check, we won't be able to alert you in case something goes wrong with it. (see [below for nested schema](#nestedatt--alert_channel_subscription)) +- `alert_settings` (Attributes) (see [below for nested schema](#nestedatt--alert_settings)) - `degraded_response_time` (Number) The response time in milliseconds starting from which a check should be considered degraded. Possible values are between 0 and 30000. (Default `15000`). - `double_check` (Boolean, Deprecated) Setting this to `true` will trigger a retry when a check fails from the failing region and another, randomly selected region before marking the check as failed. -- `environment_variable` (Block List) Key/value pairs for setting environment variables during check execution, add locked = true to keep value hidden, add secret = true to create a secret variable. These are only relevant for browser checks. Use global environment variables whenever possible. (see [below for nested schema](#nestedblock--environment_variable)) +- `environment_variable` (Attributes List) Introduce additional environment variables to the check execution environment. Only relevant for browser checks. Prefer global environment variables when possible. (see [below for nested schema](#nestedatt--environment_variable)) - `environment_variables` (Map of String, Deprecated) Key/value pairs for setting environment variables during check execution. These are only relevant for browser checks. Use global environment variables whenever possible. -- `frequency_offset` (Number) This property only valid for API high frequency checks. To create a hight frequency check, the property `frequency` must be `0` and `frequency_offset` could be `10`, `20` or `30`. +- `frequency_offset` (Number) This property is only valid for high frequency API checks. To create a high frequency check, the property `frequency` must be `0` and `frequency_offset` could be `10`, `20` or `30`. - `group_id` (Number) The id of the check group this check is part of. - `group_order` (Number) The position of this check in a check group. It determines in what order checks are run when a group is triggered from the API or from CI/CD. - `local_setup_script` (String) A valid piece of Node.js code to run in the setup phase. - `local_teardown_script` (String) A valid piece of Node.js code to run in the teardown phase. -- `locations` (Set of String) An array of one or more data center locations where to run the this check. (Default ["us-east-1"]) +- `locations` (Set of String) An array of one or more data center locations where to run the checks. - `max_response_time` (Number) The response time in milliseconds starting from which a check should be considered failing. Possible values are between 0 and 30000. (Default `30000`). - `muted` (Boolean) Determines if any notifications will be sent out when a check fails/degrades/recovers. - `private_locations` (Set of String) An array of one or more private locations slugs. -- `request` (Block Set, Max: 1) An API check might have one request config. (see [below for nested schema](#nestedblock--request)) -- `retry_strategy` (Block Set, Max: 1) A strategy for retrying failed check runs. (see [below for nested schema](#nestedblock--retry_strategy)) +- `request` (Attributes) (see [below for nested schema](#nestedatt--request)) +- `retry_strategy` (Attributes) A strategy for retrying failed check runs. (see [below for nested schema](#nestedatt--retry_strategy)) - `run_parallel` (Boolean) Determines if the check should run in all selected locations in parallel or round-robin. - `runtime_id` (String) The id of the runtime to use for this check. - `script` (String) A valid piece of Node.js JavaScript code describing a browser interaction with the Puppeteer/Playwright framework or a reference to an external JavaScript file. @@ -220,15 +220,16 @@ resource "checkly_check" "example_check" { - `should_fail` (Boolean) Allows to invert the behaviour of when a check is considered to fail. Allows for validating error status like 404. - `ssl_check` (Boolean, Deprecated) Determines if the SSL certificate should be validated for expiry. - `ssl_check_domain` (String) A valid fully qualified domain name (FQDN) to check its SSL certificate. -- `tags` (Set of String) A list of tags for organizing and filtering checks. +- `tags` (Set of String) Tags for organizing and filtering checks. - `teardown_snippet_id` (Number) An ID reference to a snippet to use in the teardown phase of an API check. - `use_global_alert_settings` (Boolean) When true, the account level alert settings will be used, not the alert setting defined on this check. ### Read-Only - `id` (String) The ID of this resource. +- `last_updated` (String) When the resource was last updated by the provider. - + ### Nested Schema for `alert_channel_subscription` Required: @@ -237,28 +238,28 @@ Required: - `channel_id` (Number) - + ### Nested Schema for `alert_settings` Optional: - `escalation_type` (String) Determines what type of escalation to use. Possible values are `RUN_BASED` or `TIME_BASED`. -- `parallel_run_failure_threshold` (Block List) (see [below for nested schema](#nestedblock--alert_settings--parallel_run_failure_threshold)) -- `reminders` (Block List) (see [below for nested schema](#nestedblock--alert_settings--reminders)) -- `run_based_escalation` (Block List) (see [below for nested schema](#nestedblock--alert_settings--run_based_escalation)) -- `ssl_certificates` (Block Set, Deprecated) (see [below for nested schema](#nestedblock--alert_settings--ssl_certificates)) -- `time_based_escalation` (Block List) (see [below for nested schema](#nestedblock--alert_settings--time_based_escalation)) +- `parallel_run_failure_threshold` (Attributes) (see [below for nested schema](#nestedatt--alert_settings--parallel_run_failure_threshold)) +- `reminders` (Attributes) (see [below for nested schema](#nestedatt--alert_settings--reminders)) +- `run_based_escalation` (Attributes) (see [below for nested schema](#nestedatt--alert_settings--run_based_escalation)) +- `ssl_certificates` (Attributes, Deprecated) At what interval the reminders should be sent. (see [below for nested schema](#nestedatt--alert_settings--ssl_certificates)) +- `time_based_escalation` (Attributes) (see [below for nested schema](#nestedatt--alert_settings--time_based_escalation)) - + ### Nested Schema for `alert_settings.parallel_run_failure_threshold` Optional: - `enabled` (Boolean) Applicable only for checks scheduled in parallel in multiple locations. -- `percentage` (Number) Possible values are `10`, `20`, `30`, `40`, `50`, `60`, `70`, `80`, `100`, and `100`. (Default `10`). +- `percentage` (Number) Possible values are `10`, `20`, `30`, `40`, `50`, `60`, `70`, `80`, `90`, and `100`. (Default `10`). - + ### Nested Schema for `alert_settings.reminders` Optional: @@ -267,7 +268,7 @@ Optional: - `interval` (Number) Possible values are `5`, `10`, `15`, and `30`. (Default `5`). - + ### Nested Schema for `alert_settings.run_based_escalation` Optional: @@ -275,7 +276,7 @@ Optional: - `failed_run_threshold` (Number) After how many failed consecutive check runs an alert notification should be sent. Possible values are between 1 and 5. (Default `1`). - + ### Nested Schema for `alert_settings.ssl_certificates` Optional: @@ -284,7 +285,7 @@ Optional: - `enabled` (Boolean) Determines if alert notifications should be sent for expiring SSL certificates. Possible values `true`, and `false`. (Default `false`). - + ### Nested Schema for `alert_settings.time_based_escalation` Optional: @@ -293,21 +294,21 @@ Optional: - + ### Nested Schema for `environment_variable` Required: -- `key` (String) -- `value` (String) +- `key` (String) The name of the environment variable. +- `value` (String, Sensitive) The value of the environment variable. By default the value is plain text and can be seen by any team member. It will also be present in check results and logs. Optional: -- `locked` (Boolean) -- `secret` (Boolean) +- `locked` (Boolean) Locked environment variables are encrypted at rest and in flight on the Checkly backend and are only decrypted when needed. Their value is hidden by default, but can be accessed by team members with the appropriate permissions. +- `secret` (Boolean) Secret environment variables are always encrypted and their value is never shown to any user. However, keep in mind that your Terraform state will still contain the value. - + ### Nested Schema for `request` Required: @@ -316,8 +317,8 @@ Required: Optional: -- `assertion` (Block Set) A request can have multiple assertions. (see [below for nested schema](#nestedblock--request--assertion)) -- `basic_auth` (Block Set, Max: 1) Set up HTTP basic authentication (username & password). (see [below for nested schema](#nestedblock--request--basic_auth)) +- `assertion` (Attributes List) (see [below for nested schema](#nestedatt--request--assertion)) +- `basic_auth` (Attributes) Credentials for Basic HTTP authentication. (see [below for nested schema](#nestedatt--request--basic_auth)) - `body` (String) The body of the request. - `body_type` (String) The `Content-Type` header of the request. Possible values `NONE`, `JSON`, `FORM`, `RAW`, and `GRAPHQL`. - `follow_redirects` (Boolean) @@ -327,31 +328,31 @@ Optional: - `query_parameters` (Map of String) - `skip_ssl` (Boolean) - + ### Nested Schema for `request.assertion` Required: - `comparison` (String) The type of comparison to be executed between expected and actual value of the assertion. Possible values `EQUALS`, `NOT_EQUALS`, `HAS_KEY`, `NOT_HAS_KEY`, `HAS_VALUE`, `NOT_HAS_VALUE`, `IS_EMPTY`, `NOT_EMPTY`, `GREATER_THAN`, `LESS_THAN`, `CONTAINS`, `NOT_CONTAINS`, `IS_NULL`, and `NOT_NULL`. - `source` (String) The source of the asserted value. Possible values `STATUS_CODE`, `JSON_BODY`, `HEADERS`, `TEXT_BODY`, and `RESPONSE_TIME`. +- `target` (String) Optional: - `property` (String) -- `target` (String) - + ### Nested Schema for `request.basic_auth` Required: -- `password` (String) +- `password` (String, Sensitive) - `username` (String) - + ### Nested Schema for `retry_strategy` Required: diff --git a/docs/resources/check_group.md b/docs/resources/check_group.md index 9fc618b..3f87038 100644 --- a/docs/resources/check_group.md +++ b/docs/resources/check_group.md @@ -3,12 +3,12 @@ page_title: "checkly_check_group Resource - terraform-provider-checkly" subcategory: "" description: |- - Check groups allow you to group together a set of related checks, which can also share default settings for various attributes. + Check groups allow you to group together a set of related checks, which can also share default settings for various attributes. --- # checkly_check_group (Resource) -Check groups allow you to group together a set of related checks, which can also share default settings for various attributes. +Check groups allow you to group together a set of related checks, which can also share default settings for various attributes. ## Example Usage @@ -145,18 +145,18 @@ resource "checkly_check_group" "test_group1" { ### Optional -- `alert_channel_subscription` (Block List) (see [below for nested schema](#nestedblock--alert_channel_subscription)) -- `alert_settings` (Block List, Max: 1) (see [below for nested schema](#nestedblock--alert_settings)) -- `api_check_defaults` (Block Set, Max: 1) (see [below for nested schema](#nestedblock--api_check_defaults)) +- `alert_channel_subscription` (Attributes List) An array of channel IDs and whether they're activated or not. If you don't set at least one alert subscription for your check, we won't be able to alert you in case something goes wrong with it. (see [below for nested schema](#nestedatt--alert_channel_subscription)) +- `alert_settings` (Attributes) (see [below for nested schema](#nestedatt--alert_settings)) +- `api_check_defaults` (Attributes) (see [below for nested schema](#nestedatt--api_check_defaults)) - `double_check` (Boolean, Deprecated) Setting this to `true` will trigger a retry when a check fails from the failing region and another, randomly selected region before marking the check as failed. -- `environment_variable` (Block List) Key/value pairs for setting environment variables during check execution, add locked = true to keep value hidden, add secret = true to create a secret variable. These are only relevant for browser checks. Use global environment variables whenever possible. (see [below for nested schema](#nestedblock--environment_variable)) +- `environment_variable` (Attributes List) Introduce additional environment variables to the check execution environment. Only relevant for browser checks. Prefer global environment variables when possible. (see [below for nested schema](#nestedatt--environment_variable)) - `environment_variables` (Map of String, Deprecated) Key/value pairs for setting environment variables during check execution. These are only relevant for browser checks. Use global environment variables whenever possible. - `local_setup_script` (String) A valid piece of Node.js code to run in the setup phase of an API check in this group. - `local_teardown_script` (String) A valid piece of Node.js code to run in the teardown phase of an API check in this group. - `locations` (Set of String) An array of one or more data center locations where to run the checks. - `muted` (Boolean) Determines if any notifications will be sent out when a check in this group fails and/or recovers. - `private_locations` (Set of String) An array of one or more private locations slugs. -- `retry_strategy` (Block Set, Max: 1) A strategy for retrying failed check runs. (see [below for nested schema](#nestedblock--retry_strategy)) +- `retry_strategy` (Attributes) A strategy for retrying failed check runs. (see [below for nested schema](#nestedatt--retry_strategy)) - `run_parallel` (Boolean) Determines if the checks in the group should run in all selected locations in parallel or round-robin. - `runtime_id` (String) The id of the runtime to use for this group. - `setup_snippet_id` (Number) An ID reference to a snippet to use in the setup phase of an API check. @@ -167,8 +167,9 @@ resource "checkly_check_group" "test_group1" { ### Read-Only - `id` (String) The ID of this resource. +- `last_updated` (String) When the resource was last updated by the provider. - + ### Nested Schema for `alert_channel_subscription` Required: @@ -177,28 +178,28 @@ Required: - `channel_id` (Number) - + ### Nested Schema for `alert_settings` Optional: - `escalation_type` (String) Determines what type of escalation to use. Possible values are `RUN_BASED` or `TIME_BASED`. -- `parallel_run_failure_threshold` (Block List) (see [below for nested schema](#nestedblock--alert_settings--parallel_run_failure_threshold)) -- `reminders` (Block List) (see [below for nested schema](#nestedblock--alert_settings--reminders)) -- `run_based_escalation` (Block List) (see [below for nested schema](#nestedblock--alert_settings--run_based_escalation)) -- `ssl_certificates` (Block Set, Deprecated) (see [below for nested schema](#nestedblock--alert_settings--ssl_certificates)) -- `time_based_escalation` (Block List) (see [below for nested schema](#nestedblock--alert_settings--time_based_escalation)) +- `parallel_run_failure_threshold` (Attributes) (see [below for nested schema](#nestedatt--alert_settings--parallel_run_failure_threshold)) +- `reminders` (Attributes) (see [below for nested schema](#nestedatt--alert_settings--reminders)) +- `run_based_escalation` (Attributes) (see [below for nested schema](#nestedatt--alert_settings--run_based_escalation)) +- `ssl_certificates` (Attributes, Deprecated) At what interval the reminders should be sent. (see [below for nested schema](#nestedatt--alert_settings--ssl_certificates)) +- `time_based_escalation` (Attributes) (see [below for nested schema](#nestedatt--alert_settings--time_based_escalation)) - + ### Nested Schema for `alert_settings.parallel_run_failure_threshold` Optional: - `enabled` (Boolean) Applicable only for checks scheduled in parallel in multiple locations. -- `percentage` (Number) Possible values are `10`, `20`, `30`, `40`, `50`, `60`, `70`, `80`, `100`, and `100`. (Default `10`). +- `percentage` (Number) Possible values are `10`, `20`, `30`, `40`, `50`, `60`, `70`, `80`, `90`, and `100`. (Default `10`). - + ### Nested Schema for `alert_settings.reminders` Optional: @@ -207,7 +208,7 @@ Optional: - `interval` (Number) Possible values are `5`, `10`, `15`, and `30`. (Default `5`). - + ### Nested Schema for `alert_settings.run_based_escalation` Optional: @@ -215,16 +216,16 @@ Optional: - `failed_run_threshold` (Number) After how many failed consecutive check runs an alert notification should be sent. Possible values are between 1 and 5. (Default `1`). - + ### Nested Schema for `alert_settings.ssl_certificates` Optional: -- `alert_threshold` (Number) At what moment in time to start alerting on SSL certificates. Possible values `3`, `7`, `14`, `30`. (Default `3`). -- `enabled` (Boolean) Determines if alert notifications should be sent for expiring SSL certificates. +- `alert_threshold` (Number) How long before SSL certificate expiry to send alerts. Possible values `3`, `7`, `14`, `30`. (Default `3`). +- `enabled` (Boolean) Determines if alert notifications should be sent for expiring SSL certificates. Possible values `true`, and `false`. (Default `false`). - + ### Nested Schema for `alert_settings.time_based_escalation` Optional: @@ -233,7 +234,7 @@ Optional: - + ### Nested Schema for `api_check_defaults` Required: @@ -242,12 +243,12 @@ Required: Optional: -- `assertion` (Block Set) (see [below for nested schema](#nestedblock--api_check_defaults--assertion)) -- `basic_auth` (Block Set, Max: 1) (see [below for nested schema](#nestedblock--api_check_defaults--basic_auth)) +- `assertion` (Attributes List) (see [below for nested schema](#nestedatt--api_check_defaults--assertion)) +- `basic_auth` (Attributes) Credentials for Basic HTTP authentication. (see [below for nested schema](#nestedatt--api_check_defaults--basic_auth)) - `headers` (Map of String) - `query_parameters` (Map of String) - + ### Nested Schema for `api_check_defaults.assertion` Required: @@ -261,31 +262,31 @@ Optional: - `property` (String) - + ### Nested Schema for `api_check_defaults.basic_auth` Required: -- `password` (String) +- `password` (String, Sensitive) - `username` (String) - + ### Nested Schema for `environment_variable` Required: -- `key` (String) -- `value` (String) +- `key` (String) The name of the environment variable. +- `value` (String, Sensitive) The value of the environment variable. By default the value is plain text and can be seen by any team member. It will also be present in check results and logs. Optional: -- `locked` (Boolean) -- `secret` (Boolean) +- `locked` (Boolean) Locked environment variables are encrypted at rest and in flight on the Checkly backend and are only decrypted when needed. Their value is hidden by default, but can be accessed by team members with the appropriate permissions. +- `secret` (Boolean) Secret environment variables are always encrypted and their value is never shown to any user. However, keep in mind that your Terraform state will still contain the value. - + ### Nested Schema for `retry_strategy` Required: diff --git a/docs/resources/dashboard.md b/docs/resources/dashboard.md index fac3c1d..45e0b04 100644 --- a/docs/resources/dashboard.md +++ b/docs/resources/dashboard.md @@ -58,3 +58,4 @@ resource "checkly_dashboard" "dashboard_1" { - `id` (String) The ID of this resource. - `key` (String, Sensitive) The access key when the dashboard is private. +- `last_updated` (String) When the resource was last updated by the provider. diff --git a/docs/resources/environment_variable.md b/docs/resources/environment_variable.md index 4308265..d71e076 100644 --- a/docs/resources/environment_variable.md +++ b/docs/resources/environment_variable.md @@ -32,7 +32,7 @@ resource "checkly_environment_variable" "variable_2" { ### Required - `key` (String) -- `value` (String) +- `value` (String, Sensitive) ### Optional @@ -42,3 +42,4 @@ resource "checkly_environment_variable" "variable_2" { ### Read-Only - `id` (String) The ID of this resource. +- `last_updated` (String) When the resource was last updated by the provider. diff --git a/docs/resources/heartbeat.md b/docs/resources/heartbeat.md index 279c19c..d881b44 100644 --- a/docs/resources/heartbeat.md +++ b/docs/resources/heartbeat.md @@ -32,13 +32,13 @@ resource "checkly_heartbeat" "example-heartbeat" { ### Required - `activated` (Boolean) Determines if the check is running or not. Possible values `true`, and `false`. -- `heartbeat` (Block Set, Min: 1, Max: 1) (see [below for nested schema](#nestedblock--heartbeat)) +- `heartbeat` (Attributes Set) (see [below for nested schema](#nestedatt--heartbeat)) - `name` (String) The name of the check. ### Optional -- `alert_channel_subscription` (Block List) (see [below for nested schema](#nestedblock--alert_channel_subscription)) -- `alert_settings` (Block List, Max: 1) (see [below for nested schema](#nestedblock--alert_settings)) +- `alert_channel_subscription` (Attributes List) An array of channel IDs and whether they're activated or not. If you don't set at least one alert subscription for your check, we won't be able to alert you in case something goes wrong with it. (see [below for nested schema](#nestedatt--alert_channel_subscription)) +- `alert_settings` (Attributes) (see [below for nested schema](#nestedatt--alert_settings)) - `muted` (Boolean) Determines if any notifications will be sent out when a check fails/degrades/recovers. - `tags` (Set of String) A list of tags for organizing and filtering checks. - `use_global_alert_settings` (Boolean) When true, the account level alert settings will be used, not the alert setting defined on this check. @@ -46,8 +46,9 @@ resource "checkly_heartbeat" "example-heartbeat" { ### Read-Only - `id` (String) The ID of this resource. +- `last_updated` (String) When the resource was last updated by the provider. - + ### Nested Schema for `heartbeat` Required: @@ -62,7 +63,7 @@ Optional: - `ping_token` (String) Custom token to generate your ping URL. Checkly will expect a ping to `https://ping.checklyhq.com/[PING_TOKEN]`. - + ### Nested Schema for `alert_channel_subscription` Required: @@ -71,28 +72,28 @@ Required: - `channel_id` (Number) - + ### Nested Schema for `alert_settings` Optional: - `escalation_type` (String) Determines what type of escalation to use. Possible values are `RUN_BASED` or `TIME_BASED`. -- `parallel_run_failure_threshold` (Block List) (see [below for nested schema](#nestedblock--alert_settings--parallel_run_failure_threshold)) -- `reminders` (Block List) (see [below for nested schema](#nestedblock--alert_settings--reminders)) -- `run_based_escalation` (Block List) (see [below for nested schema](#nestedblock--alert_settings--run_based_escalation)) -- `ssl_certificates` (Block Set, Deprecated) (see [below for nested schema](#nestedblock--alert_settings--ssl_certificates)) -- `time_based_escalation` (Block List) (see [below for nested schema](#nestedblock--alert_settings--time_based_escalation)) +- `parallel_run_failure_threshold` (Attributes) (see [below for nested schema](#nestedatt--alert_settings--parallel_run_failure_threshold)) +- `reminders` (Attributes) (see [below for nested schema](#nestedatt--alert_settings--reminders)) +- `run_based_escalation` (Attributes) (see [below for nested schema](#nestedatt--alert_settings--run_based_escalation)) +- `ssl_certificates` (Attributes, Deprecated) At what interval the reminders should be sent. (see [below for nested schema](#nestedatt--alert_settings--ssl_certificates)) +- `time_based_escalation` (Attributes) (see [below for nested schema](#nestedatt--alert_settings--time_based_escalation)) - + ### Nested Schema for `alert_settings.parallel_run_failure_threshold` Optional: - `enabled` (Boolean) Applicable only for checks scheduled in parallel in multiple locations. -- `percentage` (Number) Possible values are `10`, `20`, `30`, `40`, `50`, `60`, `70`, `80`, `100`, and `100`. (Default `10`). +- `percentage` (Number) Possible values are `10`, `20`, `30`, `40`, `50`, `60`, `70`, `80`, `90`, and `100`. (Default `10`). - + ### Nested Schema for `alert_settings.reminders` Optional: @@ -101,7 +102,7 @@ Optional: - `interval` (Number) Possible values are `5`, `10`, `15`, and `30`. (Default `5`). - + ### Nested Schema for `alert_settings.run_based_escalation` Optional: @@ -109,7 +110,7 @@ Optional: - `failed_run_threshold` (Number) After how many failed consecutive check runs an alert notification should be sent. Possible values are between 1 and 5. (Default `1`). - + ### Nested Schema for `alert_settings.ssl_certificates` Optional: @@ -118,7 +119,7 @@ Optional: - `enabled` (Boolean) Determines if alert notifications should be sent for expiring SSL certificates. Possible values `true`, and `false`. (Default `false`). - + ### Nested Schema for `alert_settings.time_based_escalation` Optional: diff --git a/docs/resources/maintenance_windows.md b/docs/resources/maintenance_windows.md index 6997df7..d3c25a9 100644 --- a/docs/resources/maintenance_windows.md +++ b/docs/resources/maintenance_windows.md @@ -45,3 +45,4 @@ resource "checkly_maintenance_windows" "maintenance-1" { ### Read-Only - `id` (String) The ID of this resource. +- `last_updated` (String) When the resource was last updated by the provider. diff --git a/docs/resources/private_location.md b/docs/resources/private_location.md index 100e3d1..a922606 100644 --- a/docs/resources/private_location.md +++ b/docs/resources/private_location.md @@ -35,3 +35,4 @@ resource "checkly_private_location" "location" { - `id` (String) The ID of this resource. - `keys` (Set of String, Sensitive) Private location API keys. +- `last_updated` (String) When the resource was last updated by the provider. diff --git a/docs/resources/snippet.md b/docs/resources/snippet.md index d9dbced..cfec9d3 100644 --- a/docs/resources/snippet.md +++ b/docs/resources/snippet.md @@ -43,9 +43,10 @@ EOT ### Required -- `name` (String) The name of the snippet +- `name` (String) The name of the snippet. - `script` (String) Your Node.js code that interacts with the API check lifecycle, or functions as a partial for browser checks. ### Read-Only - `id` (String) The ID of this resource. +- `last_updated` (String) When the resource was last updated by the provider. diff --git a/docs/resources/trigger_check.md b/docs/resources/trigger_check.md index 06134eb..68bf232 100644 --- a/docs/resources/trigger_check.md +++ b/docs/resources/trigger_check.md @@ -27,13 +27,14 @@ output "test_trigger_check_url" { ### Required -- `check_id` (String) The id of the check that you want to attach the trigger to. +- `check_id` (String) The ID of the check that you want to attach the trigger to. ### Optional -- `token` (String) The token value created to trigger the check +- `token` (String) The token value created to trigger the check. - `url` (String) The request URL to trigger the check run. ### Read-Only - `id` (String) The ID of this resource. +- `last_updated` (String) When the resource was last updated by the provider. diff --git a/docs/resources/trigger_group.md b/docs/resources/trigger_group.md index 51e0dd0..06a07d4 100644 --- a/docs/resources/trigger_group.md +++ b/docs/resources/trigger_group.md @@ -27,13 +27,14 @@ output "test_trigger_group_url" { ### Required -- `group_id` (Number) The id of the group that you want to attach the trigger to. +- `group_id` (Number) The ID of the group that you want to attach the trigger to. ### Optional -- `token` (String) The token value created to trigger the group +- `token` (String) The token value created to trigger the group. - `url` (String) The request URL to trigger the group run. ### Read-Only - `id` (String) The ID of this resource. +- `last_updated` (String) When the resource was last updated by the provider. diff --git a/tools/go.mod b/tools/go.mod new file mode 100644 index 0000000..19925b2 --- /dev/null +++ b/tools/go.mod @@ -0,0 +1,57 @@ +module tools + +go 1.23.3 + +require github.com/hashicorp/terraform-plugin-docs v0.20.0 + +require ( + github.com/BurntSushi/toml v1.2.1 // indirect + github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/ProtonMail/go-crypto v1.1.3 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/armon/go-radix v1.0.0 // indirect + github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect + github.com/cloudflare/circl v1.5.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/cli v1.1.6 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-checkpoint v0.5.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // 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.9.0 // indirect + github.com/hashicorp/terraform-exec v0.21.0 // indirect + github.com/hashicorp/terraform-json v0.23.0 // indirect + github.com/huandu/xstrings v1.3.3 // indirect + github.com/imdario/mergo v0.3.15 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/posener/complete v1.2.3 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/yuin/goldmark v1.7.7 // indirect + github.com/yuin/goldmark-meta v1.1.0 // indirect + github.com/zclconf/go-cty v1.15.0 // indirect + go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/tools/go.sum b/tools/go.sum new file mode 100644 index 0000000..adff740 --- /dev/null +++ b/tools/go.sum @@ -0,0 +1,203 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= +github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= +github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= +github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= +github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/cli v1.1.6 h1:CMOV+/LJfL1tXCOKrgAX0uRKnzjj/mpmqNXloRSy2K8= +github.com/hashicorp/cli v1.1.6/go.mod h1:MPon5QYlgjjo0BSoAiN0ESeT5fRzDjVRp+uioJ0piz4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +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-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= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +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.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE= +github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg= +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.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI= +github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= +github.com/hashicorp/terraform-plugin-docs v0.20.0 h1:ox7rm1FN0dVZaJBUzkVVh10R1r3+FeMQWL0QopQ9d7o= +github.com/hashicorp/terraform-plugin-docs v0.20.0/go.mod h1:A/+4SVMdAkQYtIBtaxV0H7AU862TxVZk/hhKaMDQB6Y= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +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/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.7 h1:5m9rrB1sW3JUMToKFQfb+FGt1U7r57IHu5GrYrG2nqU= +github.com/yuin/goldmark v1.7.7/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= +github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= +github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= +github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= +go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= +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.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +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.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +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.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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/tools.go b/tools/tools.go index 7d8afd6..efef263 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -1,8 +1,15 @@ -//go:build tools +//go:build generate package tools import ( - // [tfplugindocs](https://github.com/hashicorp/terraform-plugin-docs). _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" ) + +// Format Terraform code for use in documentation. +// If you do not have Terraform installed, you can remove the formatting command, but it is suggested +// to ensure the documentation is formatted properly. +//go:generate terraform fmt -recursive ../examples/ + +// Generate documentation. +//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate --provider-dir .. From a36062e1716fdb822fb6e984b76b595fb90545e4 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Wed, 4 Dec 2024 01:36:45 +0900 Subject: [PATCH 06/22] refactor: use a common ID attribute schema for all resource and data sources --- docs/data-sources/static_ips.md | 2 +- internal/provider/alert_channel_resource.go | 5 +---- internal/provider/check_group_resource.go | 5 +---- internal/provider/check_resource.go | 5 +---- internal/provider/dashboard_resource.go | 5 +---- .../provider/environment_variable_resource.go | 5 +---- internal/provider/heartbeat_resource.go | 5 +---- internal/provider/id_attribute.go | 15 +++++++++++++++ internal/provider/maintenance_windows_resource.go | 5 +---- internal/provider/private_location_resource.go | 5 +---- internal/provider/snippet_resource.go | 5 +---- internal/provider/static_ips_data_source.go | 5 +---- internal/provider/trigger_check_resource.go | 5 +---- internal/provider/trigger_group_resource.go | 5 +---- 14 files changed, 28 insertions(+), 49 deletions(-) create mode 100644 internal/provider/id_attribute.go diff --git a/docs/data-sources/static_ips.md b/docs/data-sources/static_ips.md index c7c073a..5c61dc6 100644 --- a/docs/data-sources/static_ips.md +++ b/docs/data-sources/static_ips.md @@ -23,4 +23,4 @@ description: |- ### Read-Only - `addresses` (Set of String) Static IP addresses for Checkly's runner infrastructure. -- `id` (String) ID of the static IPs data source. +- `id` (String) The ID of this data source. diff --git a/internal/provider/alert_channel_resource.go b/internal/provider/alert_channel_resource.go index 16e0c2f..82d3f07 100644 --- a/internal/provider/alert_channel_resource.go +++ b/internal/provider/alert_channel_resource.go @@ -54,10 +54,7 @@ func (r *AlertChannelResource) Schema( resp.Schema = schema.Schema{ Description: "Allows you to define alerting channels for the checks and groups in your account.", Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "", // TODO - }, + "id": IDResourceAttributeSchema, "last_updated": LastUpdatedAttributeSchema, "email": schema.SingleNestedAttribute{ Optional: true, diff --git a/internal/provider/check_group_resource.go b/internal/provider/check_group_resource.go index 75cd990..e87930f 100644 --- a/internal/provider/check_group_resource.go +++ b/internal/provider/check_group_resource.go @@ -45,10 +45,7 @@ func (r *CheckGroupResource) Schema( resp.Schema = schema.Schema{ Description: "Check groups allow you to group together a set of related checks, which can also share default settings for various attributes.", Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "", // TODO - }, + "id": IDResourceAttributeSchema, "last_updated": LastUpdatedAttributeSchema, "name": schema.StringAttribute{ Required: true, diff --git a/internal/provider/check_resource.go b/internal/provider/check_resource.go index f39dc34..c822b56 100644 --- a/internal/provider/check_resource.go +++ b/internal/provider/check_resource.go @@ -49,10 +49,7 @@ func (r *CheckResource) Schema( resp.Schema = schema.Schema{ Description: "Check groups allow you to group together a set of related checks, which can also share default settings for various attributes.", Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "", // TODO - }, + "id": IDResourceAttributeSchema, "last_updated": LastUpdatedAttributeSchema, "name": schema.StringAttribute{ Required: true, diff --git a/internal/provider/dashboard_resource.go b/internal/provider/dashboard_resource.go index ad39992..f2e5d3e 100644 --- a/internal/provider/dashboard_resource.go +++ b/internal/provider/dashboard_resource.go @@ -48,10 +48,7 @@ func (r *DashboardResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "", // TODO - }, + "id": IDResourceAttributeSchema, "last_updated": LastUpdatedAttributeSchema, "custom_url": schema.StringAttribute{ Required: true, diff --git a/internal/provider/environment_variable_resource.go b/internal/provider/environment_variable_resource.go index c7ce8ef..fd327b9 100644 --- a/internal/provider/environment_variable_resource.go +++ b/internal/provider/environment_variable_resource.go @@ -43,10 +43,7 @@ func (r *EnvironmentVariableResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "", // TODO - }, + "id": IDResourceAttributeSchema, "last_updated": LastUpdatedAttributeSchema, "key": schema.StringAttribute{ Required: true, diff --git a/internal/provider/heartbeat_resource.go b/internal/provider/heartbeat_resource.go index 035d1a8..3f01db2 100644 --- a/internal/provider/heartbeat_resource.go +++ b/internal/provider/heartbeat_resource.go @@ -47,10 +47,7 @@ func (r *HeartbeatResource) Schema( resp.Schema = schema.Schema{ Description: "Heartbeats allows you to monitor your cron jobs and set up alerting, so you get a notification when things break or slow down.", Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "", // TODO - }, + "id": IDResourceAttributeSchema, "last_updated": LastUpdatedAttributeSchema, "name": schema.StringAttribute{ Required: true, diff --git a/internal/provider/id_attribute.go b/internal/provider/id_attribute.go new file mode 100644 index 0000000..8a5308e --- /dev/null +++ b/internal/provider/id_attribute.go @@ -0,0 +1,15 @@ +package provider + +import ( + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +var IDResourceAttributeSchema = schema.StringAttribute{ + Computed: true, + Description: "The ID of this resource.", +} + +var IDDataSourceAttributeSchema = schema.StringAttribute{ + Computed: true, + Description: "The ID of this data source.", +} diff --git a/internal/provider/maintenance_windows_resource.go b/internal/provider/maintenance_windows_resource.go index 47bff8a..3922db3 100644 --- a/internal/provider/maintenance_windows_resource.go +++ b/internal/provider/maintenance_windows_resource.go @@ -44,10 +44,7 @@ func (r *MaintenanceWindowsResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "", // TODO - }, + "id": IDResourceAttributeSchema, "last_updated": LastUpdatedAttributeSchema, "name": schema.StringAttribute{ Required: true, diff --git a/internal/provider/private_location_resource.go b/internal/provider/private_location_resource.go index 34dd85b..547499a 100644 --- a/internal/provider/private_location_resource.go +++ b/internal/provider/private_location_resource.go @@ -44,10 +44,7 @@ func (r *PrivateLocationResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "", // TODO - }, + "id": IDResourceAttributeSchema, "last_updated": LastUpdatedAttributeSchema, "name": schema.StringAttribute{ Required: true, diff --git a/internal/provider/snippet_resource.go b/internal/provider/snippet_resource.go index 35ba695..d582393 100644 --- a/internal/provider/snippet_resource.go +++ b/internal/provider/snippet_resource.go @@ -42,10 +42,7 @@ func (r *SnippetResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "", // TODO - }, + "id": IDResourceAttributeSchema, "last_updated": LastUpdatedAttributeSchema, "name": schema.StringAttribute{ Required: true, diff --git a/internal/provider/static_ips_data_source.go b/internal/provider/static_ips_data_source.go index c13a08a..4e0efeb 100644 --- a/internal/provider/static_ips_data_source.go +++ b/internal/provider/static_ips_data_source.go @@ -51,10 +51,7 @@ func (d *StaticIPsDataSource) Schema( Description: "", // TODO MarkdownDescription: "", // TODO Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "ID of the static IPs data source.", - }, + "id": IDDataSourceAttributeSchema, "addresses": schema.SetAttribute{ ElementType: types.StringType, Computed: true, diff --git a/internal/provider/trigger_check_resource.go b/internal/provider/trigger_check_resource.go index 1080654..f057be6 100644 --- a/internal/provider/trigger_check_resource.go +++ b/internal/provider/trigger_check_resource.go @@ -42,10 +42,7 @@ func (r *TriggerCheckResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "", // TODO - }, + "id": IDResourceAttributeSchema, "last_updated": LastUpdatedAttributeSchema, "check_id": schema.StringAttribute{ Required: true, diff --git a/internal/provider/trigger_group_resource.go b/internal/provider/trigger_group_resource.go index 1d0421e..fecf7b9 100644 --- a/internal/provider/trigger_group_resource.go +++ b/internal/provider/trigger_group_resource.go @@ -42,10 +42,7 @@ func (r *TriggerGroupResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "", // TODO - }, + "id": IDResourceAttributeSchema, "last_updated": LastUpdatedAttributeSchema, "group_id": schema.Int64Attribute{ Required: true, From 3df37b88d38e6f5b969a5d6ff6cec7f5400122f6 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Wed, 4 Dec 2024 01:47:05 +0900 Subject: [PATCH 07/22] refactor: change heartbeat from legacy single-item Set to SingleNestedAttribute --- docs/resources/heartbeat.md | 2 +- internal/provider/heartbeat_resource.go | 64 +++++++++++-------------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/docs/resources/heartbeat.md b/docs/resources/heartbeat.md index d881b44..c266093 100644 --- a/docs/resources/heartbeat.md +++ b/docs/resources/heartbeat.md @@ -32,7 +32,7 @@ resource "checkly_heartbeat" "example-heartbeat" { ### Required - `activated` (Boolean) Determines if the check is running or not. Possible values `true`, and `false`. -- `heartbeat` (Attributes Set) (see [below for nested schema](#nestedatt--heartbeat)) +- `heartbeat` (Attributes) (see [below for nested schema](#nestedatt--heartbeat)) - `name` (String) The name of the check. ### Optional diff --git a/internal/provider/heartbeat_resource.go b/internal/provider/heartbeat_resource.go index 3f01db2..6425dd8 100644 --- a/internal/provider/heartbeat_resource.go +++ b/internal/provider/heartbeat_resource.go @@ -5,7 +5,6 @@ import ( "fmt" "slices" - "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" @@ -71,44 +70,39 @@ func (r *HeartbeatResource) Schema( Optional: true, Description: "When true, the account level alert settings will be used, not the alert setting defined on this check.", }, - "heartbeat": schema.SetNestedAttribute{ // TODO: Change from set to single object. - Required: true, - Validators: []validator.Set{ - setvalidator.SizeAtMost(1), + "heartbeat": schema.SingleNestedAttribute{ + Required: true, + Validators: []validator.Object{ + // TODO: period * period_unit must be between 30s and 365 days + // TODO: grace * grace_unit must be less than 365 days }, - NestedObject: schema.NestedAttributeObject{ - Validators: []validator.Object{ - // TODO: period * period_unit must be between 30s and 365 days - // TODO: grace * grace_unit must be less than 365 days + Attributes: map[string]schema.Attribute{ + "period": schema.Int32Attribute{ + Required: true, + Description: "How often you expect a ping to the ping URL.", }, - Attributes: map[string]schema.Attribute{ - "period": schema.Int32Attribute{ - Required: true, - Description: "How often you expect a ping to the ping URL.", + "period_unit": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("seconds", "minutes", "hours", "days"), }, - "period_unit": schema.StringAttribute{ - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf("seconds", "minutes", "hours", "days"), - }, - Description: "Possible values `seconds`, `minutes`, `hours` and `days`.", - }, - "grace": schema.Int32Attribute{ - Required: true, - Description: "How long Checkly should wait before triggering any alerts when a ping does not arrive within the set period.", - }, - "grace_unit": schema.StringAttribute{ - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf("seconds", "minutes", "hours", "days"), - }, - Description: "Possible values `seconds`, `minutes`, `hours` and `days`.", - }, - "ping_token": schema.StringAttribute{ - Optional: true, - Computed: true, - Description: "Custom token to generate your ping URL. Checkly will expect a ping to `https://ping.checklyhq.com/[PING_TOKEN]`.", + Description: "Possible values `seconds`, `minutes`, `hours` and `days`.", + }, + "grace": schema.Int32Attribute{ + Required: true, + Description: "How long Checkly should wait before triggering any alerts when a ping does not arrive within the set period.", + }, + "grace_unit": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("seconds", "minutes", "hours", "days"), }, + Description: "Possible values `seconds`, `minutes`, `hours` and `days`.", + }, + "ping_token": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Custom token to generate your ping URL. Checkly will expect a ping to `https://ping.checklyhq.com/[PING_TOKEN]`.", }, }, }, From 1c4aa869f6b0e058334ff37b024dbdce83a858b5 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Wed, 4 Dec 2024 01:54:13 +0900 Subject: [PATCH 08/22] refactor: move sdk utilities into a separate sdkutil package --- internal/provider/alert_channel_resource.go | 13 +++++++------ .../provider/check_api_check_defaults_attribute.go | 9 +++++---- internal/provider/check_group_resource.go | 5 +++-- internal/provider/check_headers_attribute.go | 5 +++-- .../provider/check_query_parameters_attribute.go | 5 +++-- internal/provider/check_request_attribute.go | 9 +++++---- internal/provider/check_resource.go | 3 ++- internal/provider/dashboard_resource.go | 3 ++- internal/provider/environment_variable_resource.go | 3 ++- internal/provider/heartbeat_resource.go | 3 ++- internal/provider/maintenance_windows_resource.go | 5 +++-- internal/provider/private_location_resource.go | 3 ++- internal/provider/snippet_resource.go | 5 +++-- internal/provider/trigger_check_resource.go | 5 +++-- internal/provider/trigger_group_resource.go | 5 +++-- internal/{provider/sdk.go => sdkutil/sdkutil.go} | 14 +++++++------- 16 files changed, 55 insertions(+), 40 deletions(-) rename internal/{provider/sdk.go => sdkutil/sdkutil.go} (83%) diff --git a/internal/provider/alert_channel_resource.go b/internal/provider/alert_channel_resource.go index 82d3f07..3e17011 100644 --- a/internal/provider/alert_channel_resource.go +++ b/internal/provider/alert_channel_resource.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -346,7 +347,7 @@ func (r *AlertChannelResource) Read( realizedModel, err := r.client.GetAlertChannel(ctx, id) if err != nil { - if SDKIsHTTPNotFoundError(err) { + if sdkutil.IsHTTPNotFoundError(err) { resp.State.RemoveResource(ctx) return } @@ -419,7 +420,7 @@ func (r *AlertChannelResource) Update( } } -var AlertChannelID = SDKIdentifier{ +var AlertChannelID = sdkutil.Identifier{ Path: path.Root("id"), Title: "Checkly Alert Channel ID", } @@ -667,8 +668,8 @@ type WebhookAttributeModel struct { func (m *WebhookAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelWebhook, flags RefreshFlags) diag.Diagnostics { m.Name = types.StringValue(from.Name) m.Method = types.StringValue(from.Method) - m.Headers = SDKKeyValuesIntoMap(&from.Headers) - m.QueryParameters = SDKKeyValuesIntoMap(&from.QueryParameters) + m.Headers = sdkutil.KeyValuesIntoMap(&from.Headers) + m.QueryParameters = sdkutil.KeyValuesIntoMap(&from.QueryParameters) m.Template = types.StringValue(from.Template) m.URL = types.StringValue(from.URL) m.WebhookSecret = types.StringValue(from.WebhookSecret) @@ -680,8 +681,8 @@ func (m *WebhookAttributeModel) Refresh(ctx context.Context, from *checkly.Alert func (m *WebhookAttributeModel) Render(ctx context.Context, into *checkly.AlertChannelWebhook) diag.Diagnostics { into.Name = m.Name.ValueString() into.Method = m.Method.ValueString() - into.Headers = SDKKeyValuesFromMap(m.Headers) - into.QueryParameters = SDKKeyValuesFromMap(m.QueryParameters) + into.Headers = sdkutil.KeyValuesFromMap(m.Headers) + into.QueryParameters = sdkutil.KeyValuesFromMap(m.QueryParameters) into.Template = m.Template.ValueString() into.URL = m.URL.ValueString() into.WebhookSecret = m.WebhookSecret.ValueString() diff --git a/internal/provider/check_api_check_defaults_attribute.go b/internal/provider/check_api_check_defaults_attribute.go index c0bc5e9..78f64a1 100644 --- a/internal/provider/check_api_check_defaults_attribute.go +++ b/internal/provider/check_api_check_defaults_attribute.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -41,8 +42,8 @@ func (m *CheckAPICheckDefaultsAttributeModel) Refresh(ctx context.Context, from var diags diag.Diagnostics m.URL = types.StringValue(from.BaseURL) - m.Headers = SDKKeyValuesIntoMap(&from.Headers) - m.QueryParameters = SDKKeyValuesIntoMap(&from.QueryParameters) + m.Headers = sdkutil.KeyValuesIntoMap(&from.Headers) + m.QueryParameters = sdkutil.KeyValuesIntoMap(&from.QueryParameters) diags.Append(RefreshMany(ctx, from.Assertions, m.Assertions, flags)...) @@ -55,8 +56,8 @@ func (m *CheckAPICheckDefaultsAttributeModel) Render(ctx context.Context, into * var diags diag.Diagnostics into.BaseURL = m.URL.ValueString() - into.Headers = SDKKeyValuesFromMap(m.Headers) - into.QueryParameters = SDKKeyValuesFromMap(m.QueryParameters) + into.Headers = sdkutil.KeyValuesFromMap(m.Headers) + into.QueryParameters = sdkutil.KeyValuesFromMap(m.QueryParameters) diags.Append(RenderMany(ctx, m.Assertions, into.Assertions)...) diff --git a/internal/provider/check_group_resource.go b/internal/provider/check_group_resource.go index e87930f..baf2970 100644 --- a/internal/provider/check_group_resource.go +++ b/internal/provider/check_group_resource.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -233,7 +234,7 @@ func (r *CheckGroupResource) Read( realizedModel, err := r.client.GetGroup(ctx, id) if err != nil { - if SDKIsHTTPNotFoundError(err) { + if sdkutil.IsHTTPNotFoundError(err) { resp.State.RemoveResource(ctx) return } @@ -306,7 +307,7 @@ func (r *CheckGroupResource) Update( } } -var CheckGroupID = SDKIdentifier{ +var CheckGroupID = sdkutil.Identifier{ Path: path.Root("id"), Title: "Checkly Check Group ID", } diff --git a/internal/provider/check_headers_attribute.go b/internal/provider/check_headers_attribute.go index 1fb1e23..1c9e10f 100644 --- a/internal/provider/check_headers_attribute.go +++ b/internal/provider/check_headers_attribute.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -23,13 +24,13 @@ var CheckHeadersAttributeSchema = schema.MapAttribute{ type CheckHeadersAttributeModel types.Map func (m *CheckHeadersAttributeModel) Refresh(ctx context.Context, from *[]checkly.KeyValue, flags RefreshFlags) diag.Diagnostics { - *m = CheckHeadersAttributeModel(SDKKeyValuesIntoMap(from)) + *m = CheckHeadersAttributeModel(sdkutil.KeyValuesIntoMap(from)) return nil } func (m *CheckHeadersAttributeModel) Render(ctx context.Context, into *[]checkly.KeyValue) diag.Diagnostics { - *into = SDKKeyValuesFromMap(types.Map(*m)) + *into = sdkutil.KeyValuesFromMap(types.Map(*m)) return nil } diff --git a/internal/provider/check_query_parameters_attribute.go b/internal/provider/check_query_parameters_attribute.go index 68aa293..e600cfb 100644 --- a/internal/provider/check_query_parameters_attribute.go +++ b/internal/provider/check_query_parameters_attribute.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -23,13 +24,13 @@ var CheckQueryParametersAttributeSchema = schema.MapAttribute{ type CheckQueryParametersAttributeModel types.Map func (m *CheckQueryParametersAttributeModel) Refresh(ctx context.Context, from *[]checkly.KeyValue, flags RefreshFlags) diag.Diagnostics { - *m = CheckQueryParametersAttributeModel(SDKKeyValuesIntoMap(from)) + *m = CheckQueryParametersAttributeModel(sdkutil.KeyValuesIntoMap(from)) return nil } func (m *CheckQueryParametersAttributeModel) Render(ctx context.Context, into *[]checkly.KeyValue) diag.Diagnostics { - *into = SDKKeyValuesFromMap(types.Map(*m)) + *into = sdkutil.KeyValuesFromMap(types.Map(*m)) return nil } diff --git a/internal/provider/check_request_attribute.go b/internal/provider/check_request_attribute.go index c9d9357..2c60483 100644 --- a/internal/provider/check_request_attribute.go +++ b/internal/provider/check_request_attribute.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -102,8 +103,8 @@ func (m *CheckRequestAttributeModel) Refresh(ctx context.Context, from *checkly. m.URL = types.StringValue(from.URL) m.FollowRedirects = types.BoolValue(from.FollowRedirects) m.SkipSSL = types.BoolValue(from.SkipSSL) - m.Headers = SDKKeyValuesIntoMap(&from.Headers) - m.QueryParameters = SDKKeyValuesIntoMap(&from.QueryParameters) + m.Headers = sdkutil.KeyValuesIntoMap(&from.Headers) + m.QueryParameters = sdkutil.KeyValuesIntoMap(&from.QueryParameters) m.Body = types.StringValue(from.Body) m.BodyType = types.StringValue(from.BodyType) @@ -127,8 +128,8 @@ func (m *CheckRequestAttributeModel) Render(ctx context.Context, into *checkly.R into.URL = m.URL.ValueString() into.FollowRedirects = m.FollowRedirects.ValueBool() into.SkipSSL = m.SkipSSL.ValueBool() - into.Headers = SDKKeyValuesFromMap(m.Headers) - into.QueryParameters = SDKKeyValuesFromMap(m.QueryParameters) + into.Headers = sdkutil.KeyValuesFromMap(m.Headers) + into.QueryParameters = sdkutil.KeyValuesFromMap(m.QueryParameters) into.Body = m.Body.ValueString() into.BodyType = m.Body.ValueString() diff --git a/internal/provider/check_resource.go b/internal/provider/check_resource.go index c822b56..0fefa73 100644 --- a/internal/provider/check_resource.go +++ b/internal/provider/check_resource.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -302,7 +303,7 @@ func (r *CheckResource) Read( realizedModel, err := r.client.GetCheck(ctx, state.ID.ValueString()) if err != nil { - if SDKIsHTTPNotFoundError(err) { + if sdkutil.IsHTTPNotFoundError(err) { resp.State.RemoveResource(ctx) return } diff --git a/internal/provider/dashboard_resource.go b/internal/provider/dashboard_resource.go index f2e5d3e..217b671 100644 --- a/internal/provider/dashboard_resource.go +++ b/internal/provider/dashboard_resource.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -249,7 +250,7 @@ func (r *DashboardResource) Read( realizedModel, err := r.client.GetDashboard(ctx, state.ID.ValueString()) if err != nil { - if SDKIsHTTPNotFoundError(err) { + if sdkutil.IsHTTPNotFoundError(err) { resp.State.RemoveResource(ctx) return } diff --git a/internal/provider/environment_variable_resource.go b/internal/provider/environment_variable_resource.go index fd327b9..770b933 100644 --- a/internal/provider/environment_variable_resource.go +++ b/internal/provider/environment_variable_resource.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -174,7 +175,7 @@ func (r *EnvironmentVariableResource) Read( state.ID.ValueString(), ) if err != nil { - if SDKIsHTTPNotFoundError(err) { + if sdkutil.IsHTTPNotFoundError(err) { resp.State.RemoveResource(ctx) return } diff --git a/internal/provider/heartbeat_resource.go b/internal/provider/heartbeat_resource.go index 6425dd8..2740977 100644 --- a/internal/provider/heartbeat_resource.go +++ b/internal/provider/heartbeat_resource.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -209,7 +210,7 @@ func (r *HeartbeatResource) Read( realizedModel, err := r.client.GetHeartbeatCheck(ctx, state.ID.ValueString()) if err != nil { - if SDKIsHTTPNotFoundError(err) { + if sdkutil.IsHTTPNotFoundError(err) { resp.State.RemoveResource(ctx) return } diff --git a/internal/provider/maintenance_windows_resource.go b/internal/provider/maintenance_windows_resource.go index 3922db3..f83b73d 100644 --- a/internal/provider/maintenance_windows_resource.go +++ b/internal/provider/maintenance_windows_resource.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -192,7 +193,7 @@ func (r *MaintenanceWindowsResource) Read( realizedModel, err := r.client.GetMaintenanceWindow(ctx, id) if err != nil { - if SDKIsHTTPNotFoundError(err) { + if sdkutil.IsHTTPNotFoundError(err) { resp.State.RemoveResource(ctx) return } @@ -265,7 +266,7 @@ func (r *MaintenanceWindowsResource) Update( } } -var MaintenanceWindowID = SDKIdentifier{ +var MaintenanceWindowID = sdkutil.Identifier{ Path: path.Root("id"), Title: "Checkly Maintenance Window ID", } diff --git a/internal/provider/private_location_resource.go b/internal/provider/private_location_resource.go index 547499a..e44bfa6 100644 --- a/internal/provider/private_location_resource.go +++ b/internal/provider/private_location_resource.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -168,7 +169,7 @@ func (r *PrivateLocationResource) Read( realizedModel, err := r.client.GetPrivateLocation(ctx, state.ID.ValueString()) if err != nil { - if SDKIsHTTPNotFoundError(err) { + if sdkutil.IsHTTPNotFoundError(err) { resp.State.RemoveResource(ctx) return } diff --git a/internal/provider/snippet_resource.go b/internal/provider/snippet_resource.go index d582393..9b9abce 100644 --- a/internal/provider/snippet_resource.go +++ b/internal/provider/snippet_resource.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -168,7 +169,7 @@ func (r *SnippetResource) Read( realizedModel, err := r.client.GetSnippet(ctx, id) if err != nil { - if SDKIsHTTPNotFoundError(err) { + if sdkutil.IsHTTPNotFoundError(err) { resp.State.RemoveResource(ctx) return } @@ -241,7 +242,7 @@ func (r *SnippetResource) Update( } } -var SnippetID = SDKIdentifier{ +var SnippetID = sdkutil.Identifier{ Path: path.Root("id"), Title: "Checkly Snippet ID", } diff --git a/internal/provider/trigger_check_resource.go b/internal/provider/trigger_check_resource.go index f057be6..c381f53 100644 --- a/internal/provider/trigger_check_resource.go +++ b/internal/provider/trigger_check_resource.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -160,7 +161,7 @@ func (r *TriggerCheckResource) Read( state.CheckID.ValueString(), ) if err != nil { - if SDKIsHTTPNotFoundError(err) { + if sdkutil.IsHTTPNotFoundError(err) { resp.State.RemoveResource(ctx) return } @@ -220,7 +221,7 @@ func (r *TriggerCheckResource) Update( } } -var TriggerCheckID = SDKIdentifier{ +var TriggerCheckID = sdkutil.Identifier{ Path: path.Root("id"), Title: "Checkly Trigger Check ID", } diff --git a/internal/provider/trigger_group_resource.go b/internal/provider/trigger_group_resource.go index fecf7b9..5dc1a65 100644 --- a/internal/provider/trigger_group_resource.go +++ b/internal/provider/trigger_group_resource.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( @@ -160,7 +161,7 @@ func (r *TriggerGroupResource) Read( state.GroupID.ValueInt64(), ) if err != nil { - if SDKIsHTTPNotFoundError(err) { + if sdkutil.IsHTTPNotFoundError(err) { resp.State.RemoveResource(ctx) return } @@ -220,7 +221,7 @@ func (r *TriggerGroupResource) Update( } } -var TriggerGroupID = SDKIdentifier{ +var TriggerGroupID = sdkutil.Identifier{ Path: path.Root("id"), Title: "Checkly Trigger Group ID", } diff --git a/internal/provider/sdk.go b/internal/sdkutil/sdkutil.go similarity index 83% rename from internal/provider/sdk.go rename to internal/sdkutil/sdkutil.go index 9d35eb3..14aa748 100644 --- a/internal/provider/sdk.go +++ b/internal/sdkutil/sdkutil.go @@ -1,4 +1,4 @@ -package provider +package sdkutil import ( "fmt" @@ -13,12 +13,12 @@ import ( checkly "github.com/checkly/checkly-go-sdk" ) -type SDKIdentifier struct { +type Identifier struct { Path path.Path Title string } -func (i *SDKIdentifier) FromString(id types.String) (int64, diag.Diagnostics) { +func (i *Identifier) FromString(id types.String) (int64, diag.Diagnostics) { if id.IsUnknown() { return 0, diag.Diagnostics{ diag.NewAttributeErrorDiagnostic( @@ -53,11 +53,11 @@ func (i *SDKIdentifier) FromString(id types.String) (int64, diag.Diagnostics) { return val, nil } -func (i *SDKIdentifier) IntoString(id int64) types.String { +func (i *Identifier) IntoString(id int64) types.String { return types.StringValue(fmt.Sprintf("%d", id)) } -func SDKKeyValuesFromMap(m types.Map) []checkly.KeyValue { +func KeyValuesFromMap(m types.Map) []checkly.KeyValue { if m.IsNull() { return nil } @@ -73,7 +73,7 @@ func SDKKeyValuesFromMap(m types.Map) []checkly.KeyValue { return values } -func SDKKeyValuesIntoMap(values *[]checkly.KeyValue) types.Map { +func KeyValuesIntoMap(values *[]checkly.KeyValue) types.Map { if values == nil { return types.MapNull(types.StringType) } @@ -86,7 +86,7 @@ func SDKKeyValuesIntoMap(values *[]checkly.KeyValue) types.Map { return types.MapValueMust(types.StringType, mapValues) } -func SDKIsHTTPNotFoundError(err error) bool { +func IsHTTPNotFoundError(err error) bool { // Unfortunately the SDK presents HTTP errors in a completely unusable way, // forcing us to match against string values. msg := err.Error() From 36daf1da345f63bd763edf9e835e124792aad9b4 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Wed, 4 Dec 2024 04:24:59 +0900 Subject: [PATCH 09/22] refactor: start reorganizing files into a logical structure --- .../check_api_check_defaults_attribute.go | 67 ----------- internal/provider/check_headers_attribute.go | 36 ------ .../provider/check_locations_attribute.go | 33 ------ .../check_private_locations_attribute.go | 33 ------ .../check_query_parameters_attribute.go | 36 ------ .../datasources/attributes/id_attribute.go | 10 ++ .../static_ips_data_source.go | 8 +- internal/provider/id_attribute.go | 15 --- internal/provider/interop/data.go | 34 ++++++ .../{resource_model.go => interop/model.go} | 40 +++---- internal/provider/{util.go => interop/set.go} | 2 +- internal/provider/provider.go | 52 +++----- .../{ => resources}/alert_channel_resource.go | 52 ++++---- .../alert_channel_subscription_attribute.go} | 13 +- .../attributes/alert_settings_attribute.go} | 65 +++++----- .../api_check_defaults_attribute.go | 68 +++++++++++ .../attributes/assertion_attribute.go} | 13 +- .../attributes/basic_auth_attribute.go} | 13 +- .../environment_variable_attribute.go} | 13 +- .../resources/attributes/headers_attribute.go | 37 ++++++ .../resources/attributes/id_attribute.go | 10 ++ .../attributes}/last_updated_attribute.go | 2 +- .../attributes/locations_attribute.go | 34 ++++++ .../attributes/private_locations_attribute.go | 34 ++++++ .../attributes/query_parameters_attribute.go | 37 ++++++ .../attributes/request_attribute.go} | 47 ++++---- .../attributes/retry_strategy_attribute.go} | 13 +- .../{ => resources}/check_group_resource.go | 94 ++++++++------- .../{ => resources}/check_resource.go | 112 +++++++++--------- .../{ => resources}/dashboard_resource.go | 26 ++-- .../environment_variable_resource.go | 22 ++-- .../{ => resources}/heartbeat_resource.go | 56 ++++----- .../maintenance_windows_resource.go | 26 ++-- .../private_location_resource.go | 22 ++-- .../{ => resources}/snippet_resource.go | 22 ++-- .../{ => resources}/trigger_check_resource.go | 22 ++-- .../{ => resources}/trigger_group_resource.go | 22 ++-- 37 files changed, 646 insertions(+), 595 deletions(-) delete mode 100644 internal/provider/check_api_check_defaults_attribute.go delete mode 100644 internal/provider/check_headers_attribute.go delete mode 100644 internal/provider/check_locations_attribute.go delete mode 100644 internal/provider/check_private_locations_attribute.go delete mode 100644 internal/provider/check_query_parameters_attribute.go create mode 100644 internal/provider/datasources/attributes/id_attribute.go rename internal/provider/{ => datasources}/static_ips_data_source.go (92%) delete mode 100644 internal/provider/id_attribute.go create mode 100644 internal/provider/interop/data.go rename internal/provider/{resource_model.go => interop/model.go} (62%) rename internal/provider/{util.go => interop/set.go} (97%) rename internal/provider/{ => resources}/alert_channel_resource.go (91%) rename internal/provider/{check_alert_channel_subscription_attribute.go => resources/attributes/alert_channel_subscription_attribute.go} (62%) rename internal/provider/{check_alert_settings_attribute.go => resources/attributes/alert_settings_attribute.go} (70%) create mode 100644 internal/provider/resources/attributes/api_check_defaults_attribute.go rename internal/provider/{check_assertion_attribute.go => resources/attributes/assertion_attribute.go} (82%) rename internal/provider/{check_basic_auth_attribute.go => resources/attributes/basic_auth_attribute.go} (62%) rename internal/provider/{check_environment_variable_attribute.go => resources/attributes/environment_variable_attribute.go} (83%) create mode 100644 internal/provider/resources/attributes/headers_attribute.go create mode 100644 internal/provider/resources/attributes/id_attribute.go rename internal/provider/{ => resources/attributes}/last_updated_attribute.go (95%) create mode 100644 internal/provider/resources/attributes/locations_attribute.go create mode 100644 internal/provider/resources/attributes/private_locations_attribute.go create mode 100644 internal/provider/resources/attributes/query_parameters_attribute.go rename internal/provider/{check_request_attribute.go => resources/attributes/request_attribute.go} (68%) rename internal/provider/{check_retry_strategy_attribute.go => resources/attributes/retry_strategy_attribute.go} (84%) rename internal/provider/{ => resources}/check_group_resource.go (80%) rename internal/provider/{ => resources}/check_resource.go (83%) rename internal/provider/{ => resources}/dashboard_resource.go (92%) rename internal/provider/{ => resources}/environment_variable_resource.go (89%) rename internal/provider/{ => resources}/heartbeat_resource.go (84%) rename internal/provider/{ => resources}/maintenance_windows_resource.go (89%) rename internal/provider/{ => resources}/private_location_resource.go (89%) rename internal/provider/{ => resources}/snippet_resource.go (88%) rename internal/provider/{ => resources}/trigger_check_resource.go (88%) rename internal/provider/{ => resources}/trigger_group_resource.go (88%) diff --git a/internal/provider/check_api_check_defaults_attribute.go b/internal/provider/check_api_check_defaults_attribute.go deleted file mode 100644 index 78f64a1..0000000 --- a/internal/provider/check_api_check_defaults_attribute.go +++ /dev/null @@ -1,67 +0,0 @@ -package provider - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" - - checkly "github.com/checkly/checkly-go-sdk" - "github.com/checkly/terraform-provider-checkly/internal/sdkutil" -) - -var ( - _ ResourceModel[checkly.APICheckDefaults] = (*CheckAPICheckDefaultsAttributeModel)(nil) -) - -var CheckAPICheckDefaultsAttributeSchema = schema.SingleNestedAttribute{ - Optional: true, - Computed: true, - Attributes: map[string]schema.Attribute{ - "url": schema.StringAttribute{ - Required: true, - Description: "The base url for this group which you can reference with the `GROUP_BASE_URL` variable in all group checks.", - }, - "headers": CheckHeadersAttributeSchema, - "query_parameters": CheckQueryParametersAttributeSchema, - "assertion": CheckAssertionAttributeSchema, - "basic_auth": CheckBasicAuthAttributeSchema, - }, -} - -type CheckAPICheckDefaultsAttributeModel struct { - URL types.String `tfsdk:"url"` - Headers types.Map `tfsdk:"headers"` - QueryParameters types.Map `tfsdk:"query_parameters"` - Assertions []CheckAssertionAttributeModel `tfsdk:"assertion"` - BasicAuth CheckBasicAuthAttributeModel `tfsdk:"basic_auth"` -} - -func (m *CheckAPICheckDefaultsAttributeModel) Refresh(ctx context.Context, from *checkly.APICheckDefaults, flags RefreshFlags) diag.Diagnostics { - var diags diag.Diagnostics - - m.URL = types.StringValue(from.BaseURL) - m.Headers = sdkutil.KeyValuesIntoMap(&from.Headers) - m.QueryParameters = sdkutil.KeyValuesIntoMap(&from.QueryParameters) - - diags.Append(RefreshMany(ctx, from.Assertions, m.Assertions, flags)...) - - diags.Append(m.BasicAuth.Refresh(ctx, &from.BasicAuth, flags)...) - - return diags -} - -func (m *CheckAPICheckDefaultsAttributeModel) Render(ctx context.Context, into *checkly.APICheckDefaults) diag.Diagnostics { - var diags diag.Diagnostics - - into.BaseURL = m.URL.ValueString() - into.Headers = sdkutil.KeyValuesFromMap(m.Headers) - into.QueryParameters = sdkutil.KeyValuesFromMap(m.QueryParameters) - - diags.Append(RenderMany(ctx, m.Assertions, into.Assertions)...) - - diags.Append(m.BasicAuth.Render(ctx, &into.BasicAuth)...) - - return diags -} diff --git a/internal/provider/check_headers_attribute.go b/internal/provider/check_headers_attribute.go deleted file mode 100644 index 1c9e10f..0000000 --- a/internal/provider/check_headers_attribute.go +++ /dev/null @@ -1,36 +0,0 @@ -package provider - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" - - checkly "github.com/checkly/checkly-go-sdk" - "github.com/checkly/terraform-provider-checkly/internal/sdkutil" -) - -var ( - _ ResourceModel[[]checkly.KeyValue] = (*CheckHeadersAttributeModel)(nil) -) - -var CheckHeadersAttributeSchema = schema.MapAttribute{ - ElementType: types.StringType, - Optional: true, - Computed: true, // TODO: Really? -} - -type CheckHeadersAttributeModel types.Map - -func (m *CheckHeadersAttributeModel) Refresh(ctx context.Context, from *[]checkly.KeyValue, flags RefreshFlags) diag.Diagnostics { - *m = CheckHeadersAttributeModel(sdkutil.KeyValuesIntoMap(from)) - - return nil -} - -func (m *CheckHeadersAttributeModel) Render(ctx context.Context, into *[]checkly.KeyValue) diag.Diagnostics { - *into = sdkutil.KeyValuesFromMap(types.Map(*m)) - - return nil -} diff --git a/internal/provider/check_locations_attribute.go b/internal/provider/check_locations_attribute.go deleted file mode 100644 index 628a4e9..0000000 --- a/internal/provider/check_locations_attribute.go +++ /dev/null @@ -1,33 +0,0 @@ -package provider - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -var ( - _ ResourceModel[[]string] = (*CheckLocationsAttributeModel)(nil) -) - -var CheckLocationsAttributeSchema = schema.SetAttribute{ - Description: "An array of one or more data center locations where to run the checks.", - ElementType: types.StringType, - Optional: true, -} - -type CheckLocationsAttributeModel types.Set - -func (m *CheckLocationsAttributeModel) Refresh(ctx context.Context, from *[]string, flags RefreshFlags) diag.Diagnostics { - *m = CheckLocationsAttributeModel(IntoUntypedStringSet(from)) - - return nil -} - -func (m *CheckLocationsAttributeModel) Render(ctx context.Context, into *[]string) diag.Diagnostics { - *into = FromUntypedStringSet(types.Set(*m)) - - return nil -} diff --git a/internal/provider/check_private_locations_attribute.go b/internal/provider/check_private_locations_attribute.go deleted file mode 100644 index 2108937..0000000 --- a/internal/provider/check_private_locations_attribute.go +++ /dev/null @@ -1,33 +0,0 @@ -package provider - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -var ( - _ ResourceModel[[]string] = (*CheckPrivateLocationsAttributeModel)(nil) -) - -var CheckPrivateLocationsAttributeSchema = schema.SetAttribute{ - Description: "An array of one or more private locations slugs.", - ElementType: types.StringType, - Optional: true, -} - -type CheckPrivateLocationsAttributeModel types.Set - -func (m *CheckPrivateLocationsAttributeModel) Refresh(ctx context.Context, from *[]string, flags RefreshFlags) diag.Diagnostics { - *m = CheckPrivateLocationsAttributeModel(IntoUntypedStringSet(from)) - - return nil -} - -func (m *CheckPrivateLocationsAttributeModel) Render(ctx context.Context, into *[]string) diag.Diagnostics { - *into = FromUntypedStringSet(types.Set(*m)) - - return nil -} diff --git a/internal/provider/check_query_parameters_attribute.go b/internal/provider/check_query_parameters_attribute.go deleted file mode 100644 index e600cfb..0000000 --- a/internal/provider/check_query_parameters_attribute.go +++ /dev/null @@ -1,36 +0,0 @@ -package provider - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" - - checkly "github.com/checkly/checkly-go-sdk" - "github.com/checkly/terraform-provider-checkly/internal/sdkutil" -) - -var ( - _ ResourceModel[[]checkly.KeyValue] = (*CheckQueryParametersAttributeModel)(nil) -) - -var CheckQueryParametersAttributeSchema = schema.MapAttribute{ - ElementType: types.StringType, - Optional: true, - Computed: true, // TODO: Really? -} - -type CheckQueryParametersAttributeModel types.Map - -func (m *CheckQueryParametersAttributeModel) Refresh(ctx context.Context, from *[]checkly.KeyValue, flags RefreshFlags) diag.Diagnostics { - *m = CheckQueryParametersAttributeModel(sdkutil.KeyValuesIntoMap(from)) - - return nil -} - -func (m *CheckQueryParametersAttributeModel) Render(ctx context.Context, into *[]checkly.KeyValue) diag.Diagnostics { - *into = sdkutil.KeyValuesFromMap(types.Map(*m)) - - return nil -} diff --git a/internal/provider/datasources/attributes/id_attribute.go b/internal/provider/datasources/attributes/id_attribute.go new file mode 100644 index 0000000..17aab7a --- /dev/null +++ b/internal/provider/datasources/attributes/id_attribute.go @@ -0,0 +1,10 @@ +package attributes + +import ( + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +var IDAttributeSchema = schema.StringAttribute{ + Computed: true, + Description: "The ID of this data source.", +} diff --git a/internal/provider/static_ips_data_source.go b/internal/provider/datasources/static_ips_data_source.go similarity index 92% rename from internal/provider/static_ips_data_source.go rename to internal/provider/datasources/static_ips_data_source.go index 4e0efeb..ea6bdf9 100644 --- a/internal/provider/static_ips_data_source.go +++ b/internal/provider/datasources/static_ips_data_source.go @@ -1,4 +1,4 @@ -package provider +package datasources import ( "context" @@ -12,6 +12,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/datasources/attributes" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" ) var ( @@ -51,7 +53,7 @@ func (d *StaticIPsDataSource) Schema( Description: "", // TODO MarkdownDescription: "", // TODO Attributes: map[string]schema.Attribute{ - "id": IDDataSourceAttributeSchema, + "id": attributes.IDAttributeSchema, "addresses": schema.SetAttribute{ ElementType: types.StringType, Computed: true, @@ -79,7 +81,7 @@ func (d *StaticIPsDataSource) Configure( req datasource.ConfigureRequest, resp *datasource.ConfigureResponse, ) { - client, diags := ClientFromProviderData(req.ProviderData) + client, diags := interop.ClientFromProviderData(req.ProviderData) if diags.HasError() { resp.Diagnostics.Append(diags...) return diff --git a/internal/provider/id_attribute.go b/internal/provider/id_attribute.go deleted file mode 100644 index 8a5308e..0000000 --- a/internal/provider/id_attribute.go +++ /dev/null @@ -1,15 +0,0 @@ -package provider - -import ( - "github.com/hashicorp/terraform-plugin-framework/resource/schema" -) - -var IDResourceAttributeSchema = schema.StringAttribute{ - Computed: true, - Description: "The ID of this resource.", -} - -var IDDataSourceAttributeSchema = schema.StringAttribute{ - Computed: true, - Description: "The ID of this data source.", -} diff --git a/internal/provider/interop/data.go b/internal/provider/interop/data.go new file mode 100644 index 0000000..316d84f --- /dev/null +++ b/internal/provider/interop/data.go @@ -0,0 +1,34 @@ +package interop + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + checkly "github.com/checkly/checkly-go-sdk" +) + +func ClientFromProviderData(providerData any) (checkly.Client, diag.Diagnostics) { + if providerData == nil { + return nil, diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Missing Configure Type", + "Expected checkly.Client, got nil. Please report this issue "+ + "to the provider developers.", + ), + } + } + + client, ok := providerData.(checkly.Client) + if !ok { + return nil, diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Unexpected Configure Type", + fmt.Sprintf("Expected checkly.Client, got: %T. Please report "+ + "this issue to the provider developers.", providerData), + ), + } + } + + return client, nil +} diff --git a/internal/provider/resource_model.go b/internal/provider/interop/model.go similarity index 62% rename from internal/provider/resource_model.go rename to internal/provider/interop/model.go index 9491160..a253390 100644 --- a/internal/provider/resource_model.go +++ b/internal/provider/interop/model.go @@ -1,4 +1,4 @@ -package provider +package interop import ( "context" @@ -9,10 +9,10 @@ import ( type RefreshFlags int const ( - ModelCreated RefreshFlags = 1 << iota - ModelUpdated + Created RefreshFlags = 1 << iota + Updated - ModelLoaded RefreshFlags = 0 + Loaded RefreshFlags = 0 ) func (f RefreshFlags) Contains(other RefreshFlags) bool { @@ -20,26 +20,26 @@ func (f RefreshFlags) Contains(other RefreshFlags) bool { } func (f RefreshFlags) Updated() bool { - return f.Contains(ModelUpdated) + return f.Contains(Updated) } func (f RefreshFlags) Created() bool { - return f.Contains(ModelCreated) + return f.Contains(Created) } -type Render[SDKModel any] interface { - Render(ctx context.Context, into *SDKModel) diag.Diagnostics +type Render[T any] interface { + Render(ctx context.Context, into *T) diag.Diagnostics } func RenderMany[ - SDKModel any, + T any, R any, RPtr interface { - Render[SDKModel] + Render[T] *R }, Sources ~[]R, - Results ~[]SDKModel, + Results ~[]T, ]( ctx context.Context, sources Sources, @@ -48,7 +48,7 @@ func RenderMany[ diags diag.Diagnostics, ) { for _, source := range sources { - var result SDKModel + var result T diags.Append(RPtr(&source).Render(ctx, &result)...) if diags.HasError() { @@ -61,18 +61,18 @@ func RenderMany[ return diags } -type Refresh[SDKModel any] interface { - Refresh(ctx context.Context, from *SDKModel, flags RefreshFlags) diag.Diagnostics +type Refresh[T any] interface { + Refresh(ctx context.Context, from *T, flags RefreshFlags) diag.Diagnostics } func RefreshMany[ - SDKModel any, + T any, R any, RPtr interface { - Refresh[SDKModel] + Refresh[T] *R }, - Sources ~[]SDKModel, + Sources ~[]T, Results ~[]R, ]( ctx context.Context, @@ -96,7 +96,7 @@ func RefreshMany[ return diags } -type ResourceModel[SDKModel any] interface { - Render[SDKModel] - Refresh[SDKModel] +type Model[T any] interface { + Render[T] + Refresh[T] } diff --git a/internal/provider/util.go b/internal/provider/interop/set.go similarity index 97% rename from internal/provider/util.go rename to internal/provider/interop/set.go index e221f3a..c565817 100644 --- a/internal/provider/util.go +++ b/internal/provider/interop/set.go @@ -1,4 +1,4 @@ -package provider +package interop import ( "github.com/hashicorp/terraform-plugin-framework/attr" diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 6715070..d294588 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -7,7 +7,6 @@ import ( "os" "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/provider" @@ -17,6 +16,8 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/datasources" + "github.com/checkly/terraform-provider-checkly/internal/provider/resources" ) const ( @@ -201,50 +202,25 @@ func (p *ChecklyProvider) Configure( resp.ResourceData = client } -func ClientFromProviderData(providerData any) (checkly.Client, diag.Diagnostics) { - if providerData == nil { - return nil, diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Missing Configure Type", - "Expected checkly.Client, got nil. Please report this issue "+ - "to the provider developers.", - ), - } - } - - client, ok := providerData.(checkly.Client) - if !ok { - return nil, diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Unexpected Configure Type", - fmt.Sprintf("Expected checkly.Client, got: %T. Please report "+ - "this issue to the provider developers.", providerData), - ), - } - } - - return client, nil -} - func (p *ChecklyProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ - NewAlertChannelResource, - NewCheckGroupResource, - NewCheckResource, - NewDashboardResource, - NewEnvironmentVariableResource, - NewHeartbeatResource, - NewMaintenanceWindowsResource, - NewPrivateLocationResource, - NewSnippetResource, - NewTriggerCheckResource, - NewTriggerGroupResource, + resources.NewAlertChannelResource, + resources.NewCheckGroupResource, + resources.NewCheckResource, + resources.NewDashboardResource, + resources.NewEnvironmentVariableResource, + resources.NewHeartbeatResource, + resources.NewMaintenanceWindowsResource, + resources.NewPrivateLocationResource, + resources.NewSnippetResource, + resources.NewTriggerCheckResource, + resources.NewTriggerGroupResource, } } func (p *ChecklyProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ - NewStaticIPsDataSource, + datasources.NewStaticIPsDataSource, } } diff --git a/internal/provider/alert_channel_resource.go b/internal/provider/resources/alert_channel_resource.go similarity index 91% rename from internal/provider/alert_channel_resource.go rename to internal/provider/resources/alert_channel_resource.go index 3e17011..c0f12f0 100644 --- a/internal/provider/alert_channel_resource.go +++ b/internal/provider/resources/alert_channel_resource.go @@ -1,4 +1,4 @@ -package provider +package resources import ( "context" @@ -18,6 +18,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/provider/resources/attributes" "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) @@ -55,8 +57,8 @@ func (r *AlertChannelResource) Schema( resp.Schema = schema.Schema{ Description: "Allows you to define alerting channels for the checks and groups in your account.", Attributes: map[string]schema.Attribute{ - "id": IDResourceAttributeSchema, - "last_updated": LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, + "last_updated": attributes.LastUpdatedAttributeSchema, "email": schema.SingleNestedAttribute{ Optional: true, Attributes: map[string]schema.Attribute{ @@ -242,7 +244,7 @@ func (r *AlertChannelResource) Configure( req resource.ConfigureRequest, resp *resource.ConfigureResponse, ) { - client, diags := ClientFromProviderData(req.ProviderData) + client, diags := interop.ClientFromProviderData(req.ProviderData) if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -287,7 +289,7 @@ func (r *AlertChannelResource) Create( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Created)...) if resp.Diagnostics.HasError() { return } @@ -360,7 +362,7 @@ func (r *AlertChannelResource) Read( return } - resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, interop.Loaded)...) if resp.Diagnostics.HasError() { return } @@ -409,7 +411,7 @@ func (r *AlertChannelResource) Update( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Updated)...) if resp.Diagnostics.HasError() { return } @@ -426,14 +428,14 @@ var AlertChannelID = sdkutil.Identifier{ } var ( - _ ResourceModel[checkly.AlertChannel] = (*AlertChannelResourceModel)(nil) - _ ResourceModel[checkly.AlertChannelEmail] = (*EmailAttributeModel)(nil) - _ ResourceModel[checkly.AlertChannelSlack] = (*SlackAttributeModel)(nil) - _ ResourceModel[checkly.AlertChannelSMS] = (*SMSAttributeModel)(nil) - _ ResourceModel[checkly.AlertChannelCall] = (*CallAttributeModel)(nil) - _ ResourceModel[checkly.AlertChannelWebhook] = (*WebhookAttributeModel)(nil) - _ ResourceModel[checkly.AlertChannelOpsgenie] = (*OpsgenieAttributeModel)(nil) - _ ResourceModel[checkly.AlertChannelPagerduty] = (*PagerdutyAttributeModel)(nil) + _ interop.Model[checkly.AlertChannel] = (*AlertChannelResourceModel)(nil) + _ interop.Model[checkly.AlertChannelEmail] = (*EmailAttributeModel)(nil) + _ interop.Model[checkly.AlertChannelSlack] = (*SlackAttributeModel)(nil) + _ interop.Model[checkly.AlertChannelSMS] = (*SMSAttributeModel)(nil) + _ interop.Model[checkly.AlertChannelCall] = (*CallAttributeModel)(nil) + _ interop.Model[checkly.AlertChannelWebhook] = (*WebhookAttributeModel)(nil) + _ interop.Model[checkly.AlertChannelOpsgenie] = (*OpsgenieAttributeModel)(nil) + _ interop.Model[checkly.AlertChannelPagerduty] = (*PagerdutyAttributeModel)(nil) ) type AlertChannelResourceModel struct { @@ -453,7 +455,7 @@ type AlertChannelResourceModel struct { SSLExpiryThreshold types.Int32 `tfsdk:"ssl_expiry_threshold"` } -func (m *AlertChannelResourceModel) Refresh(ctx context.Context, from *checkly.AlertChannel, flags RefreshFlags) diag.Diagnostics { +func (m *AlertChannelResourceModel) Refresh(ctx context.Context, from *checkly.AlertChannel, flags interop.RefreshFlags) diag.Diagnostics { var diags diag.Diagnostics if flags.Created() { @@ -461,7 +463,7 @@ func (m *AlertChannelResourceModel) Refresh(ctx context.Context, from *checkly.A } if flags.Created() || flags.Updated() { - m.LastUpdated = LastUpdatedNow() + m.LastUpdated = attributes.LastUpdatedNow() } m.Email = nil @@ -565,7 +567,7 @@ func (m *AlertChannelResourceModel) Render(ctx context.Context, into *checkly.Al diags.Append(m.Pagerduty.Render(ctx, into.Pagerduty)...) default: // TODO: Use diags instead - panic("bug: impossible AlertChannelResourceModel state: no type set") + panic("bug: impossible AlertChannelinterop.Model state: no type set") } into.SendRecovery = m.SendRecovery.ValueBoolPointer() @@ -585,7 +587,7 @@ type EmailAttributeModel struct { Address types.String `tfsdk:"address"` } -func (m *EmailAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelEmail, flags RefreshFlags) diag.Diagnostics { +func (m *EmailAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelEmail, flags interop.RefreshFlags) diag.Diagnostics { m.Address = types.StringValue(from.Address) return nil @@ -602,7 +604,7 @@ type SlackAttributeModel struct { Channel types.String `tfsdk:"channel"` } -func (m *SlackAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelSlack, flags RefreshFlags) diag.Diagnostics { +func (m *SlackAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelSlack, flags interop.RefreshFlags) diag.Diagnostics { m.URL = types.StringValue(from.WebhookURL) m.Channel = types.StringValue(from.Channel) @@ -621,7 +623,7 @@ type SMSAttributeModel struct { Number types.String `tfsdk:"number"` } -func (m *SMSAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelSMS, flags RefreshFlags) diag.Diagnostics { +func (m *SMSAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelSMS, flags interop.RefreshFlags) diag.Diagnostics { m.Name = types.StringValue(from.Name) m.Number = types.StringValue(from.Number) @@ -640,7 +642,7 @@ type CallAttributeModel struct { Number types.String `tfsdk:"number"` } -func (m *CallAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelCall, flags RefreshFlags) diag.Diagnostics { +func (m *CallAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelCall, flags interop.RefreshFlags) diag.Diagnostics { m.Name = types.StringValue(from.Name) m.Number = types.StringValue(from.Number) @@ -665,7 +667,7 @@ type WebhookAttributeModel struct { WebhookType types.String `tfsdk:"webhook_type"` } -func (m *WebhookAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelWebhook, flags RefreshFlags) diag.Diagnostics { +func (m *WebhookAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelWebhook, flags interop.RefreshFlags) diag.Diagnostics { m.Name = types.StringValue(from.Name) m.Method = types.StringValue(from.Method) m.Headers = sdkutil.KeyValuesIntoMap(&from.Headers) @@ -698,7 +700,7 @@ type OpsgenieAttributeModel struct { Priority types.String `tfsdk:"priority"` } -func (m *OpsgenieAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelOpsgenie, flags RefreshFlags) diag.Diagnostics { +func (m *OpsgenieAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelOpsgenie, flags interop.RefreshFlags) diag.Diagnostics { m.Name = types.StringValue(from.Name) m.APIKey = types.StringValue(from.APIKey) m.Region = types.StringValue(from.Region) @@ -722,7 +724,7 @@ type PagerdutyAttributeModel struct { Account types.String `tfsdk:"account"` } -func (m *PagerdutyAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelPagerduty, flags RefreshFlags) diag.Diagnostics { +func (m *PagerdutyAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelPagerduty, flags interop.RefreshFlags) diag.Diagnostics { m.ServiceKey = types.StringValue(from.ServiceKey) m.ServiceName = types.StringValue(from.ServiceName) m.Account = types.StringValue(from.Account) diff --git a/internal/provider/check_alert_channel_subscription_attribute.go b/internal/provider/resources/attributes/alert_channel_subscription_attribute.go similarity index 62% rename from internal/provider/check_alert_channel_subscription_attribute.go rename to internal/provider/resources/attributes/alert_channel_subscription_attribute.go index 980bdb9..120c94b 100644 --- a/internal/provider/check_alert_channel_subscription_attribute.go +++ b/internal/provider/resources/attributes/alert_channel_subscription_attribute.go @@ -1,4 +1,4 @@ -package provider +package attributes import ( "context" @@ -8,13 +8,14 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" ) var ( - _ ResourceModel[checkly.AlertChannelSubscription] = (*CheckAlertChannelSubscriptionAttributeModel)(nil) + _ interop.Model[checkly.AlertChannelSubscription] = (*AlertChannelSubscriptionAttributeModel)(nil) ) -var CheckAlertChannelSubscriptionAttributeSchema = schema.ListNestedAttribute{ +var AlertChannelSubscriptionAttributeSchema = schema.ListNestedAttribute{ Description: "An array of channel IDs and whether they're activated or " + "not. If you don't set at least one alert subscription for your " + "check, we won't be able to alert you in case something goes wrong " + @@ -32,19 +33,19 @@ var CheckAlertChannelSubscriptionAttributeSchema = schema.ListNestedAttribute{ }, } -type CheckAlertChannelSubscriptionAttributeModel struct { +type AlertChannelSubscriptionAttributeModel struct { ChannelID types.Int64 `tfsdk:"channel_id"` Activated types.Bool `tfsdk:"activated"` } -func (m *CheckAlertChannelSubscriptionAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelSubscription, flags RefreshFlags) diag.Diagnostics { +func (m *AlertChannelSubscriptionAttributeModel) Refresh(ctx context.Context, from *checkly.AlertChannelSubscription, flags interop.RefreshFlags) diag.Diagnostics { m.ChannelID = types.Int64Value(from.ChannelID) m.Activated = types.BoolValue(from.Activated) return nil } -func (m *CheckAlertChannelSubscriptionAttributeModel) Render(ctx context.Context, into *checkly.AlertChannelSubscription) diag.Diagnostics { +func (m *AlertChannelSubscriptionAttributeModel) Render(ctx context.Context, into *checkly.AlertChannelSubscription) diag.Diagnostics { into.ChannelID = m.ChannelID.ValueInt64() into.Activated = m.Activated.ValueBool() diff --git a/internal/provider/check_alert_settings_attribute.go b/internal/provider/resources/attributes/alert_settings_attribute.go similarity index 70% rename from internal/provider/check_alert_settings_attribute.go rename to internal/provider/resources/attributes/alert_settings_attribute.go index a7fc6ab..d672200 100644 --- a/internal/provider/check_alert_settings_attribute.go +++ b/internal/provider/resources/attributes/alert_settings_attribute.go @@ -1,4 +1,4 @@ -package provider +package attributes import ( "context" @@ -14,18 +14,19 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" ) var ( - _ ResourceModel[checkly.AlertSettings] = (*CheckAlertSettingsAttributeModel)(nil) - _ ResourceModel[checkly.RunBasedEscalation] = (*CheckRunBasedEscalationAttributeModel)(nil) - _ ResourceModel[checkly.TimeBasedEscalation] = (*CheckTimeBasedEscalationAttributeModel)(nil) - _ ResourceModel[checkly.Reminders] = (*CheckRemindersAttributeModel)(nil) - _ ResourceModel[checkly.ParallelRunFailureThreshold] = (*CheckParallelRunFailureThresholdAttributeModel)(nil) - _ ResourceModel[checkly.SSLCertificates] = (*CheckSSLCertificatesAttributeModel)(nil) + _ interop.Model[checkly.AlertSettings] = (*AlertSettingsAttributeModel)(nil) + _ interop.Model[checkly.RunBasedEscalation] = (*RunBasedEscalationAttributeModel)(nil) + _ interop.Model[checkly.TimeBasedEscalation] = (*TimeBasedEscalationAttributeModel)(nil) + _ interop.Model[checkly.Reminders] = (*RemindersAttributeModel)(nil) + _ interop.Model[checkly.ParallelRunFailureThreshold] = (*ParallelRunFailureThresholdAttributeModel)(nil) + _ interop.Model[checkly.SSLCertificates] = (*SSLCertificatesAttributeModel)(nil) ) -var CheckAlertSettingsAttributeSchema = schema.SingleNestedAttribute{ +var AlertSettingsAttributeSchema = schema.SingleNestedAttribute{ Optional: true, Computed: true, Attributes: map[string]schema.Attribute{ @@ -136,16 +137,16 @@ var CheckAlertSettingsAttributeSchema = schema.SingleNestedAttribute{ }, } -type CheckAlertSettingsAttributeModel struct { - EscalationType types.String `tfsdk:"escalation_type"` - RunBasedEscalations CheckRunBasedEscalationAttributeModel `tfsdk:"run_based_escalation"` - TimeBasedEscalations CheckTimeBasedEscalationAttributeModel `tfsdk:"time_based_escalation"` - Reminders CheckRemindersAttributeModel `tfsdk:"reminders"` - ParallelRunFailureThreshold CheckParallelRunFailureThresholdAttributeModel `tfsdk:"parallel_run_failure_threshold"` - SSLCertificates CheckSSLCertificatesAttributeModel `tfsdk:"ssl_certificates"` +type AlertSettingsAttributeModel struct { + EscalationType types.String `tfsdk:"escalation_type"` + RunBasedEscalations RunBasedEscalationAttributeModel `tfsdk:"run_based_escalation"` + TimeBasedEscalations TimeBasedEscalationAttributeModel `tfsdk:"time_based_escalation"` + Reminders RemindersAttributeModel `tfsdk:"reminders"` + ParallelRunFailureThreshold ParallelRunFailureThresholdAttributeModel `tfsdk:"parallel_run_failure_threshold"` + SSLCertificates SSLCertificatesAttributeModel `tfsdk:"ssl_certificates"` } -func (m *CheckAlertSettingsAttributeModel) Refresh(ctx context.Context, from *checkly.AlertSettings, flags RefreshFlags) diag.Diagnostics { +func (m *AlertSettingsAttributeModel) Refresh(ctx context.Context, from *checkly.AlertSettings, flags interop.RefreshFlags) diag.Diagnostics { var diags diag.Diagnostics m.EscalationType = types.StringValue(from.EscalationType) @@ -166,7 +167,7 @@ func (m *CheckAlertSettingsAttributeModel) Refresh(ctx context.Context, from *ch return diags } -func (m *CheckAlertSettingsAttributeModel) Render(ctx context.Context, into *checkly.AlertSettings) diag.Diagnostics { +func (m *AlertSettingsAttributeModel) Render(ctx context.Context, into *checkly.AlertSettings) diag.Diagnostics { var diags diag.Diagnostics switch m.EscalationType.ValueString() { @@ -187,89 +188,89 @@ func (m *CheckAlertSettingsAttributeModel) Render(ctx context.Context, into *che return diags } -type CheckRunBasedEscalationAttributeModel struct { +type RunBasedEscalationAttributeModel struct { FailedRunThreshold types.Int32 `tfsdk:"failed_run_threshold"` } -func (m *CheckRunBasedEscalationAttributeModel) Refresh(ctx context.Context, from *checkly.RunBasedEscalation, flags RefreshFlags) diag.Diagnostics { +func (m *RunBasedEscalationAttributeModel) Refresh(ctx context.Context, from *checkly.RunBasedEscalation, flags interop.RefreshFlags) diag.Diagnostics { m.FailedRunThreshold = types.Int32Value(int32(from.FailedRunThreshold)) return nil } -func (m *CheckRunBasedEscalationAttributeModel) Render(ctx context.Context, into *checkly.RunBasedEscalation) diag.Diagnostics { +func (m *RunBasedEscalationAttributeModel) Render(ctx context.Context, into *checkly.RunBasedEscalation) diag.Diagnostics { into.FailedRunThreshold = int(m.FailedRunThreshold.ValueInt32()) return nil } -type CheckTimeBasedEscalationAttributeModel struct { +type TimeBasedEscalationAttributeModel struct { MinutesFailingThreshold types.Int32 `tfsdk:"minutes_failing_threshold"` } -func (m *CheckTimeBasedEscalationAttributeModel) Refresh(ctx context.Context, from *checkly.TimeBasedEscalation, flags RefreshFlags) diag.Diagnostics { +func (m *TimeBasedEscalationAttributeModel) Refresh(ctx context.Context, from *checkly.TimeBasedEscalation, flags interop.RefreshFlags) diag.Diagnostics { m.MinutesFailingThreshold = types.Int32Value(int32(from.MinutesFailingThreshold)) return nil } -func (m *CheckTimeBasedEscalationAttributeModel) Render(ctx context.Context, into *checkly.TimeBasedEscalation) diag.Diagnostics { +func (m *TimeBasedEscalationAttributeModel) Render(ctx context.Context, into *checkly.TimeBasedEscalation) diag.Diagnostics { into.MinutesFailingThreshold = int(m.MinutesFailingThreshold.ValueInt32()) return nil } -type CheckRemindersAttributeModel struct { +type RemindersAttributeModel struct { Amount types.Int32 `tfsdk:"amount"` Interval types.Int32 `tfsdk:"interval"` } -func (m *CheckRemindersAttributeModel) Refresh(ctx context.Context, from *checkly.Reminders, flags RefreshFlags) diag.Diagnostics { +func (m *RemindersAttributeModel) Refresh(ctx context.Context, from *checkly.Reminders, flags interop.RefreshFlags) diag.Diagnostics { m.Amount = types.Int32Value(int32(from.Amount)) m.Interval = types.Int32Value(int32(from.Interval)) return nil } -func (m *CheckRemindersAttributeModel) Render(ctx context.Context, into *checkly.Reminders) diag.Diagnostics { +func (m *RemindersAttributeModel) Render(ctx context.Context, into *checkly.Reminders) diag.Diagnostics { into.Amount = int(m.Amount.ValueInt32()) into.Interval = int(m.Interval.ValueInt32()) return nil } -type CheckParallelRunFailureThresholdAttributeModel struct { +type ParallelRunFailureThresholdAttributeModel struct { Enabled types.Bool `tfsdk:"enabled"` Percentage types.Int32 `tfsdk:"percentage"` } -func (m *CheckParallelRunFailureThresholdAttributeModel) Refresh(ctx context.Context, from *checkly.ParallelRunFailureThreshold, flags RefreshFlags) diag.Diagnostics { +func (m *ParallelRunFailureThresholdAttributeModel) Refresh(ctx context.Context, from *checkly.ParallelRunFailureThreshold, flags interop.RefreshFlags) diag.Diagnostics { m.Enabled = types.BoolValue(from.Enabled) m.Percentage = types.Int32Value(int32(from.Percentage)) return nil } -func (m *CheckParallelRunFailureThresholdAttributeModel) Render(ctx context.Context, into *checkly.ParallelRunFailureThreshold) diag.Diagnostics { +func (m *ParallelRunFailureThresholdAttributeModel) Render(ctx context.Context, into *checkly.ParallelRunFailureThreshold) diag.Diagnostics { into.Enabled = m.Enabled.ValueBool() into.Percentage = int(m.Percentage.ValueInt32()) return nil } -type CheckSSLCertificatesAttributeModel struct { +type SSLCertificatesAttributeModel struct { Enabled types.Bool `tfsdk:"enabled"` AlertThreshold types.Int32 `tfsdk:"alert_threshold"` } -func (m *CheckSSLCertificatesAttributeModel) Refresh(ctx context.Context, from *checkly.SSLCertificates, flags RefreshFlags) diag.Diagnostics { +func (m *SSLCertificatesAttributeModel) Refresh(ctx context.Context, from *checkly.SSLCertificates, flags interop.RefreshFlags) diag.Diagnostics { m.Enabled = types.BoolValue(from.Enabled) m.AlertThreshold = types.Int32Value(int32(from.AlertThreshold)) return nil } -func (m *CheckSSLCertificatesAttributeModel) Render(ctx context.Context, into *checkly.SSLCertificates) diag.Diagnostics { +func (m *SSLCertificatesAttributeModel) Render(ctx context.Context, into *checkly.SSLCertificates) diag.Diagnostics { into.Enabled = m.Enabled.ValueBool() into.AlertThreshold = int(m.AlertThreshold.ValueInt32()) diff --git a/internal/provider/resources/attributes/api_check_defaults_attribute.go b/internal/provider/resources/attributes/api_check_defaults_attribute.go new file mode 100644 index 0000000..72950bc --- /dev/null +++ b/internal/provider/resources/attributes/api_check_defaults_attribute.go @@ -0,0 +1,68 @@ +package attributes + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" +) + +var ( + _ interop.Model[checkly.APICheckDefaults] = (*APICheckDefaultsAttributeModel)(nil) +) + +var APICheckDefaultsAttributeSchema = schema.SingleNestedAttribute{ + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "url": schema.StringAttribute{ + Required: true, + Description: "The base url for this group which you can reference with the `GROUP_BASE_URL` variable in all group checks.", + }, + "headers": HeadersAttributeSchema, + "query_parameters": QueryParametersAttributeSchema, + "assertion": AssertionAttributeSchema, + "basic_auth": BasicAuthAttributeSchema, + }, +} + +type APICheckDefaultsAttributeModel struct { + URL types.String `tfsdk:"url"` + Headers types.Map `tfsdk:"headers"` + QueryParameters types.Map `tfsdk:"query_parameters"` + Assertions []AssertionAttributeModel `tfsdk:"assertion"` + BasicAuth BasicAuthAttributeModel `tfsdk:"basic_auth"` +} + +func (m *APICheckDefaultsAttributeModel) Refresh(ctx context.Context, from *checkly.APICheckDefaults, flags interop.RefreshFlags) diag.Diagnostics { + var diags diag.Diagnostics + + m.URL = types.StringValue(from.BaseURL) + m.Headers = sdkutil.KeyValuesIntoMap(&from.Headers) + m.QueryParameters = sdkutil.KeyValuesIntoMap(&from.QueryParameters) + + diags.Append(interop.RefreshMany(ctx, from.Assertions, m.Assertions, flags)...) + + diags.Append(m.BasicAuth.Refresh(ctx, &from.BasicAuth, flags)...) + + return diags +} + +func (m *APICheckDefaultsAttributeModel) Render(ctx context.Context, into *checkly.APICheckDefaults) diag.Diagnostics { + var diags diag.Diagnostics + + into.BaseURL = m.URL.ValueString() + into.Headers = sdkutil.KeyValuesFromMap(m.Headers) + into.QueryParameters = sdkutil.KeyValuesFromMap(m.QueryParameters) + + diags.Append(interop.RenderMany(ctx, m.Assertions, into.Assertions)...) + + diags.Append(m.BasicAuth.Render(ctx, &into.BasicAuth)...) + + return diags +} diff --git a/internal/provider/check_assertion_attribute.go b/internal/provider/resources/attributes/assertion_attribute.go similarity index 82% rename from internal/provider/check_assertion_attribute.go rename to internal/provider/resources/attributes/assertion_attribute.go index 250ff60..6e1406d 100644 --- a/internal/provider/check_assertion_attribute.go +++ b/internal/provider/resources/attributes/assertion_attribute.go @@ -1,4 +1,4 @@ -package provider +package attributes import ( "context" @@ -10,13 +10,14 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" ) var ( - _ ResourceModel[checkly.Assertion] = (*CheckAssertionAttributeModel)(nil) + _ interop.Model[checkly.Assertion] = (*AssertionAttributeModel)(nil) ) -var CheckAssertionAttributeSchema = schema.ListNestedAttribute{ +var AssertionAttributeSchema = schema.ListNestedAttribute{ Optional: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ @@ -65,14 +66,14 @@ var CheckAssertionAttributeSchema = schema.ListNestedAttribute{ }, } -type CheckAssertionAttributeModel struct { +type AssertionAttributeModel struct { Source types.String `tfsdk:"source"` Property types.String `tfsdk:"property"` Comparison types.String `tfsdk:"comparison"` Target types.String `tfsdk:"target"` } -func (m *CheckAssertionAttributeModel) Refresh(ctx context.Context, from *checkly.Assertion, flags RefreshFlags) diag.Diagnostics { +func (m *AssertionAttributeModel) Refresh(ctx context.Context, from *checkly.Assertion, flags interop.RefreshFlags) diag.Diagnostics { m.Source = types.StringValue(from.Source) m.Property = types.StringValue(from.Property) m.Comparison = types.StringValue(from.Comparison) @@ -81,7 +82,7 @@ func (m *CheckAssertionAttributeModel) Refresh(ctx context.Context, from *checkl return nil } -func (m *CheckAssertionAttributeModel) Render(ctx context.Context, into *checkly.Assertion) diag.Diagnostics { +func (m *AssertionAttributeModel) Render(ctx context.Context, into *checkly.Assertion) diag.Diagnostics { into.Source = m.Source.ValueString() into.Property = m.Property.ValueString() into.Comparison = m.Comparison.ValueString() diff --git a/internal/provider/check_basic_auth_attribute.go b/internal/provider/resources/attributes/basic_auth_attribute.go similarity index 62% rename from internal/provider/check_basic_auth_attribute.go rename to internal/provider/resources/attributes/basic_auth_attribute.go index 3c52434..9bee705 100644 --- a/internal/provider/check_basic_auth_attribute.go +++ b/internal/provider/resources/attributes/basic_auth_attribute.go @@ -1,19 +1,20 @@ -package provider +package attributes import ( "context" "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" ) var ( - _ ResourceModel[checkly.BasicAuth] = (*CheckBasicAuthAttributeModel)(nil) + _ interop.Model[checkly.BasicAuth] = (*BasicAuthAttributeModel)(nil) ) -var CheckBasicAuthAttributeSchema = schema.SingleNestedAttribute{ +var BasicAuthAttributeSchema = schema.SingleNestedAttribute{ Description: "Credentials for Basic HTTP authentication.", Optional: true, Computed: true, @@ -28,19 +29,19 @@ var CheckBasicAuthAttributeSchema = schema.SingleNestedAttribute{ }, } -type CheckBasicAuthAttributeModel struct { +type BasicAuthAttributeModel struct { Username types.String `tfsdk:"username"` Password types.String `tfsdk:"password"` } -func (m *CheckBasicAuthAttributeModel) Refresh(ctx context.Context, from *checkly.BasicAuth, flags RefreshFlags) diag.Diagnostics { +func (m *BasicAuthAttributeModel) Refresh(ctx context.Context, from *checkly.BasicAuth, flags interop.RefreshFlags) diag.Diagnostics { m.Username = types.StringValue(from.Username) m.Password = types.StringValue(from.Password) return nil } -func (m *CheckBasicAuthAttributeModel) Render(ctx context.Context, into *checkly.BasicAuth) diag.Diagnostics { +func (m *BasicAuthAttributeModel) Render(ctx context.Context, into *checkly.BasicAuth) diag.Diagnostics { into.Username = m.Username.ValueString() into.Password = m.Password.ValueString() diff --git a/internal/provider/check_environment_variable_attribute.go b/internal/provider/resources/attributes/environment_variable_attribute.go similarity index 83% rename from internal/provider/check_environment_variable_attribute.go rename to internal/provider/resources/attributes/environment_variable_attribute.go index be32ed5..3e25b5f 100644 --- a/internal/provider/check_environment_variable_attribute.go +++ b/internal/provider/resources/attributes/environment_variable_attribute.go @@ -1,4 +1,4 @@ -package provider +package attributes import ( "context" @@ -12,13 +12,14 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" ) var ( - _ ResourceModel[checkly.EnvironmentVariable] = (*CheckEnvironmentVariableAttributeModel)(nil) + _ interop.Model[checkly.EnvironmentVariable] = (*EnvironmentVariableAttributeModel)(nil) ) -var CheckEnvironmentVariableAttributeSchema = schema.ListNestedAttribute{ +var EnvironmentVariableAttributeSchema = schema.ListNestedAttribute{ Optional: true, Description: "Introduce additional environment variables to the check " + "execution environment. Only relevant for browser checks. Prefer " + @@ -72,14 +73,14 @@ var CheckEnvironmentVariableAttributeSchema = schema.ListNestedAttribute{ }, } -type CheckEnvironmentVariableAttributeModel struct { +type EnvironmentVariableAttributeModel struct { Key types.String `tfsdk:"key"` Value types.String `tfsdk:"value"` Locked types.Bool `tfsdk:"locked"` Secret types.Bool `tfsdk:"secret"` } -func (m *CheckEnvironmentVariableAttributeModel) Refresh(ctx context.Context, from *checkly.EnvironmentVariable, flags RefreshFlags) diag.Diagnostics { +func (m *EnvironmentVariableAttributeModel) Refresh(ctx context.Context, from *checkly.EnvironmentVariable, flags interop.RefreshFlags) diag.Diagnostics { m.Key = types.StringValue(from.Key) m.Value = types.StringValue(from.Value) m.Locked = types.BoolValue(from.Locked) @@ -88,7 +89,7 @@ func (m *CheckEnvironmentVariableAttributeModel) Refresh(ctx context.Context, fr return nil } -func (m *CheckEnvironmentVariableAttributeModel) Render(ctx context.Context, into *checkly.EnvironmentVariable) diag.Diagnostics { +func (m *EnvironmentVariableAttributeModel) Render(ctx context.Context, into *checkly.EnvironmentVariable) diag.Diagnostics { into.Key = m.Key.ValueString() into.Value = m.Value.ValueString() into.Locked = m.Locked.ValueBool() diff --git a/internal/provider/resources/attributes/headers_attribute.go b/internal/provider/resources/attributes/headers_attribute.go new file mode 100644 index 0000000..cc4feec --- /dev/null +++ b/internal/provider/resources/attributes/headers_attribute.go @@ -0,0 +1,37 @@ +package attributes + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" +) + +var ( + _ interop.Model[[]checkly.KeyValue] = (*HeadersAttributeModel)(nil) +) + +var HeadersAttributeSchema = schema.MapAttribute{ + ElementType: types.StringType, + Optional: true, + Computed: true, // TODO: Really? +} + +type HeadersAttributeModel types.Map + +func (m *HeadersAttributeModel) Refresh(ctx context.Context, from *[]checkly.KeyValue, flags interop.RefreshFlags) diag.Diagnostics { + *m = HeadersAttributeModel(sdkutil.KeyValuesIntoMap(from)) + + return nil +} + +func (m *HeadersAttributeModel) Render(ctx context.Context, into *[]checkly.KeyValue) diag.Diagnostics { + *into = sdkutil.KeyValuesFromMap(types.Map(*m)) + + return nil +} diff --git a/internal/provider/resources/attributes/id_attribute.go b/internal/provider/resources/attributes/id_attribute.go new file mode 100644 index 0000000..f35ef6e --- /dev/null +++ b/internal/provider/resources/attributes/id_attribute.go @@ -0,0 +1,10 @@ +package attributes + +import ( + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +var IDAttributeSchema = schema.StringAttribute{ + Computed: true, + Description: "The ID of this resource.", +} diff --git a/internal/provider/last_updated_attribute.go b/internal/provider/resources/attributes/last_updated_attribute.go similarity index 95% rename from internal/provider/last_updated_attribute.go rename to internal/provider/resources/attributes/last_updated_attribute.go index d711bdf..0bcf4dd 100644 --- a/internal/provider/last_updated_attribute.go +++ b/internal/provider/resources/attributes/last_updated_attribute.go @@ -1,4 +1,4 @@ -package provider +package attributes import ( "time" diff --git a/internal/provider/resources/attributes/locations_attribute.go b/internal/provider/resources/attributes/locations_attribute.go new file mode 100644 index 0000000..821e983 --- /dev/null +++ b/internal/provider/resources/attributes/locations_attribute.go @@ -0,0 +1,34 @@ +package attributes + +import ( + "context" + + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ interop.Model[[]string] = (*LocationsAttributeModel)(nil) +) + +var LocationsAttributeSchema = schema.SetAttribute{ + Description: "An array of one or more data center locations where to run the checks.", + ElementType: types.StringType, + Optional: true, +} + +type LocationsAttributeModel types.Set + +func (m *LocationsAttributeModel) Refresh(ctx context.Context, from *[]string, flags interop.RefreshFlags) diag.Diagnostics { + *m = LocationsAttributeModel(interop.IntoUntypedStringSet(from)) + + return nil +} + +func (m *LocationsAttributeModel) Render(ctx context.Context, into *[]string) diag.Diagnostics { + *into = interop.FromUntypedStringSet(types.Set(*m)) + + return nil +} diff --git a/internal/provider/resources/attributes/private_locations_attribute.go b/internal/provider/resources/attributes/private_locations_attribute.go new file mode 100644 index 0000000..2b8e50b --- /dev/null +++ b/internal/provider/resources/attributes/private_locations_attribute.go @@ -0,0 +1,34 @@ +package attributes + +import ( + "context" + + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ interop.Model[[]string] = (*PrivateLocationsAttributeModel)(nil) +) + +var PrivateLocationsAttributeSchema = schema.SetAttribute{ + Description: "An array of one or more private locations slugs.", + ElementType: types.StringType, + Optional: true, +} + +type PrivateLocationsAttributeModel types.Set + +func (m *PrivateLocationsAttributeModel) Refresh(ctx context.Context, from *[]string, flags interop.RefreshFlags) diag.Diagnostics { + *m = PrivateLocationsAttributeModel(interop.IntoUntypedStringSet(from)) + + return nil +} + +func (m *PrivateLocationsAttributeModel) Render(ctx context.Context, into *[]string) diag.Diagnostics { + *into = interop.FromUntypedStringSet(types.Set(*m)) + + return nil +} diff --git a/internal/provider/resources/attributes/query_parameters_attribute.go b/internal/provider/resources/attributes/query_parameters_attribute.go new file mode 100644 index 0000000..a8812e2 --- /dev/null +++ b/internal/provider/resources/attributes/query_parameters_attribute.go @@ -0,0 +1,37 @@ +package attributes + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/sdkutil" +) + +var ( + _ interop.Model[[]checkly.KeyValue] = (*QueryParametersAttributeModel)(nil) +) + +var QueryParametersAttributeSchema = schema.MapAttribute{ + ElementType: types.StringType, + Optional: true, + Computed: true, // TODO: Really? +} + +type QueryParametersAttributeModel types.Map + +func (m *QueryParametersAttributeModel) Refresh(ctx context.Context, from *[]checkly.KeyValue, flags interop.RefreshFlags) diag.Diagnostics { + *m = QueryParametersAttributeModel(sdkutil.KeyValuesIntoMap(from)) + + return nil +} + +func (m *QueryParametersAttributeModel) Render(ctx context.Context, into *[]checkly.KeyValue) diag.Diagnostics { + *into = sdkutil.KeyValuesFromMap(types.Map(*m)) + + return nil +} diff --git a/internal/provider/check_request_attribute.go b/internal/provider/resources/attributes/request_attribute.go similarity index 68% rename from internal/provider/check_request_attribute.go rename to internal/provider/resources/attributes/request_attribute.go index 2c60483..8d35460 100644 --- a/internal/provider/check_request_attribute.go +++ b/internal/provider/resources/attributes/request_attribute.go @@ -1,4 +1,4 @@ -package provider +package attributes import ( "context" @@ -11,14 +11,15 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) var ( - _ ResourceModel[checkly.Request] = (*CheckRequestAttributeModel)(nil) + _ interop.Model[checkly.Request] = (*RequestAttributeModel)(nil) ) -var CheckRequestAttributeSchema = schema.SingleNestedAttribute{ +var RequestAttributeSchema = schema.SingleNestedAttribute{ Optional: true, Attributes: map[string]schema.Attribute{ "method": schema.StringAttribute{ @@ -46,8 +47,8 @@ var CheckRequestAttributeSchema = schema.SingleNestedAttribute{ "skip_ssl": schema.BoolAttribute{ Optional: true, }, - "headers": CheckHeadersAttributeSchema, - "query_parameters": CheckQueryParametersAttributeSchema, + "headers": HeadersAttributeSchema, + "query_parameters": QueryParametersAttributeSchema, "body": schema.StringAttribute{ Optional: true, Description: "The body of the request.", @@ -67,8 +68,8 @@ var CheckRequestAttributeSchema = schema.SingleNestedAttribute{ }, Description: "The `Content-Type` header of the request. Possible values `NONE`, `JSON`, `FORM`, `RAW`, and `GRAPHQL`.", }, - "assertion": CheckAssertionAttributeSchema, - "basic_auth": CheckBasicAuthAttributeSchema, + "assertion": AssertionAttributeSchema, + "basic_auth": BasicAuthAttributeSchema, "ip_family": schema.StringAttribute{ Optional: true, Computed: true, @@ -84,21 +85,21 @@ var CheckRequestAttributeSchema = schema.SingleNestedAttribute{ }, } -type CheckRequestAttributeModel struct { - Method types.String `tfsdk:"method"` - URL types.String `tfsdk:"url"` - FollowRedirects types.Bool `tfsdk:"follow_redirects"` - SkipSSL types.Bool `tfsdk:"skip_ssl"` - Headers types.Map `tfsdk:"headers"` - QueryParameters types.Map `tfsdk:"query_parameters"` - Body types.String `tfsdk:"body"` - BodyType types.String `tfsdk:"body_type"` - Assertions []CheckAssertionAttributeModel `tfsdk:"assertion"` - BasicAuth CheckBasicAuthAttributeModel `tfsdk:"basic_auth"` - IPFamily types.String `tfsdk:"ip_family"` +type RequestAttributeModel struct { + Method types.String `tfsdk:"method"` + URL types.String `tfsdk:"url"` + FollowRedirects types.Bool `tfsdk:"follow_redirects"` + SkipSSL types.Bool `tfsdk:"skip_ssl"` + Headers types.Map `tfsdk:"headers"` + QueryParameters types.Map `tfsdk:"query_parameters"` + Body types.String `tfsdk:"body"` + BodyType types.String `tfsdk:"body_type"` + Assertions []AssertionAttributeModel `tfsdk:"assertion"` + BasicAuth BasicAuthAttributeModel `tfsdk:"basic_auth"` + IPFamily types.String `tfsdk:"ip_family"` } -func (m *CheckRequestAttributeModel) Refresh(ctx context.Context, from *checkly.Request, flags RefreshFlags) diag.Diagnostics { +func (m *RequestAttributeModel) Refresh(ctx context.Context, from *checkly.Request, flags interop.RefreshFlags) diag.Diagnostics { m.Method = types.StringValue(from.Method) m.URL = types.StringValue(from.URL) m.FollowRedirects = types.BoolValue(from.FollowRedirects) @@ -108,7 +109,7 @@ func (m *CheckRequestAttributeModel) Refresh(ctx context.Context, from *checkly. m.Body = types.StringValue(from.Body) m.BodyType = types.StringValue(from.BodyType) - diags := RefreshMany(ctx, from.Assertions, m.Assertions, flags) + diags := interop.RefreshMany(ctx, from.Assertions, m.Assertions, flags) if diags.HasError() { return diags } @@ -123,7 +124,7 @@ func (m *CheckRequestAttributeModel) Refresh(ctx context.Context, from *checkly. return nil } -func (m *CheckRequestAttributeModel) Render(ctx context.Context, into *checkly.Request) diag.Diagnostics { +func (m *RequestAttributeModel) Render(ctx context.Context, into *checkly.Request) diag.Diagnostics { into.Method = m.Method.ValueString() into.URL = m.URL.ValueString() into.FollowRedirects = m.FollowRedirects.ValueBool() @@ -133,7 +134,7 @@ func (m *CheckRequestAttributeModel) Render(ctx context.Context, into *checkly.R into.Body = m.Body.ValueString() into.BodyType = m.Body.ValueString() - diags := RenderMany(ctx, m.Assertions, into.Assertions) + diags := interop.RenderMany(ctx, m.Assertions, into.Assertions) if diags.HasError() { return diags } diff --git a/internal/provider/check_retry_strategy_attribute.go b/internal/provider/resources/attributes/retry_strategy_attribute.go similarity index 84% rename from internal/provider/check_retry_strategy_attribute.go rename to internal/provider/resources/attributes/retry_strategy_attribute.go index 0680b39..3b4edf2 100644 --- a/internal/provider/check_retry_strategy_attribute.go +++ b/internal/provider/resources/attributes/retry_strategy_attribute.go @@ -1,4 +1,4 @@ -package provider +package attributes import ( "context" @@ -13,13 +13,14 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" ) var ( - _ ResourceModel[checkly.RetryStrategy] = (*CheckRetryStrategyAttributeModel)(nil) + _ interop.Model[checkly.RetryStrategy] = (*RetryStrategyAttributeModel)(nil) ) -var CheckRetryStrategyAttributeSchema = schema.SingleNestedAttribute{ +var RetryStrategyAttributeSchema = schema.SingleNestedAttribute{ Optional: true, Computed: true, Attributes: map[string]schema.Attribute{ @@ -68,7 +69,7 @@ var CheckRetryStrategyAttributeSchema = schema.SingleNestedAttribute{ Description: "A strategy for retrying failed check runs.", } -type CheckRetryStrategyAttributeModel struct { +type RetryStrategyAttributeModel struct { Type types.String `tfsdk:"type"` BaseBackoffSeconds types.Int32 `tfsdk:"base_backoff_seconds"` MaxRetries types.Int32 `tfsdk:"max_retries"` @@ -76,7 +77,7 @@ type CheckRetryStrategyAttributeModel struct { SameRegion types.Bool `tfsdk:"same_region"` } -func (m *CheckRetryStrategyAttributeModel) Refresh(ctx context.Context, from *checkly.RetryStrategy, flags RefreshFlags) diag.Diagnostics { +func (m *RetryStrategyAttributeModel) Refresh(ctx context.Context, from *checkly.RetryStrategy, flags interop.RefreshFlags) diag.Diagnostics { m.Type = types.StringValue(from.Type) m.BaseBackoffSeconds = types.Int32Value(int32(from.BaseBackoffSeconds)) m.MaxRetries = types.Int32Value(int32(from.MaxRetries)) @@ -86,7 +87,7 @@ func (m *CheckRetryStrategyAttributeModel) Refresh(ctx context.Context, from *ch return nil } -func (m *CheckRetryStrategyAttributeModel) Render(ctx context.Context, into *checkly.RetryStrategy) diag.Diagnostics { +func (m *RetryStrategyAttributeModel) Render(ctx context.Context, into *checkly.RetryStrategy) diag.Diagnostics { into.Type = m.Type.ValueString() into.BaseBackoffSeconds = int(m.BaseBackoffSeconds.ValueInt32()) into.MaxRetries = int(m.MaxRetries.ValueInt32()) diff --git a/internal/provider/check_group_resource.go b/internal/provider/resources/check_group_resource.go similarity index 80% rename from internal/provider/check_group_resource.go rename to internal/provider/resources/check_group_resource.go index baf2970..66c7144 100644 --- a/internal/provider/check_group_resource.go +++ b/internal/provider/resources/check_group_resource.go @@ -1,4 +1,4 @@ -package provider +package resources import ( "context" @@ -13,6 +13,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/provider/resources/attributes" "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) @@ -46,8 +48,8 @@ func (r *CheckGroupResource) Schema( resp.Schema = schema.Schema{ Description: "Check groups allow you to group together a set of related checks, which can also share default settings for various attributes.", Attributes: map[string]schema.Attribute{ - "id": IDResourceAttributeSchema, - "last_updated": LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, + "last_updated": attributes.LastUpdatedAttributeSchema, "name": schema.StringAttribute{ Required: true, Description: "The name of the check group.", @@ -68,8 +70,8 @@ func (r *CheckGroupResource) Schema( Optional: true, Description: "Determines if the checks in the group should run in all selected locations in parallel or round-robin.", }, - "locations": CheckLocationsAttributeSchema, - "private_locations": CheckPrivateLocationsAttributeSchema, + "locations": attributes.LocationsAttributeSchema, + "private_locations": attributes.PrivateLocationsAttributeSchema, "environment_variables": schema.MapAttribute{ ElementType: types.StringType, Validators: []validator.Map{ @@ -81,7 +83,7 @@ func (r *CheckGroupResource) Schema( Description: "Key/value pairs for setting environment variables during check execution. These are only relevant for browser checks. Use global environment variables whenever possible.", DeprecationMessage: "The property `environment_variables` is deprecated and will be removed in a future version. Consider using the new `environment_variable` list.", }, - "environment_variable": CheckEnvironmentVariableAttributeSchema, + "environment_variable": attributes.EnvironmentVariableAttributeSchema, "double_check": schema.BoolAttribute{ Optional: true, Description: "Setting this to `true` will trigger a retry when a check fails from the failing region and another, randomly selected region before marking the check as failed.", @@ -112,14 +114,14 @@ func (r *CheckGroupResource) Schema( Optional: true, Description: "The id of the runtime to use for this group.", }, - "alert_channel_subscription": CheckAlertChannelSubscriptionAttributeSchema, - "alert_settings": CheckAlertSettingsAttributeSchema, + "alert_channel_subscription": attributes.AlertChannelSubscriptionAttributeSchema, + "alert_settings": attributes.AlertSettingsAttributeSchema, "use_global_alert_settings": schema.BoolAttribute{ Optional: true, Description: "When true, the account level alert settings will be used, not the alert setting defined on this check group.", }, - "api_check_defaults": CheckAPICheckDefaultsAttributeSchema, - "retry_strategy": CheckRetryStrategyAttributeSchema, + "api_check_defaults": attributes.APICheckDefaultsAttributeSchema, + "retry_strategy": attributes.RetryStrategyAttributeSchema, }, } } @@ -129,7 +131,7 @@ func (r *CheckGroupResource) Configure( req resource.ConfigureRequest, resp *resource.ConfigureResponse, ) { - client, diags := ClientFromProviderData(req.ProviderData) + client, diags := interop.ClientFromProviderData(req.ProviderData) if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -174,7 +176,7 @@ func (r *CheckGroupResource) Create( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Created)...) if resp.Diagnostics.HasError() { return } @@ -247,7 +249,7 @@ func (r *CheckGroupResource) Read( return } - resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, interop.Loaded)...) if resp.Diagnostics.HasError() { return } @@ -296,7 +298,7 @@ func (r *CheckGroupResource) Update( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Updated)...) if resp.Diagnostics.HasError() { return } @@ -313,42 +315,42 @@ var CheckGroupID = sdkutil.Identifier{ } var ( - _ ResourceModel[checkly.Group] = (*CheckGroupResourceModel)(nil) + _ interop.Model[checkly.Group] = (*CheckGroupResourceModel)(nil) ) type CheckGroupResourceModel struct { - ID types.String `tfsdk:"id"` - LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. - Name types.String `tfsdk:"name"` - Concurrency types.Int32 `tfsdk:"concurrency"` - Activated types.Bool `tfsdk:"activated"` - Muted types.Bool `tfsdk:"muted"` - RunParallel types.Bool `tfsdk:"run_parallel"` - Locations CheckLocationsAttributeModel `tfsdk:"locations"` - PrivateLocations CheckPrivateLocationsAttributeModel `tfsdk:"private_locations"` - EnvironmentVariables types.Map `tfsdk:"environment_variables"` - EnvironmentVariable []CheckEnvironmentVariableAttributeModel `tfsdk:"environment_variable"` - DoubleCheck types.Bool `tfsdk:"double_check"` - Tags types.Set `tfsdk:"tags"` - SetupSnippetID types.Int64 `tfsdk:"setup_snippet_id"` - TearDownSnippetID types.Int64 `tfsdk:"teardown_snippet_id"` - LocalSetupScript types.String `tfsdk:"local_setup_script"` - LocalTearDownScript types.String `tfsdk:"local_teardown_script"` - RuntimeID types.String `tfsdk:"runtime_id"` - AlertChannelSubscriptions []CheckAlertChannelSubscriptionAttributeModel `tfsdk:"alert_channel_subscription"` - AlertSettings CheckAlertSettingsAttributeModel `tfsdk:"alert_settings"` - UseGlobalAlertSettings types.Bool `tfsdk:"use_global_alert_settings"` - APICheckDefaults CheckAPICheckDefaultsAttributeModel `tfsdk:"api_check_defaults"` - RetryStrategy *CheckRetryStrategyAttributeModel `tfsdk:"retry_strategy"` + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + Name types.String `tfsdk:"name"` + Concurrency types.Int32 `tfsdk:"concurrency"` + Activated types.Bool `tfsdk:"activated"` + Muted types.Bool `tfsdk:"muted"` + RunParallel types.Bool `tfsdk:"run_parallel"` + Locations attributes.LocationsAttributeModel `tfsdk:"locations"` + PrivateLocations attributes.PrivateLocationsAttributeModel `tfsdk:"private_locations"` + EnvironmentVariables types.Map `tfsdk:"environment_variables"` + EnvironmentVariable []attributes.EnvironmentVariableAttributeModel `tfsdk:"environment_variable"` + DoubleCheck types.Bool `tfsdk:"double_check"` + Tags types.Set `tfsdk:"tags"` + SetupSnippetID types.Int64 `tfsdk:"setup_snippet_id"` + TearDownSnippetID types.Int64 `tfsdk:"teardown_snippet_id"` + LocalSetupScript types.String `tfsdk:"local_setup_script"` + LocalTearDownScript types.String `tfsdk:"local_teardown_script"` + RuntimeID types.String `tfsdk:"runtime_id"` + AlertChannelSubscriptions []attributes.AlertChannelSubscriptionAttributeModel `tfsdk:"alert_channel_subscription"` + AlertSettings attributes.AlertSettingsAttributeModel `tfsdk:"alert_settings"` + UseGlobalAlertSettings types.Bool `tfsdk:"use_global_alert_settings"` + APICheckDefaults attributes.APICheckDefaultsAttributeModel `tfsdk:"api_check_defaults"` + RetryStrategy *attributes.RetryStrategyAttributeModel `tfsdk:"retry_strategy"` } -func (m *CheckGroupResourceModel) Refresh(ctx context.Context, from *checkly.Group, flags RefreshFlags) diag.Diagnostics { +func (m *CheckGroupResourceModel) Refresh(ctx context.Context, from *checkly.Group, flags interop.RefreshFlags) diag.Diagnostics { if flags.Created() { m.ID = CheckGroupID.IntoString(from.ID) } if flags.Created() || flags.Updated() { - m.LastUpdated = LastUpdatedNow() + m.LastUpdated = attributes.LastUpdatedNow() } m.Name = types.StringValue(from.Name) @@ -375,7 +377,7 @@ func (m *CheckGroupResourceModel) Refresh(ctx context.Context, from *checkly.Gro } else { m.EnvironmentVariable = nil - diags := RefreshMany(ctx, from.EnvironmentVariables, m.EnvironmentVariable, flags) + diags := interop.RefreshMany(ctx, from.EnvironmentVariables, m.EnvironmentVariable, flags) if diags.HasError() { return diags } @@ -383,7 +385,7 @@ func (m *CheckGroupResourceModel) Refresh(ctx context.Context, from *checkly.Gro m.DoubleCheck = types.BoolValue(from.DoubleCheck) - m.Tags = IntoUntypedStringSet(&from.Tags) + m.Tags = interop.IntoUntypedStringSet(&from.Tags) m.SetupSnippetID = types.Int64Value(from.SetupSnippetID) m.TearDownSnippetID = types.Int64Value(from.TearDownSnippetID) @@ -396,7 +398,7 @@ func (m *CheckGroupResourceModel) Refresh(ctx context.Context, from *checkly.Gro m.RuntimeID = types.StringNull() } - diags = RefreshMany(ctx, from.AlertChannelSubscriptions, m.AlertChannelSubscriptions, flags) + diags = interop.RefreshMany(ctx, from.AlertChannelSubscriptions, m.AlertChannelSubscriptions, flags) if diags.HasError() { return diags } @@ -450,7 +452,7 @@ func (m *CheckGroupResourceModel) Render(ctx context.Context, into *checkly.Grou } else { into.EnvironmentVariables = nil - diags := RenderMany(ctx, m.EnvironmentVariable, into.EnvironmentVariables) + diags := interop.RenderMany(ctx, m.EnvironmentVariable, into.EnvironmentVariables) if diags.HasError() { return diags } @@ -458,7 +460,7 @@ func (m *CheckGroupResourceModel) Render(ctx context.Context, into *checkly.Grou into.DoubleCheck = m.DoubleCheck.ValueBool() - into.Tags = FromUntypedStringSet(m.Tags) + into.Tags = interop.FromUntypedStringSet(m.Tags) into.SetupSnippetID = m.SetupSnippetID.ValueInt64() into.TearDownSnippetID = m.TearDownSnippetID.ValueInt64() @@ -472,7 +474,7 @@ func (m *CheckGroupResourceModel) Render(ctx context.Context, into *checkly.Grou into.RuntimeID = nil } - diags = RenderMany(ctx, m.AlertChannelSubscriptions, into.AlertChannelSubscriptions) + diags = interop.RenderMany(ctx, m.AlertChannelSubscriptions, into.AlertChannelSubscriptions) if diags.HasError() { return diags } diff --git a/internal/provider/check_resource.go b/internal/provider/resources/check_resource.go similarity index 83% rename from internal/provider/check_resource.go rename to internal/provider/resources/check_resource.go index 0fefa73..38340fc 100644 --- a/internal/provider/check_resource.go +++ b/internal/provider/resources/check_resource.go @@ -1,4 +1,4 @@ -package provider +package resources import ( "context" @@ -17,6 +17,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/provider/resources/attributes" "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) @@ -50,8 +52,8 @@ func (r *CheckResource) Schema( resp.Schema = schema.Schema{ Description: "Check groups allow you to group together a set of related checks, which can also share default settings for various attributes.", Attributes: map[string]schema.Attribute{ - "id": IDResourceAttributeSchema, - "last_updated": LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, + "last_updated": attributes.LastUpdatedAttributeSchema, "name": schema.StringAttribute{ Required: true, Description: "The name of the check.", @@ -104,7 +106,7 @@ func (r *CheckResource) Schema( Optional: true, Description: "An array of one or more data center locations where to run the checks.", }, - "private_locations": CheckPrivateLocationsAttributeSchema, + "private_locations": attributes.PrivateLocationsAttributeSchema, "script": schema.StringAttribute{ Optional: true, Computed: true, @@ -140,7 +142,7 @@ func (r *CheckResource) Schema( Description: "Key/value pairs for setting environment variables during check execution. These are only relevant for browser checks. Use global environment variables whenever possible.", DeprecationMessage: "The property `environment_variables` is deprecated and will be removed in a future version. Consider using the new `environment_variable` list.", }, - "environment_variable": CheckEnvironmentVariableAttributeSchema, + "environment_variable": attributes.EnvironmentVariableAttributeSchema, "double_check": schema.BoolAttribute{ Optional: true, Description: "Setting this to `true` will trigger a retry when a check fails from the failing region and another, randomly selected region before marking the check as failed.", @@ -185,13 +187,13 @@ func (r *CheckResource) Schema( // TODO: If type == MULTI_STEP, use GetRuntime to check whether // the runtime supports MULTI_STEP }, - "alert_channel_subscription": CheckAlertChannelSubscriptionAttributeSchema, - "alert_settings": CheckAlertSettingsAttributeSchema, + "alert_channel_subscription": attributes.AlertChannelSubscriptionAttributeSchema, + "alert_settings": attributes.AlertSettingsAttributeSchema, "use_global_alert_settings": schema.BoolAttribute{ Optional: true, Description: "When true, the account level alert settings will be used, not the alert setting defined on this check.", }, - "request": CheckRequestAttributeSchema, // TODO: can only be set if type == API + "request": attributes.RequestAttributeSchema, // TODO: can only be set if type == API "group_id": schema.Int64Attribute{ Optional: true, Description: "The id of the check group this check is part of.", @@ -200,7 +202,7 @@ func (r *CheckResource) Schema( Optional: true, Description: "The position of this check in a check group. It determines in what order checks are run when a group is triggered from the API or from CI/CD.", }, - "retry_strategy": CheckRetryStrategyAttributeSchema, + "retry_strategy": attributes.RetryStrategyAttributeSchema, }, } } @@ -210,7 +212,7 @@ func (r *CheckResource) Configure( req resource.ConfigureRequest, resp *resource.ConfigureResponse, ) { - client, diags := ClientFromProviderData(req.ProviderData) + client, diags := interop.ClientFromProviderData(req.ProviderData) if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -255,7 +257,7 @@ func (r *CheckResource) Create( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Created)...) if resp.Diagnostics.HasError() { return } @@ -316,7 +318,7 @@ func (r *CheckResource) Read( return } - resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, interop.Loaded)...) if resp.Diagnostics.HasError() { return } @@ -359,7 +361,7 @@ func (r *CheckResource) Update( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Updated)...) if resp.Diagnostics.HasError() { return } @@ -371,52 +373,52 @@ func (r *CheckResource) Update( } var ( - _ ResourceModel[checkly.Check] = (*CheckResourceModel)(nil) + _ interop.Model[checkly.Check] = (*CheckResourceModel)(nil) ) type CheckResourceModel struct { - ID types.String `tfsdk:"id"` - LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. - Name types.String `tfsdk:"name"` - Type types.String `tfsdk:"type"` - Frequency types.Int32 `tfsdk:"frequency"` - FrequencyOffset types.Int32 `tfsdk:"frequency_offset"` - Activated types.Bool `tfsdk:"activated"` - Muted types.Bool `tfsdk:"muted"` - ShouldFail types.Bool `tfsdk:"should_fail"` - RunParallel types.Bool `tfsdk:"run_parallel"` - Locations CheckLocationsAttributeModel `tfsdk:"locations"` - PrivateLocations CheckPrivateLocationsAttributeModel `tfsdk:"private_locations"` - Script types.String `tfsdk:"script"` - DegradedResponseTime types.Int32 `tfsdk:"degraded_response_time"` - MaxResponseTime types.Int32 `tfsdk:"max_response_time"` - EnvironmentVariables types.Map `tfsdk:"environment_variables"` - EnvironmentVariable []CheckEnvironmentVariableAttributeModel `tfsdk:"environment_variable"` - DoubleCheck types.Bool `tfsdk:"double_check"` - Tags types.Set `tfsdk:"tags"` - SSLCheck types.Bool `tfsdk:"ssl_check"` - SSLCheckDomain types.String `tfsdk:"ssl_check_domain"` - SetupSnippetID types.Int64 `tfsdk:"setup_snippet_id"` - TearDownSnippetID types.Int64 `tfsdk:"teardown_snippet_id"` - LocalSetupScript types.String `tfsdk:"local_setup_script"` - LocalTearDownScript types.String `tfsdk:"local_teardown_script"` - RuntimeID types.String `tfsdk:"runtime_id"` - AlertChannelSubscriptions []CheckAlertChannelSubscriptionAttributeModel `tfsdk:"alert_channel_subscription"` - AlertSettings CheckAlertSettingsAttributeModel `tfsdk:"alert_settings"` - UseGlobalAlertSettings types.Bool `tfsdk:"use_global_alert_settings"` - Request CheckRequestAttributeModel `tfsdk:"request"` - GroupID types.Int64 `tfsdk:"group_id"` - GroupOrder types.Int32 `tfsdk:"group_order"` - RetryStrategy *CheckRetryStrategyAttributeModel `tfsdk:"retry_strategy"` + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + Name types.String `tfsdk:"name"` + Type types.String `tfsdk:"type"` + Frequency types.Int32 `tfsdk:"frequency"` + FrequencyOffset types.Int32 `tfsdk:"frequency_offset"` + Activated types.Bool `tfsdk:"activated"` + Muted types.Bool `tfsdk:"muted"` + ShouldFail types.Bool `tfsdk:"should_fail"` + RunParallel types.Bool `tfsdk:"run_parallel"` + Locations attributes.LocationsAttributeModel `tfsdk:"locations"` + PrivateLocations attributes.PrivateLocationsAttributeModel `tfsdk:"private_locations"` + Script types.String `tfsdk:"script"` + DegradedResponseTime types.Int32 `tfsdk:"degraded_response_time"` + MaxResponseTime types.Int32 `tfsdk:"max_response_time"` + EnvironmentVariables types.Map `tfsdk:"environment_variables"` + EnvironmentVariable []attributes.EnvironmentVariableAttributeModel `tfsdk:"environment_variable"` + DoubleCheck types.Bool `tfsdk:"double_check"` + Tags types.Set `tfsdk:"tags"` + SSLCheck types.Bool `tfsdk:"ssl_check"` + SSLCheckDomain types.String `tfsdk:"ssl_check_domain"` + SetupSnippetID types.Int64 `tfsdk:"setup_snippet_id"` + TearDownSnippetID types.Int64 `tfsdk:"teardown_snippet_id"` + LocalSetupScript types.String `tfsdk:"local_setup_script"` + LocalTearDownScript types.String `tfsdk:"local_teardown_script"` + RuntimeID types.String `tfsdk:"runtime_id"` + AlertChannelSubscriptions []attributes.AlertChannelSubscriptionAttributeModel `tfsdk:"alert_channel_subscription"` + AlertSettings attributes.AlertSettingsAttributeModel `tfsdk:"alert_settings"` + UseGlobalAlertSettings types.Bool `tfsdk:"use_global_alert_settings"` + Request attributes.RequestAttributeModel `tfsdk:"request"` + GroupID types.Int64 `tfsdk:"group_id"` + GroupOrder types.Int32 `tfsdk:"group_order"` + RetryStrategy *attributes.RetryStrategyAttributeModel `tfsdk:"retry_strategy"` } -func (m *CheckResourceModel) Refresh(ctx context.Context, from *checkly.Check, flags RefreshFlags) diag.Diagnostics { +func (m *CheckResourceModel) Refresh(ctx context.Context, from *checkly.Check, flags interop.RefreshFlags) diag.Diagnostics { if flags.Created() { m.ID = types.StringValue(from.ID) } if flags.Created() || flags.Updated() { - m.LastUpdated = LastUpdatedNow() + m.LastUpdated = attributes.LastUpdatedNow() } m.Name = types.StringValue(from.Name) @@ -450,7 +452,7 @@ func (m *CheckResourceModel) Refresh(ctx context.Context, from *checkly.Check, f } else { m.EnvironmentVariable = nil - diags := RefreshMany(ctx, from.EnvironmentVariables, m.EnvironmentVariable, flags) + diags := interop.RefreshMany(ctx, from.EnvironmentVariables, m.EnvironmentVariable, flags) if diags.HasError() { return diags } @@ -458,7 +460,7 @@ func (m *CheckResourceModel) Refresh(ctx context.Context, from *checkly.Check, f m.DoubleCheck = types.BoolValue(from.DoubleCheck) - m.Tags = IntoUntypedStringSet(&from.Tags) + m.Tags = interop.IntoUntypedStringSet(&from.Tags) m.SSLCheck = types.BoolValue(from.SSLCheck) m.SSLCheckDomain = types.StringValue(from.SSLCheckDomain) @@ -474,7 +476,7 @@ func (m *CheckResourceModel) Refresh(ctx context.Context, from *checkly.Check, f m.RuntimeID = types.StringNull() } - diags = RefreshMany(ctx, from.AlertChannelSubscriptions, m.AlertChannelSubscriptions, flags) + diags = interop.RefreshMany(ctx, from.AlertChannelSubscriptions, m.AlertChannelSubscriptions, flags) if diags.HasError() { return diags } @@ -538,7 +540,7 @@ func (m *CheckResourceModel) Render(ctx context.Context, into *checkly.Check) di } else { into.EnvironmentVariables = nil - diags := RenderMany(ctx, m.EnvironmentVariable, into.EnvironmentVariables) + diags := interop.RenderMany(ctx, m.EnvironmentVariable, into.EnvironmentVariables) if diags.HasError() { return diags } @@ -546,7 +548,7 @@ func (m *CheckResourceModel) Render(ctx context.Context, into *checkly.Check) di into.DoubleCheck = m.DoubleCheck.ValueBool() - into.Tags = FromUntypedStringSet(m.Tags) + into.Tags = interop.FromUntypedStringSet(m.Tags) into.SSLCheck = m.SSLCheck.ValueBool() into.SSLCheckDomain = m.SSLCheckDomain.ValueString() @@ -563,7 +565,7 @@ func (m *CheckResourceModel) Render(ctx context.Context, into *checkly.Check) di into.RuntimeID = nil } - diags = RenderMany(ctx, m.AlertChannelSubscriptions, into.AlertChannelSubscriptions) + diags = interop.RenderMany(ctx, m.AlertChannelSubscriptions, into.AlertChannelSubscriptions) if diags.HasError() { return diags } diff --git a/internal/provider/dashboard_resource.go b/internal/provider/resources/dashboard_resource.go similarity index 92% rename from internal/provider/dashboard_resource.go rename to internal/provider/resources/dashboard_resource.go index 217b671..7f99c1b 100644 --- a/internal/provider/dashboard_resource.go +++ b/internal/provider/resources/dashboard_resource.go @@ -1,4 +1,4 @@ -package provider +package resources import ( "context" @@ -17,6 +17,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/provider/resources/attributes" "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) @@ -49,8 +51,8 @@ func (r *DashboardResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": IDResourceAttributeSchema, - "last_updated": LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, + "last_updated": attributes.LastUpdatedAttributeSchema, "custom_url": schema.StringAttribute{ Required: true, Description: "A subdomain name under 'checklyhq.com'. Needs to be unique across all users.", @@ -157,7 +159,7 @@ func (r *DashboardResource) Configure( req resource.ConfigureRequest, resp *resource.ConfigureResponse, ) { - client, diags := ClientFromProviderData(req.ProviderData) + client, diags := interop.ClientFromProviderData(req.ProviderData) if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -202,7 +204,7 @@ func (r *DashboardResource) Create( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Created)...) if resp.Diagnostics.HasError() { return } @@ -263,7 +265,7 @@ func (r *DashboardResource) Read( return } - resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, interop.Loaded)...) if resp.Diagnostics.HasError() { return } @@ -306,7 +308,7 @@ func (r *DashboardResource) Update( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Updated)...) if resp.Diagnostics.HasError() { return } @@ -318,7 +320,7 @@ func (r *DashboardResource) Update( } var ( - _ ResourceModel[checkly.Dashboard] = (*DashboardResourceModel)(nil) + _ interop.Model[checkly.Dashboard] = (*DashboardResourceModel)(nil) ) type DashboardResourceModel struct { @@ -343,13 +345,13 @@ type DashboardResourceModel struct { Key types.String `tfsdk:"key"` } -func (m *DashboardResourceModel) Refresh(ctx context.Context, from *checkly.Dashboard, flags RefreshFlags) diag.Diagnostics { +func (m *DashboardResourceModel) Refresh(ctx context.Context, from *checkly.Dashboard, flags interop.RefreshFlags) diag.Diagnostics { if flags.Created() { m.ID = types.StringValue(from.DashboardID) } if flags.Created() || flags.Updated() { - m.LastUpdated = LastUpdatedNow() + m.LastUpdated = attributes.LastUpdatedNow() } m.CustomURL = types.StringValue(from.CustomUrl) @@ -367,7 +369,7 @@ func (m *DashboardResourceModel) Refresh(ctx context.Context, from *checkly.Dash m.HideTags = types.BoolValue(from.HideTags) m.UseTagsAndOperator = types.BoolValue(from.UseTagsAndOperator) m.IsPrivate = types.BoolValue(from.IsPrivate) - m.Tags = IntoUntypedStringSet(&from.Tags) + m.Tags = interop.IntoUntypedStringSet(&from.Tags) if from.IsPrivate { if len(from.Keys) > 0 { @@ -394,7 +396,7 @@ func (m *DashboardResourceModel) Render(ctx context.Context, into *checkly.Dashb into.ChecksPerPage = int(m.ChecksPerPage.ValueInt32()) into.PaginationRate = int(m.PaginationRate.ValueInt32()) into.Paginate = m.Paginate.ValueBool() - into.Tags = FromUntypedStringSet(m.Tags) + into.Tags = interop.FromUntypedStringSet(m.Tags) into.HideTags = m.HideTags.ValueBool() into.UseTagsAndOperator = m.UseTagsAndOperator.ValueBool() diff --git a/internal/provider/environment_variable_resource.go b/internal/provider/resources/environment_variable_resource.go similarity index 89% rename from internal/provider/environment_variable_resource.go rename to internal/provider/resources/environment_variable_resource.go index 770b933..3870a39 100644 --- a/internal/provider/environment_variable_resource.go +++ b/internal/provider/resources/environment_variable_resource.go @@ -1,4 +1,4 @@ -package provider +package resources import ( "context" @@ -12,6 +12,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/provider/resources/attributes" "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) @@ -44,8 +46,8 @@ func (r *EnvironmentVariableResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": IDResourceAttributeSchema, - "last_updated": LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, + "last_updated": attributes.LastUpdatedAttributeSchema, "key": schema.StringAttribute{ Required: true, Description: "", // TODO @@ -76,7 +78,7 @@ func (r *EnvironmentVariableResource) Configure( req resource.ConfigureRequest, resp *resource.ConfigureResponse, ) { - client, diags := ClientFromProviderData(req.ProviderData) + client, diags := interop.ClientFromProviderData(req.ProviderData) if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -124,7 +126,7 @@ func (r *EnvironmentVariableResource) Create( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Created)...) if resp.Diagnostics.HasError() { return } @@ -188,7 +190,7 @@ func (r *EnvironmentVariableResource) Read( return } - resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, interop.Loaded)...) if resp.Diagnostics.HasError() { return } @@ -231,7 +233,7 @@ func (r *EnvironmentVariableResource) Update( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Updated)...) if resp.Diagnostics.HasError() { return } @@ -243,7 +245,7 @@ func (r *EnvironmentVariableResource) Update( } var ( - _ ResourceModel[checkly.EnvironmentVariable] = (*EnvironmentVariableResourceModel)(nil) + _ interop.Model[checkly.EnvironmentVariable] = (*EnvironmentVariableResourceModel)(nil) ) type EnvironmentVariableResourceModel struct { @@ -255,13 +257,13 @@ type EnvironmentVariableResourceModel struct { Secret types.Bool `tfsdk:"secret"` } -func (m *EnvironmentVariableResourceModel) Refresh(ctx context.Context, from *checkly.EnvironmentVariable, flags RefreshFlags) diag.Diagnostics { +func (m *EnvironmentVariableResourceModel) Refresh(ctx context.Context, from *checkly.EnvironmentVariable, flags interop.RefreshFlags) diag.Diagnostics { if flags.Created() { m.ID = types.StringValue(from.Key) } if flags.Created() || flags.Updated() { - m.LastUpdated = LastUpdatedNow() + m.LastUpdated = attributes.LastUpdatedNow() } m.Key = types.StringValue(from.Key) diff --git a/internal/provider/heartbeat_resource.go b/internal/provider/resources/heartbeat_resource.go similarity index 84% rename from internal/provider/heartbeat_resource.go rename to internal/provider/resources/heartbeat_resource.go index 2740977..e0b0b28 100644 --- a/internal/provider/heartbeat_resource.go +++ b/internal/provider/resources/heartbeat_resource.go @@ -1,4 +1,4 @@ -package provider +package resources import ( "context" @@ -14,6 +14,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/provider/resources/attributes" "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) @@ -47,8 +49,8 @@ func (r *HeartbeatResource) Schema( resp.Schema = schema.Schema{ Description: "Heartbeats allows you to monitor your cron jobs and set up alerting, so you get a notification when things break or slow down.", Attributes: map[string]schema.Attribute{ - "id": IDResourceAttributeSchema, - "last_updated": LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, + "last_updated": attributes.LastUpdatedAttributeSchema, "name": schema.StringAttribute{ Required: true, Description: "The name of the check.", @@ -66,7 +68,7 @@ func (r *HeartbeatResource) Schema( Optional: true, Description: "A list of tags for organizing and filtering checks.", }, - "alert_settings": CheckAlertSettingsAttributeSchema, + "alert_settings": attributes.AlertSettingsAttributeSchema, "use_global_alert_settings": schema.BoolAttribute{ Optional: true, Description: "When true, the account level alert settings will be used, not the alert setting defined on this check.", @@ -107,7 +109,7 @@ func (r *HeartbeatResource) Schema( }, }, }, - "alert_channel_subscription": CheckAlertChannelSubscriptionAttributeSchema, + "alert_channel_subscription": attributes.AlertChannelSubscriptionAttributeSchema, }, } } @@ -117,7 +119,7 @@ func (r *HeartbeatResource) Configure( req resource.ConfigureRequest, resp *resource.ConfigureResponse, ) { - client, diags := ClientFromProviderData(req.ProviderData) + client, diags := interop.ClientFromProviderData(req.ProviderData) if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -162,7 +164,7 @@ func (r *HeartbeatResource) Create( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Created)...) if resp.Diagnostics.HasError() { return } @@ -223,7 +225,7 @@ func (r *HeartbeatResource) Read( return } - resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, interop.Loaded)...) if resp.Diagnostics.HasError() { return } @@ -266,7 +268,7 @@ func (r *HeartbeatResource) Update( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Updated)...) if resp.Diagnostics.HasError() { return } @@ -278,24 +280,24 @@ func (r *HeartbeatResource) Update( } var ( - _ ResourceModel[checkly.HeartbeatCheck] = (*HeartbeatResourceModel)(nil) - _ ResourceModel[checkly.Heartbeat] = (*HeartbeatAttributeModel)(nil) + _ interop.Model[checkly.HeartbeatCheck] = (*HeartbeatResourceModel)(nil) + _ interop.Model[checkly.Heartbeat] = (*HeartbeatAttributeModel)(nil) ) type HeartbeatResourceModel struct { - ID types.String `tfsdk:"id"` - LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. - Name types.String `tfsdk:"name"` - Activated types.Bool `tfsdk:"activated"` - Muted types.Bool `tfsdk:"muted"` - Tags types.Set `tfsdk:"tags"` - AlertSettings CheckAlertSettingsAttributeModel `tfsdk:"alert_settings"` - UseGlobalAlertSettings types.Bool `tfsdk:"use_global_alert_settings"` - Heartbeat HeartbeatAttributeModel `tfsdk:"heartbeat"` - AlertChannelSubscriptions []CheckAlertChannelSubscriptionAttributeModel `tfsdk:"alert_channel_subscription"` + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. + Name types.String `tfsdk:"name"` + Activated types.Bool `tfsdk:"activated"` + Muted types.Bool `tfsdk:"muted"` + Tags types.Set `tfsdk:"tags"` + AlertSettings attributes.AlertSettingsAttributeModel `tfsdk:"alert_settings"` + UseGlobalAlertSettings types.Bool `tfsdk:"use_global_alert_settings"` + Heartbeat HeartbeatAttributeModel `tfsdk:"heartbeat"` + AlertChannelSubscriptions []attributes.AlertChannelSubscriptionAttributeModel `tfsdk:"alert_channel_subscription"` } -func (m *HeartbeatResourceModel) Refresh(ctx context.Context, from *checkly.HeartbeatCheck, flags RefreshFlags) diag.Diagnostics { +func (m *HeartbeatResourceModel) Refresh(ctx context.Context, from *checkly.HeartbeatCheck, flags interop.RefreshFlags) diag.Diagnostics { var diags diag.Diagnostics if flags.Created() { @@ -303,7 +305,7 @@ func (m *HeartbeatResourceModel) Refresh(ctx context.Context, from *checkly.Hear } if flags.Created() || flags.Updated() { - m.LastUpdated = LastUpdatedNow() + m.LastUpdated = attributes.LastUpdatedNow() } m.Name = types.StringValue(from.Name) @@ -311,7 +313,7 @@ func (m *HeartbeatResourceModel) Refresh(ctx context.Context, from *checkly.Hear m.Muted = types.BoolValue(from.Muted) slices.Sort(from.Tags) - m.Tags = IntoUntypedStringSet(&from.Tags) + m.Tags = interop.IntoUntypedStringSet(&from.Tags) diags.Append(m.AlertSettings.Refresh(ctx, &from.AlertSettings, flags)...) if diags.HasError() { @@ -327,7 +329,7 @@ func (m *HeartbeatResourceModel) Refresh(ctx context.Context, from *checkly.Hear m.AlertChannelSubscriptions = nil for _, sub := range from.AlertChannelSubscriptions { - var subModel CheckAlertChannelSubscriptionAttributeModel + var subModel attributes.AlertChannelSubscriptionAttributeModel diags.Append(subModel.Refresh(ctx, &sub, flags)...) if diags.HasError() { return diags @@ -345,7 +347,7 @@ func (m *HeartbeatResourceModel) Render(ctx context.Context, into *checkly.Heart into.Name = m.Name.ValueString() into.Activated = m.Activated.ValueBool() into.Muted = m.Muted.ValueBool() - into.Tags = FromUntypedStringSet(m.Tags) + into.Tags = interop.FromUntypedStringSet(m.Tags) diags.Append(m.AlertSettings.Render(ctx, &into.AlertSettings)...) @@ -364,7 +366,7 @@ type HeartbeatAttributeModel struct { PingToken types.String `tfsdk:"ping_token"` } -func (m *HeartbeatAttributeModel) Refresh(ctx context.Context, from *checkly.Heartbeat, flags RefreshFlags) diag.Diagnostics { +func (m *HeartbeatAttributeModel) Refresh(ctx context.Context, from *checkly.Heartbeat, flags interop.RefreshFlags) diag.Diagnostics { m.Period = types.Int32Value(int32(from.Period)) m.PeriodUnit = types.StringValue(from.PeriodUnit) m.Grace = types.Int32Value(int32(from.Grace)) diff --git a/internal/provider/maintenance_windows_resource.go b/internal/provider/resources/maintenance_windows_resource.go similarity index 89% rename from internal/provider/maintenance_windows_resource.go rename to internal/provider/resources/maintenance_windows_resource.go index f83b73d..54c0df5 100644 --- a/internal/provider/maintenance_windows_resource.go +++ b/internal/provider/resources/maintenance_windows_resource.go @@ -1,4 +1,4 @@ -package provider +package resources import ( "context" @@ -13,6 +13,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/provider/resources/attributes" "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) @@ -45,8 +47,8 @@ func (r *MaintenanceWindowsResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": IDResourceAttributeSchema, - "last_updated": LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, + "last_updated": attributes.LastUpdatedAttributeSchema, "name": schema.StringAttribute{ Required: true, Description: "The maintenance window name.", @@ -88,7 +90,7 @@ func (r *MaintenanceWindowsResource) Configure( req resource.ConfigureRequest, resp *resource.ConfigureResponse, ) { - client, diags := ClientFromProviderData(req.ProviderData) + client, diags := interop.ClientFromProviderData(req.ProviderData) if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -133,7 +135,7 @@ func (r *MaintenanceWindowsResource) Create( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Created)...) if resp.Diagnostics.HasError() { return } @@ -206,7 +208,7 @@ func (r *MaintenanceWindowsResource) Read( return } - resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, interop.Loaded)...) if resp.Diagnostics.HasError() { return } @@ -255,7 +257,7 @@ func (r *MaintenanceWindowsResource) Update( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Updated)...) if resp.Diagnostics.HasError() { return } @@ -272,7 +274,7 @@ var MaintenanceWindowID = sdkutil.Identifier{ } var ( - _ ResourceModel[checkly.MaintenanceWindow] = (*MaintenanceWindowsResourceModel)(nil) + _ interop.Model[checkly.MaintenanceWindow] = (*MaintenanceWindowsResourceModel)(nil) ) type MaintenanceWindowsResourceModel struct { @@ -287,13 +289,13 @@ type MaintenanceWindowsResourceModel struct { Tags types.Set `tfsdk:"tags"` } -func (m *MaintenanceWindowsResourceModel) Refresh(ctx context.Context, from *checkly.MaintenanceWindow, flags RefreshFlags) diag.Diagnostics { +func (m *MaintenanceWindowsResourceModel) Refresh(ctx context.Context, from *checkly.MaintenanceWindow, flags interop.RefreshFlags) diag.Diagnostics { if flags.Created() { m.ID = MaintenanceWindowID.IntoString(from.ID) } if flags.Created() || flags.Updated() { - m.LastUpdated = LastUpdatedNow() + m.LastUpdated = attributes.LastUpdatedNow() } m.Name = types.StringValue(from.Name) @@ -302,7 +304,7 @@ func (m *MaintenanceWindowsResourceModel) Refresh(ctx context.Context, from *che m.RepeatUnit = types.StringValue(from.RepeatUnit) m.RepeatEndsAt = types.StringValue(from.RepeatEndsAt) m.RepeatInterval = types.Int32Value(int32(from.RepeatInterval)) - m.Tags = IntoUntypedStringSet(&from.Tags) + m.Tags = interop.IntoUntypedStringSet(&from.Tags) return nil } @@ -314,7 +316,7 @@ func (m *MaintenanceWindowsResourceModel) Render(ctx context.Context, into *chec into.RepeatUnit = m.RepeatUnit.ValueString() into.RepeatEndsAt = m.RepeatEndsAt.ValueString() into.RepeatInterval = int(m.RepeatInterval.ValueInt32()) - into.Tags = FromUntypedStringSet(m.Tags) + into.Tags = interop.FromUntypedStringSet(m.Tags) return nil } diff --git a/internal/provider/private_location_resource.go b/internal/provider/resources/private_location_resource.go similarity index 89% rename from internal/provider/private_location_resource.go rename to internal/provider/resources/private_location_resource.go index e44bfa6..56a99dc 100644 --- a/internal/provider/private_location_resource.go +++ b/internal/provider/resources/private_location_resource.go @@ -1,4 +1,4 @@ -package provider +package resources import ( "context" @@ -13,6 +13,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/provider/resources/attributes" "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) @@ -45,8 +47,8 @@ func (r *PrivateLocationResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": IDResourceAttributeSchema, - "last_updated": LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, + "last_updated": attributes.LastUpdatedAttributeSchema, "name": schema.StringAttribute{ Required: true, Description: "The private location name.", @@ -76,7 +78,7 @@ func (r *PrivateLocationResource) Configure( req resource.ConfigureRequest, resp *resource.ConfigureResponse, ) { - client, diags := ClientFromProviderData(req.ProviderData) + client, diags := interop.ClientFromProviderData(req.ProviderData) if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -121,7 +123,7 @@ func (r *PrivateLocationResource) Create( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Created)...) if resp.Diagnostics.HasError() { return } @@ -182,7 +184,7 @@ func (r *PrivateLocationResource) Read( return } - resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, interop.Loaded)...) if resp.Diagnostics.HasError() { return } @@ -225,7 +227,7 @@ func (r *PrivateLocationResource) Update( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Updated)...) if resp.Diagnostics.HasError() { return } @@ -237,7 +239,7 @@ func (r *PrivateLocationResource) Update( } var ( - _ ResourceModel[checkly.PrivateLocation] = (*PrivateLocationResourceModel)(nil) + _ interop.Model[checkly.PrivateLocation] = (*PrivateLocationResourceModel)(nil) ) type PrivateLocationResourceModel struct { @@ -249,13 +251,13 @@ type PrivateLocationResourceModel struct { Keys types.Set `tfsdk:"keys"` } -func (m *PrivateLocationResourceModel) Refresh(ctx context.Context, from *checkly.PrivateLocation, flags RefreshFlags) diag.Diagnostics { +func (m *PrivateLocationResourceModel) Refresh(ctx context.Context, from *checkly.PrivateLocation, flags interop.RefreshFlags) diag.Diagnostics { if flags.Created() { m.ID = types.StringValue(from.ID) } if flags.Created() || flags.Updated() { - m.LastUpdated = LastUpdatedNow() + m.LastUpdated = attributes.LastUpdatedNow() } m.Name = types.StringValue(from.Name) diff --git a/internal/provider/snippet_resource.go b/internal/provider/resources/snippet_resource.go similarity index 88% rename from internal/provider/snippet_resource.go rename to internal/provider/resources/snippet_resource.go index 9b9abce..a9c7cef 100644 --- a/internal/provider/snippet_resource.go +++ b/internal/provider/resources/snippet_resource.go @@ -1,4 +1,4 @@ -package provider +package resources import ( "context" @@ -11,6 +11,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/provider/resources/attributes" "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) @@ -43,8 +45,8 @@ func (r *SnippetResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": IDResourceAttributeSchema, - "last_updated": LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, + "last_updated": attributes.LastUpdatedAttributeSchema, "name": schema.StringAttribute{ Required: true, Description: "The name of the snippet.", @@ -64,7 +66,7 @@ func (r *SnippetResource) Configure( req resource.ConfigureRequest, resp *resource.ConfigureResponse, ) { - client, diags := ClientFromProviderData(req.ProviderData) + client, diags := interop.ClientFromProviderData(req.ProviderData) if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -109,7 +111,7 @@ func (r *SnippetResource) Create( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Created)...) if resp.Diagnostics.HasError() { return } @@ -182,7 +184,7 @@ func (r *SnippetResource) Read( return } - resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, interop.Loaded)...) if resp.Diagnostics.HasError() { return } @@ -231,7 +233,7 @@ func (r *SnippetResource) Update( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Updated)...) if resp.Diagnostics.HasError() { return } @@ -248,7 +250,7 @@ var SnippetID = sdkutil.Identifier{ } var ( - _ ResourceModel[checkly.Snippet] = (*SnippetResourceModel)(nil) + _ interop.Model[checkly.Snippet] = (*SnippetResourceModel)(nil) ) type SnippetResourceModel struct { @@ -258,13 +260,13 @@ type SnippetResourceModel struct { Script types.String `tfsdk:"script"` } -func (m *SnippetResourceModel) Refresh(ctx context.Context, from *checkly.Snippet, flags RefreshFlags) diag.Diagnostics { +func (m *SnippetResourceModel) Refresh(ctx context.Context, from *checkly.Snippet, flags interop.RefreshFlags) diag.Diagnostics { if flags.Created() { m.ID = SnippetID.IntoString(from.ID) } if flags.Created() || flags.Updated() { - m.LastUpdated = LastUpdatedNow() + m.LastUpdated = attributes.LastUpdatedNow() } m.Name = types.StringValue(from.Name) diff --git a/internal/provider/trigger_check_resource.go b/internal/provider/resources/trigger_check_resource.go similarity index 88% rename from internal/provider/trigger_check_resource.go rename to internal/provider/resources/trigger_check_resource.go index c381f53..f496598 100644 --- a/internal/provider/trigger_check_resource.go +++ b/internal/provider/resources/trigger_check_resource.go @@ -1,4 +1,4 @@ -package provider +package resources import ( "context" @@ -11,6 +11,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/provider/resources/attributes" "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) @@ -43,8 +45,8 @@ func (r *TriggerCheckResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": IDResourceAttributeSchema, - "last_updated": LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, + "last_updated": attributes.LastUpdatedAttributeSchema, "check_id": schema.StringAttribute{ Required: true, Description: "The ID of the check that you want to attach the trigger to.", @@ -68,7 +70,7 @@ func (r *TriggerCheckResource) Configure( req resource.ConfigureRequest, resp *resource.ConfigureResponse, ) { - client, diags := ClientFromProviderData(req.ProviderData) + client, diags := interop.ClientFromProviderData(req.ProviderData) if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -110,7 +112,7 @@ func (r *TriggerCheckResource) Create( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Created)...) if resp.Diagnostics.HasError() { return } @@ -174,7 +176,7 @@ func (r *TriggerCheckResource) Read( return } - resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, interop.Loaded)...) if resp.Diagnostics.HasError() { return } @@ -210,7 +212,7 @@ func (r *TriggerCheckResource) Update( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Updated)...) if resp.Diagnostics.HasError() { return } @@ -227,7 +229,7 @@ var TriggerCheckID = sdkutil.Identifier{ } var ( - _ ResourceModel[checkly.TriggerCheck] = (*TriggerCheckResourceModel)(nil) + _ interop.Model[checkly.TriggerCheck] = (*TriggerCheckResourceModel)(nil) ) type TriggerCheckResourceModel struct { @@ -238,7 +240,7 @@ type TriggerCheckResourceModel struct { URL types.String `tfsdk:"url"` } -func (m *TriggerCheckResourceModel) Refresh(ctx context.Context, from *checkly.TriggerCheck, flags RefreshFlags) diag.Diagnostics { +func (m *TriggerCheckResourceModel) Refresh(ctx context.Context, from *checkly.TriggerCheck, flags interop.RefreshFlags) diag.Diagnostics { // TODO: Always update ID? CheckID, which is used for lookup, is user-modifiable, // and we could receive back a complete different ID. if flags.Created() { @@ -246,7 +248,7 @@ func (m *TriggerCheckResourceModel) Refresh(ctx context.Context, from *checkly.T } if flags.Created() || flags.Updated() { - m.LastUpdated = LastUpdatedNow() + m.LastUpdated = attributes.LastUpdatedNow() } m.CheckID = types.StringValue(from.CheckId) diff --git a/internal/provider/trigger_group_resource.go b/internal/provider/resources/trigger_group_resource.go similarity index 88% rename from internal/provider/trigger_group_resource.go rename to internal/provider/resources/trigger_group_resource.go index 5dc1a65..c9feef5 100644 --- a/internal/provider/trigger_group_resource.go +++ b/internal/provider/resources/trigger_group_resource.go @@ -1,4 +1,4 @@ -package provider +package resources import ( "context" @@ -11,6 +11,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" checkly "github.com/checkly/checkly-go-sdk" + "github.com/checkly/terraform-provider-checkly/internal/provider/interop" + "github.com/checkly/terraform-provider-checkly/internal/provider/resources/attributes" "github.com/checkly/terraform-provider-checkly/internal/sdkutil" ) @@ -43,8 +45,8 @@ func (r *TriggerGroupResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": IDResourceAttributeSchema, - "last_updated": LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, + "last_updated": attributes.LastUpdatedAttributeSchema, "group_id": schema.Int64Attribute{ Required: true, Description: "The ID of the group that you want to attach the trigger to.", @@ -68,7 +70,7 @@ func (r *TriggerGroupResource) Configure( req resource.ConfigureRequest, resp *resource.ConfigureResponse, ) { - client, diags := ClientFromProviderData(req.ProviderData) + client, diags := interop.ClientFromProviderData(req.ProviderData) if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -110,7 +112,7 @@ func (r *TriggerGroupResource) Create( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelCreated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Created)...) if resp.Diagnostics.HasError() { return } @@ -174,7 +176,7 @@ func (r *TriggerGroupResource) Read( return } - resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, ModelLoaded)...) + resp.Diagnostics.Append(state.Refresh(ctx, realizedModel, interop.Loaded)...) if resp.Diagnostics.HasError() { return } @@ -210,7 +212,7 @@ func (r *TriggerGroupResource) Update( return } - resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, ModelUpdated)...) + resp.Diagnostics.Append(plan.Refresh(ctx, realizedModel, interop.Updated)...) if resp.Diagnostics.HasError() { return } @@ -227,7 +229,7 @@ var TriggerGroupID = sdkutil.Identifier{ } var ( - _ ResourceModel[checkly.TriggerGroup] = (*TriggerGroupResourceModel)(nil) + _ interop.Model[checkly.TriggerGroup] = (*TriggerGroupResourceModel)(nil) ) type TriggerGroupResourceModel struct { @@ -238,13 +240,13 @@ type TriggerGroupResourceModel struct { URL types.String `tfsdk:"url"` } -func (m *TriggerGroupResourceModel) Refresh(ctx context.Context, from *checkly.TriggerGroup, flags RefreshFlags) diag.Diagnostics { +func (m *TriggerGroupResourceModel) Refresh(ctx context.Context, from *checkly.TriggerGroup, flags interop.RefreshFlags) diag.Diagnostics { if flags.Created() { m.ID = TriggerGroupID.IntoString(from.ID) } if flags.Created() || flags.Updated() { - m.LastUpdated = LastUpdatedNow() + m.LastUpdated = attributes.LastUpdatedNow() } m.GroupID = types.Int64Value(from.GroupId) From 4fb539e1d17d087d4dd128b69ab5af02aa52742d Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Wed, 4 Dec 2024 04:47:21 +0900 Subject: [PATCH 10/22] refactor: use more accurate name for string set utility file --- internal/provider/interop/{set.go => stringset.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename internal/provider/interop/{set.go => stringset.go} (100%) diff --git a/internal/provider/interop/set.go b/internal/provider/interop/stringset.go similarity index 100% rename from internal/provider/interop/set.go rename to internal/provider/interop/stringset.go From 9c53f12ebdadca896af0cc93c1064b6f3d2cd14f Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Wed, 4 Dec 2024 04:51:20 +0900 Subject: [PATCH 11/22] chore: remove files that have become unused --- checkly/error_with_log.go | 24 --------- checkly/helpers.go | 20 -------- checkly/provider.go | 102 -------------------------------------- 3 files changed, 146 deletions(-) delete mode 100644 checkly/error_with_log.go delete mode 100644 checkly/helpers.go delete mode 100644 checkly/provider.go diff --git a/checkly/error_with_log.go b/checkly/error_with_log.go deleted file mode 100644 index 25acb78..0000000 --- a/checkly/error_with_log.go +++ /dev/null @@ -1,24 +0,0 @@ -package checkly - -import ( - "encoding/json" - "fmt" -) - -// ErrorLog defines ErrorLog type -type ErrorLog map[string]interface{} - -// ErrorWithLog defines checkly error type -type ErrorWithLog struct { - Err string - Data *ErrorLog -} - -func (e ErrorWithLog) Error() string { - data, _ := json.Marshal(e.Data) - return fmt.Sprintf("%s [%s]", e.Err, data) -} - -func makeError(err string, l *ErrorLog) ErrorWithLog { - return ErrorWithLog{err, l} -} diff --git a/checkly/helpers.go b/checkly/helpers.go deleted file mode 100644 index a9233e4..0000000 --- a/checkly/helpers.go +++ /dev/null @@ -1,20 +0,0 @@ -package checkly - -import ( - "os" - "strconv" - "time" -) - -func apiCallTimeout() time.Duration { - timeout := os.Getenv("API_CALL_TIMEOUT") - if timeout != "" { - v, err := strconv.ParseInt(timeout, 10, 64) - if err != nil || v < 1 { - panic("Invalid API_CALL_TIMEOUT value, must be a positive number") - } else { - return time.Duration(v) * time.Second - } - } - return 15 * time.Second -} diff --git a/checkly/provider.go b/checkly/provider.go deleted file mode 100644 index 797126b..0000000 --- a/checkly/provider.go +++ /dev/null @@ -1,102 +0,0 @@ -package checkly - -import ( - "fmt" - "io" - "os" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - "github.com/checkly/checkly-go-sdk" -) - -// Provider makes the provider available to Terraform. -func Provider() *schema.Provider { - return &schema.Provider{ - Schema: map[string]*schema.Schema{ - "api_key": { - Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc("CHECKLY_API_KEY", nil), - }, - "api_url": { - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("CHECKLY_API_URL", nil), - }, - "account_id": { - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("CHECKLY_ACCOUNT_ID", nil), - }, - }, - ResourcesMap: map[string]*schema.Resource{ - "checkly_check": resourceCheck(), - "checkly_heartbeat": resourceHeartbeat(), - "checkly_check_group": resourceCheckGroup(), - "checkly_snippet": resourceSnippet(), - "checkly_dashboard": resourceDashboard(), - "checkly_maintenance_windows": resourceMaintenanceWindow(), - "checkly_alert_channel": resourceAlertChannel(), - "checkly_trigger_check": resourceTriggerCheck(), - "checkly_trigger_group": resourceTriggerGroup(), - "checkly_environment_variable": resourceEnvironmentVariable(), - "checkly_private_location": resourcePrivateLocation(), - }, - DataSourcesMap: map[string]*schema.Resource{ - "checkly_static_ips": dataSourceStaticIPs(), - }, - ConfigureFunc: func(r *schema.ResourceData) (interface{}, error) { - debugLog := os.Getenv("CHECKLY_DEBUG_LOG") - var debugOutput io.Writer - if debugLog != "" { - debugFile, err := os.OpenFile(debugLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) - if err != nil { - panic(fmt.Sprintf("can't write to debug log file: %v", err)) - } - debugOutput = debugFile - } - - apiKey := "" - switch v := r.Get("api_key").(type) { - case string: - apiKey = v - } - - apiUrl := "" - switch v := r.Get("api_url").(type) { - case string: - apiUrl = v - } - - if apiUrl == "" { - apiUrl = "https://api.checklyhq.com" - } - - client := checkly.NewClient( - apiUrl, - apiKey, - nil, - debugOutput, - ) - - accountId := "" - switch v := r.Get("account_id").(type) { - case string: - accountId = v - } - if accountId != "" { - client.SetAccountId(accountId) - } - - checklyApiSource := os.Getenv("CHECKLY_API_SOURCE") - if checklyApiSource != "" { - client.SetChecklySource(checklyApiSource) - } else { - client.SetChecklySource("TF") - } - - return client, nil - }, - } -} From dc1869aa7b6c9184a0cc3b222d17cd81b2a15a5e Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Sat, 7 Dec 2024 01:34:01 +0900 Subject: [PATCH 12/22] chore: go mod tidy --- go.mod | 25 +++---------------------- go.sum | 55 +------------------------------------------------------ 2 files changed, 4 insertions(+), 76 deletions(-) diff --git a/go.mod b/go.mod index b9b4e5f..c829ac1 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.23.3 require ( github.com/hashicorp/terraform-plugin-framework v1.13.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-log v0.9.0 ) @@ -15,7 +14,6 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-cmp v0.6.0 github.com/gruntwork-io/terratest v0.41.16 - github.com/hashicorp/terraform-plugin-docs v0.19.4 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 ) @@ -24,18 +22,10 @@ require ( cloud.google.com/go/compute/metadata v0.5.0 // indirect cloud.google.com/go/iam v0.7.0 // indirect cloud.google.com/go/storage v1.27.0 // indirect - github.com/BurntSushi/toml v1.2.1 // indirect - github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect - github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect - github.com/armon/go-radix v1.0.0 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect - github.com/bgentry/speakeasy v0.1.0 // indirect - github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.16.0 // indirect @@ -43,7 +33,6 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect - github.com/hashicorp/cli v1.1.6 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -61,17 +50,16 @@ require ( 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-plugin-go v0.25.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 - github.com/huandu/xstrings v1.3.3 // indirect - github.com/imdario/mergo v0.3.15 // indirect github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.15.11 // indirect + github.com/kr/pretty v0.3.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -81,22 +69,16 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/posener/complete v1.2.3 // indirect - github.com/shopspring/decimal v1.3.1 // indirect - github.com/spf13/cast v1.5.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/stretchr/testify v1.8.3 // indirect github.com/tmccombs/hcl2json v0.3.3 // indirect github.com/ulikunitz/xz v0.5.10 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/yuin/goldmark v1.7.1 // indirect - github.com/yuin/goldmark-meta v1.1.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect - go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.26.0 // indirect - golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/mod v0.19.0 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect @@ -110,6 +92,5 @@ require ( google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c // indirect google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 64d6913..f1116c8 100644 --- a/go.sum +++ b/go.sum @@ -187,17 +187,7 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= -github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -213,16 +203,10 @@ github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJE github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= -github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= -github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= -github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -268,8 +252,6 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= @@ -359,7 +341,6 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -383,8 +364,6 @@ github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/gruntwork-io/terratest v0.41.16 h1:s3ZyyVLWwrZ3KD44VIVHidBZI0Kpr9RkdqY8VEYIEIU= github.com/gruntwork-io/terratest v0.41.16/go.mod h1:O6gajNBjO1wvc7Wl9WtbO+ORcdnhAV2GQiBE71ycwIk= -github.com/hashicorp/cli v1.1.6 h1:CMOV+/LJfL1tXCOKrgAX0uRKnzjj/mpmqNXloRSy2K8= -github.com/hashicorp/cli v1.1.6/go.mod h1:MPon5QYlgjjo0BSoAiN0ESeT5fRzDjVRp+uioJ0piz4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -399,7 +378,6 @@ github.com/hashicorp/go-getter v1.7.1 h1:SWiSWN/42qdpR0MdhaOc/bLR48PLuP1ZQtYLRlM github.com/hashicorp/go-getter v1.7.1/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 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.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= @@ -427,8 +405,6 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW 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-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c= -github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA= 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-validators v0.15.0 h1:RXMmu7JgpFjnI1a5QjMCBb11usrW2OtAG+iOTIj5c9Y= @@ -445,13 +421,8 @@ github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= -github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= @@ -491,11 +462,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 h1:ofNAzWCcyTALn2Zv40+8XitdzCgXY6e9qvXwN9W0YXg= github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -507,7 +475,6 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= @@ -517,25 +484,18 @@ github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFz github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -572,10 +532,6 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= -github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= -github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.8.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= @@ -584,8 +540,6 @@ github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgr github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= -go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= -go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -603,7 +557,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= 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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -616,8 +569,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -694,7 +645,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -807,14 +757,12 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.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/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1123,7 +1071,6 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From f03271ab6c2f0680f3880677ae50d54a531ab45d Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Sat, 7 Dec 2024 03:49:54 +0900 Subject: [PATCH 13/22] feat: move component registration to a separate package This way future tests from child packages can refer to provider.New() without introducing circular references. It also makes some potentially interesting testing capabilities possible. --- internal/provider/globalregistry/registry.go | 69 ++++++++++++++++++++ internal/provider/provider.go | 37 ++--------- internal/provider/provider_test.go | 35 ++++++++++ internal/provider/registry.go | 15 +++++ main.go | 7 +- 5 files changed, 129 insertions(+), 34 deletions(-) create mode 100644 internal/provider/globalregistry/registry.go create mode 100644 internal/provider/provider_test.go create mode 100644 internal/provider/registry.go diff --git a/internal/provider/globalregistry/registry.go b/internal/provider/globalregistry/registry.go new file mode 100644 index 0000000..9346e54 --- /dev/null +++ b/internal/provider/globalregistry/registry.go @@ -0,0 +1,69 @@ +package globalregistry + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/checkly/terraform-provider-checkly/internal/provider" + "github.com/checkly/terraform-provider-checkly/internal/provider/datasources" + "github.com/checkly/terraform-provider-checkly/internal/provider/resources" +) + +var ( + _ provider.Registry = (*registry)(nil) +) + +type registry struct { + resources []func() resource.Resource + dataSources []func() datasource.DataSource + functions []func() function.Function +} + +func (r *registry) Resources(ctx context.Context) []func() resource.Resource { + return r.resources +} + +func (r *registry) DataSources(ctx context.Context) []func() datasource.DataSource { + return r.dataSources +} + +func (r *registry) Functions(ctx context.Context) []func() function.Function { + return r.functions +} + +func (r *registry) RegisterResource(factory ...func() resource.Resource) { + r.resources = append(r.resources, factory...) +} + +func (r *registry) RegisterDataSource(factory ...func() datasource.DataSource) { + r.dataSources = append(r.dataSources, factory...) +} + +func (r *registry) RegisterFunction(factory ...func() function.Function) { + r.functions = append(r.functions, factory...) +} + +var Registry = new(registry) + +func init() { + Registry.RegisterResource( + resources.NewAlertChannelResource, + resources.NewCheckGroupResource, + resources.NewCheckResource, + resources.NewDashboardResource, + resources.NewEnvironmentVariableResource, + resources.NewHeartbeatResource, + resources.NewMaintenanceWindowsResource, + resources.NewPrivateLocationResource, + resources.NewSnippetResource, + resources.NewTriggerCheckResource, + resources.NewTriggerGroupResource, + ) + + Registry.RegisterDataSource( + datasources.NewStaticIPsDataSource, + ) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index d294588..d19c875 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -6,18 +6,13 @@ import ( "io" "os" - "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" - "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" checkly "github.com/checkly/checkly-go-sdk" - "github.com/checkly/terraform-provider-checkly/internal/provider/datasources" - "github.com/checkly/terraform-provider-checkly/internal/provider/resources" ) const ( @@ -31,6 +26,7 @@ var ( type ChecklyProvider struct { version string + Registry } type ChecklyProviderModel struct { @@ -202,36 +198,11 @@ func (p *ChecklyProvider) Configure( resp.ResourceData = client } -func (p *ChecklyProvider) Resources(ctx context.Context) []func() resource.Resource { - return []func() resource.Resource{ - resources.NewAlertChannelResource, - resources.NewCheckGroupResource, - resources.NewCheckResource, - resources.NewDashboardResource, - resources.NewEnvironmentVariableResource, - resources.NewHeartbeatResource, - resources.NewMaintenanceWindowsResource, - resources.NewPrivateLocationResource, - resources.NewSnippetResource, - resources.NewTriggerCheckResource, - resources.NewTriggerGroupResource, - } -} - -func (p *ChecklyProvider) DataSources(ctx context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{ - datasources.NewStaticIPsDataSource, - } -} - -func (p *ChecklyProvider) Functions(ctx context.Context) []func() function.Function { - return []func() function.Function{} -} - -func New(version string) func() provider.Provider { +func New(version string, registry Registry) func() provider.Provider { return func() provider.Provider { return &ChecklyProvider{ - version: version, + version: version, + Registry: registry, } } } diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go new file mode 100644 index 0000000..c084601 --- /dev/null +++ b/internal/provider/provider_test.go @@ -0,0 +1,35 @@ +package provider + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func protoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { + return map[string]func() (tfprotov6.ProviderServer, error){ + "checkly": providerserver.NewProtocol6WithError(New("test")()), + } +} + +func TestProviderConfigMissingAPIKey(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: ` + provider "checkly" { + } + + // Must use the provider or it won't get configured. + data "checkly_static_ips" "test" {} + `, + ExpectError: regexp.MustCompile("Missing Checkly API Key"), + }, + }, + }) +} diff --git a/internal/provider/registry.go b/internal/provider/registry.go new file mode 100644 index 0000000..a7bd5af --- /dev/null +++ b/internal/provider/registry.go @@ -0,0 +1,15 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +type Registry interface { + Resources(ctx context.Context) []func() resource.Resource + DataSources(ctx context.Context) []func() datasource.DataSource + Functions(ctx context.Context) []func() function.Function +} diff --git a/main.go b/main.go index ebc129e..69ce3c8 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/checkly/terraform-provider-checkly/internal/provider" + "github.com/checkly/terraform-provider-checkly/internal/provider/globalregistry" ) var ( @@ -25,7 +26,11 @@ func main() { Debug: debug, } - err := providerserver.Serve(context.Background(), provider.New(version), opts) + err := providerserver.Serve( + context.Background(), + provider.New(version, globalregistry.Registry), + opts, + ) if err != nil { log.Fatal(err.Error()) From b5a674c11cc0e06e1f688f017c6c577f1d9daa7b Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Sat, 7 Dec 2024 03:53:00 +0900 Subject: [PATCH 14/22] fix: ClientFromProviderData must allow nil ProviderData --- internal/provider/interop/data.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/internal/provider/interop/data.go b/internal/provider/interop/data.go index 316d84f..41aa7ff 100644 --- a/internal/provider/interop/data.go +++ b/internal/provider/interop/data.go @@ -9,14 +9,9 @@ import ( ) func ClientFromProviderData(providerData any) (checkly.Client, diag.Diagnostics) { + // Not set when called via ValidateResourceConfig RPC. if providerData == nil { - return nil, diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Missing Configure Type", - "Expected checkly.Client, got nil. Please report this issue "+ - "to the provider developers.", - ), - } + return nil, nil } client, ok := providerData.(checkly.Client) From 44e087a83027b67969727b7e83d0d901b3dda0e3 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Sat, 7 Dec 2024 03:55:56 +0900 Subject: [PATCH 15/22] feat: add first provider tests Also added the possibility for the provider to ignore environment variables if requested. This makes it possible to test scenarios where the various configuration options are not set, even if environment variables have to be set for the rest of the test suite. --- go.mod | 21 ++++++++------- go.sum | 42 ++++++++++++++++-------------- internal/provider/options.go | 27 +++++++++++++++++++ internal/provider/provider.go | 37 +++++++++++++++++++++++--- internal/provider/provider_test.go | 34 +++++++++++++++++++++--- 5 files changed, 123 insertions(+), 38 deletions(-) create mode 100644 internal/provider/options.go diff --git a/go.mod b/go.mod index c829ac1..f113ba2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,9 @@ go 1.23.3 require ( github.com/hashicorp/terraform-plugin-framework v1.13.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-log v0.9.0 + github.com/hashicorp/terraform-plugin-testing v1.11.0 ) require ( @@ -14,7 +16,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-cmp v0.6.0 github.com/gruntwork-io/terratest v0.41.16 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 ) require ( @@ -45,12 +47,11 @@ require ( github.com/hashicorp/go-safetemp v1.0.0 // 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-plugin-go v0.25.0 // indirect + github.com/hashicorp/terraform-json v0.23.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 @@ -78,13 +79,13 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.26.0 // indirect - golang.org/x/mod v0.19.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/oauth2 v0.22.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.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/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.103.0 // indirect diff --git a/go.sum b/go.sum index f1116c8..b8a4099 100644 --- a/go.sum +++ b/go.sum @@ -394,17 +394,17 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -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/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.9.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= -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/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-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-validators v0.15.0 h1:RXMmu7JgpFjnI1a5QjMCBb11usrW2OtAG+iOTIj5c9Y= @@ -413,8 +413,10 @@ github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974r 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-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= @@ -557,8 +559,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -595,8 +597,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -688,8 +690,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/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.0.0-20220929204114-8fcdb60fdcc0/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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -758,13 +760,13 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.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/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -776,8 +778,8 @@ golang.org/x/text v0.3.6/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -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.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/internal/provider/options.go b/internal/provider/options.go new file mode 100644 index 0000000..b5016b3 --- /dev/null +++ b/internal/provider/options.go @@ -0,0 +1,27 @@ +package provider + +type Options struct { + UseCredentialsFromEnvironment bool +} + +func DefaultOptions() *Options { + return &Options{ + UseCredentialsFromEnvironment: true, + } +} + +type Option interface { + Apply(opts *Options) +} + +type OptionFunc func(opts *Options) + +func (f OptionFunc) Apply(opts *Options) { + f(opts) +} + +func WithUseCredentialsFromEnvironment(enabled bool) Option { + return OptionFunc(func(opts *Options) { + opts.UseCredentialsFromEnvironment = enabled + }) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index d19c875..88e805e 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "strings" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/provider" @@ -26,6 +27,7 @@ var ( type ChecklyProvider struct { version string + options *Options Registry } @@ -70,6 +72,8 @@ func (p *ChecklyProvider) Configure( req provider.ConfigureRequest, resp *provider.ConfigureResponse, ) { + tflog.Debug(ctx, "Configuring provider") + var config ChecklyProviderModel resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) @@ -117,9 +121,15 @@ func (p *ChecklyProvider) Configure( return } - apiKey := os.Getenv("CHECKLY_API_KEY") - apiURL := os.Getenv("CHECKLY_API_URL") - accountID := os.Getenv("CHECKLY_ACCOUNT_ID") + var apiKey string + var apiURL string + var accountID string + + if p.options.UseCredentialsFromEnvironment { + apiKey = os.Getenv("CHECKLY_API_KEY") + apiURL = os.Getenv("CHECKLY_API_URL") + accountID = os.Getenv("CHECKLY_ACCOUNT_ID") + } if !config.APIKey.IsNull() { apiKey = config.APIKey.ValueString() @@ -145,6 +155,19 @@ func (p *ChecklyProvider) Configure( ) } + if strings.HasPrefix(apiKey, "cu_") && accountID == "" { + resp.Diagnostics.AddAttributeError( + path.Root("account_id"), + "Missing Checkly Account ID", + "The provider cannot create the Checkly API client as there is "+ + "a missing or empty value for the Checkly Account ID, which "+ + `is required when using User API Keys (keys with "cu_" `+ + "prefix). Set the value in the configuration or use the "+ + "CHECKLY_ACCOUNT_ID environment variable. If either is already "+ + "set, ensure the value is not empty.", + ) + } + if apiURL == "" { apiURL = defaultAPIURL } @@ -198,10 +221,16 @@ func (p *ChecklyProvider) Configure( resp.ResourceData = client } -func New(version string, registry Registry) func() provider.Provider { +func New(version string, registry Registry, options ...Option) func() provider.Provider { + opts := DefaultOptions() + for _, opt := range options { + opt.Apply(opts) + } + return func() provider.Provider { return &ChecklyProvider{ version: version, + options: opts, Registry: registry, } } diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index c084601..01c343b 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -1,23 +1,27 @@ -package provider +package provider_test import ( "regexp" "testing" + "github.com/checkly/terraform-provider-checkly/internal/provider" + "github.com/checkly/terraform-provider-checkly/internal/provider/globalregistry" "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -func protoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { +func protoV6ProviderFactories(options ...provider.Option) map[string]func() (tfprotov6.ProviderServer, error) { return map[string]func() (tfprotov6.ProviderServer, error){ - "checkly": providerserver.NewProtocol6WithError(New("test")()), + "checkly": providerserver.NewProtocol6WithError(provider.New("test", globalregistry.Registry, options...)()), } } func TestProviderConfigMissingAPIKey(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: protoV6ProviderFactories(), + ProtoV6ProviderFactories: protoV6ProviderFactories( + provider.WithUseCredentialsFromEnvironment(false), + ), Steps: []resource.TestStep{ { @@ -33,3 +37,25 @@ func TestProviderConfigMissingAPIKey(t *testing.T) { }, }) } + +func TestProviderConfigMissingAccountIDWithUserAPIKey(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories( + provider.WithUseCredentialsFromEnvironment(false), + ), + + Steps: []resource.TestStep{ + { + Config: ` + provider "checkly" { + api_key = "cu_foo" + } + + // Must use the provider or it won't get configured. + data "checkly_static_ips" "test" {} + `, + ExpectError: regexp.MustCompile("Missing Checkly Account ID"), + }, + }, + }) +} From e17ef61d03cfa492b456d24a3587a8bd0507e7bb Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Sat, 7 Dec 2024 03:58:52 +0900 Subject: [PATCH 16/22] feat: convert static_ips data source tests --- checkly/data_source_static_ips_test.go | 135 -------------- .../static_ips_data_source_test.go | 173 ++++++++++++++++++ 2 files changed, 173 insertions(+), 135 deletions(-) delete mode 100644 checkly/data_source_static_ips_test.go create mode 100644 internal/provider/datasources/static_ips_data_source_test.go diff --git a/checkly/data_source_static_ips_test.go b/checkly/data_source_static_ips_test.go deleted file mode 100644 index 0ba4bf4..0000000 --- a/checkly/data_source_static_ips_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package checkly - -import ( - "regexp" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAccStaticIPsAll(t *testing.T) { - config := `data "checkly_static_ips" "test" {}` - - accTestCase(t, []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "data.checkly_static_ips.test", - "addresses.#", - "162", - ), - ), - }, - }) -} - -func TestAccStaticIPsTwoRegionsOnly(t *testing.T) { - config := `data "checkly_static_ips" "test" { - locations = ["us-east-1","ap-southeast-1"] - }` - - accTestCase(t, []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "data.checkly_static_ips.test", - "addresses.#", - "20", - ), - ), - }, - }) -} - -func TestAccStaticIPsIPv6Only(t *testing.T) { - config := `data "checkly_static_ips" "test" { - ip_family = "IPv6" - }` - - accTestCase(t, []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "data.checkly_static_ips.test", - "addresses.#", - "22", - ), - ), - }, - }) -} - -func TestAccStaticIPsIPv4Only(t *testing.T) { - config := `data "checkly_static_ips" "test" { - ip_family = "IPv4" - }` - - accTestCase(t, []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "data.checkly_static_ips.test", - "addresses.#", - "140", - ), - ), - }, - }) -} - -func TestAccStaticIPsIPv6AndOneRegionOnly(t *testing.T) { - config := `data "checkly_static_ips" "test" { - ip_family = "IPv6" - locations = ["us-east-1"] - }` - - accTestCase(t, []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "data.checkly_static_ips.test", - "addresses.#", - "1", - ), - ), - }, - }) -} - -func TestAccStaticIPsIPv4AndOneRegionOnly(t *testing.T) { - config := `data "checkly_static_ips" "test" { - ip_family = "IPv4" - locations = ["us-east-1"] - }` - - accTestCase(t, []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "data.checkly_static_ips.test", - "addresses.#", - "12", - ), - ), - }, - }) -} - -func TestAccStaticIPsInvalidIPFamily(t *testing.T) { - config := `data "checkly_static_ips" "test" { - ip_family = "invalid" - }` - - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`"ip_family" must be either "IPv6" or "IPv4"`), - }, - }) -} diff --git a/internal/provider/datasources/static_ips_data_source_test.go b/internal/provider/datasources/static_ips_data_source_test.go new file mode 100644 index 0000000..4f0a2bd --- /dev/null +++ b/internal/provider/datasources/static_ips_data_source_test.go @@ -0,0 +1,173 @@ +package datasources_test + +import ( + "regexp" + "testing" + + "github.com/checkly/terraform-provider-checkly/internal/provider" + "github.com/checkly/terraform-provider-checkly/internal/provider/globalregistry" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func protoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { + return map[string]func() (tfprotov6.ProviderServer, error){ + "checkly": providerserver.NewProtocol6WithError(provider.New("test", globalregistry.Registry)()), + } +} + +func TestAccStaticIPsAll(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: ` + data "checkly_static_ips" "test" {} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr( + "data.checkly_static_ips.test", + "addresses.#", + "162", + ), + ), + }, + }, + }) +} + +func TestAccStaticIPsTwoRegionsOnly(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: ` + data "checkly_static_ips" "test" { + locations = ["us-east-1","ap-southeast-1"] + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr( + "data.checkly_static_ips.test", + "addresses.#", + "20", + ), + ), + }, + }, + }) +} + +func TestAccStaticIPsIPv6Only(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: ` + data "checkly_static_ips" "test" { + ip_family = "IPv6" + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr( + "data.checkly_static_ips.test", + "addresses.#", + "22", + ), + ), + }, + }, + }) +} + +func TestAccStaticIPsIPv4Only(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: ` + data "checkly_static_ips" "test" { + ip_family = "IPv4" + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr( + "data.checkly_static_ips.test", + "addresses.#", + "140", + ), + ), + }, + }, + }) +} + +func TestAccStaticIPsIPv6AndOneRegionOnly(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: ` + data "checkly_static_ips" "test" { + ip_family = "IPv6" + locations = ["us-east-1"] + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr( + "data.checkly_static_ips.test", + "addresses.#", + "1", + ), + ), + }, + }, + }) +} + +func TestAccStaticIPsIPv4AndOneRegionOnly(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: ` + data "checkly_static_ips" "test" { + ip_family = "IPv4" + locations = ["us-east-1"] + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr( + "data.checkly_static_ips.test", + "addresses.#", + "12", + ), + ), + }, + }, + }) +} + +func TestAccStaticIPsInvalidIPFamily(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: ` + data "checkly_static_ips" "test" { + ip_family = "invalid" + } + `, + ExpectError: regexp.MustCompile(`Invalid Attribute Value Match`), + }, + }, + }) +} From 9f6dd5b55d76c227df7474a0f25b23b0d872e02a Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Sat, 7 Dec 2024 04:02:44 +0900 Subject: [PATCH 17/22] chore: remove old provider test file --- checkly/provider_test.go | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 checkly/provider_test.go diff --git a/checkly/provider_test.go b/checkly/provider_test.go deleted file mode 100644 index 4704aa9..0000000 --- a/checkly/provider_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package checkly - -import ( - "testing" -) - -func TestProvider(t *testing.T) { - if err := Provider().InternalValidate(); err != nil { - t.Fatalf("err: %s", err) - } -} From 1c19375eea91bbcabdbfda407e148a4603f327cc Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Sat, 7 Dec 2024 04:14:14 +0900 Subject: [PATCH 18/22] feat: convert snippet resource tests --- checkly/resource_snippets_test.go | 46 ---------------- internal/provider/resources/shared_test.go | 14 +++++ .../resources/snippet_resource_test.go | 54 +++++++++++++++++++ 3 files changed, 68 insertions(+), 46 deletions(-) delete mode 100644 checkly/resource_snippets_test.go create mode 100644 internal/provider/resources/shared_test.go create mode 100644 internal/provider/resources/snippet_resource_test.go diff --git a/checkly/resource_snippets_test.go b/checkly/resource_snippets_test.go deleted file mode 100644 index 08536db..0000000 --- a/checkly/resource_snippets_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package checkly - -import ( - "regexp" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAccSnippetCheckRequiredFields(t *testing.T) { - config := `resource "checkly_snippet" "test" {}` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "name" is required`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "script" is required`), - }, - }) -} - -func TestAccSnippetSuccess(t *testing.T) { - config := `resource "checkly_snippet" "test" { - name = "foo" - script = "console.log('bar')" - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "checkly_snippet.test", - "name", - "foo", - ), - resource.TestCheckResourceAttr( - "checkly_snippet.test", - "script", - "console.log('bar')", - ), - ), - }, - }) -} diff --git a/internal/provider/resources/shared_test.go b/internal/provider/resources/shared_test.go new file mode 100644 index 0000000..e6d6422 --- /dev/null +++ b/internal/provider/resources/shared_test.go @@ -0,0 +1,14 @@ +package resources_test + +import ( + "github.com/checkly/terraform-provider-checkly/internal/provider" + "github.com/checkly/terraform-provider-checkly/internal/provider/globalregistry" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" +) + +func protoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { + return map[string]func() (tfprotov6.ProviderServer, error){ + "checkly": providerserver.NewProtocol6WithError(provider.New("test", globalregistry.Registry)()), + } +} diff --git a/internal/provider/resources/snippet_resource_test.go b/internal/provider/resources/snippet_resource_test.go new file mode 100644 index 0000000..9daab74 --- /dev/null +++ b/internal/provider/resources/snippet_resource_test.go @@ -0,0 +1,54 @@ +package resources_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccSnippetCheckRequiredFields(t *testing.T) { + config := `resource "checkly_snippet" "test" {}` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`The argument "name" is required`), + }, + { + Config: config, + ExpectError: regexp.MustCompile(`The argument "script" is required`), + }, + }, + }) +} + +func TestAccSnippetSuccess(t *testing.T) { + config := `resource "checkly_snippet" "test" { + name = "foo" + script = "console.log('bar')" + }` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "checkly_snippet.test", + "name", + "foo", + ), + resource.TestCheckResourceAttr( + "checkly_snippet.test", + "script", + "console.log('bar')", + ), + ), + }, + }, + }) +} From 7e915e3375690e40e8be8908f552738502c726db Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Sat, 7 Dec 2024 04:16:32 +0900 Subject: [PATCH 19/22] chore: reorder imports so that our own code is in its own section --- internal/provider/datasources/static_ips_data_source_test.go | 5 +++-- internal/provider/provider_test.go | 5 +++-- internal/provider/resources/shared_test.go | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/internal/provider/datasources/static_ips_data_source_test.go b/internal/provider/datasources/static_ips_data_source_test.go index 4f0a2bd..f92361f 100644 --- a/internal/provider/datasources/static_ips_data_source_test.go +++ b/internal/provider/datasources/static_ips_data_source_test.go @@ -4,11 +4,12 @@ import ( "regexp" "testing" - "github.com/checkly/terraform-provider-checkly/internal/provider" - "github.com/checkly/terraform-provider-checkly/internal/provider/globalregistry" "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/checkly/terraform-provider-checkly/internal/provider" + "github.com/checkly/terraform-provider-checkly/internal/provider/globalregistry" ) func protoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index 01c343b..6084cf1 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -4,11 +4,12 @@ import ( "regexp" "testing" - "github.com/checkly/terraform-provider-checkly/internal/provider" - "github.com/checkly/terraform-provider-checkly/internal/provider/globalregistry" "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/checkly/terraform-provider-checkly/internal/provider" + "github.com/checkly/terraform-provider-checkly/internal/provider/globalregistry" ) func protoV6ProviderFactories(options ...provider.Option) map[string]func() (tfprotov6.ProviderServer, error) { diff --git a/internal/provider/resources/shared_test.go b/internal/provider/resources/shared_test.go index e6d6422..59ac909 100644 --- a/internal/provider/resources/shared_test.go +++ b/internal/provider/resources/shared_test.go @@ -1,10 +1,11 @@ package resources_test import ( - "github.com/checkly/terraform-provider-checkly/internal/provider" - "github.com/checkly/terraform-provider-checkly/internal/provider/globalregistry" "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/checkly/terraform-provider-checkly/internal/provider" + "github.com/checkly/terraform-provider-checkly/internal/provider/globalregistry" ) func protoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { From f01b2868ed6645de6079f24138acf1118cde1c74 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Sat, 7 Dec 2024 04:21:45 +0900 Subject: [PATCH 20/22] feat: convert private_location resource tests --- checkly/resource_private_locations_test.go | 81 ---------------- .../private_location_resource_test.go | 93 +++++++++++++++++++ 2 files changed, 93 insertions(+), 81 deletions(-) delete mode 100644 checkly/resource_private_locations_test.go create mode 100644 internal/provider/resources/private_location_resource_test.go diff --git a/checkly/resource_private_locations_test.go b/checkly/resource_private_locations_test.go deleted file mode 100644 index d5e5f4e..0000000 --- a/checkly/resource_private_locations_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package checkly - -import ( - "regexp" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAccPrivateLocationCheckRequiredFields(t *testing.T) { - config := `resource "checkly_private_location" "test" {}` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "name" is required`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "slug_name" is required`), - }, - }) -} - -func TestAccPrivateLocationSuccess(t *testing.T) { - config := `resource "checkly_private_location" "test" { - name = "New Private Location" - slug_name = "new-private-location" - icon = "bell-fill" - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "checkly_private_location.test", - "name", - "New Private Location", - ), - resource.TestCheckResourceAttr( - "checkly_private_location.test", - "slug_name", - "new-private-location", - ), - resource.TestCheckResourceAttr( - "checkly_private_location.test", - "icon", - "bell-fill", - ), - ), - }, - }) -} - -func TestAccPrivateLocationDefaultIcon(t *testing.T) { - config := `resource "checkly_private_location" "without_icon" { - name = "New Private Location" - slug_name = "new-private-location" - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "checkly_private_location.without_icon", - "name", - "New Private Location", - ), - resource.TestCheckResourceAttr( - "checkly_private_location.without_icon", - "slug_name", - "new-private-location", - ), - resource.TestCheckResourceAttr( - "checkly_private_location.without_icon", - "icon", - "location", - ), - ), - }, - }) -} diff --git a/internal/provider/resources/private_location_resource_test.go b/internal/provider/resources/private_location_resource_test.go new file mode 100644 index 0000000..37a2fb3 --- /dev/null +++ b/internal/provider/resources/private_location_resource_test.go @@ -0,0 +1,93 @@ +package resources_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccPrivateLocationCheckRequiredFields(t *testing.T) { + config := `resource "checkly_private_location" "test" {}` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`The argument "name" is required`), + }, + { + Config: config, + ExpectError: regexp.MustCompile(`The argument "slug_name" is required`), + }, + }, + }) +} + +func TestAccPrivateLocationSuccess(t *testing.T) { + config := `resource "checkly_private_location" "test" { + name = "New Private Location" + slug_name = "new-private-location" + icon = "bell-fill" + }` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "checkly_private_location.test", + "name", + "New Private Location", + ), + resource.TestCheckResourceAttr( + "checkly_private_location.test", + "slug_name", + "new-private-location", + ), + resource.TestCheckResourceAttr( + "checkly_private_location.test", + "icon", + "bell-fill", + ), + ), + }, + }, + }) +} + +func TestAccPrivateLocationDefaultIcon(t *testing.T) { + config := `resource "checkly_private_location" "without_icon" { + name = "New Private Location" + slug_name = "new-private-location" + }` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "checkly_private_location.without_icon", + "name", + "New Private Location", + ), + resource.TestCheckResourceAttr( + "checkly_private_location.without_icon", + "slug_name", + "new-private-location", + ), + resource.TestCheckResourceAttr( + "checkly_private_location.without_icon", + "icon", + "location", + ), + ), + }, + }, + }) +} From c4c8f40598094a999666197badf8d6505c906562 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Sat, 7 Dec 2024 04:44:40 +0900 Subject: [PATCH 21/22] feat: convert environment variable resource tests and fix behavior --- checkly/resource_environment_variable_test.go | 58 --------------- .../environment_variable_resource.go | 8 ++- .../environment_variable_resource_test.go | 70 +++++++++++++++++++ 3 files changed, 77 insertions(+), 59 deletions(-) delete mode 100644 checkly/resource_environment_variable_test.go create mode 100644 internal/provider/resources/environment_variable_resource_test.go diff --git a/checkly/resource_environment_variable_test.go b/checkly/resource_environment_variable_test.go deleted file mode 100644 index 09fbccf..0000000 --- a/checkly/resource_environment_variable_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package checkly - -import ( - "regexp" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAccEnvVarCheckRequiredFields(t *testing.T) { - config := `resource "checkly_environment_variable" "test" {}` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "key" is required`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "value" is required`), - }, - }) -} - -func TestAccEnvVarSuccess(t *testing.T) { - config := `resource "checkly_environment_variable" "test" { - key = "API_URL" - value = "https://api.checklyhq.com" - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "checkly_environment_variable.test", - "key", - "API_URL", - ), - resource.TestCheckResourceAttr( - "checkly_environment_variable.test", - "value", - "https://api.checklyhq.com", - ), - ), - }, - }) -} - -func TestAccSecretEnvVarSuccess(t *testing.T) { - accTestCase(t, []resource.TestStep{ - { - Config: `resource "checkly_environment_variable" "test" { - key = "SECRET" - value = "https://api.checklyhq.com" - secret = true - }`, - }, - }) -} diff --git a/internal/provider/resources/environment_variable_resource.go b/internal/provider/resources/environment_variable_resource.go index 3870a39..4bbae46 100644 --- a/internal/provider/resources/environment_variable_resource.go +++ b/internal/provider/resources/environment_variable_resource.go @@ -267,10 +267,16 @@ func (m *EnvironmentVariableResourceModel) Refresh(ctx context.Context, from *ch } m.Key = types.StringValue(from.Key) - m.Value = types.StringValue(from.Value) m.Locked = types.BoolValue(from.Locked) m.Secret = types.BoolValue(from.Secret) + // We can never receive a secret value back from the server. Just assume + // the value is still unchanged and only update state if we're not dealing + // with a secret. + if !from.Secret { + m.Value = types.StringValue(from.Value) + } + return nil } diff --git a/internal/provider/resources/environment_variable_resource_test.go b/internal/provider/resources/environment_variable_resource_test.go new file mode 100644 index 0000000..99bd50a --- /dev/null +++ b/internal/provider/resources/environment_variable_resource_test.go @@ -0,0 +1,70 @@ +package resources_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccEnvVarCheckRequiredFields(t *testing.T) { + config := `resource "checkly_environment_variable" "test" {}` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`The argument "key" is required`), + }, + { + Config: config, + ExpectError: regexp.MustCompile(`The argument "value" is required`), + }, + }, + }) +} + +func TestAccEnvVarSuccess(t *testing.T) { + config := `resource "checkly_environment_variable" "test" { + key = "API_URL" + value = "https://api.checklyhq.com" + }` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "checkly_environment_variable.test", + "key", + "API_URL", + ), + resource.TestCheckResourceAttr( + "checkly_environment_variable.test", + "value", + "https://api.checklyhq.com", + ), + ), + }, + }, + }) +} + +func TestAccSecretEnvVarSuccess(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: `resource "checkly_environment_variable" "test" { + key = "SECRET" + value = "https://api.checklyhq.com" + secret = true + }`, + }, + }, + }) +} From 0391be7d7650f9bc902d6932b4e936168c9905ad Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Tue, 14 Jan 2025 16:27:40 +0900 Subject: [PATCH 22/22] feat: convert the rest of the codebase with new abstractions Tests do not pass yet. --- GNUmakefile | 4 +- checkly/integration_test.go | 50 - checkly/resource_alert_channel_test.go | 202 ---- checkly/resource_check_group_test.go | 427 --------- checkly/resource_check_test.go | 698 -------------- checkly/resource_heartbeat_test.go | 192 ---- checkly/test_util.go | 65 -- docs/index.md | 6 +- docs/resources/alert_channel.md | 34 +- docs/resources/check.md | 46 +- docs/resources/check_group.md | 63 +- docs/resources/dashboard.md | 1 - docs/resources/environment_variable.md | 1 - docs/resources/heartbeat.md | 5 +- docs/resources/maintenance_windows.md | 1 - docs/resources/private_location.md | 1 - docs/resources/snippet.md | 1 - docs/resources/trigger_check.md | 1 - docs/resources/trigger_group.md | 1 - examples/provider/provider.tf | 8 +- .../checkly_alert_channel/resource.tf | 35 +- examples/resources/checkly_check/resource.tf | 47 +- .../resources/checkly_check_group/resource.tf | 62 +- .../resources/checkly_heartbeat/resource.tf | 4 +- go.mod | 33 +- go.sum | 862 ------------------ internal/provider/interop/convert.go | 453 +++++++++ internal/provider/interop/model.go | 61 -- .../resources/alert_channel_resource.go | 491 +++++----- .../resources/alert_channel_resource_test.go | 223 +++++ .../alert_channel_subscription_attribute.go | 6 + .../attributes/alert_settings_attribute.go | 362 +++++--- .../api_check_defaults_attribute.go | 37 +- .../attributes/assertion_attribute.go | 5 + .../attributes/basic_auth_attribute.go | 5 + .../environment_variable_attribute.go | 5 + .../resources/attributes/id_attribute.go | 5 + .../attributes/last_updated_attribute.go | 17 - .../attributes/locations_attribute.go | 17 +- .../attributes/private_locations_attribute.go | 17 +- .../resources/attributes/request_attribute.go | 49 +- .../attributes/retry_strategy_attribute.go | 5 + .../resources/check_group_resource.go | 108 ++- .../resources/check_group_resource_test.go | 433 +++++++++ internal/provider/resources/check_resource.go | 124 ++- .../provider/resources/check_resource_test.go | 717 +++++++++++++++ .../provider/resources/dashboard_resource.go | 8 +- .../environment_variable_resource.go | 18 +- .../provider/resources/heartbeat_resource.go | 248 +++-- .../resources/heartbeat_resource_test.go | 215 +++++ .../resources/maintenance_windows_resource.go | 8 +- .../resources/private_location_resource.go | 18 +- internal/provider/resources/shared_test.go | 28 + .../provider/resources/snippet_resource.go | 14 +- .../resources/trigger_check_resource.go | 16 +- .../resources/trigger_group_resource.go | 16 +- 56 files changed, 3185 insertions(+), 3394 deletions(-) delete mode 100644 checkly/integration_test.go delete mode 100644 checkly/resource_alert_channel_test.go delete mode 100644 checkly/resource_check_group_test.go delete mode 100644 checkly/resource_check_test.go delete mode 100644 checkly/resource_heartbeat_test.go delete mode 100644 checkly/test_util.go create mode 100644 internal/provider/interop/convert.go create mode 100644 internal/provider/resources/alert_channel_resource_test.go delete mode 100644 internal/provider/resources/attributes/last_updated_attribute.go create mode 100644 internal/provider/resources/check_group_resource_test.go create mode 100644 internal/provider/resources/check_resource_test.go create mode 100644 internal/provider/resources/heartbeat_resource_test.go diff --git a/GNUmakefile b/GNUmakefile index ed9d3ac..4f6e645 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -33,8 +33,8 @@ dev: cd local && TF_LOG=TRACE terraform init -upgrade fmt: - go fmt ./checkly - terraform fmt + go fmt ./... + terraform fmt -recursive # Generate docs generate: diff --git a/checkly/integration_test.go b/checkly/integration_test.go deleted file mode 100644 index fae3f59..0000000 --- a/checkly/integration_test.go +++ /dev/null @@ -1,50 +0,0 @@ -//go:build integration -// +build integration - -package checkly - -import ( - "os" - "testing" - - "github.com/gruntwork-io/terratest/modules/terraform" -) - -func getAPIKey(t *testing.T) string { - key := os.Getenv("CHECKLY_API_KEY") - if key == "" { - t.Fatal("'CHECKLY_API_KEY' must be set for integration tests") - } - - return key -} - -func getAccountId(t *testing.T) string { - key := os.Getenv("CHECKLY_ACCOUNT_ID") - if key == "" { - t.Fatal("'CHECKLY_ACCOUNT_ID' must be set for integration tests") - } - return key -} - -func TestChecklyTerraformIntegration(t *testing.T) { - t.Parallel() - terraformOptions := &terraform.Options{ - TerraformDir: "../", - Vars: map[string]interface{}{ - "checkly_api_key": getAPIKey(t), - "checkly_account_id": getAccountId(t), - }, - } - defer terraform.Destroy(t, terraformOptions) - terraform.InitAndApply(t, terraformOptions) - planPath := "./test.plan" - exit, err := terraform.GetExitCodeForTerraformCommandE(t, terraformOptions, terraform.FormatArgs(terraformOptions, "plan", "--out="+planPath, "-input=false", "-lock=true", "-detailed-exitcode")...) - if err != nil { - t.Fatal(err) - } - defer os.Remove(planPath) - if exit != terraform.DefaultSuccessExitCode { - t.Fatalf("want DefaultSuccessExitCode, got %d", exit) - } -} diff --git a/checkly/resource_alert_channel_test.go b/checkly/resource_alert_channel_test.go deleted file mode 100644 index 884c0c0..0000000 --- a/checkly/resource_alert_channel_test.go +++ /dev/null @@ -1,202 +0,0 @@ -package checkly - -import ( - "fmt" - "regexp" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAccEmail(t *testing.T) { - t.Parallel() - accTestCase(t, []resource.TestStep{ - { - Config: `resource "checkly_alert_channel" "t1" { - email { - address = "info@example.com" - } - send_recovery = false - send_failure = false - send_degraded = false - ssl_expiry = false - ssl_expiry_threshold = 10 - }`, - }, - }) -} - -func TestAccSlack(t *testing.T) { - t.Parallel() - accTestCase(t, []resource.TestStep{ - { - Config: `resource "checkly_alert_channel" "slack_ac" { - slack { - channel = "checkly_alerts" - url = "https://hooks.slack.com/services/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" - } - send_recovery = true - send_failure = true - send_degraded = false - ssl_expiry = true - ssl_expiry_threshold = 11 - }`, - }, - }) -} - -func TestAccSMS(t *testing.T) { - t.Parallel() - accTestCase(t, []resource.TestStep{ - { - Config: `resource "checkly_alert_channel" "sms_ac" { - sms { - name = "smsalerts" - number = "4917512345678" - } - }`, - }, - }) -} - -func TestAccOpsgenie(t *testing.T) { - t.Parallel() - accTestCase(t, []resource.TestStep{ - { - Config: `resource "checkly_alert_channel" "opsgenie_ac" { - opsgenie { - name = "opsalert" - api_key = "key1" - region = "EU" - priority = "P1" - } - }`, - }, - }) -} - -func TestAccPagerduty(t *testing.T) { - t.Parallel() - accTestCase(t, []resource.TestStep{ - { - Config: `resource "checkly_alert_channel" "pagerduty_ac" { - pagerduty { - account = "checkly" - service_key = "key1" - service_name = "pdalert" - } - }`, - }, - }) -} - -func TestAccWebhook(t *testing.T) { - t.Parallel() - accTestCase(t, []resource.TestStep{ - { - Config: `resource "checkly_alert_channel" "webhook_ac" { - webhook { - name = "webhhookalerts" - method = "get" - headers = { - X-HEADER-1 = "foo" - } - query_parameters = { - query1 = "bar" - } - template = "tmpl" - url = "https://example.com/webhook" - webhook_secret = "foo-secret" - } - }`, - }, - }) -} - -func TestAccFail(t *testing.T) { - t.Parallel() - cases := []struct { - Config string - Error string - }{ - { - Config: `resource "checkly_alert_channel" "t1" { - email { } - }`, - Error: `The argument "address" is required`, - }, - { - Config: `resource "checkly_alert_channel" "t1" { - sms { - } - }`, - Error: `The argument "number" is required`, - }, - { - Config: `resource "checkly_alert_channel" "t1" { - slack { - } - }`, - Error: `The argument "channel" is required`, - }, - { - Config: `resource "checkly_alert_channel" "t1" { - slack { - } - }`, - Error: `Missing required argument`, - }, - { - Config: `resource "checkly_alert_channel" "t1" { - webhook { - } - }`, - Error: `The argument "name" is required`, - }, - { - Config: `resource "checkly_alert_channel" "t1" { - webhook { - } - }`, - Error: `The argument "url" is required`, - }, - { - Config: `resource "checkly_alert_channel" "t1" { - opsgenie { - } - }`, - Error: `The argument "api_key" is required`, - }, - { - Config: `resource "checkly_alert_channel" "t1" { - opsgenie { - } - }`, - Error: `The argument "priority" is required`, - }, - { - Config: `resource "checkly_alert_channel" "t1" { - opsgenie { - } - }`, - Error: `The argument "region" is required`, - }, - { - Config: `resource "checkly_alert_channel" "t1" { - pagerduty { - } - }`, - Error: `The argument "service_key" is required`, - }, - } - for key, tc := range cases { - t.Run(fmt.Sprintf("%d", key), func(t *testing.T) { - accTestCase(t, []resource.TestStep{ - { - Config: tc.Config, - ExpectError: regexp.MustCompile(tc.Error), - }, - }) - }) - } -} diff --git a/checkly/resource_check_group_test.go b/checkly/resource_check_group_test.go deleted file mode 100644 index eabc14a..0000000 --- a/checkly/resource_check_group_test.go +++ /dev/null @@ -1,427 +0,0 @@ -package checkly - -import ( - "regexp" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - - "github.com/checkly/checkly-go-sdk" -) - -func TestEncodeDecodeGroupResource(t *testing.T) { - res := resourceCheckGroup() - data := res.TestResourceData() - resourceDataFromCheckGroup(&wantGroup, data) - gotGroup, err := checkGroupFromResourceData(data) - if err != nil { - t.Fatal(err) - } - if !cmp.Equal(wantGroup, gotGroup) { - t.Error(cmp.Diff(wantGroup, gotGroup)) - } -} - -var wantGroup = checkly.Group{ - Name: "test", - Activated: true, - Muted: false, - Tags: []string{"auto"}, - Locations: []string{"eu-west-1"}, - PrivateLocations: &[]string{}, - Concurrency: 3, - APICheckDefaults: checkly.APICheckDefaults{ - BaseURL: "example.com/api/test", - Headers: []checkly.KeyValue{ - { - Key: "X-Test", - Value: "foo", - }, - }, - QueryParameters: []checkly.KeyValue{ - { - Key: "query", - Value: "foo", - }, - }, - Assertions: []checkly.Assertion{ - { - Source: checkly.StatusCode, - Comparison: checkly.Equals, - Target: "200", - }, - }, - BasicAuth: checkly.BasicAuth{ - Username: "user", - Password: "pass", - }, - }, - EnvironmentVariables: []checkly.EnvironmentVariable{ - { - Key: "ENVTEST", - Value: "Hello world", - }, - }, - DoubleCheck: true, - UseGlobalAlertSettings: false, - AlertSettings: checkly.AlertSettings{ - EscalationType: checkly.RunBased, - RunBasedEscalation: checkly.RunBasedEscalation{ - FailedRunThreshold: 1, - }, - Reminders: checkly.Reminders{ - Amount: 0, - Interval: 5, - }, - }, - LocalSetupScript: "setup-test", - LocalTearDownScript: "teardown-test", - AlertChannelSubscriptions: []checkly.AlertChannelSubscription{}, -} - -func TestAccCheckGroupEmptyConfig(t *testing.T) { - config := `resource "checkly_check_group" "test" {}` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "name" is required`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "concurrency" is required`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "activated" is required`), - }, - }) -} - -func TestAccCheckGroupInvalid(t *testing.T) { - accTestCase(t, []resource.TestStep{ - { - Config: testCheckGroup_invalid, - ExpectError: regexp.MustCompile(`Inappropriate value for attribute "locations"`), - }, - { - Config: testCheckGroup_invalid, - ExpectError: regexp.MustCompile(`Inappropriate value for attribute "muted"`), - }, - { - Config: testCheckGroup_invalid, - ExpectError: regexp.MustCompile(`Inappropriate value for attribute "activated"`), - }, - { - Config: testCheckGroup_invalid, - ExpectError: regexp.MustCompile(`The argument "concurrency" is required`), - }, - { - Config: testCheckGroup_invalid, - ExpectError: regexp.MustCompile(`Missing required argument`), - }, - }) -} - -func TestAccCheckGroupBasic(t *testing.T) { - accTestCase(t, []resource.TestStep{ - { - Config: testCheckGroup_basic, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "name", - "test", - ), - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "activated", - "true", - ), - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "muted", - "false", - ), - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "concurrency", - "3", - ), - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "locations.#", - "2", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "locations.*", - "us-east-1", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "locations.*", - "eu-central-1", - ), - ), - }, - }) -} - -func TestAccCheckGroupWithApiDefaults(t *testing.T) { - accTestCase(t, []resource.TestStep{ - { - Config: testCheckGroup_withApiDefaults, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "name", - "test", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "api_check_defaults.*.url", - "http://api.example.com/", - ), - ), - }, - }) -} - -func TestAccCheckGroupFull(t *testing.T) { - accTestCase(t, []resource.TestStep{ - { - Config: testCheckGroup_full, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "name", - "test", - ), - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "activated", - "true", - ), - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "muted", - "false", - ), - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "concurrency", - "3", - ), - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "double_check", - "true", - ), - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "use_global_alert_settings", - "false", - ), - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "locations.#", - "2", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "locations.*", - "us-east-1", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "locations.*", - "eu-central-1", - ), - resource.TestCheckResourceAttr( - "checkly_check_group.test", - "environment_variables.FOO", - "BAR", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "alert_settings.*.escalation_type", - "RUN_BASED", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "alert_settings.*.reminders.#", - "1", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "alert_settings.*.reminders.*.amount", - "2", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "alert_settings.*.reminders.*.interval", - "5", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "alert_settings.*.run_based_escalation.#", - "1", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "alert_settings.*.run_based_escalation.*.failed_run_threshold", - "1", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "api_check_defaults.#", - "1", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "api_check_defaults.*.assertion.#", - "1", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "api_check_defaults.*.assertion.*.comparison", - "EQUALS", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "api_check_defaults.*.assertion.*.property", - "", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "api_check_defaults.*.assertion.*.source", - "STATUS_CODE", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "api_check_defaults.*.assertion.*.target", - "200", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "api_check_defaults.*.basic_auth.#", - "1", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "api_check_defaults.*.basic_auth.*.password", - "pass", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "api_check_defaults.*.basic_auth.*.username", - "user", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "api_check_defaults.*.headers.X-Test", - "foo", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "api_check_defaults.*.query_parameters.query", - "foo", - ), - testCheckResourceAttrExpr( - "checkly_check_group.test", - "api_check_defaults.*.url", - "http://example.com/", - ), - ), - }, - }) -} - -const testCheckGroup_invalid = ` - resource "checkly_check_group" "test" { - name = "test" - activated = "invalid" - muted = "invalid" - locations = "invalid" - } -` - -const testCheckGroup_basic = ` - resource "checkly_check_group" "test" { - name = "test" - activated = true - muted = false - concurrency = 3 - locations = [ - "us-east-1", - "eu-central-1", - ] - } -` - -const testCheckGroup_withApiDefaults = ` - resource "checkly_check_group" "test" { - name = "test" - activated = true - muted = false - concurrency = 3 - locations = [ - "eu-west-1", - "eu-west-2" - ] - api_check_defaults { - url = "http://api.example.com/" - } - } -` - -const testCheckGroup_full = ` - resource "checkly_check_group" "test" { - name = "test" - activated = true - muted = false - concurrency = 3 - double_check = true - use_global_alert_settings = false - locations = [ "us-east-1", "eu-central-1" ] - api_check_defaults { - url = "http://example.com/" - headers = { - X-Test = "foo" - } - query_parameters = { - query = "foo" - } - assertion { - source = "STATUS_CODE" - property = "" - comparison = "EQUALS" - target = "200" - } - basic_auth { - username = "user" - password = "pass" - } - } - environment_variables = { - FOO = "BAR" - } - alert_settings { - escalation_type = "RUN_BASED" - run_based_escalation { - failed_run_threshold = 1 - } - reminders { - amount = 2 - interval = 5 - } - parallel_run_failure_threshold { - enabled = false - percentage = 10 - } - } - local_setup_script = "setup-test" - local_teardown_script = "teardown-test" - } -` diff --git a/checkly/resource_check_test.go b/checkly/resource_check_test.go deleted file mode 100644 index f388f22..0000000 --- a/checkly/resource_check_test.go +++ /dev/null @@ -1,698 +0,0 @@ -package checkly - -import ( - "net/http" - "regexp" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - - "github.com/checkly/checkly-go-sdk" -) - -func TestAccCheckRequiredFields(t *testing.T) { - config := `resource "checkly_check" "test" {}` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "type" is required, but no definition was found.`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "name" is required, but no definition was found.`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "activated" is required, but no definition was found.`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "frequency" is required, but no definition was found.`), - }, - }) -} - -func TestAccBrowserCheckInvalidInputs(t *testing.T) { - config := `resource "checkly_check" "test" { - name = 1 - type = "BROWSER" - activated = "invalid" - should_fail = "invalid" - double_check = "invalid" - use_global_alert_settings = "invalid" - locations = "invalid" - script = 4 - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`Inappropriate value for attribute "activated"`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "frequency" is required`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`Inappropriate value for attribute "should_fail"`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`Inappropriate value for attribute "use_global_alert_settings"`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`Inappropriate value for attribute "double_check"`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`Inappropriate value for attribute "locations"`), - }, - }) -} - -func TestAccBrowserCheckMissingScript(t *testing.T) { - config := `resource "checkly_check" "test" { - type = "BROWSER" - activated = true - frequency = 10 - name = "browser check" - locations = [ "us-west-1" ] - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`API error 1: unexpected response status 400`), - }, - }) -} - -func TestAccBrowserCheckBasic(t *testing.T) { - accTestCase(t, []resource.TestStep{ - { - Config: browserCheck_basic, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "checkly_check.test", - "name", - "Browser Check", - ), - resource.TestCheckResourceAttr( - "checkly_check.test", - "type", - "BROWSER", - ), - resource.TestCheckResourceAttr( - "checkly_check.test", - "activated", - "true", - ), - resource.TestCheckResourceAttr( - "checkly_check.test", - "script", - "console.log('test')", - ), - resource.TestCheckResourceAttr( - "checkly_check.test", - "locations.#", - "2", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "locations.*", - "us-east-1", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "locations.*", - "eu-central-1", - ), - resource.TestCheckResourceAttr( - "checkly_check.test", - "tags.#", - "2", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "tags.*", - "browser", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "tags.*", - "e2e", - ), - resource.TestCheckNoResourceAttr( - "checkly_check.test", - "request", - ), - ), - }, - }) -} - -func TestAccApiCheckBasic(t *testing.T) { - accTestCase(t, []resource.TestStep{ - { - Config: apiCheck_basic, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "checkly_check.test", - "name", - "API Check 1", - ), - resource.TestCheckResourceAttr( - "checkly_check.test", - "activated", - "true", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "locations.*", - "eu-central-1", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "locations.*", - "us-east-1", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.method", - "GET", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.url", - "https://api.checklyhq.com/public-stats", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.assertion.*.comparison", - "EQUALS", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.assertion.*.property", - "", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.assertion.*.source", - "STATUS_CODE", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.assertion.*.target", - "200", - ), - ), - }, - }) -} - -func TestAccMultiStepCheckRuntimeValidation(t *testing.T) { - unsupportedRuntime := `resource "checkly_check" "test" { - name = "test" - type = "MULTI_STEP" - activated = true - frequency = 5 - locations = ["eu-central-1"] - script = "console.log('test')" - runtime_id = "2023.02" - }` - noSpecifiedRuntime := `resource "checkly_check" "test" { - name = "test" - type = "MULTI_STEP" - activated = true - frequency = 5 - locations = ["eu-central-1"] - script = "console.log('test')" - }` - accTestCase(t, []resource.TestStep{ - { - Config: unsupportedRuntime, - ExpectError: regexp.MustCompile("Error: runtime 2023.02 does not support MULTI_STEP checks"), - }, - { - Config: noSpecifiedRuntime, - Check: resource.TestCheckNoResourceAttr( - "checkly_check.test", - "runtime_id", - ), - }, - }) -} - -func TestAccMultiStepCheckBasic(t *testing.T) { - accTestCase(t, []resource.TestStep{ - { - Config: multiStepCheck_basic, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "checkly_check.test", - "name", - "MultiStep Check", - ), - resource.TestCheckResourceAttr( - "checkly_check.test", - "type", - "MULTI_STEP", - ), - resource.TestCheckResourceAttr( - "checkly_check.test", - "runtime_id", - "2023.09", - ), - resource.TestCheckResourceAttr( - "checkly_check.test", - "activated", - "true", - ), - resource.TestCheckResourceAttr( - "checkly_check.test", - "script", - "console.log('test')", - ), - resource.TestCheckResourceAttr( - "checkly_check.test", - "locations.#", - "2", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "locations.*", - "us-east-1", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "locations.*", - "eu-central-1", - ), - resource.TestCheckResourceAttr( - "checkly_check.test", - "tags.#", - "2", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "tags.*", - "browser", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "tags.*", - "e2e", - ), - resource.TestCheckNoResourceAttr( - "checkly_check.test", - "request", - ), - ), - }, - }) -} - -func TestAccApiCheckFull(t *testing.T) { - accTestCase(t, []resource.TestStep{ - { - Config: apiCheck_full, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "checkly_check.test", - "degraded_response_time", - "15000", - ), - resource.TestCheckResourceAttr( - "checkly_check.test", - "max_response_time", - "30000", - ), - resource.TestCheckNoResourceAttr( - "checkly_check.test", - "environment_variables", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - `"locations.#"`, - "3", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - `"request.*.headers.X-CUSTOM-1"`, - "1", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.headers.X-CUSTOM-2", - "FOO", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.query_parameters.param1", - "123", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.query_parameters.param2", - "bar", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.basic_auth.*.username", - "user", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.basic_auth.*.password", - "pass", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.assertion.#", - "3", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.assertion.*.comparison", - "EQUALS", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.assertion.*.comparison", - "GREATER_THAN", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.assertion.*.target", - "200", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.assertion.*.target", - "no-cache", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.assertion.*.target", - "100", - ), - ), - }, - }) -} - -func TestAccApiCheckMore(t *testing.T) { - accTestCase(t, []resource.TestStep{ - { - Config: apiCheck_post, - Check: resource.ComposeTestCheckFunc( - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.method", - "POST", - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.body", - `{\"message\":\"hello checkly\",\"messageId\":1}`, - ), - testCheckResourceAttrExpr( - "checkly_check.test", - "request.*.body_type", - "JSON", - ), - ), - }, - { - Config: apiCheck_withEmptyBasicAuth, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "checkly_check.test", - "name", - "api check with empty basic_auth", - ), - ), - }, - }) -} - -var wantCheck = checkly.Check{ - Name: "My test check", - Type: checkly.TypeAPI, - Frequency: 1, - Activated: true, - Muted: false, - ShouldFail: false, - Locations: []string{"eu-west-1"}, - PrivateLocations: &[]string{}, - Script: "foo", - DegradedResponseTime: 15000, - MaxResponseTime: 30000, - EnvironmentVariables: []checkly.EnvironmentVariable{ - { - Key: "ENVTEST", - Value: "Hello world", - }, - }, - DoubleCheck: false, - Tags: []string{ - "foo", - "bar", - }, - SSLCheck: false, - LocalSetupScript: "bogus", - LocalTearDownScript: "bogus", - AlertSettings: checkly.AlertSettings{ - EscalationType: checkly.RunBased, - RunBasedEscalation: checkly.RunBasedEscalation{ - FailedRunThreshold: 1, - }, - Reminders: checkly.Reminders{ - Interval: 5, - }, - }, - UseGlobalAlertSettings: false, - Request: checkly.Request{ - Method: http.MethodGet, - URL: "http://example.com", - Headers: []checkly.KeyValue{ - { - Key: "X-Test", - Value: "foo", - }, - }, - QueryParameters: []checkly.KeyValue{ - { - Key: "query", - Value: "foo", - }, - }, - Assertions: []checkly.Assertion{ - { - Source: checkly.StatusCode, - Comparison: checkly.Equals, - Target: "200", - }, - }, - Body: "", - BodyType: "NONE", - BasicAuth: &checkly.BasicAuth{ - Username: "example", - Password: "pass", - }, - }, -} - -func TestEncodeDecodeResource(t *testing.T) { - res := resourceCheck() - data := res.TestResourceData() - wantCheck.AlertChannelSubscriptions = []checkly.AlertChannelSubscription{} - resourceDataFromCheck(&wantCheck, data) - got, err := checkFromResourceData(data) - if err != nil { - t.Fatal(err) - } - if !cmp.Equal(wantCheck, got) { - t.Error(cmp.Diff(wantCheck, got)) - } -} - -const browserCheck_basic = ` - resource "checkly_check" "test" { - name = "Browser Check" - type = "BROWSER" - activated = true - should_fail = false - frequency = 720 - double_check = true - use_global_alert_settings = true - locations = [ "us-east-1", "eu-central-1" ] - tags = [ "browser", "e2e" ] - script = "console.log('test')" - } -` -const multiStepCheck_basic = ` - resource "checkly_check" "test" { - name = "MultiStep Check" - type = "MULTI_STEP" - activated = true - should_fail = false - frequency = 720 - double_check = true - use_global_alert_settings = true - locations = [ "us-east-1", "eu-central-1" ] - tags = [ "api", "multi-step" ] - runtime_id = "2023.09" - script = "console.log('test')" - } -` - -const apiCheck_basic = ` - resource "checkly_check" "test" { - name = "API Check 1" - type = "API" - frequency = 60 - activated = true - muted = true - double_check = true - max_response_time = 18000 - locations = [ "us-east-1", "eu-central-1" ] - use_global_alert_settings = true - request { - method = "GET" - url = "https://api.checklyhq.com/public-stats" - assertion { - comparison = "EQUALS" - property = "" - source = "STATUS_CODE" - target = "200" - } - } - } -` - -const apiCheck_full = ` - resource "checkly_check" "test" { - name = "apiCheck_full" - type = "API" - frequency = 120 - activated = true - muted = true - double_check = true - degraded_response_time = 15000 - max_response_time = 30000 - environment_variables = null - locations = [ - "eu-central-1", - "us-east-1", - "ap-northeast-1" - ] - request { - method = "GET" - url = "https://api.checklyhq.com/public-stats" - follow_redirects = true - headers = { - X-CUSTOM-1 = 1 - X-CUSTOM-2 = "foo" - } - query_parameters = { - param1 = 123 - param2 = "bar" - } - basic_auth { - username = "user" - password = "pass" - } - assertion { - comparison = "EQUALS" - property = "" - source = "STATUS_CODE" - target = "200" - } - assertion { - comparison = "EQUALS" - property = "cache-control" - source = "HEADERS" - target = "no-cache" - } - assertion { - comparison = "GREATER_THAN" - property = "$.apiCheckResults" - source = "JSON_BODY" - target = "100" - } - } - - alert_settings { - escalation_type = "RUN_BASED" - reminders { - amount = 0 - interval = 5 - } - run_based_escalation { - failed_run_threshold = 1 - } - parallel_run_failure_threshold { - enabled = false - percentage = 10 - } - } - } -` - -const apiCheck_post = ` - resource "checkly_check" "test" { - name = "apiCheck_post" - type = "API" - activated = true - double_check = true - frequency = 720 - locations = [ "eu-central-1", "us-east-2" ] - max_response_time = 18000 - muted = true - environment_variables = null - request { - method = "POST" - url = "https://jsonplaceholder.typicode.com/posts" - headers = { - Content-type = "application/json; charset=UTF-8" - } - body = "{\"message\":\"hello checkly\",\"messageId\":1}" - body_type = "JSON" - } - use_global_alert_settings = true - } -` - -const apiCheck_withEmptyBasicAuth = ` - resource "checkly_check" "test" { - name = "api check with empty basic_auth" - type = "API" - activated = true - should_fail = false - frequency = 1 - degraded_response_time = 3000 - max_response_time = 6000 - tags = [ - "testing", - "bug" - ] - locations = [ "eu-central-1" ] - request { - follow_redirects = false - url = "https://api.checklyhq.com/public-stats" - basic_auth { - username = "" - password = "" - } - assertion { - source = "STATUS_CODE" - property = "" - comparison = "EQUALS" - target = "200" - } - } - } -` diff --git a/checkly/resource_heartbeat_test.go b/checkly/resource_heartbeat_test.go deleted file mode 100644 index 3d11882..0000000 --- a/checkly/resource_heartbeat_test.go +++ /dev/null @@ -1,192 +0,0 @@ -package checkly - -import ( - "regexp" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAccHeartbeatRequiredFields(t *testing.T) { - config := `resource "checkly_heartbeat" "test" {}` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "name" is required, but no definition was found.`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "activated" is required, but no definition was found.`), - }, - }) -} - -func TestAccHeartbeatCheckInvalidInputs(t *testing.T) { - config := `resource "checkly_check" "test" { - name = 1 - activated = "invalid" - use_global_alert_settings = "invalid" - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`Inappropriate value for attribute "activated"`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`Inappropriate value for attribute "use_global_alert_settings"`), - }, - }) -} - -func TestAccHeartbeatCheckMissingHeartbeatBlock(t *testing.T) { - config := `resource "checkly_heartbeat" "test" { - activated = true - name = "heartbeat check" - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`At least 1 "heartbeat" blocks are required.`), - }, - }) -} - -func TestAccHeartbeatCheckMissingHeartbeatFields(t *testing.T) { - config := `resource "checkly_heartbeat" "test" { - activated = true - name = "heartbeat check" - heartbeat { - - } - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "grace" is required, but no definition was found.`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "grace_unit" is required, but no definition was found.`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "period" is required, but no definition was found.`), - }, - { - Config: config, - ExpectError: regexp.MustCompile(`The argument "period_unit" is required, but no definition was found.`), - }, - }) -} - -func TestAccHeartbeatCheckPeriodTooBig(t *testing.T) { - config := `resource "checkly_heartbeat" "test" { - activated = true - name = "heartbeat check" - heartbeat { - period = 366 - period_unit = "days" - grace = 0 - grace_unit = "seconds" - } - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`translation error: period must be between 30 seconds and 365 days`), - }, - }) -} - -func TestAccHeartbeatCheckPeriodTooSmall(t *testing.T) { - config := `resource "checkly_heartbeat" "test" { - activated = true - name = "heartbeat check" - heartbeat { - period = 5 - period_unit = "seconds" - grace = 0 - grace_unit = "seconds" - } - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`translation error: period must be between 30 seconds and 365 days`), - }, - }) -} - -func TestAccHeartbeatCheckInvalidPeriodUnit(t *testing.T) { - config := `resource "checkly_heartbeat" "test" { - activated = true - name = "heartbeat check" - heartbeat { - period = 5 - period_unit = "lightyear" - grace = 0 - grace_unit = "seconds" - } - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`"heartbeat\.0\.period_unit" must be one of \[seconds minutes hours days\], got lightyear`), - }, - }) -} - -func TestAccHeartbeatCheckInvalidGraceUnit(t *testing.T) { - config := `resource "checkly_heartbeat" "test" { - activated = true - name = "heartbeat check" - heartbeat { - period = 5 - period_unit = "days" - grace = 0 - grace_unit = "lightyear" - } - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - ExpectError: regexp.MustCompile(`"heartbeat\.0\.grace_unit" must be one of \[seconds minutes hours days\], got lightyear`), - }, - }) -} - -func TestAccHeartbeatCheckCreate(t *testing.T) { - config := `resource "checkly_heartbeat" "test" { - activated = true - name = "heartbeat check" - heartbeat { - period = 5 - period_unit = "days" - grace = 0 - grace_unit = "seconds" - } - }` - accTestCase(t, []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "checkly_heartbeat.test", - "name", - "heartbeat check", - ), - testCheckResourceAttrExpr( - "checkly_heartbeat.test", - "heartbeat.*.period", - "5", - ), - testCheckResourceAttrExpr( - "checkly_heartbeat.test", - "heartbeat.*.period_unit", - "days", - ), - ), - }, - }) -} diff --git a/checkly/test_util.go b/checkly/test_util.go deleted file mode 100644 index d95147e..0000000 --- a/checkly/test_util.go +++ /dev/null @@ -1,65 +0,0 @@ -package checkly - -import ( - "encoding/json" - "fmt" - "os" - "regexp" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" -) - -var testAccProviders map[string]*schema.Provider - -func init() { - testAccProviders = map[string]*schema.Provider{ - "checkly": Provider(), - } -} - -func testAccPreCheck(t *testing.T) { - if os.Getenv("CHECKLY_API_KEY") == "" { - t.Fatal("CHECKLY_API_KEY must be set for acceptance tests") - } - - if os.Getenv("CHECKLY_ACCOUNT_ID") == "" { - t.Fatal("CHECKLY_ACCOUNT_ID must be set for acceptance tests") - } -} - -func accTestCase(t *testing.T, steps []resource.TestStep) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, - Providers: testAccProviders, - CheckDestroy: nil, - Steps: steps, - }) -} - -// test resource using regular expressions -// this helps testing arrays which have irregular indices; -// needed because we get things like "alert_settings.2888461220.escalation_type": "RUN_BASED" -func testCheckResourceAttrExpr(resource, attrExpr, value string) func(s *terraform.State) error { - return func(s *terraform.State) error { - if len(s.Modules) < 1 { - return fmt.Errorf("testCheckResourceAttrExpr: state has no modules") - } - if _, ok := s.Modules[0].Resources[resource]; !ok { - return fmt.Errorf("Resource not found: %s", resource) - } - marshaled, _ := json.Marshal(s.Modules[0].Resources[resource].Primary.Attributes) - r, err := regexp.Compile(attrExpr) - if err != nil { - return err - } - if !r.MatchString(string(marshaled)) { - return fmt.Errorf(`Couldn't find [%s: "%s"] in %s`, attrExpr, value, string(marshaled)) - } - return err - } -} diff --git a/docs/index.md b/docs/index.md index 2b31e62..316481b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -61,14 +61,14 @@ resource "checkly_check" "example_check" { "us-west-1" ] - request { + request = { url = "https://api.example.com/" follow_redirects = true - assertion { + assertions = [{ source = "STATUS_CODE" comparison = "EQUALS" target = "200" - } + }] } } ``` diff --git a/docs/resources/alert_channel.md b/docs/resources/alert_channel.md index 01f36fc..4596edf 100644 --- a/docs/resources/alert_channel.md +++ b/docs/resources/alert_channel.md @@ -15,7 +15,7 @@ Allows you to define alerting channels for the checks and groups in your account ```terraform # An Email alert channel resource "checkly_alert_channel" "email_ac" { - email { + email = { address = "john@example.com" } send_recovery = true @@ -27,7 +27,7 @@ resource "checkly_alert_channel" "email_ac" { # A SMS alert channel resource "checkly_alert_channel" "sms_ac" { - sms { + sms = { name = "john" number = "+5491100001111" } @@ -37,7 +37,7 @@ resource "checkly_alert_channel" "sms_ac" { # A Slack alert channel resource "checkly_alert_channel" "slack_ac" { - slack { + slack = { channel = "#checkly-notifications" url = "https://hooks.slack.com/services/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" } @@ -45,7 +45,7 @@ resource "checkly_alert_channel" "slack_ac" { # An Opsgenie alert channel resource "checkly_alert_channel" "opsgenie_ac" { - opsgenie { + opsgenie = { name = "opsalerts" api_key = "fookey" region = "fooregion" @@ -55,7 +55,7 @@ resource "checkly_alert_channel" "opsgenie_ac" { # A Pagerduty alert channel resource "checkly_alert_channel" "pagerduty_ac" { - pagerduty { + pagerduty = { account = "checkly" service_key = "key1" service_name = "pdalert" @@ -64,7 +64,7 @@ resource "checkly_alert_channel" "pagerduty_ac" { # A Webhook alert channel resource "checkly_alert_channel" "webhook_ac" { - webhook { + webhook = { name = "foo" method = "get" template = "footemplate" @@ -75,7 +75,7 @@ resource "checkly_alert_channel" "webhook_ac" { # A Firehydran alert channel integration resource "checkly_alert_channel" "firehydrant_ac" { - webhook { + webhook = { name = "firehydrant" method = "post" template = < ### Nested Schema for `call` diff --git a/docs/resources/check.md b/docs/resources/check.md index 17bb7bb..b8a3ea2 100644 --- a/docs/resources/check.md +++ b/docs/resources/check.md @@ -26,15 +26,15 @@ resource "checkly_check" "example_check" { "us-west-1" ] - request { + request = { url = "https://api.example.com/" follow_redirects = true skip_ssl = false - assertion { + assertions = [{ source = "STATUS_CODE" comparison = "EQUALS" target = "200" - } + }] } } @@ -54,19 +54,19 @@ resource "checkly_check" "example_check_2" { "ap-south-1", ] - alert_settings { + alert_settings = { escalation_type = "RUN_BASED" - run_based_escalation { + run_based_escalation = { failed_run_threshold = 1 } - reminders { + reminders = { amount = 1 } } - retry_strategy { + retry_strategy = { type = "FIXED" base_backoff_seconds = 60 max_duration_seconds = 600 @@ -74,7 +74,7 @@ resource "checkly_check" "example_check_2" { same_region = false } - request { + request = { follow_redirects = true skip_ssl = false url = "http://api.example.com/" @@ -87,21 +87,21 @@ resource "checkly_check" "example_check_2" { X-Bogus = "bogus" } - assertion { + assertion = { source = "JSON_BODY" property = "code" comparison = "HAS_VALUE" target = "authentication.failed" } - assertion { + assertion = { source = "STATUS_CODE" property = "" comparison = "EQUALS" target = "401" } - basic_auth { + basic_auth = { username = "" password = "" } @@ -137,13 +137,13 @@ EOT # Connection checks with alert channels resource "checkly_alert_channel" "email_ac1" { - email { + email = { address = "info1@example.com" } } resource "checkly_alert_channel" "email_ac2" { - email { + email = { address = "info2@example.com" } } @@ -152,15 +152,16 @@ resource "checkly_check" "example_check" { name = "Example check" # ... - alert_channel_subscription { - channel_id = checkly_alert_channel.email_ac1.id - activated = true - } - - alert_channel_subscription { - channel_id = checkly_alert_channel.email_ac2.id - activated = true - } + alert_channel_subscriptions = [ + { + channel_id = checkly_alert_channel.email_ac1.id + activated = true + }, + { + channel_id = checkly_alert_channel.email_ac2.id + activated = true + } + ] } # An alternative syntax for add the script is by referencing an external file @@ -227,7 +228,6 @@ resource "checkly_check" "example_check" { ### Read-Only - `id` (String) The ID of this resource. -- `last_updated` (String) When the resource was last updated by the provider. ### Nested Schema for `alert_channel_subscription` diff --git a/docs/resources/check_group.md b/docs/resources/check_group.md index 3f87038..5a1fa31 100644 --- a/docs/resources/check_group.md +++ b/docs/resources/check_group.md @@ -25,7 +25,7 @@ resource "checkly_check_group" "test_group1" { "eu-west-1", ] concurrency = 3 - api_check_defaults { + api_check_defaults = { url = "http://example.com/" headers = { X-Test = "foo" @@ -35,48 +35,49 @@ resource "checkly_check_group" "test_group1" { query = "foo" } - assertion { + assertion = { source = "STATUS_CODE" property = "" comparison = "EQUALS" target = "200" } - assertion { + assertion = { source = "TEXT_BODY" property = "" comparison = "CONTAINS" target = "welcome" } - basic_auth { + basic_auth = { username = "user" password = "pass" } } - environment_variable { - key = "TEST_ENV_VAR" - value = "Hello world" - locked = false - } - - environment_variable { - key = "ADDITIONAL_ENV_VAR" - value = "test value" - locked = true - } + environment_variables = [ + { + key = "TEST_ENV_VAR" + value = "Hello world" + locked = false + }, + { + key = "ADDITIONAL_ENV_VAR" + value = "test value" + locked = true + } + ] use_global_alert_settings = false - alert_settings { + alert_settings = { escalation_type = "RUN_BASED" - run_based_escalation { + run_based_escalation = { failed_run_threshold = 1 } - reminders { + reminders = { amount = 2 interval = 5 } @@ -96,7 +97,7 @@ resource "checkly_check" "test_check1" { "us-west-1" ] - request { + request = { url = "https://api.example.com/" } group_id = checkly_check_group.test_group1.id @@ -106,13 +107,13 @@ resource "checkly_check" "test_check1" { # Using with alert channels resource "checkly_alert_channel" "email_ac1" { - email { + email = { address = "info@example.com" } } resource "checkly_alert_channel" "email_ac2" { - email { + email = { address = "info2@example.com" } } @@ -122,15 +123,16 @@ resource "checkly_alert_channel" "email_ac2" { resource "checkly_check_group" "test_group1" { name = "My test group 1" - alert_channel_subscription { - channel_id = checkly_alert_channel.email_ac1.id - activated = true - } - - alert_channel_subscription { - channel_id = checkly_alert_channel.email_ac2.id - activated = true - } + alert_channel_subscriptions = [ + { + channel_id = checkly_alert_channel.email_ac1.id + activated = true + }, + { + channel_id = checkly_alert_channel.email_ac2.id + activated = true + } + ] } ``` @@ -167,7 +169,6 @@ resource "checkly_check_group" "test_group1" { ### Read-Only - `id` (String) The ID of this resource. -- `last_updated` (String) When the resource was last updated by the provider. ### Nested Schema for `alert_channel_subscription` diff --git a/docs/resources/dashboard.md b/docs/resources/dashboard.md index 45e0b04..fac3c1d 100644 --- a/docs/resources/dashboard.md +++ b/docs/resources/dashboard.md @@ -58,4 +58,3 @@ resource "checkly_dashboard" "dashboard_1" { - `id` (String) The ID of this resource. - `key` (String, Sensitive) The access key when the dashboard is private. -- `last_updated` (String) When the resource was last updated by the provider. diff --git a/docs/resources/environment_variable.md b/docs/resources/environment_variable.md index d71e076..8cba751 100644 --- a/docs/resources/environment_variable.md +++ b/docs/resources/environment_variable.md @@ -42,4 +42,3 @@ resource "checkly_environment_variable" "variable_2" { ### Read-Only - `id` (String) The ID of this resource. -- `last_updated` (String) When the resource was last updated by the provider. diff --git a/docs/resources/heartbeat.md b/docs/resources/heartbeat.md index c266093..b611007 100644 --- a/docs/resources/heartbeat.md +++ b/docs/resources/heartbeat.md @@ -16,7 +16,7 @@ Heartbeats allows you to monitor your cron jobs and set up alerting, so you get resource "checkly_heartbeat" "example-heartbeat" { name = "Example heartbeat" activated = true - heartbeat { + heartbeat = { period = 7 period_unit = "days" grace = 1 @@ -46,7 +46,6 @@ resource "checkly_heartbeat" "example-heartbeat" { ### Read-Only - `id` (String) The ID of this resource. -- `last_updated` (String) When the resource was last updated by the provider. ### Nested Schema for `heartbeat` @@ -58,7 +57,7 @@ Required: - `period` (Number) How often you expect a ping to the ping URL. - `period_unit` (String) Possible values `seconds`, `minutes`, `hours` and `days`. -Optional: +Read-Only: - `ping_token` (String) Custom token to generate your ping URL. Checkly will expect a ping to `https://ping.checklyhq.com/[PING_TOKEN]`. diff --git a/docs/resources/maintenance_windows.md b/docs/resources/maintenance_windows.md index d3c25a9..6997df7 100644 --- a/docs/resources/maintenance_windows.md +++ b/docs/resources/maintenance_windows.md @@ -45,4 +45,3 @@ resource "checkly_maintenance_windows" "maintenance-1" { ### Read-Only - `id` (String) The ID of this resource. -- `last_updated` (String) When the resource was last updated by the provider. diff --git a/docs/resources/private_location.md b/docs/resources/private_location.md index a922606..100e3d1 100644 --- a/docs/resources/private_location.md +++ b/docs/resources/private_location.md @@ -35,4 +35,3 @@ resource "checkly_private_location" "location" { - `id` (String) The ID of this resource. - `keys` (Set of String, Sensitive) Private location API keys. -- `last_updated` (String) When the resource was last updated by the provider. diff --git a/docs/resources/snippet.md b/docs/resources/snippet.md index cfec9d3..2dd64ba 100644 --- a/docs/resources/snippet.md +++ b/docs/resources/snippet.md @@ -49,4 +49,3 @@ EOT ### Read-Only - `id` (String) The ID of this resource. -- `last_updated` (String) When the resource was last updated by the provider. diff --git a/docs/resources/trigger_check.md b/docs/resources/trigger_check.md index 68bf232..0fbf5eb 100644 --- a/docs/resources/trigger_check.md +++ b/docs/resources/trigger_check.md @@ -37,4 +37,3 @@ output "test_trigger_check_url" { ### Read-Only - `id` (String) The ID of this resource. -- `last_updated` (String) When the resource was last updated by the provider. diff --git a/docs/resources/trigger_group.md b/docs/resources/trigger_group.md index 06a07d4..b51da1a 100644 --- a/docs/resources/trigger_group.md +++ b/docs/resources/trigger_group.md @@ -37,4 +37,3 @@ output "test_trigger_group_url" { ### Read-Only - `id` (String) The ID of this resource. -- `last_updated` (String) When the resource was last updated by the provider. diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf index 16499e0..05482bc 100644 --- a/examples/provider/provider.tf +++ b/examples/provider/provider.tf @@ -31,13 +31,13 @@ resource "checkly_check" "example_check" { "us-west-1" ] - request { + request = { url = "https://api.example.com/" follow_redirects = true - assertion { + assertions = [{ source = "STATUS_CODE" comparison = "EQUALS" target = "200" - } + }] } -} \ No newline at end of file +} diff --git a/examples/resources/checkly_alert_channel/resource.tf b/examples/resources/checkly_alert_channel/resource.tf index b581b60..05e63d5 100644 --- a/examples/resources/checkly_alert_channel/resource.tf +++ b/examples/resources/checkly_alert_channel/resource.tf @@ -1,6 +1,6 @@ # An Email alert channel resource "checkly_alert_channel" "email_ac" { - email { + email = { address = "john@example.com" } send_recovery = true @@ -12,7 +12,7 @@ resource "checkly_alert_channel" "email_ac" { # A SMS alert channel resource "checkly_alert_channel" "sms_ac" { - sms { + sms = { name = "john" number = "+5491100001111" } @@ -22,7 +22,7 @@ resource "checkly_alert_channel" "sms_ac" { # A Slack alert channel resource "checkly_alert_channel" "slack_ac" { - slack { + slack = { channel = "#checkly-notifications" url = "https://hooks.slack.com/services/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" } @@ -30,7 +30,7 @@ resource "checkly_alert_channel" "slack_ac" { # An Opsgenie alert channel resource "checkly_alert_channel" "opsgenie_ac" { - opsgenie { + opsgenie = { name = "opsalerts" api_key = "fookey" region = "fooregion" @@ -40,7 +40,7 @@ resource "checkly_alert_channel" "opsgenie_ac" { # A Pagerduty alert channel resource "checkly_alert_channel" "pagerduty_ac" { - pagerduty { + pagerduty = { account = "checkly" service_key = "key1" service_name = "pdalert" @@ -49,7 +49,7 @@ resource "checkly_alert_channel" "pagerduty_ac" { # A Webhook alert channel resource "checkly_alert_channel" "webhook_ac" { - webhook { + webhook = { name = "foo" method = "get" template = "footemplate" @@ -60,7 +60,7 @@ resource "checkly_alert_channel" "webhook_ac" { # A Firehydran alert channel integration resource "checkly_alert_channel" "firehydrant_ac" { - webhook { + webhook = { name = "firehydrant" method = "post" template = < 3600*24*365 { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + valuePath, + fmt.Sprintf(`value in combination with %s must be less than or equal to 365 days`, unitPath.String()), + fmt.Sprintf("%d %s", value, unit), + )) + } + } + + if !heartbeatAttributeModel.Grace.IsUnknown() && !heartbeatAttributeModel.GraceUnit.IsUnknown() { + value := heartbeatAttributeModel.Grace.ValueInt32() + valuePath := path.Root("heartbeat").AtName("grace") + + unit := heartbeatAttributeModel.GraceUnit.ValueString() + unitPath := path.Root("heartbeat").AtName("grace_unit") + + seconds := valueWithUnitToSeconds(value, unit) + + if seconds > 3600*24*365 { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + valuePath, + fmt.Sprintf(`value in combination with %s must be less than or equal to 365 days`, unitPath.String()), + fmt.Sprintf("%d %s", value, unit), + )) + } + } +} + func (r *HeartbeatResource) Configure( ctx context.Context, req resource.ConfigureRequest, @@ -285,16 +337,15 @@ var ( ) type HeartbeatResourceModel struct { - ID types.String `tfsdk:"id"` - LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. - Name types.String `tfsdk:"name"` - Activated types.Bool `tfsdk:"activated"` - Muted types.Bool `tfsdk:"muted"` - Tags types.Set `tfsdk:"tags"` - AlertSettings attributes.AlertSettingsAttributeModel `tfsdk:"alert_settings"` - UseGlobalAlertSettings types.Bool `tfsdk:"use_global_alert_settings"` - Heartbeat HeartbeatAttributeModel `tfsdk:"heartbeat"` - AlertChannelSubscriptions []attributes.AlertChannelSubscriptionAttributeModel `tfsdk:"alert_channel_subscription"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Activated types.Bool `tfsdk:"activated"` + Muted types.Bool `tfsdk:"muted"` + Tags types.Set `tfsdk:"tags"` + AlertSettings types.Object `tfsdk:"alert_settings"` + UseGlobalAlertSettings types.Bool `tfsdk:"use_global_alert_settings"` + Heartbeat types.Object `tfsdk:"heartbeat"` + AlertChannelSubscriptions types.List `tfsdk:"alert_channel_subscription"` } func (m *HeartbeatResourceModel) Refresh(ctx context.Context, from *checkly.HeartbeatCheck, flags interop.RefreshFlags) diag.Diagnostics { @@ -304,10 +355,6 @@ func (m *HeartbeatResourceModel) Refresh(ctx context.Context, from *checkly.Hear m.ID = types.StringValue(from.ID) } - if flags.Created() || flags.Updated() { - m.LastUpdated = attributes.LastUpdatedNow() - } - m.Name = types.StringValue(from.Name) m.Activated = types.BoolValue(from.Activated) m.Muted = types.BoolValue(from.Muted) @@ -315,27 +362,21 @@ func (m *HeartbeatResourceModel) Refresh(ctx context.Context, from *checkly.Hear slices.Sort(from.Tags) m.Tags = interop.IntoUntypedStringSet(&from.Tags) - diags.Append(m.AlertSettings.Refresh(ctx, &from.AlertSettings, flags)...) + m.AlertSettings, _, diags = attributes.AlertSettingsAttributeGluer.RefreshToObject(ctx, &from.AlertSettings, flags) if diags.HasError() { return diags } m.UseGlobalAlertSettings = types.BoolValue(from.UseGlobalAlertSettings) - diags.Append(m.Heartbeat.Refresh(ctx, &from.Heartbeat, flags)...) + m.Heartbeat, _, diags = HeartbeatAttributeGluer.RefreshToObject(ctx, &from.Heartbeat, flags) if diags.HasError() { return diags } - m.AlertChannelSubscriptions = nil - for _, sub := range from.AlertChannelSubscriptions { - var subModel attributes.AlertChannelSubscriptionAttributeModel - diags.Append(subModel.Refresh(ctx, &sub, flags)...) - if diags.HasError() { - return diags - } - - m.AlertChannelSubscriptions = append(m.AlertChannelSubscriptions, subModel) + m.AlertChannelSubscriptions, _, diags = attributes.AlertChannelSubscriptionAttributeGluer.RefreshToList(ctx, &from.AlertChannelSubscriptions, flags) + if diags.HasError() { + return diags } return diags @@ -349,15 +390,71 @@ func (m *HeartbeatResourceModel) Render(ctx context.Context, into *checkly.Heart into.Muted = m.Muted.ValueBool() into.Tags = interop.FromUntypedStringSet(m.Tags) - diags.Append(m.AlertSettings.Render(ctx, &into.AlertSettings)...) + into.AlertSettings, _, diags = attributes.AlertSettingsAttributeGluer.RenderFromObject(ctx, m.AlertSettings) + if diags.HasError() { + return diags + } into.UseGlobalAlertSettings = m.UseGlobalAlertSettings.ValueBool() - diags.Append(m.Heartbeat.Render(ctx, &into.Heartbeat)...) + into.Heartbeat, _, diags = HeartbeatAttributeGluer.RenderFromObject(ctx, m.Heartbeat) + if diags.HasError() { + return diags + } + + into.AlertChannelSubscriptions, _, diags = attributes.AlertChannelSubscriptionAttributeGluer.RenderFromList(ctx, m.AlertChannelSubscriptions) + if diags.HasError() { + return diags + } return diags } +var HeartbeatAttributeSchema = schema.SingleNestedAttribute{ + Required: true, + Attributes: map[string]schema.Attribute{ + "period": schema.Int32Attribute{ + Description: "How often you expect a ping to the ping URL.", + Required: true, + }, + "period_unit": schema.StringAttribute{ + Description: "Possible values `seconds`, `minutes`, `hours` and `days`.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "seconds", + "minutes", + "hours", + "days", + ), + }, + }, + "grace": schema.Int32Attribute{ + Description: "How long Checkly should wait before triggering any alerts when a ping does not arrive within the set period.", + Required: true, + }, + "grace_unit": schema.StringAttribute{ + Description: "Possible values `seconds`, `minutes`, `hours` and `days`.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "seconds", + "minutes", + "hours", + "days", + ), + }, + }, + "ping_token": schema.StringAttribute{ + Description: "Custom token to generate your ping URL. Checkly will expect a ping to `https://ping.checklyhq.com/[PING_TOKEN]`.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, +} + type HeartbeatAttributeModel struct { Period types.Int32 `tfsdk:"period"` PeriodUnit types.String `tfsdk:"period_unit"` @@ -366,6 +463,11 @@ type HeartbeatAttributeModel struct { PingToken types.String `tfsdk:"ping_token"` } +var HeartbeatAttributeGluer = interop.GluerForSingleNestedAttribute[ + checkly.Heartbeat, + HeartbeatAttributeModel, +](HeartbeatAttributeSchema) + func (m *HeartbeatAttributeModel) Refresh(ctx context.Context, from *checkly.Heartbeat, flags interop.RefreshFlags) diag.Diagnostics { m.Period = types.Int32Value(int32(from.Period)) m.PeriodUnit = types.StringValue(from.PeriodUnit) diff --git a/internal/provider/resources/heartbeat_resource_test.go b/internal/provider/resources/heartbeat_resource_test.go new file mode 100644 index 0000000..6496ce0 --- /dev/null +++ b/internal/provider/resources/heartbeat_resource_test.go @@ -0,0 +1,215 @@ +package resources_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccHeartbeatRequiredFields(t *testing.T) { + config := `resource "checkly_heartbeat" "test" {}` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`The argument "name" is required, but no definition was found.`), + }, + { + Config: config, + ExpectError: regexp.MustCompile(`The argument "activated" is required, but no definition was found.`), + }, + { + Config: config, + ExpectError: regexp.MustCompile(`The argument "heartbeat" is required, but no definition was found.`), + }, + }, + }) +} + +func TestAccHeartbeatCheckInvalidInputs(t *testing.T) { + config := `resource "checkly_check" "test" { + name = 1 + activated = "invalid" + use_global_alert_settings = "invalid" + }` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`Inappropriate value for attribute "activated"`), + }, + { + Config: config, + ExpectError: regexp.MustCompile(`Inappropriate value for attribute "use_global_alert_settings"`), + }, + }, + }) +} + +func TestAccHeartbeatCheckMissingHeartbeatFields(t *testing.T) { + config := `resource "checkly_heartbeat" "test" { + activated = true + name = "heartbeat check" + heartbeat = { + + } + }` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`The argument "grace" is required, but no definition was found.`), + }, + { + Config: config, + ExpectError: regexp.MustCompile(`The argument "grace_unit" is required, but no definition was found.`), + }, + { + Config: config, + ExpectError: regexp.MustCompile(`The argument "period" is required, but no definition was found.`), + }, + { + Config: config, + ExpectError: regexp.MustCompile(`The argument "period_unit" is required, but no definition was found.`), + }, + }, + }) +} + +func TestAccHeartbeatCheckPeriodTooBig(t *testing.T) { + config := `resource "checkly_heartbeat" "test" { + activated = true + name = "heartbeat check" + heartbeat = { + period = 366 + period_unit = "days" + grace = 0 + grace_unit = "seconds" + } + }` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`Attribute heartbeat\.period value [\s\S]*?must be less than or equal to 365 days`), + }, + }, + }) +} + +func TestAccHeartbeatCheckPeriodTooSmall(t *testing.T) { + config := `resource "checkly_heartbeat" "test" { + activated = true + name = "heartbeat check" + heartbeat = { + period = 5 + period_unit = "seconds" + grace = 0 + grace_unit = "seconds" + } + }` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`Attribute heartbeat.period value[\s\S]*?must be greater than or equal to 30s`), + }, + }, + }) +} + +func TestAccHeartbeatCheckInvalidPeriodUnit(t *testing.T) { + config := `resource "checkly_heartbeat" "test" { + activated = true + name = "heartbeat check" + heartbeat = { + period = 5 + period_unit = "lightyear" + grace = 0 + grace_unit = "seconds" + } + }` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`Attribute heartbeat\.period_unit value must be one of`), + }, + }, + }) +} + +func TestAccHeartbeatCheckInvalidGraceUnit(t *testing.T) { + config := `resource "checkly_heartbeat" "test" { + activated = true + name = "heartbeat check" + heartbeat = { + period = 5 + period_unit = "days" + grace = 0 + grace_unit = "lightyear" + } + }` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`Attribute heartbeat\.grace_unit value must be one of`), + }, + }, + }) +} + +func TestAccHeartbeatCheckCreate(t *testing.T) { + config := `resource "checkly_heartbeat" "test" { + activated = true + name = "heartbeat check" + heartbeat = { + period = 5 + period_unit = "days" + grace = 0 + grace_unit = "seconds" + } + }` + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), + + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "checkly_heartbeat.test", + "name", + "heartbeat check", + ), + resource.TestCheckResourceAttr( + "checkly_heartbeat.test", + "heartbeat.period", + "5", + ), + resource.TestCheckResourceAttr( + "checkly_heartbeat.test", + "heartbeat.period_unit", + "days", + ), + ), + }, + }, + }) +} diff --git a/internal/provider/resources/maintenance_windows_resource.go b/internal/provider/resources/maintenance_windows_resource.go index 54c0df5..c43cb4d 100644 --- a/internal/provider/resources/maintenance_windows_resource.go +++ b/internal/provider/resources/maintenance_windows_resource.go @@ -47,8 +47,7 @@ func (r *MaintenanceWindowsResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": attributes.IDAttributeSchema, - "last_updated": attributes.LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, "name": schema.StringAttribute{ Required: true, Description: "The maintenance window name.", @@ -279,7 +278,6 @@ var ( type MaintenanceWindowsResourceModel struct { ID types.String `tfsdk:"id"` - LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. Name types.String `tfsdk:"name"` StartsAt types.String `tfsdk:"starts_at"` EndsAt types.String `tfsdk:"ends_at"` @@ -294,10 +292,6 @@ func (m *MaintenanceWindowsResourceModel) Refresh(ctx context.Context, from *che m.ID = MaintenanceWindowID.IntoString(from.ID) } - if flags.Created() || flags.Updated() { - m.LastUpdated = attributes.LastUpdatedNow() - } - m.Name = types.StringValue(from.Name) m.StartsAt = types.StringValue(from.StartsAt) m.EndsAt = types.StringValue(from.EndsAt) diff --git a/internal/provider/resources/private_location_resource.go b/internal/provider/resources/private_location_resource.go index 56a99dc..951c026 100644 --- a/internal/provider/resources/private_location_resource.go +++ b/internal/provider/resources/private_location_resource.go @@ -47,8 +47,7 @@ func (r *PrivateLocationResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": attributes.IDAttributeSchema, - "last_updated": attributes.LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, "name": schema.StringAttribute{ Required: true, Description: "The private location name.", @@ -243,12 +242,11 @@ var ( ) type PrivateLocationResourceModel struct { - ID types.String `tfsdk:"id"` - LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. - Name types.String `tfsdk:"name"` - SlugName types.String `tfsdk:"slug_name"` - Icon types.String `tfsdk:"icon"` - Keys types.Set `tfsdk:"keys"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + SlugName types.String `tfsdk:"slug_name"` + Icon types.String `tfsdk:"icon"` + Keys types.Set `tfsdk:"keys"` } func (m *PrivateLocationResourceModel) Refresh(ctx context.Context, from *checkly.PrivateLocation, flags interop.RefreshFlags) diag.Diagnostics { @@ -256,10 +254,6 @@ func (m *PrivateLocationResourceModel) Refresh(ctx context.Context, from *checkl m.ID = types.StringValue(from.ID) } - if flags.Created() || flags.Updated() { - m.LastUpdated = attributes.LastUpdatedNow() - } - m.Name = types.StringValue(from.Name) m.SlugName = types.StringValue(from.SlugName) m.Icon = types.StringValue(from.Icon) diff --git a/internal/provider/resources/shared_test.go b/internal/provider/resources/shared_test.go index 59ac909..38f31e0 100644 --- a/internal/provider/resources/shared_test.go +++ b/internal/provider/resources/shared_test.go @@ -1,8 +1,13 @@ package resources_test import ( + "encoding/json" + "fmt" + "regexp" + "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/checkly/terraform-provider-checkly/internal/provider" "github.com/checkly/terraform-provider-checkly/internal/provider/globalregistry" @@ -13,3 +18,26 @@ func protoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, err "checkly": providerserver.NewProtocol6WithError(provider.New("test", globalregistry.Registry)()), } } + +// test resource using regular expressions +// this helps testing arrays which have irregular indices; +// needed because we get things like "alert_settings.2888461220.escalation_type": "RUN_BASED" +func testCheckResourceAttrExpr(resource, attrExpr, value string) func(s *terraform.State) error { + return func(s *terraform.State) error { + if len(s.Modules) < 1 { + return fmt.Errorf("testCheckResourceAttrExpr: state has no modules") + } + if _, ok := s.Modules[0].Resources[resource]; !ok { + return fmt.Errorf("Resource not found: %s", resource) + } + marshaled, _ := json.Marshal(s.Modules[0].Resources[resource].Primary.Attributes) + r, err := regexp.Compile(attrExpr) + if err != nil { + return err + } + if !r.MatchString(string(marshaled)) { + return fmt.Errorf(`Couldn't find [%s: "%s"] in %s`, attrExpr, value, string(marshaled)) + } + return err + } +} diff --git a/internal/provider/resources/snippet_resource.go b/internal/provider/resources/snippet_resource.go index a9c7cef..706ae09 100644 --- a/internal/provider/resources/snippet_resource.go +++ b/internal/provider/resources/snippet_resource.go @@ -45,8 +45,7 @@ func (r *SnippetResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": attributes.IDAttributeSchema, - "last_updated": attributes.LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, "name": schema.StringAttribute{ Required: true, Description: "The name of the snippet.", @@ -254,10 +253,9 @@ var ( ) type SnippetResourceModel struct { - ID types.String `tfsdk:"id"` - LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. - Name types.String `tfsdk:"name"` - Script types.String `tfsdk:"script"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Script types.String `tfsdk:"script"` } func (m *SnippetResourceModel) Refresh(ctx context.Context, from *checkly.Snippet, flags interop.RefreshFlags) diag.Diagnostics { @@ -265,10 +263,6 @@ func (m *SnippetResourceModel) Refresh(ctx context.Context, from *checkly.Snippe m.ID = SnippetID.IntoString(from.ID) } - if flags.Created() || flags.Updated() { - m.LastUpdated = attributes.LastUpdatedNow() - } - m.Name = types.StringValue(from.Name) m.Script = types.StringValue(from.Script) diff --git a/internal/provider/resources/trigger_check_resource.go b/internal/provider/resources/trigger_check_resource.go index f496598..032da93 100644 --- a/internal/provider/resources/trigger_check_resource.go +++ b/internal/provider/resources/trigger_check_resource.go @@ -45,8 +45,7 @@ func (r *TriggerCheckResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": attributes.IDAttributeSchema, - "last_updated": attributes.LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, "check_id": schema.StringAttribute{ Required: true, Description: "The ID of the check that you want to attach the trigger to.", @@ -233,11 +232,10 @@ var ( ) type TriggerCheckResourceModel struct { - ID types.String `tfsdk:"id"` - LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. - CheckID types.String `tfsdk:"check_id"` - Token types.String `tfsdk:"token"` - URL types.String `tfsdk:"url"` + ID types.String `tfsdk:"id"` + CheckID types.String `tfsdk:"check_id"` + Token types.String `tfsdk:"token"` + URL types.String `tfsdk:"url"` } func (m *TriggerCheckResourceModel) Refresh(ctx context.Context, from *checkly.TriggerCheck, flags interop.RefreshFlags) diag.Diagnostics { @@ -247,10 +245,6 @@ func (m *TriggerCheckResourceModel) Refresh(ctx context.Context, from *checkly.T m.ID = TriggerCheckID.IntoString(from.ID) } - if flags.Created() || flags.Updated() { - m.LastUpdated = attributes.LastUpdatedNow() - } - m.CheckID = types.StringValue(from.CheckId) m.Token = types.StringValue(from.Token) m.URL = types.StringValue(from.URL) diff --git a/internal/provider/resources/trigger_group_resource.go b/internal/provider/resources/trigger_group_resource.go index c9feef5..b31ff56 100644 --- a/internal/provider/resources/trigger_group_resource.go +++ b/internal/provider/resources/trigger_group_resource.go @@ -45,8 +45,7 @@ func (r *TriggerGroupResource) Schema( ) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": attributes.IDAttributeSchema, - "last_updated": attributes.LastUpdatedAttributeSchema, + "id": attributes.IDAttributeSchema, "group_id": schema.Int64Attribute{ Required: true, Description: "The ID of the group that you want to attach the trigger to.", @@ -233,11 +232,10 @@ var ( ) type TriggerGroupResourceModel struct { - ID types.String `tfsdk:"id"` - LastUpdated types.String `tfsdk:"last_updated"` // FIXME: Keep this? Old code did not have it. - GroupID types.Int64 `tfsdk:"group_id"` - Token types.String `tfsdk:"token"` - URL types.String `tfsdk:"url"` + ID types.String `tfsdk:"id"` + GroupID types.Int64 `tfsdk:"group_id"` + Token types.String `tfsdk:"token"` + URL types.String `tfsdk:"url"` } func (m *TriggerGroupResourceModel) Refresh(ctx context.Context, from *checkly.TriggerGroup, flags interop.RefreshFlags) diag.Diagnostics { @@ -245,10 +243,6 @@ func (m *TriggerGroupResourceModel) Refresh(ctx context.Context, from *checkly.T m.ID = TriggerGroupID.IntoString(from.ID) } - if flags.Created() || flags.Updated() { - m.LastUpdated = attributes.LastUpdatedNow() - } - m.GroupID = types.Int64Value(from.GroupId) m.Token = types.StringValue(from.Token) m.URL = types.StringValue(from.URL)