From 1c9c345f03db2164ef1eeeb692ffbe02a1dc35bb Mon Sep 17 00:00:00 2001 From: Integralist Date: Fri, 14 Apr 2023 17:38:49 +0100 Subject: [PATCH 1/6] fix merge conflict --- Makefile | 3 +- bin/developer_overrides.tfrc | 8 ++ go.sum | 4 - provider/datasource_sites.go | 205 ++++++++++++++++++++++++++++++ provider/datasource_sites_test.go | 43 +++++++ provider/lib.go | 31 ++++- provider/provider.go | 3 + 7 files changed, 287 insertions(+), 10 deletions(-) create mode 100644 bin/developer_overrides.tfrc create mode 100644 provider/datasource_sites.go create mode 100644 provider/datasource_sites_test.go diff --git a/Makefile b/Makefile index 1f63627..c5107d1 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ check: terraform init terraform plan +.PHONY: all all: build check lint: @@ -23,4 +24,4 @@ sweep: docs: go install github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs && tfplugindocs generate -.PHONY: docs +.PHONY: clean docs test diff --git a/bin/developer_overrides.tfrc b/bin/developer_overrides.tfrc new file mode 100644 index 0000000..4454c68 --- /dev/null +++ b/bin/developer_overrides.tfrc @@ -0,0 +1,8 @@ +provider_installation { + + dev_overrides { + "signalsciences/sigsci" = "/Users/integralist/Code/fastly/terraform-provider-sigsci/bin" + } + + direct {} +} 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..42c8a89 --- /dev/null +++ b/provider/datasource_sites.go @@ -0,0 +1,205 @@ +package provider + +import ( + "fmt" + + "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{ + "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 + } + + id, err := genID(sites) + if err != nil { + return fmt.Errorf("failed to generate a resource ID: %w", err) + } + + d.SetId(id) + + return d.Set("sites", flattenSites(sites)) +} + +// flattenSites models data into format suitable for saving to Terraform state. +func flattenSites(data []sigsci.Site) []map[string]any { + result := make([]map[string]any, len(data)) + if len(data) == 0 { + return result + } + + for i, 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) + } + } + result[i] = data + } + + return result +} diff --git a/provider/datasource_sites_test.go b/provider/datasource_sites_test.go new file mode 100644 index 0000000..361b1ab --- /dev/null +++ b/provider/datasource_sites_test.go @@ -0,0 +1,43 @@ +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: `data "sigsci_sites" "example" {}`, + 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 at least one site to be returned") + } + + return nil + }, + ), + }, + }, + }) +} diff --git a/provider/lib.go b/provider/lib.go index dd37f80..b75eb89 100644 --- a/provider/lib.go +++ b/provider/lib.go @@ -1,7 +1,11 @@ package provider import ( + "bytes" + "crypto/sha512" + "encoding/json" "fmt" + "io" "net/http" "sort" "strconv" @@ -38,7 +42,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 +141,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 +389,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 +479,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 +517,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 } @@ -584,3 +587,21 @@ func validateRegion(val interface{}, key string) ([]string, []error) { return nil, []error{fmt.Errorf("received region name %q is invalid. should be in (%s)", val.(string), strings.Join(regionList, ", "))} } + +// genID marshals data into JSON and returns a hash of the bytes. +// +// The returned hash is typically used as input to SetId(). +func genID(data any) (string, error) { + bs, err := json.Marshal(data) + if err != nil { + return "", err + } + + h := sha512.New() + if _, err := io.Copy(h, bytes.NewReader(bs)); err != nil { + return "", err + } + + digest := fmt.Sprintf("%x\n", h.Sum(nil)) + return digest, nil +} 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 From 2b1c2c2394635130f6b462c97b9618fc3eb1def9 Mon Sep 17 00:00:00 2001 From: Integralist Date: Fri, 14 Apr 2023 15:36:44 +0100 Subject: [PATCH 2/6] feat(data-source/sites): allow filtering of sites by name/display_name --- provider/datasource_sites.go | 36 +++++++++++++++++++------------ provider/datasource_sites_test.go | 26 +++++++++++++++++++--- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/provider/datasource_sites.go b/provider/datasource_sites.go index 42c8a89..3202349 100644 --- a/provider/datasource_sites.go +++ b/provider/datasource_sites.go @@ -1,8 +1,6 @@ package provider import ( - "fmt" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/signalsciences/go-sigsci" ) @@ -11,6 +9,11 @@ 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, @@ -149,24 +152,21 @@ func readSites(d *schema.ResourceData, m any) error { return err } - id, err := genID(sites) - if err != nil { - return fmt.Errorf("failed to generate a resource ID: %w", err) - } + d.SetId("list_sites") - d.SetId(id) + filter := d.Get("filter").(string) - return d.Set("sites", flattenSites(sites)) + return d.Set("sites", flattenSites(sites, filter)) } // flattenSites models data into format suitable for saving to Terraform state. -func flattenSites(data []sigsci.Site) []map[string]any { - result := make([]map[string]any, len(data)) +func flattenSites(data []sigsci.Site, filter string) []map[string]any { + results := []map[string]any{} if len(data) == 0 { - return result + return results } - for i, site := range data { + for _, site := range data { data := map[string]any{ "agent_anon_mode": site.AgentAnonMode, "agent_level": site.AgentLevel, @@ -198,8 +198,16 @@ func flattenSites(data []sigsci.Site) []map[string]any { delete(data, k) } } - result[i] = data + 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 result + return results } diff --git a/provider/datasource_sites_test.go b/provider/datasource_sites_test.go index 361b1ab..16b827b 100644 --- a/provider/datasource_sites_test.go +++ b/provider/datasource_sites_test.go @@ -19,7 +19,20 @@ func TestAccSigSciDataSourceSites(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: `data "sigsci_sites" "example" {}`, + 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"] @@ -30,13 +43,20 @@ func TestAccSigSciDataSourceSites(t *testing.T) { return err } - if sites < 1 { - return fmt.Errorf("expected at least one site to be returned") + 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, // FIXME: Why is there a diff on next plan? }, }, }) From 5f5673515e8c65c2443608a1ebb8d801ff46b875 Mon Sep 17 00:00:00 2001 From: Integralist Date: Fri, 14 Apr 2023 15:37:20 +0100 Subject: [PATCH 3/6] build(make): support local testing of provider --- .gitignore | 1 + Makefile | 7 +++++-- scripts/generate-dev-overrides.sh | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100755 scripts/generate-dev-overrides.sh diff --git a/.gitignore b/.gitignore index f3afb6d..51aeefe 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ override.tf.json terraform-provider-sigsci dist/ +bin diff --git a/Makefile b/Makefile index c5107d1..fe186c1 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ +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 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 "" From 13885cac2ad4ba2fae472bb16593ede78deb18ae Mon Sep 17 00:00:00 2001 From: Integralist Date: Fri, 14 Apr 2023 15:46:36 +0100 Subject: [PATCH 4/6] docs: remove FIXME as it is expected, after doing some local testing --- provider/datasource_sites_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider/datasource_sites_test.go b/provider/datasource_sites_test.go index 16b827b..ddd3153 100644 --- a/provider/datasource_sites_test.go +++ b/provider/datasource_sites_test.go @@ -56,7 +56,7 @@ func TestAccSigSciDataSourceSites(t *testing.T) { return nil }, ), - ExpectNonEmptyPlan: true, // FIXME: Why is there a diff on next plan? + ExpectNonEmptyPlan: true, }, }, }) From 7b810e8525b18ad42b09f377fd5e9cd5967aed01 Mon Sep 17 00:00:00 2001 From: Integralist Date: Fri, 14 Apr 2023 17:36:49 +0100 Subject: [PATCH 5/6] fix(lint): remove genID as it's no longer used --- provider/lib.go | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/provider/lib.go b/provider/lib.go index b75eb89..70510be 100644 --- a/provider/lib.go +++ b/provider/lib.go @@ -1,11 +1,7 @@ package provider import ( - "bytes" - "crypto/sha512" - "encoding/json" "fmt" - "io" "net/http" "sort" "strconv" @@ -587,21 +583,3 @@ func validateRegion(val interface{}, key string) ([]string, []error) { return nil, []error{fmt.Errorf("received region name %q is invalid. should be in (%s)", val.(string), strings.Join(regionList, ", "))} } - -// genID marshals data into JSON and returns a hash of the bytes. -// -// The returned hash is typically used as input to SetId(). -func genID(data any) (string, error) { - bs, err := json.Marshal(data) - if err != nil { - return "", err - } - - h := sha512.New() - if _, err := io.Copy(h, bytes.NewReader(bs)); err != nil { - return "", err - } - - digest := fmt.Sprintf("%x\n", h.Sum(nil)) - return digest, nil -} From 74024840469d207468a596e88960520e0812afd3 Mon Sep 17 00:00:00 2001 From: Integralist Date: Fri, 14 Apr 2023 17:42:30 +0100 Subject: [PATCH 6/6] omit bin directory --- .gitignore | 2 +- bin/developer_overrides.tfrc | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 bin/developer_overrides.tfrc diff --git a/.gitignore b/.gitignore index 51aeefe..1ff3831 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,4 @@ override.tf.json terraform-provider-sigsci dist/ -bin +bin/ diff --git a/bin/developer_overrides.tfrc b/bin/developer_overrides.tfrc deleted file mode 100644 index 4454c68..0000000 --- a/bin/developer_overrides.tfrc +++ /dev/null @@ -1,8 +0,0 @@ -provider_installation { - - dev_overrides { - "signalsciences/sigsci" = "/Users/integralist/Code/fastly/terraform-provider-sigsci/bin" - } - - direct {} -}