diff --git a/.gitignore b/.gitignore index f3afb6d..1ff3831 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ override.tf.json terraform-provider-sigsci dist/ +bin/ diff --git a/Makefile b/Makefile index 1f63627..fe186c1 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,15 @@ +clean: + rm -rf ./bin -build: - go build -o terraform-provider-sigsci +build: clean + go build -o bin/terraform-provider-sigsci + @sh -c "'$(CURDIR)/scripts/generate-dev-overrides.sh'" check: terraform init terraform plan +.PHONY: all all: build check lint: @@ -23,4 +27,4 @@ sweep: docs: go install github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs && tfplugindocs generate -.PHONY: docs +.PHONY: clean docs test diff --git a/go.sum b/go.sum index f2375ac..b94851b 100644 --- a/go.sum +++ b/go.sum @@ -257,10 +257,6 @@ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 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/signalsciences/go-sigsci v0.1.13 h1:S8YVQ7ei0hybgWqGj0krle6GkA7l8k23VFgNP3ipQwc= -github.com/signalsciences/go-sigsci v0.1.13/go.mod h1:CXwoXk81ZwFdne6o8cnAYwxvke5kcLg7zE6Bl/e1KUo= -github.com/signalsciences/go-sigsci v0.1.14 h1:a2ucWDWDjXn93NXrVBcd7zyOMjxUcPEznTpCg0CLB+g= -github.com/signalsciences/go-sigsci v0.1.14/go.mod h1:CXwoXk81ZwFdne6o8cnAYwxvke5kcLg7zE6Bl/e1KUo= github.com/signalsciences/go-sigsci v0.1.15 h1:Z1Wkli5NtN8OsvBBWBgXRS0LpAsmVrl1dgCPqFrKglo= github.com/signalsciences/go-sigsci v0.1.15/go.mod h1:CXwoXk81ZwFdne6o8cnAYwxvke5kcLg7zE6Bl/e1KUo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= diff --git a/provider/datasource_sites.go b/provider/datasource_sites.go new file mode 100644 index 0000000..3202349 --- /dev/null +++ b/provider/datasource_sites.go @@ -0,0 +1,213 @@ +package provider + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/signalsciences/go-sigsci" +) + +func dataSourceSites() *schema.Resource { + return &schema.Resource{ + Read: readSites, + Schema: map[string]*schema.Schema{ + "filter": { + Type: schema.TypeString, + Optional: true, + Description: "Filter listed domains by either the site 'name' or 'display_name'", + }, + "sites": { + Type: schema.TypeList, + Computed: true, + Description: "A list of all sites for a given corp.", + Elem: &schema.Resource{ + // NOTE: The API returns multiple objects with a single 'uri' field. + // To avoid extra type complexity we flatten those hierarchies. + // These are the fields below that have the '_uri' suffix. + Schema: map[string]*schema.Schema{ + "agent_anon_mode": { + Type: schema.TypeString, + Computed: true, + Description: "Agent IP anonimization mode - 'EU' or 'off'", + }, + "agent_level": { + Type: schema.TypeString, + Computed: true, + Description: "Agent action level - 'block', 'log' or 'off'", + }, + "agents_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's agents", + }, + "alerts_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's alerts", + }, + "analytics_events_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's analytics events", + }, + "blacklist_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's blacklist", + }, + "block_duration_secs": { + Type: schema.TypeInt, + Computed: true, + Description: "Duration to block an IP in seconds", + }, + "block_http_code": { + Type: schema.TypeInt, + Computed: true, + Description: "HTTP response code to send when when traffic is being blocked", + }, + "block_redirect_url": { + Type: schema.TypeString, + Computed: true, + Description: "URL to redirect to when blockHTTPCode is 301 or 302", + }, + "created": { + Type: schema.TypeString, + Computed: true, + Description: "Created RFC3339 date time", + }, + "display_name": { + Type: schema.TypeString, + Computed: true, + Description: "Display name of the site", + }, + "events_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's events", + }, + "header_links_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's header links", + }, + "integrations_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's integrations", + }, + "members_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's members", + }, + "monitors_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's monitors", + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: "Identifying name of the site", + }, + "redactions_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's redactions", + }, + "requests_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's requests", + }, + "suspicious_ips_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's suspicious IPs", + }, + "top_attacks_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's top attacks", + }, + "whitelist_uri": { + Type: schema.TypeString, + Computed: true, + Description: "Reference to the site's whitelist", + }, + }, + }, + }, + }, + } +} + +func readSites(d *schema.ResourceData, m any) error { + pm := m.(providerMetadata) + sc := pm.Client + corp := pm.Corp + + // API documentation: + // https://docs.fastly.com/signalsciences/api/#_corps__corpName__sites_get + sites, err := sc.ListSites(corp) + if err != nil { + return err + } + + d.SetId("list_sites") + + filter := d.Get("filter").(string) + + return d.Set("sites", flattenSites(sites, filter)) +} + +// flattenSites models data into format suitable for saving to Terraform state. +func flattenSites(data []sigsci.Site, filter string) []map[string]any { + results := []map[string]any{} + if len(data) == 0 { + return results + } + + for _, site := range data { + data := map[string]any{ + "agent_anon_mode": site.AgentAnonMode, + "agent_level": site.AgentLevel, + "agents_uri": site.Agents["uri"], + "alerts_uri": site.Alerts["uri"], + "analytics_events_uri": site.AnalyticsEvents["uri"], + "blacklist_uri": site.Blacklist["uri"], + "block_duration_secs": site.BlockDurationSeconds, + "block_http_code": site.BlockHTTPCode, + "block_redirect_url": site.BlockRedirectURL, + "created": site.Created.String(), + "display_name": site.DisplayName, + "events_uri": site.Events["uri"], + "header_links_uri": site.HeaderLinks["uri"], + "integrations_uri": site.Integrations["uri"], + "members_uri": site.Members["uri"], + "monitors_uri": site.Monitors["uri"], + "name": site.Name, + "redactions_uri": site.Redactions["uri"], + "requests_uri": site.Requests["uri"], + "suspicious_ips_uri": site.SuspiciousIPs["uri"], + "top_attacks_uri": site.TopAttacks["uri"], + "whitelist_uri": site.Whitelist["uri"], + } + + // Prune any empty values that come from the default string value in structs. + for k, v := range data { + if v == "" { + delete(data, k) + } + } + results = append(results, data) + } + + if filter != "" { + for idx, site := range results { + if site["name"] == filter || site["display_name"] == filter { + return results[idx : idx+1] + } + } + } + + return results +} diff --git a/provider/datasource_sites_test.go b/provider/datasource_sites_test.go new file mode 100644 index 0000000..ddd3153 --- /dev/null +++ b/provider/datasource_sites_test.go @@ -0,0 +1,63 @@ +package provider + +import ( + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccSigSciDataSourceSites(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: ` + resource "sigsci_site" "example1" { + short_name = "terraform_site1" + display_name = "terraform test example site1" + } + resource "sigsci_site" "example2" { + short_name = "terraform_site2" + display_name = "terraform test example site2" + } + data "sigsci_sites" "example" { + depends_on = [sigsci_site.example1, sigsci_site.example2] + filter = "terraform_site2" + } + `, + Check: resource.ComposeTestCheckFunc( + func(s *terraform.State) error { + r := s.RootModule().Resources["data.sigsci_sites.example"] + a := r.Primary.Attributes + + sites, err := strconv.Atoi(a["sites.#"]) + if err != nil { + return err + } + + if sites != 1 { + return fmt.Errorf("expected one site to be returned as per the filter") + } + + got := a["sites.0.name"] + want := "terraform_site2" + if got != want { + return fmt.Errorf("got: %s, want: %s", got, want) + } + + return nil + }, + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} diff --git a/provider/lib.go b/provider/lib.go index dd37f80..70510be 100644 --- a/provider/lib.go +++ b/provider/lib.go @@ -38,7 +38,7 @@ func expandStringArray(entries *schema.Set) []string { } func flattenDetections(detections []sigsci.Detection) []interface{} { - var detectionsSet = make([]interface{}, len(detections)) + detectionsSet := make([]interface{}, len(detections)) for i, detection := range detections { fieldSet := make([]interface{}, len(detection.Fields)) for j, field := range detection.Fields { @@ -137,7 +137,7 @@ func expandAlerts(entries *schema.Set) []sigsci.Alert { } func flattenAlerts(alerts []sigsci.Alert) []interface{} { - var alertsSet = make([]interface{}, len(alerts)) + alertsSet := make([]interface{}, len(alerts)) for i, alert := range alerts { alertsSet[i] = map[string]interface{}{ "id": alert.ID, @@ -385,7 +385,7 @@ func expandRuleConditions(conditionsResource *schema.Set) []sigsci.Condition { } func flattenRuleConditions(conditions []sigsci.Condition) []interface{} { - var conditionsMap = make([]interface{}, len(conditions)) + conditionsMap := make([]interface{}, len(conditions)) for i, condition := range conditions { conditionMap := map[string]interface{}{ "type": condition.Type, @@ -475,7 +475,7 @@ func flattenRuleRateLimit(rateLimit *sigsci.RateLimit) map[string]string { } func flattenRuleActions(actions []sigsci.Action, customResponseCode bool) []interface{} { - var actionsMap = make([]interface{}, len(actions)) + actionsMap := make([]interface{}, len(actions)) for i, action := range actions { actionMap := map[string]interface{}{ @@ -513,7 +513,6 @@ func resourceSiteImport(siteID string) (site string, id string, err error) { var siteImporter = schema.ResourceImporter{ State: func(d *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { site, id, err := resourceSiteImport(d.Id()) - if err != nil { return nil, err } diff --git a/provider/provider.go b/provider/provider.go index 9161136..82cff8b 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -80,6 +80,9 @@ func Provider() terraform.ResourceProvider { "sigsci_edge_deployment": resourceEdgeDeployment(), "sigsci_edge_deployment_service": resourceEdgeDeploymentService(), }, + DataSourcesMap: map[string]*schema.Resource{ + "sigsci_sites": dataSourceSites(), + }, } provider.ConfigureFunc = providerConfigure() return provider diff --git a/scripts/generate-dev-overrides.sh b/scripts/generate-dev-overrides.sh new file mode 100755 index 0000000..059fb22 --- /dev/null +++ b/scripts/generate-dev-overrides.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +BIN_DIR=$PWD/bin +OVERRIDES_FILENAME=developer_overrides.tfrc + +cat <$BIN_DIR/$OVERRIDES_FILENAME +provider_installation { + + dev_overrides { + "signalsciences/sigsci" = "$BIN_DIR" + } + + direct {} +} +EOF + +echo "" +echo "A development overrides file has been generated at ./bin/$OVERRIDES_FILENAME." +echo "To make Terraform temporarily use your locally built version of the provider, set TF_CLI_CONFIG_FILE within your terminal" +echo "" +printf "\texport TF_CLI_CONFIG_FILE=$BIN_DIR/$OVERRIDES_FILENAME" +echo ""