add towerops_check resource for service check management
- new towerops_check resource supporting HTTP, TCP, DNS, and ping checks - flat schema with type-specific fields (url, host, port, hostname, etc.) - client CRUD methods for /api/v1/checks endpoints - resource documentation, examples, and provider index updated - acceptance tests for all check types, update, import, and error handling
This commit is contained in:
parent
3ff5ea41b3
commit
bc3dbfe419
8 changed files with 1729 additions and 0 deletions
|
|
@ -133,6 +133,24 @@ output "agent_token" {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Service Checks
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
resource "towerops_check" "web_health" {
|
||||||
|
name = "Web Health Check"
|
||||||
|
check_type = "http"
|
||||||
|
url = "https://example.com/health"
|
||||||
|
expected_status = 200
|
||||||
|
content_match = "\"status\":\"ok\""
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "towerops_check" "gateway_ping" {
|
||||||
|
name = "Gateway Reachability"
|
||||||
|
check_type = "ping"
|
||||||
|
host = "10.0.0.1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Integration
|
### Integration
|
||||||
|
|
||||||
```terraform
|
```terraform
|
||||||
|
|
|
||||||
166
docs/resources/check.md
Normal file
166
docs/resources/check.md
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
---
|
||||||
|
page_title: "towerops_check Resource - TowerOps"
|
||||||
|
description: |-
|
||||||
|
Manages a TowerOps service check.
|
||||||
|
---
|
||||||
|
|
||||||
|
# towerops_check (Resource)
|
||||||
|
|
||||||
|
Manages a TowerOps service check. Service checks monitor the availability and responsiveness of network services. Supported check types: HTTP, TCP, DNS, and ping.
|
||||||
|
|
||||||
|
Configuration fields are flattened into top-level attributes rather than nested inside a config block. Only the fields relevant to the chosen `check_type` are used.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
### HTTP Health Check
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
resource "towerops_check" "web_health" {
|
||||||
|
name = "Web Health Check"
|
||||||
|
check_type = "http"
|
||||||
|
url = "https://example.com/health"
|
||||||
|
expected_status = 200
|
||||||
|
content_match = "\"status\":\"ok\""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### HTTP Check with Custom Settings
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
resource "towerops_check" "api_check" {
|
||||||
|
name = "API Endpoint"
|
||||||
|
check_type = "http"
|
||||||
|
device_id = towerops_device.web_server.id
|
||||||
|
url = "https://api.example.com/v1/status"
|
||||||
|
method = "POST"
|
||||||
|
expected_status = 200
|
||||||
|
verify_ssl = true
|
||||||
|
follow_redirects = false
|
||||||
|
interval_seconds = 30
|
||||||
|
timeout_ms = 10000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### TCP Port Check
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
resource "towerops_check" "radius_port" {
|
||||||
|
name = "FreeRADIUS Auth Port"
|
||||||
|
check_type = "tcp"
|
||||||
|
device_id = towerops_device.radius.id
|
||||||
|
host = "10.0.0.5"
|
||||||
|
port = 1812
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### TCP Check with Send/Expect
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
resource "towerops_check" "smtp_banner" {
|
||||||
|
name = "SMTP Banner Check"
|
||||||
|
check_type = "tcp"
|
||||||
|
host = "mail.example.com"
|
||||||
|
port = 25
|
||||||
|
expect_string = "220"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DNS Resolution Check
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
resource "towerops_check" "dns_resolution" {
|
||||||
|
name = "DNS Resolution"
|
||||||
|
check_type = "dns"
|
||||||
|
device_id = towerops_device.dns_server.id
|
||||||
|
hostname = "google.com"
|
||||||
|
dns_server = "10.0.0.1"
|
||||||
|
record_type = "A"
|
||||||
|
expected_result = "142.250.80.46"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ping Check
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
resource "towerops_check" "gateway_ping" {
|
||||||
|
name = "Gateway Reachability"
|
||||||
|
check_type = "ping"
|
||||||
|
device_id = towerops_device.gateway.id
|
||||||
|
host = "10.0.0.1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ping Check with Custom Count
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
resource "towerops_check" "wan_ping" {
|
||||||
|
name = "WAN Link Ping"
|
||||||
|
check_type = "ping"
|
||||||
|
host = "8.8.8.8"
|
||||||
|
ping_count = 5
|
||||||
|
interval_seconds = 120
|
||||||
|
timeout_ms = 10000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `name` (String) - The name of the check.
|
||||||
|
- `check_type` (String) - The type of check: `http`, `tcp`, `dns`, or `ping`. Changing this forces a new resource.
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
|
||||||
|
- `description` (String) - A description of the check.
|
||||||
|
- `enabled` (Boolean) - Whether the check is enabled. Default: `true`.
|
||||||
|
- `device_id` (String) - The ID of the device this check is associated with.
|
||||||
|
- `agent_token_id` (String) - The ID of the agent token that executes this check.
|
||||||
|
- `interval_seconds` (Number) - How often the check runs, in seconds. Default: `60`.
|
||||||
|
- `timeout_ms` (Number) - Timeout for check execution, in milliseconds. Default: `5000`.
|
||||||
|
- `retry_interval_seconds` (Number) - Interval between retries after a failure, in seconds. Default: `30`.
|
||||||
|
- `max_check_attempts` (Number) - Number of consecutive failures before transitioning to hard state. Default: `3`.
|
||||||
|
|
||||||
|
#### HTTP Check Fields (used when `check_type = "http"`)
|
||||||
|
|
||||||
|
- `url` (String) - The URL to check. Required for HTTP checks.
|
||||||
|
- `method` (String) - HTTP method to use. Default: `"GET"`.
|
||||||
|
- `expected_status` (Number) - Expected HTTP status code. Default: `200`.
|
||||||
|
- `verify_ssl` (Boolean) - Whether to verify SSL certificates. Default: `true`.
|
||||||
|
- `follow_redirects` (Boolean) - Whether to follow HTTP redirects. Default: `true`.
|
||||||
|
- `content_match` (String) - Regex pattern to match against the response body.
|
||||||
|
|
||||||
|
#### TCP Check Fields (used when `check_type = "tcp"`)
|
||||||
|
|
||||||
|
- `host` (String) - The host to connect to. Required for TCP checks.
|
||||||
|
- `port` (Number) - The TCP port to check. Required for TCP checks.
|
||||||
|
- `send_string` (String) - Data to send after establishing the TCP connection.
|
||||||
|
- `expect_string` (String) - Expected response string.
|
||||||
|
|
||||||
|
#### DNS Check Fields (used when `check_type = "dns"`)
|
||||||
|
|
||||||
|
- `hostname` (String) - The hostname to resolve. Required for DNS checks.
|
||||||
|
- `record_type` (String) - DNS record type (`A`, `AAAA`, `CNAME`, `MX`, `TXT`, `NS`, `PTR`). Default: `"A"`.
|
||||||
|
- `dns_server` (String) - DNS server to query. Uses system default if not set.
|
||||||
|
- `expected_result` (String) - Expected DNS resolution result.
|
||||||
|
|
||||||
|
#### Ping Check Fields (used when `check_type = "ping"`)
|
||||||
|
|
||||||
|
- `host` (String) - The host to ping. Required for ping checks.
|
||||||
|
- `ping_count` (Number) - Number of ping packets to send. Default: `3`.
|
||||||
|
|
||||||
|
### Read-Only
|
||||||
|
|
||||||
|
- `id` (String) - The unique identifier of the check.
|
||||||
|
- `current_state` (Number) - Current check state (0=OK, 1=WARNING, 2=CRITICAL, 3=UNKNOWN).
|
||||||
|
- `current_state_type` (String) - Current state type (`soft` or `hard`).
|
||||||
|
- `last_check_at` (String) - Timestamp of the last check execution.
|
||||||
|
- `inserted_at` (String) - The timestamp when the check was created.
|
||||||
|
|
||||||
|
## Import
|
||||||
|
|
||||||
|
Checks can be imported using their UUID:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
terraform import towerops_check.web_health 7c9e6679-7425-40de-944b-e07fc1f90ae7
|
||||||
|
```
|
||||||
|
|
@ -83,6 +83,29 @@ resource "towerops_maintenance_window" "network_upgrade" {
|
||||||
ends_at = "2024-03-15T06:00:00Z"
|
ends_at = "2024-03-15T06:00:00Z"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Service checks
|
||||||
|
resource "towerops_check" "web_health" {
|
||||||
|
name = "Web Health Check"
|
||||||
|
check_type = "http"
|
||||||
|
url = "https://example.com/health"
|
||||||
|
expected_status = 200
|
||||||
|
content_match = "\"status\":\"ok\""
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "towerops_check" "dns_resolution" {
|
||||||
|
name = "DNS Resolution"
|
||||||
|
check_type = "dns"
|
||||||
|
hostname = "google.com"
|
||||||
|
dns_server = "10.0.0.1"
|
||||||
|
record_type = "A"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "towerops_check" "gateway_ping" {
|
||||||
|
name = "Gateway Reachability"
|
||||||
|
check_type = "ping"
|
||||||
|
host = "10.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
# Output the site ID
|
# Output the site ID
|
||||||
output "site_id" {
|
output "site_id" {
|
||||||
value = towerops_site.main_office.id
|
value = towerops_site.main_office.id
|
||||||
|
|
|
||||||
32
examples/resources/towerops_check/resource.tf
Normal file
32
examples/resources/towerops_check/resource.tf
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# HTTP health check
|
||||||
|
resource "towerops_check" "web_health" {
|
||||||
|
name = "Web Health Check"
|
||||||
|
check_type = "http"
|
||||||
|
url = "https://example.com/health"
|
||||||
|
expected_status = 200
|
||||||
|
content_match = "\"status\":\"ok\""
|
||||||
|
}
|
||||||
|
|
||||||
|
# TCP port check
|
||||||
|
resource "towerops_check" "radius_port" {
|
||||||
|
name = "FreeRADIUS Auth Port"
|
||||||
|
check_type = "tcp"
|
||||||
|
host = "10.0.0.5"
|
||||||
|
port = 1812
|
||||||
|
}
|
||||||
|
|
||||||
|
# DNS resolution check
|
||||||
|
resource "towerops_check" "dns_resolution" {
|
||||||
|
name = "DNS Resolution"
|
||||||
|
check_type = "dns"
|
||||||
|
hostname = "google.com"
|
||||||
|
dns_server = "10.0.0.1"
|
||||||
|
record_type = "A"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ping check
|
||||||
|
resource "towerops_check" "gateway_ping" {
|
||||||
|
name = "Gateway Reachability"
|
||||||
|
check_type = "ping"
|
||||||
|
host = "10.0.0.1"
|
||||||
|
}
|
||||||
660
internal/provider/check_resource.go
Normal file
660
internal/provider/check_resource.go
Normal file
|
|
@ -0,0 +1,660 @@
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"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/int64default"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ resource.Resource = &CheckResource{}
|
||||||
|
var _ resource.ResourceWithImportState = &CheckResource{}
|
||||||
|
|
||||||
|
// CheckResource defines the resource implementation.
|
||||||
|
type CheckResource struct {
|
||||||
|
client *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckResourceModel describes the resource data model.
|
||||||
|
type CheckResourceModel struct {
|
||||||
|
ID types.String `tfsdk:"id"`
|
||||||
|
Name types.String `tfsdk:"name"`
|
||||||
|
CheckType types.String `tfsdk:"check_type"`
|
||||||
|
Description types.String `tfsdk:"description"`
|
||||||
|
Enabled types.Bool `tfsdk:"enabled"`
|
||||||
|
DeviceID types.String `tfsdk:"device_id"`
|
||||||
|
AgentTokenID types.String `tfsdk:"agent_token_id"`
|
||||||
|
IntervalSeconds types.Int64 `tfsdk:"interval_seconds"`
|
||||||
|
TimeoutMs types.Int64 `tfsdk:"timeout_ms"`
|
||||||
|
RetryIntervalSeconds types.Int64 `tfsdk:"retry_interval_seconds"`
|
||||||
|
MaxCheckAttempts types.Int64 `tfsdk:"max_check_attempts"`
|
||||||
|
|
||||||
|
// HTTP config
|
||||||
|
URL types.String `tfsdk:"url"`
|
||||||
|
Method types.String `tfsdk:"method"`
|
||||||
|
ExpectedStatus types.Int64 `tfsdk:"expected_status"`
|
||||||
|
VerifySSL types.Bool `tfsdk:"verify_ssl"`
|
||||||
|
FollowRedirects types.Bool `tfsdk:"follow_redirects"`
|
||||||
|
ContentMatch types.String `tfsdk:"content_match"`
|
||||||
|
|
||||||
|
// TCP config
|
||||||
|
Host types.String `tfsdk:"host"`
|
||||||
|
Port types.Int64 `tfsdk:"port"`
|
||||||
|
SendString types.String `tfsdk:"send_string"`
|
||||||
|
ExpectString types.String `tfsdk:"expect_string"`
|
||||||
|
|
||||||
|
// DNS config
|
||||||
|
Hostname types.String `tfsdk:"hostname"`
|
||||||
|
RecordType types.String `tfsdk:"record_type"`
|
||||||
|
DNSServer types.String `tfsdk:"dns_server"`
|
||||||
|
ExpectedResult types.String `tfsdk:"expected_result"`
|
||||||
|
|
||||||
|
// Ping config
|
||||||
|
PingCount types.Int64 `tfsdk:"ping_count"`
|
||||||
|
|
||||||
|
// Computed
|
||||||
|
CurrentState types.Int64 `tfsdk:"current_state"`
|
||||||
|
CurrentStateType types.String `tfsdk:"current_state_type"`
|
||||||
|
LastCheckAt types.String `tfsdk:"last_check_at"`
|
||||||
|
InsertedAt types.String `tfsdk:"inserted_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCheckResource creates a new check resource.
|
||||||
|
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: "Manages a TowerOps service check. Supports HTTP, TCP, DNS, and ping check types.",
|
||||||
|
Attributes: map[string]schema.Attribute{
|
||||||
|
"id": schema.StringAttribute{
|
||||||
|
Description: "The unique identifier of the check.",
|
||||||
|
Computed: true,
|
||||||
|
PlanModifiers: []planmodifier.String{
|
||||||
|
stringplanmodifier.UseStateForUnknown(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"name": schema.StringAttribute{
|
||||||
|
Description: "The name of the check.",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"check_type": schema.StringAttribute{
|
||||||
|
Description: "The type of check: http, tcp, dns, or ping.",
|
||||||
|
Required: true,
|
||||||
|
PlanModifiers: []planmodifier.String{
|
||||||
|
stringplanmodifier.RequiresReplace(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": schema.StringAttribute{
|
||||||
|
Description: "A description of the check.",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"enabled": schema.BoolAttribute{
|
||||||
|
Description: "Whether the check is enabled.",
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Default: booldefault.StaticBool(true),
|
||||||
|
},
|
||||||
|
"device_id": schema.StringAttribute{
|
||||||
|
Description: "The ID of the device this check is associated with.",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"agent_token_id": schema.StringAttribute{
|
||||||
|
Description: "The ID of the agent token that executes this check.",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"interval_seconds": schema.Int64Attribute{
|
||||||
|
Description: "How often the check runs, in seconds.",
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Default: int64default.StaticInt64(60),
|
||||||
|
},
|
||||||
|
"timeout_ms": schema.Int64Attribute{
|
||||||
|
Description: "Timeout for the check execution, in milliseconds.",
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Default: int64default.StaticInt64(5000),
|
||||||
|
},
|
||||||
|
"retry_interval_seconds": schema.Int64Attribute{
|
||||||
|
Description: "Interval between retries after a failure, in seconds.",
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Default: int64default.StaticInt64(30),
|
||||||
|
},
|
||||||
|
"max_check_attempts": schema.Int64Attribute{
|
||||||
|
Description: "Number of consecutive failures before transitioning to hard state.",
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Default: int64default.StaticInt64(3),
|
||||||
|
},
|
||||||
|
|
||||||
|
// HTTP config
|
||||||
|
"url": schema.StringAttribute{
|
||||||
|
Description: "The URL to check. Required for HTTP checks.",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"method": schema.StringAttribute{
|
||||||
|
Description: "HTTP method to use. Defaults to GET.",
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Default: stringdefault.StaticString("GET"),
|
||||||
|
},
|
||||||
|
"expected_status": schema.Int64Attribute{
|
||||||
|
Description: "Expected HTTP status code. Defaults to 200.",
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Default: int64default.StaticInt64(200),
|
||||||
|
},
|
||||||
|
"verify_ssl": schema.BoolAttribute{
|
||||||
|
Description: "Whether to verify SSL certificates. Defaults to true.",
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Default: booldefault.StaticBool(true),
|
||||||
|
},
|
||||||
|
"follow_redirects": schema.BoolAttribute{
|
||||||
|
Description: "Whether to follow HTTP redirects. Defaults to true.",
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Default: booldefault.StaticBool(true),
|
||||||
|
},
|
||||||
|
"content_match": schema.StringAttribute{
|
||||||
|
Description: "Regex pattern to match against HTTP response body.",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// TCP config
|
||||||
|
"host": schema.StringAttribute{
|
||||||
|
Description: "The host to check. Used for TCP and ping checks.",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"port": schema.Int64Attribute{
|
||||||
|
Description: "The TCP port to check. Required for TCP checks.",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"send_string": schema.StringAttribute{
|
||||||
|
Description: "Data to send after TCP connection.",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"expect_string": schema.StringAttribute{
|
||||||
|
Description: "Expected response string for TCP checks.",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// DNS config
|
||||||
|
"hostname": schema.StringAttribute{
|
||||||
|
Description: "The hostname to resolve. Required for DNS checks.",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"record_type": schema.StringAttribute{
|
||||||
|
Description: "DNS record type (A, AAAA, CNAME, MX, TXT, NS, PTR). Defaults to A.",
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Default: stringdefault.StaticString("A"),
|
||||||
|
},
|
||||||
|
"dns_server": schema.StringAttribute{
|
||||||
|
Description: "DNS server to query. Uses system default if not set.",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"expected_result": schema.StringAttribute{
|
||||||
|
Description: "Expected DNS resolution result.",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Ping config
|
||||||
|
"ping_count": schema.Int64Attribute{
|
||||||
|
Description: "Number of ping packets to send. Defaults to 3.",
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Default: int64default.StaticInt64(3),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Computed
|
||||||
|
"current_state": schema.Int64Attribute{
|
||||||
|
Description: "Current check state (0=OK, 1=WARNING, 2=CRITICAL, 3=UNKNOWN).",
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"current_state_type": schema.StringAttribute{
|
||||||
|
Description: "Current state type (soft or hard).",
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"last_check_at": schema.StringAttribute{
|
||||||
|
Description: "Timestamp of the last check execution.",
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"inserted_at": schema.StringAttribute{
|
||||||
|
Description: "The timestamp when the check was created.",
|
||||||
|
Computed: true,
|
||||||
|
PlanModifiers: []planmodifier.String{
|
||||||
|
stringplanmodifier.UseStateForUnknown(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CheckResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||||
|
if req.ProviderData == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client, ok := req.ProviderData.(*Client)
|
||||||
|
if !ok {
|
||||||
|
resp.Diagnostics.AddError(
|
||||||
|
"Unexpected Resource Configure Type",
|
||||||
|
fmt.Sprintf("Expected *Client, got: %T", req.ProviderData),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CheckResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||||
|
var data CheckResourceModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
check := Check{
|
||||||
|
Name: data.Name.ValueString(),
|
||||||
|
CheckType: data.CheckType.ValueString(),
|
||||||
|
Config: buildConfig(data),
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.Description.IsNull() {
|
||||||
|
desc := data.Description.ValueString()
|
||||||
|
check.Description = &desc
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.Enabled.IsNull() {
|
||||||
|
enabled := data.Enabled.ValueBool()
|
||||||
|
check.Enabled = &enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.DeviceID.IsNull() {
|
||||||
|
id := data.DeviceID.ValueString()
|
||||||
|
check.DeviceID = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.AgentTokenID.IsNull() {
|
||||||
|
id := data.AgentTokenID.ValueString()
|
||||||
|
check.AgentTokenID = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.IntervalSeconds.IsNull() {
|
||||||
|
v := int(data.IntervalSeconds.ValueInt64())
|
||||||
|
check.IntervalSeconds = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.TimeoutMs.IsNull() {
|
||||||
|
v := int(data.TimeoutMs.ValueInt64())
|
||||||
|
check.TimeoutMs = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.RetryIntervalSeconds.IsNull() {
|
||||||
|
v := int(data.RetryIntervalSeconds.ValueInt64())
|
||||||
|
check.RetryIntervalSeconds = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.MaxCheckAttempts.IsNull() {
|
||||||
|
v := int(data.MaxCheckAttempts.ValueInt64())
|
||||||
|
check.MaxCheckAttempts = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
created, err := r.client.CreateCheck(check)
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Failed to create check", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mapCheckToState(created, &data)
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CheckResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||||
|
var data CheckResourceModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
check, err := r.client.GetCheck(data.ID.ValueString())
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrNotFound) {
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Diagnostics.AddError("Failed to read check", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mapCheckToState(check, &data)
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CheckResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||||
|
var data CheckResourceModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
check := Check{
|
||||||
|
Name: data.Name.ValueString(),
|
||||||
|
CheckType: data.CheckType.ValueString(),
|
||||||
|
Config: buildConfig(data),
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.Description.IsNull() {
|
||||||
|
desc := data.Description.ValueString()
|
||||||
|
check.Description = &desc
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.Enabled.IsNull() {
|
||||||
|
enabled := data.Enabled.ValueBool()
|
||||||
|
check.Enabled = &enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.DeviceID.IsNull() {
|
||||||
|
id := data.DeviceID.ValueString()
|
||||||
|
check.DeviceID = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.AgentTokenID.IsNull() {
|
||||||
|
id := data.AgentTokenID.ValueString()
|
||||||
|
check.AgentTokenID = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.IntervalSeconds.IsNull() {
|
||||||
|
v := int(data.IntervalSeconds.ValueInt64())
|
||||||
|
check.IntervalSeconds = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.TimeoutMs.IsNull() {
|
||||||
|
v := int(data.TimeoutMs.ValueInt64())
|
||||||
|
check.TimeoutMs = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.RetryIntervalSeconds.IsNull() {
|
||||||
|
v := int(data.RetryIntervalSeconds.ValueInt64())
|
||||||
|
check.RetryIntervalSeconds = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.MaxCheckAttempts.IsNull() {
|
||||||
|
v := int(data.MaxCheckAttempts.ValueInt64())
|
||||||
|
check.MaxCheckAttempts = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := r.client.UpdateCheck(data.ID.ValueString(), check)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrNotFound) {
|
||||||
|
// Check deleted outside Terraform, recreate
|
||||||
|
created, createErr := r.client.CreateCheck(check)
|
||||||
|
if createErr != nil {
|
||||||
|
resp.Diagnostics.AddError("Failed to create check (after 404 on update)", createErr.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mapCheckToState(created, &data)
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Diagnostics.AddError("Failed to update check", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mapCheckToState(updated, &data)
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CheckResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||||
|
var data CheckResourceModel
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.client.DeleteCheck(data.ID.ValueString())
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Failed to delete check", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CheckResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||||
|
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildConfig constructs the config map from flat Terraform attributes based on check_type.
|
||||||
|
func buildConfig(data CheckResourceModel) map[string]any {
|
||||||
|
config := map[string]any{}
|
||||||
|
checkType := data.CheckType.ValueString()
|
||||||
|
|
||||||
|
switch checkType {
|
||||||
|
case "http":
|
||||||
|
if !data.URL.IsNull() {
|
||||||
|
config["url"] = data.URL.ValueString()
|
||||||
|
}
|
||||||
|
if !data.Method.IsNull() {
|
||||||
|
config["method"] = data.Method.ValueString()
|
||||||
|
}
|
||||||
|
if !data.ExpectedStatus.IsNull() {
|
||||||
|
config["expected_status"] = int(data.ExpectedStatus.ValueInt64())
|
||||||
|
}
|
||||||
|
if !data.VerifySSL.IsNull() {
|
||||||
|
config["verify_ssl"] = data.VerifySSL.ValueBool()
|
||||||
|
}
|
||||||
|
if !data.FollowRedirects.IsNull() {
|
||||||
|
config["follow_redirects"] = data.FollowRedirects.ValueBool()
|
||||||
|
}
|
||||||
|
if !data.ContentMatch.IsNull() {
|
||||||
|
config["regex"] = data.ContentMatch.ValueString()
|
||||||
|
}
|
||||||
|
|
||||||
|
case "tcp":
|
||||||
|
if !data.Host.IsNull() {
|
||||||
|
config["host"] = data.Host.ValueString()
|
||||||
|
}
|
||||||
|
if !data.Port.IsNull() {
|
||||||
|
config["port"] = int(data.Port.ValueInt64())
|
||||||
|
}
|
||||||
|
if !data.SendString.IsNull() {
|
||||||
|
config["send"] = data.SendString.ValueString()
|
||||||
|
}
|
||||||
|
if !data.ExpectString.IsNull() {
|
||||||
|
config["expect"] = data.ExpectString.ValueString()
|
||||||
|
}
|
||||||
|
|
||||||
|
case "dns":
|
||||||
|
if !data.Hostname.IsNull() {
|
||||||
|
config["hostname"] = data.Hostname.ValueString()
|
||||||
|
}
|
||||||
|
if !data.RecordType.IsNull() {
|
||||||
|
config["record_type"] = data.RecordType.ValueString()
|
||||||
|
}
|
||||||
|
if !data.DNSServer.IsNull() {
|
||||||
|
config["server"] = data.DNSServer.ValueString()
|
||||||
|
}
|
||||||
|
if !data.ExpectedResult.IsNull() {
|
||||||
|
config["expected"] = data.ExpectedResult.ValueString()
|
||||||
|
}
|
||||||
|
|
||||||
|
case "ping":
|
||||||
|
if !data.Host.IsNull() {
|
||||||
|
config["host"] = data.Host.ValueString()
|
||||||
|
}
|
||||||
|
if !data.PingCount.IsNull() {
|
||||||
|
config["count"] = int(data.PingCount.ValueInt64())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapCheckToState maps an API Check response back into the Terraform state model.
|
||||||
|
func mapCheckToState(check *Check, data *CheckResourceModel) {
|
||||||
|
data.ID = types.StringValue(check.ID)
|
||||||
|
data.Name = types.StringValue(check.Name)
|
||||||
|
data.CheckType = types.StringValue(check.CheckType)
|
||||||
|
data.InsertedAt = types.StringValue(check.InsertedAt)
|
||||||
|
|
||||||
|
if check.Description != nil {
|
||||||
|
data.Description = types.StringValue(*check.Description)
|
||||||
|
} else {
|
||||||
|
data.Description = types.StringNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
if check.Enabled != nil {
|
||||||
|
data.Enabled = types.BoolValue(*check.Enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
if check.DeviceID != nil {
|
||||||
|
data.DeviceID = types.StringValue(*check.DeviceID)
|
||||||
|
} else {
|
||||||
|
data.DeviceID = types.StringNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
if check.AgentTokenID != nil {
|
||||||
|
data.AgentTokenID = types.StringValue(*check.AgentTokenID)
|
||||||
|
} else {
|
||||||
|
data.AgentTokenID = types.StringNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
if check.IntervalSeconds != nil {
|
||||||
|
data.IntervalSeconds = types.Int64Value(int64(*check.IntervalSeconds))
|
||||||
|
}
|
||||||
|
|
||||||
|
if check.TimeoutMs != nil {
|
||||||
|
data.TimeoutMs = types.Int64Value(int64(*check.TimeoutMs))
|
||||||
|
}
|
||||||
|
|
||||||
|
if check.RetryIntervalSeconds != nil {
|
||||||
|
data.RetryIntervalSeconds = types.Int64Value(int64(*check.RetryIntervalSeconds))
|
||||||
|
}
|
||||||
|
|
||||||
|
if check.MaxCheckAttempts != nil {
|
||||||
|
data.MaxCheckAttempts = types.Int64Value(int64(*check.MaxCheckAttempts))
|
||||||
|
}
|
||||||
|
|
||||||
|
if check.CurrentState != nil {
|
||||||
|
data.CurrentState = types.Int64Value(int64(*check.CurrentState))
|
||||||
|
} else {
|
||||||
|
data.CurrentState = types.Int64Null()
|
||||||
|
}
|
||||||
|
|
||||||
|
if check.CurrentStateType != nil {
|
||||||
|
data.CurrentStateType = types.StringValue(*check.CurrentStateType)
|
||||||
|
} else {
|
||||||
|
data.CurrentStateType = types.StringNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
if check.LastCheckAt != nil {
|
||||||
|
data.LastCheckAt = types.StringValue(*check.LastCheckAt)
|
||||||
|
} else {
|
||||||
|
data.LastCheckAt = types.StringNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack config into flat fields based on check_type
|
||||||
|
unpackConfig(check.Config, check.CheckType, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackConfig reads the config map and sets flat Terraform attributes.
|
||||||
|
func unpackConfig(config map[string]any, checkType string, data *CheckResourceModel) {
|
||||||
|
if config == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch checkType {
|
||||||
|
case "http":
|
||||||
|
if v, ok := config["url"].(string); ok {
|
||||||
|
data.URL = types.StringValue(v)
|
||||||
|
}
|
||||||
|
if v, ok := config["method"].(string); ok {
|
||||||
|
data.Method = types.StringValue(v)
|
||||||
|
}
|
||||||
|
if v, ok := configInt(config, "expected_status"); ok {
|
||||||
|
data.ExpectedStatus = types.Int64Value(int64(v))
|
||||||
|
}
|
||||||
|
if v, ok := config["verify_ssl"].(bool); ok {
|
||||||
|
data.VerifySSL = types.BoolValue(v)
|
||||||
|
}
|
||||||
|
if v, ok := config["follow_redirects"].(bool); ok {
|
||||||
|
data.FollowRedirects = types.BoolValue(v)
|
||||||
|
}
|
||||||
|
if v, ok := config["regex"].(string); ok {
|
||||||
|
data.ContentMatch = types.StringValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "tcp":
|
||||||
|
if v, ok := config["host"].(string); ok {
|
||||||
|
data.Host = types.StringValue(v)
|
||||||
|
}
|
||||||
|
if v, ok := configInt(config, "port"); ok {
|
||||||
|
data.Port = types.Int64Value(int64(v))
|
||||||
|
}
|
||||||
|
if v, ok := config["send"].(string); ok {
|
||||||
|
data.SendString = types.StringValue(v)
|
||||||
|
}
|
||||||
|
if v, ok := config["expect"].(string); ok {
|
||||||
|
data.ExpectString = types.StringValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "dns":
|
||||||
|
if v, ok := config["hostname"].(string); ok {
|
||||||
|
data.Hostname = types.StringValue(v)
|
||||||
|
}
|
||||||
|
if v, ok := config["record_type"].(string); ok {
|
||||||
|
data.RecordType = types.StringValue(v)
|
||||||
|
}
|
||||||
|
if v, ok := config["server"].(string); ok {
|
||||||
|
data.DNSServer = types.StringValue(v)
|
||||||
|
}
|
||||||
|
if v, ok := config["expected"].(string); ok {
|
||||||
|
data.ExpectedResult = types.StringValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "ping":
|
||||||
|
if v, ok := config["host"].(string); ok {
|
||||||
|
data.Host = types.StringValue(v)
|
||||||
|
}
|
||||||
|
if v, ok := configInt(config, "count"); ok {
|
||||||
|
data.PingCount = types.Int64Value(int64(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// configInt extracts an integer from a config map, handling JSON's float64 encoding.
|
||||||
|
func configInt(config map[string]any, key string) (int, bool) {
|
||||||
|
v, ok := config[key]
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
switch n := v.(type) {
|
||||||
|
case float64:
|
||||||
|
return int(n), true
|
||||||
|
case int:
|
||||||
|
return n, true
|
||||||
|
case int64:
|
||||||
|
return int(n), true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
756
internal/provider/check_resource_test.go
Normal file
756
internal/provider/check_resource_test.go
Normal file
|
|
@ -0,0 +1,756 @@
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccCheckResource_http(t *testing.T) {
|
||||||
|
var checkID string
|
||||||
|
var mu sync.Mutex
|
||||||
|
enabled := true
|
||||||
|
interval := 60
|
||||||
|
timeout := 5000
|
||||||
|
retry := 30
|
||||||
|
maxAttempts := 3
|
||||||
|
currentState := 3
|
||||||
|
stateType := "soft"
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/api/v1/checks":
|
||||||
|
checkID = "test-http-check-id"
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(Check{
|
||||||
|
ID: checkID,
|
||||||
|
Name: "Web Health",
|
||||||
|
CheckType: "http",
|
||||||
|
Enabled: &enabled,
|
||||||
|
IntervalSeconds: &interval,
|
||||||
|
TimeoutMs: &timeout,
|
||||||
|
RetryIntervalSeconds: &retry,
|
||||||
|
MaxCheckAttempts: &maxAttempts,
|
||||||
|
CurrentState: ¤tState,
|
||||||
|
CurrentStateType: &stateType,
|
||||||
|
Config: map[string]any{
|
||||||
|
"url": "https://example.com/health",
|
||||||
|
"method": "GET",
|
||||||
|
"expected_status": 200,
|
||||||
|
"verify_ssl": true,
|
||||||
|
"follow_redirects": true,
|
||||||
|
},
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(Check{
|
||||||
|
ID: checkID,
|
||||||
|
Name: "Web Health",
|
||||||
|
CheckType: "http",
|
||||||
|
Enabled: &enabled,
|
||||||
|
IntervalSeconds: &interval,
|
||||||
|
TimeoutMs: &timeout,
|
||||||
|
RetryIntervalSeconds: &retry,
|
||||||
|
MaxCheckAttempts: &maxAttempts,
|
||||||
|
CurrentState: ¤tState,
|
||||||
|
CurrentStateType: &stateType,
|
||||||
|
Config: map[string]any{
|
||||||
|
"url": "https://example.com/health",
|
||||||
|
"method": "GET",
|
||||||
|
"expected_status": 200,
|
||||||
|
"verify_ssl": true,
|
||||||
|
"follow_redirects": true,
|
||||||
|
},
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodDelete && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(server.URL),
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccCheckResourceHTTP(server.URL),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "name", "Web Health"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "check_type", "http"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "url", "https://example.com/health"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "expected_status", "200"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "enabled", "true"),
|
||||||
|
resource.TestCheckResourceAttrSet("towerops_check.test", "id"),
|
||||||
|
resource.TestCheckResourceAttrSet("towerops_check.test", "inserted_at"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccCheckResource_tcp(t *testing.T) {
|
||||||
|
var checkID string
|
||||||
|
var mu sync.Mutex
|
||||||
|
enabled := true
|
||||||
|
interval := 60
|
||||||
|
timeout := 5000
|
||||||
|
retry := 30
|
||||||
|
maxAttempts := 3
|
||||||
|
currentState := 3
|
||||||
|
stateType := "soft"
|
||||||
|
deviceID := "device-123"
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/api/v1/checks":
|
||||||
|
checkID = "test-tcp-check-id"
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(Check{
|
||||||
|
ID: checkID,
|
||||||
|
Name: "MySQL Port",
|
||||||
|
CheckType: "tcp",
|
||||||
|
DeviceID: &deviceID,
|
||||||
|
Enabled: &enabled,
|
||||||
|
IntervalSeconds: &interval,
|
||||||
|
TimeoutMs: &timeout,
|
||||||
|
RetryIntervalSeconds: &retry,
|
||||||
|
MaxCheckAttempts: &maxAttempts,
|
||||||
|
CurrentState: ¤tState,
|
||||||
|
CurrentStateType: &stateType,
|
||||||
|
Config: map[string]any{
|
||||||
|
"host": "10.0.0.5",
|
||||||
|
"port": 3306,
|
||||||
|
},
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(Check{
|
||||||
|
ID: checkID,
|
||||||
|
Name: "MySQL Port",
|
||||||
|
CheckType: "tcp",
|
||||||
|
DeviceID: &deviceID,
|
||||||
|
Enabled: &enabled,
|
||||||
|
IntervalSeconds: &interval,
|
||||||
|
TimeoutMs: &timeout,
|
||||||
|
RetryIntervalSeconds: &retry,
|
||||||
|
MaxCheckAttempts: &maxAttempts,
|
||||||
|
CurrentState: ¤tState,
|
||||||
|
CurrentStateType: &stateType,
|
||||||
|
Config: map[string]any{
|
||||||
|
"host": "10.0.0.5",
|
||||||
|
"port": 3306,
|
||||||
|
},
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodDelete && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(server.URL),
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccCheckResourceTCP(server.URL),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "name", "MySQL Port"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "check_type", "tcp"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "host", "10.0.0.5"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "port", "3306"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "device_id", "device-123"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccCheckResource_dns(t *testing.T) {
|
||||||
|
var checkID string
|
||||||
|
var mu sync.Mutex
|
||||||
|
enabled := true
|
||||||
|
interval := 60
|
||||||
|
timeout := 5000
|
||||||
|
retry := 30
|
||||||
|
maxAttempts := 3
|
||||||
|
currentState := 3
|
||||||
|
stateType := "soft"
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/api/v1/checks":
|
||||||
|
checkID = "test-dns-check-id"
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(Check{
|
||||||
|
ID: checkID,
|
||||||
|
Name: "DNS Resolution",
|
||||||
|
CheckType: "dns",
|
||||||
|
Enabled: &enabled,
|
||||||
|
IntervalSeconds: &interval,
|
||||||
|
TimeoutMs: &timeout,
|
||||||
|
RetryIntervalSeconds: &retry,
|
||||||
|
MaxCheckAttempts: &maxAttempts,
|
||||||
|
CurrentState: ¤tState,
|
||||||
|
CurrentStateType: &stateType,
|
||||||
|
Config: map[string]any{
|
||||||
|
"hostname": "google.com",
|
||||||
|
"server": "8.8.8.8",
|
||||||
|
"record_type": "A",
|
||||||
|
},
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(Check{
|
||||||
|
ID: checkID,
|
||||||
|
Name: "DNS Resolution",
|
||||||
|
CheckType: "dns",
|
||||||
|
Enabled: &enabled,
|
||||||
|
IntervalSeconds: &interval,
|
||||||
|
TimeoutMs: &timeout,
|
||||||
|
RetryIntervalSeconds: &retry,
|
||||||
|
MaxCheckAttempts: &maxAttempts,
|
||||||
|
CurrentState: ¤tState,
|
||||||
|
CurrentStateType: &stateType,
|
||||||
|
Config: map[string]any{
|
||||||
|
"hostname": "google.com",
|
||||||
|
"server": "8.8.8.8",
|
||||||
|
"record_type": "A",
|
||||||
|
},
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodDelete && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(server.URL),
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccCheckResourceDNS(server.URL),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "name", "DNS Resolution"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "check_type", "dns"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "hostname", "google.com"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "dns_server", "8.8.8.8"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "record_type", "A"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccCheckResource_ping(t *testing.T) {
|
||||||
|
var checkID string
|
||||||
|
var mu sync.Mutex
|
||||||
|
enabled := true
|
||||||
|
interval := 60
|
||||||
|
timeout := 5000
|
||||||
|
retry := 30
|
||||||
|
maxAttempts := 3
|
||||||
|
currentState := 3
|
||||||
|
stateType := "soft"
|
||||||
|
deviceID := "device-gw"
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/api/v1/checks":
|
||||||
|
checkID = "test-ping-check-id"
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(Check{
|
||||||
|
ID: checkID,
|
||||||
|
Name: "Gateway Ping",
|
||||||
|
CheckType: "ping",
|
||||||
|
DeviceID: &deviceID,
|
||||||
|
Enabled: &enabled,
|
||||||
|
IntervalSeconds: &interval,
|
||||||
|
TimeoutMs: &timeout,
|
||||||
|
RetryIntervalSeconds: &retry,
|
||||||
|
MaxCheckAttempts: &maxAttempts,
|
||||||
|
CurrentState: ¤tState,
|
||||||
|
CurrentStateType: &stateType,
|
||||||
|
Config: map[string]any{
|
||||||
|
"host": "10.0.0.1",
|
||||||
|
"count": 3,
|
||||||
|
},
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(Check{
|
||||||
|
ID: checkID,
|
||||||
|
Name: "Gateway Ping",
|
||||||
|
CheckType: "ping",
|
||||||
|
DeviceID: &deviceID,
|
||||||
|
Enabled: &enabled,
|
||||||
|
IntervalSeconds: &interval,
|
||||||
|
TimeoutMs: &timeout,
|
||||||
|
RetryIntervalSeconds: &retry,
|
||||||
|
MaxCheckAttempts: &maxAttempts,
|
||||||
|
CurrentState: ¤tState,
|
||||||
|
CurrentStateType: &stateType,
|
||||||
|
Config: map[string]any{
|
||||||
|
"host": "10.0.0.1",
|
||||||
|
"count": 3,
|
||||||
|
},
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodDelete && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(server.URL),
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccCheckResourcePing(server.URL),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "name", "Gateway Ping"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "check_type", "ping"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "host", "10.0.0.1"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "ping_count", "3"),
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "device_id", "device-gw"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccCheckResource_update(t *testing.T) {
|
||||||
|
var checkID string
|
||||||
|
var currentName string
|
||||||
|
var mu sync.Mutex
|
||||||
|
enabled := true
|
||||||
|
interval := 60
|
||||||
|
timeout := 5000
|
||||||
|
retry := 30
|
||||||
|
maxAttempts := 3
|
||||||
|
currentState := 3
|
||||||
|
stateType := "soft"
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/api/v1/checks":
|
||||||
|
checkID = "test-update-check-id"
|
||||||
|
currentName = "Original"
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(Check{
|
||||||
|
ID: checkID,
|
||||||
|
Name: currentName,
|
||||||
|
CheckType: "http",
|
||||||
|
Enabled: &enabled,
|
||||||
|
IntervalSeconds: &interval,
|
||||||
|
TimeoutMs: &timeout,
|
||||||
|
RetryIntervalSeconds: &retry,
|
||||||
|
MaxCheckAttempts: &maxAttempts,
|
||||||
|
CurrentState: ¤tState,
|
||||||
|
CurrentStateType: &stateType,
|
||||||
|
Config: map[string]any{
|
||||||
|
"url": "https://example.com",
|
||||||
|
"method": "GET",
|
||||||
|
"expected_status": 200,
|
||||||
|
"verify_ssl": true,
|
||||||
|
"follow_redirects": true,
|
||||||
|
},
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(Check{
|
||||||
|
ID: checkID,
|
||||||
|
Name: currentName,
|
||||||
|
CheckType: "http",
|
||||||
|
Enabled: &enabled,
|
||||||
|
IntervalSeconds: &interval,
|
||||||
|
TimeoutMs: &timeout,
|
||||||
|
RetryIntervalSeconds: &retry,
|
||||||
|
MaxCheckAttempts: &maxAttempts,
|
||||||
|
CurrentState: ¤tState,
|
||||||
|
CurrentStateType: &stateType,
|
||||||
|
Config: map[string]any{
|
||||||
|
"url": "https://example.com",
|
||||||
|
"method": "GET",
|
||||||
|
"expected_status": 200,
|
||||||
|
"verify_ssl": true,
|
||||||
|
"follow_redirects": true,
|
||||||
|
},
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodPatch && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
var body map[string]Check
|
||||||
|
json.NewDecoder(r.Body).Decode(&body)
|
||||||
|
currentName = body["check"].Name
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(Check{
|
||||||
|
ID: checkID,
|
||||||
|
Name: currentName,
|
||||||
|
CheckType: "http",
|
||||||
|
Enabled: &enabled,
|
||||||
|
IntervalSeconds: &interval,
|
||||||
|
TimeoutMs: &timeout,
|
||||||
|
RetryIntervalSeconds: &retry,
|
||||||
|
MaxCheckAttempts: &maxAttempts,
|
||||||
|
CurrentState: ¤tState,
|
||||||
|
CurrentStateType: &stateType,
|
||||||
|
Config: map[string]any{
|
||||||
|
"url": "https://example.com",
|
||||||
|
"method": "GET",
|
||||||
|
"expected_status": 200,
|
||||||
|
"verify_ssl": true,
|
||||||
|
"follow_redirects": true,
|
||||||
|
},
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodDelete && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(server.URL),
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccCheckResourceHTTPNamed(server.URL, "Original"),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "name", "Original"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: testAccCheckResourceHTTPNamed(server.URL, "Updated"),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "name", "Updated"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccCheckResource_recreateOn404(t *testing.T) {
|
||||||
|
var checkID string
|
||||||
|
var checkDeleted bool
|
||||||
|
var mu sync.Mutex
|
||||||
|
enabled := true
|
||||||
|
interval := 60
|
||||||
|
timeout := 5000
|
||||||
|
retry := 30
|
||||||
|
maxAttempts := 3
|
||||||
|
currentState := 3
|
||||||
|
stateType := "soft"
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
checkResp := Check{
|
||||||
|
ID: "new-check-id",
|
||||||
|
Name: "Web Health",
|
||||||
|
CheckType: "http",
|
||||||
|
Enabled: &enabled,
|
||||||
|
IntervalSeconds: &interval,
|
||||||
|
TimeoutMs: &timeout,
|
||||||
|
RetryIntervalSeconds: &retry,
|
||||||
|
MaxCheckAttempts: &maxAttempts,
|
||||||
|
CurrentState: ¤tState,
|
||||||
|
CurrentStateType: &stateType,
|
||||||
|
Config: map[string]any{
|
||||||
|
"url": "https://example.com/health",
|
||||||
|
"method": "GET",
|
||||||
|
"expected_status": 200,
|
||||||
|
"verify_ssl": true,
|
||||||
|
"follow_redirects": true,
|
||||||
|
},
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/api/v1/checks":
|
||||||
|
checkID = "new-check-id"
|
||||||
|
checkDeleted = false
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(checkResp)
|
||||||
|
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
if checkDeleted {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(`{"error": "check not found"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(checkResp)
|
||||||
|
|
||||||
|
case r.Method == http.MethodPatch && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
if checkDeleted {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(`{"error": "check not found"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(checkResp)
|
||||||
|
|
||||||
|
case r.Method == http.MethodDelete && r.URL.Path == "/api/v1/checks/"+checkID:
|
||||||
|
checkDeleted = true
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(server.URL),
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccCheckResourceHTTP(server.URL),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("towerops_check.test", "name", "Web Health"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PreConfig: func() {
|
||||||
|
mu.Lock()
|
||||||
|
checkDeleted = true
|
||||||
|
mu.Unlock()
|
||||||
|
},
|
||||||
|
Config: testAccCheckResourceHTTP(server.URL),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttrSet("towerops_check.test", "id"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccCheckResource_importState(t *testing.T) {
|
||||||
|
var checkID string
|
||||||
|
var mu sync.Mutex
|
||||||
|
enabled := true
|
||||||
|
interval := 60
|
||||||
|
timeout := 5000
|
||||||
|
retry := 30
|
||||||
|
maxAttempts := 3
|
||||||
|
currentState := 3
|
||||||
|
stateType := "soft"
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
checkResp := Check{
|
||||||
|
ID: "imported-check-id",
|
||||||
|
Name: "Web Health",
|
||||||
|
CheckType: "http",
|
||||||
|
Enabled: &enabled,
|
||||||
|
IntervalSeconds: &interval,
|
||||||
|
TimeoutMs: &timeout,
|
||||||
|
RetryIntervalSeconds: &retry,
|
||||||
|
MaxCheckAttempts: &maxAttempts,
|
||||||
|
CurrentState: ¤tState,
|
||||||
|
CurrentStateType: &stateType,
|
||||||
|
Config: map[string]any{
|
||||||
|
"url": "https://example.com/health",
|
||||||
|
"method": "GET",
|
||||||
|
"expected_status": 200,
|
||||||
|
"verify_ssl": true,
|
||||||
|
"follow_redirects": true,
|
||||||
|
},
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/api/v1/checks":
|
||||||
|
checkID = "imported-check-id"
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(checkResp)
|
||||||
|
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/checks/imported-check-id":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(checkResp)
|
||||||
|
|
||||||
|
case r.Method == http.MethodDelete && r.URL.Path == "/api/v1/checks/imported-check-id":
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
_ = checkID
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(server.URL),
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccCheckResourceHTTP(server.URL),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ResourceName: "towerops_check.test",
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
ImportStateVerifyIgnore: []string{"ping_count", "record_type"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccCheckResource_createError(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodPost && r.URL.Path == "/api/v1/checks" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte(`{"error": "name is required"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(server.URL),
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccCheckResourceHTTP(server.URL),
|
||||||
|
ExpectError: regexp.MustCompile(`Failed to create check`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HCL config helpers
|
||||||
|
|
||||||
|
func testAccCheckResourceHTTP(apiURL string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
provider "towerops" {
|
||||||
|
token = "test-token"
|
||||||
|
api_url = %q
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "towerops_check" "test" {
|
||||||
|
name = "Web Health"
|
||||||
|
check_type = "http"
|
||||||
|
url = "https://example.com/health"
|
||||||
|
expected_status = 200
|
||||||
|
}
|
||||||
|
`, apiURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckResourceHTTPNamed(apiURL, name string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
provider "towerops" {
|
||||||
|
token = "test-token"
|
||||||
|
api_url = %q
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "towerops_check" "test" {
|
||||||
|
name = %q
|
||||||
|
check_type = "http"
|
||||||
|
url = "https://example.com"
|
||||||
|
expected_status = 200
|
||||||
|
}
|
||||||
|
`, apiURL, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckResourceTCP(apiURL string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
provider "towerops" {
|
||||||
|
token = "test-token"
|
||||||
|
api_url = %q
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "towerops_check" "test" {
|
||||||
|
name = "MySQL Port"
|
||||||
|
check_type = "tcp"
|
||||||
|
device_id = "device-123"
|
||||||
|
host = "10.0.0.5"
|
||||||
|
port = 3306
|
||||||
|
}
|
||||||
|
`, apiURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckResourceDNS(apiURL string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
provider "towerops" {
|
||||||
|
token = "test-token"
|
||||||
|
api_url = %q
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "towerops_check" "test" {
|
||||||
|
name = "DNS Resolution"
|
||||||
|
check_type = "dns"
|
||||||
|
hostname = "google.com"
|
||||||
|
dns_server = "8.8.8.8"
|
||||||
|
record_type = "A"
|
||||||
|
}
|
||||||
|
`, apiURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckResourcePing(apiURL string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
provider "towerops" {
|
||||||
|
token = "test-token"
|
||||||
|
api_url = %q
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "towerops_check" "test" {
|
||||||
|
name = "Gateway Ping"
|
||||||
|
check_type = "ping"
|
||||||
|
device_id = "device-gw"
|
||||||
|
host = "10.0.0.1"
|
||||||
|
ping_count = 3
|
||||||
|
}
|
||||||
|
`, apiURL)
|
||||||
|
}
|
||||||
|
|
@ -559,6 +559,79 @@ func (c *Client) DeleteMaintenanceWindow(id string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check represents a TowerOps service check.
|
||||||
|
type Check struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CheckType string `json:"check_type"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
Enabled *bool `json:"enabled,omitempty"`
|
||||||
|
DeviceID *string `json:"device_id,omitempty"`
|
||||||
|
AgentTokenID *string `json:"agent_token_id,omitempty"`
|
||||||
|
IntervalSeconds *int `json:"interval_seconds,omitempty"`
|
||||||
|
TimeoutMs *int `json:"timeout_ms,omitempty"`
|
||||||
|
RetryIntervalSeconds *int `json:"retry_interval_seconds,omitempty"`
|
||||||
|
MaxCheckAttempts *int `json:"max_check_attempts,omitempty"`
|
||||||
|
Config map[string]any `json:"config"`
|
||||||
|
CurrentState *int `json:"current_state,omitempty"`
|
||||||
|
CurrentStateType *string `json:"current_state_type,omitempty"`
|
||||||
|
LastCheckAt *string `json:"last_check_at,omitempty"`
|
||||||
|
InsertedAt string `json:"inserted_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCheck creates a new service check.
|
||||||
|
func (c *Client) CreateCheck(check Check) (*Check, error) {
|
||||||
|
body := map[string]Check{"check": check}
|
||||||
|
respBody, err := c.doRequest(http.MethodPost, "/api/v1/checks", body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Check
|
||||||
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCheck retrieves a check by ID.
|
||||||
|
func (c *Client) GetCheck(id string) (*Check, error) {
|
||||||
|
respBody, err := c.doRequest(http.MethodGet, "/api/v1/checks/"+id, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Check
|
||||||
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCheck updates an existing check.
|
||||||
|
func (c *Client) UpdateCheck(id string, check Check) (*Check, error) {
|
||||||
|
body := map[string]Check{"check": check}
|
||||||
|
respBody, err := c.doRequest(http.MethodPatch, "/api/v1/checks/"+id, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Check
|
||||||
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCheck deletes a check.
|
||||||
|
func (c *Client) DeleteCheck(id string) error {
|
||||||
|
_, err := c.doRequest(http.MethodDelete, "/api/v1/checks/"+id, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// GetOrganization retrieves the current organization settings.
|
// GetOrganization retrieves the current organization settings.
|
||||||
func (c *Client) GetOrganization() (*Organization, error) {
|
func (c *Client) GetOrganization() (*Organization, error) {
|
||||||
respBody, err := c.doRequest(http.MethodGet, "/api/v1/organization", nil)
|
respBody, err := c.doRequest(http.MethodGet, "/api/v1/organization", nil)
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ func (p *ToweropsProvider) Resources(ctx context.Context) []func() resource.Reso
|
||||||
NewOrganizationResource,
|
NewOrganizationResource,
|
||||||
NewSiteResource,
|
NewSiteResource,
|
||||||
NewDeviceResource,
|
NewDeviceResource,
|
||||||
|
NewCheckResource,
|
||||||
NewScheduleResource,
|
NewScheduleResource,
|
||||||
NewEscalationPolicyResource,
|
NewEscalationPolicyResource,
|
||||||
NewAgentResource,
|
NewAgentResource,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue