Skip to content

Commit

Permalink
Add data provider for monitoring IPs (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
PetrHeinz authored Aug 28, 2024
1 parent 50392de commit 6e5c346
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
strategy:
matrix:
terraform-version: ["0.14", "1.0", "1.8", "latest"]
config: [examples/basic, examples/advanced, examples/on_call_calendars]
config: [examples/basic, examples/advanced, examples/ips, examples/on_call_calendars]
fail-fast: false
runs-on: ubuntu-latest
steps:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ GOLANGCI_LINT := golangci-lint run --disable-all \
-E staticcheck \
-E typecheck \
-E unused
VERSION := 0.11.9
VERSION := 0.11.10
.PHONY: test build

help:
Expand Down
28 changes: 28 additions & 0 deletions docs/data-sources/betteruptime_ip_list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "betteruptime_ip_list Data Source - terraform-provider-better-uptime"
subcategory: ""
description: |-
Monitoring IPs lookup.
---

# betteruptime_ip_list (Data Source)

Monitoring IPs lookup.



<!-- schema generated by tfplugindocs -->
## Schema

### Optional

- **filter_clusters** (List of String) The optional list of clusters used to filter the IPs.

### Read-Only

- **all_clusters** (List of String) The list of all clusters.
- **id** (String) The internal ID of the resource, can be ignored.
- **ips** (List of String) The list of IPs used for monitoring.


17 changes: 17 additions & 0 deletions examples/ips/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
This directory contains a sample Terraform configuration for getting all IPs used by Better Stack for Uptime monitoring.
These IPs can be used to configure resources in other providers accordingly.

## Usage

```shell script
git clone https://github.com/BetterStackHQ/terraform-provider-better-uptime && \
cd terraform-provider-better-uptime/examples/ips

echo '# See variables.tf for more.
betteruptime_api_token = "XXXXXXXXXXXXXXXXXXXXXXXX"
betteruptime_clusters = ["us"]
' > terraform.tfvars

terraform init
terraform apply
```
7 changes: 7 additions & 0 deletions examples/ips/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
provider "betteruptime" {
api_token = var.betteruptime_api_token
}

data "betteruptime_ip_list" "this" {
filter_clusters = var.betteruptime_clusters
}
7 changes: 7 additions & 0 deletions examples/ips/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
output "monitoring_ips" {
value = data.betteruptime_ip_list.this.ips
}

output "monitoring_clusters" {
value = data.betteruptime_ip_list.this.all_clusters
}
15 changes: 15 additions & 0 deletions examples/ips/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
variable "betteruptime_api_token" {
type = string
description = <<EOF
Better Stack Uptime API Token
(https://betterstack.com/docs/uptime/api/getting-started-with-uptime-api/#obtaining-an-uptime-api-token)
EOF
# The value can be omitted if BETTERUPTIME_API_TOKEN env var is set.
default = null
}

variable "betteruptime_clusters" {
type = list(string)
description = "Names of the clusters to fetch IPs from. Omit to fetch all IPs."
default = null
}
9 changes: 9 additions & 0 deletions examples/ips/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 0.14"
required_providers {
betteruptime = {
source = "BetterStackHQ/better-uptime"
version = ">= 0.11.10"
}
}
}
93 changes: 93 additions & 0 deletions internal/provider/data_ip_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package provider

import (
"bytes"
"context"
"encoding/json"
"io"
"net"
"net/http"
"sort"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func newIpListDataSource() *schema.Resource {
return &schema.Resource{
ReadContext: ipListLookup,
Description: "Monitoring IPs lookup.",
Schema: ipListSchema,
}
}

func ipListLookup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
res, err := meta.(*client).Get(ctx, "/ips-by-cluster.json")
if err != nil {
return diag.FromErr(err)
}
defer func() {
// Keep-Alive.
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()
body, err := io.ReadAll(res.Body)
if err != nil {
return diag.FromErr(err)
}
if res.StatusCode != http.StatusOK {
return diag.Errorf("GET %s returned %d: %s", res.Request.URL.String(), res.StatusCode, string(body))
}

// Unmarshal the JSON into a map
var ipData map[string][]string
err = json.Unmarshal(body, &ipData)
if err != nil {
return diag.FromErr(err)
}

// Retrieve the clusters specified by the user from the Terraform resource data
requestedClusters := d.Get("filter_clusters").([]interface{})
filterClusters := make(map[string]bool)
for _, cluster := range requestedClusters {
filterClusters[cluster.(string)] = true
}

// Filter IPs based on the specified clusters, and fetch all clusters
var filteredIPs []string
var allClusters []string
for cluster, ips := range ipData {
if len(filterClusters) == 0 || filterClusters[cluster] {
filteredIPs = append(filteredIPs, ips...)
}
allClusters = append(allClusters, cluster)
}

// Sort the IPs and cluster to be deterministic
sort.Slice(filteredIPs, func(i, j int) bool {
ip1 := net.ParseIP(filteredIPs[i])
ip2 := net.ParseIP(filteredIPs[j])

if ip1.To4() != nil && ip2.To4() == nil {
return true // IPv4 before IPv6
}
if ip1.To4() == nil && ip2.To4() != nil {
return false // IPv6 after IPv4
}

// Compare IPs directly
return bytes.Compare(ip1, ip2) < 0
})
sort.Strings(allClusters)

// Set the data in the Terraform schema
d.SetId("betterstack_ip_list")
if err := d.Set("ips", filteredIPs); err != nil {
return diag.FromErr(err)
}
if err := d.Set("all_clusters", allClusters); err != nil {
return diag.FromErr(err)
}

return nil
}
79 changes: 79 additions & 0 deletions internal/provider/data_ip_list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package provider

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func TestIpListData(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Log("Received " + r.Method + " " + r.RequestURI)

switch {
case r.Method == http.MethodGet && r.RequestURI == "/ips-by-cluster.json":
_, _ = w.Write([]byte(`{"eu":["139.162.215.1","139.162.215.2"],"us":["66.228.56.1","66.228.56.2","66.228.56.3"]}`))
default:
t.Fatal("Unexpected " + r.Method + " " + r.RequestURI)
t.Fail()
}
}))
defer server.Close()

resource.Test(t, resource.TestCase{
IsUnitTest: true,
ProviderFactories: map[string]func() (*schema.Provider, error){
"betteruptime": func() (*schema.Provider, error) {
return New(WithURL(server.URL)), nil
},
},
Steps: []resource.TestStep{
{
Config: `
provider "betteruptime" {
api_token = "foo"
}
data "betteruptime_ip_list" "this" {
}
`,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("data.betteruptime_ip_list.this", "id"),
resource.TestCheckResourceAttr("data.betteruptime_ip_list.this", "ips.0", "66.228.56.1"),
resource.TestCheckResourceAttr("data.betteruptime_ip_list.this", "ips.1", "66.228.56.2"),
resource.TestCheckResourceAttr("data.betteruptime_ip_list.this", "ips.2", "66.228.56.3"),
resource.TestCheckResourceAttr("data.betteruptime_ip_list.this", "ips.3", "139.162.215.1"),
resource.TestCheckResourceAttr("data.betteruptime_ip_list.this", "ips.4", "139.162.215.2"),
resource.TestCheckNoResourceAttr("data.betteruptime_ip_list.this", "ips.5"),
resource.TestCheckResourceAttr("data.betteruptime_ip_list.this", "all_clusters.0", "eu"),
resource.TestCheckResourceAttr("data.betteruptime_ip_list.this", "all_clusters.1", "us"),
resource.TestCheckNoResourceAttr("data.betteruptime_ip_list.this", "all_clusters.2"),
),
},
{
Config: `
provider "betteruptime" {
api_token = "foo"
}
data "betteruptime_ip_list" "this" {
filter_clusters = ["us"]
}
`,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("data.betteruptime_ip_list.this", "id"),
resource.TestCheckResourceAttr("data.betteruptime_ip_list.this", "ips.0", "66.228.56.1"),
resource.TestCheckResourceAttr("data.betteruptime_ip_list.this", "ips.1", "66.228.56.2"),
resource.TestCheckResourceAttr("data.betteruptime_ip_list.this", "ips.2", "66.228.56.3"),
resource.TestCheckNoResourceAttr("data.betteruptime_ip_list.this", "ips.3"),
resource.TestCheckResourceAttr("data.betteruptime_ip_list.this", "all_clusters.0", "eu"),
resource.TestCheckResourceAttr("data.betteruptime_ip_list.this", "all_clusters.1", "us"),
resource.TestCheckNoResourceAttr("data.betteruptime_ip_list.this", "all_clusters.2"),
),
},
},
})
}
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func New(opts ...Option) *schema.Provider {
"betteruptime_severity": newSeverityDataSource(),
"betteruptime_slack_integration": newSlackIntegrationDataSource(),
"betteruptime_incoming_webhook": newIncomingWebhookDataSource(),
"betteruptime_ip_list": newIpListDataSource(),
},
ResourcesMap: map[string]*schema.Resource{
"betteruptime_email_integration": newEmailIntegrationResource(),
Expand Down
41 changes: 41 additions & 0 deletions internal/provider/resource_ip_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package provider

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

var ipListSchema = map[string]*schema.Schema{
"id": {
Description: "The internal ID of the resource, can be ignored.",
Type: schema.TypeString,
Optional: false,
Computed: true,
},
"ips": {
Description: "The list of IPs used for monitoring.",
Type: schema.TypeList,
Optional: false,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"all_clusters": {
Description: "The list of all clusters.",
Type: schema.TypeList,
Optional: false,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"filter_clusters": {
Description: "The optional list of clusters used to filter the IPs.",
Type: schema.TypeList,
Optional: true,
Computed: false,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
}

0 comments on commit 6e5c346

Please sign in to comment.