diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..5b891cd --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @hashicorp/terraform-devex \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1165dac --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +# See GitHub's documentation for more information on this file: +# https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a424679 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +# Terraform Provider release workflow. +name: Release + +# This GitHub action creates a release when a tag that matches the pattern +# "v*" (e.g. v0.1.0) is created. +on: + push: + tags: + - 'v*' + +# Releases need permissions to read and write the repository contents. +# GitHub considers creating releases and uploading assets as writing contents. +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + with: + # Allow goreleaser to access older tag information. + fetch-depth: 0 + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version-file: 'go.mod' + cache: true + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@82a020f1f7f605c65dd2449b392a52c3fcfef7ef # v6.0.0 + id: import_gpg + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.PASSPHRASE }} + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 + with: + args: release --clean + env: + # GitHub sets the GITHUB_TOKEN secret automatically. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..71ab567 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,77 @@ +# Terraform Provider testing workflow. +name: Tests + +# This GitHub action runs your tests for each pull request and push. +# Optionally, you can turn it on using a schedule for regular testing. +on: + pull_request: + paths-ignore: + - 'README.md' + push: + paths-ignore: + - 'README.md' + +# Testing only needs permissions to read the repository contents. +permissions: + contents: read + +jobs: + # Ensure project builds before running testing matrix + build: + name: Build + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version-file: 'go.mod' + cache: true + - run: go mod download + - run: go build -v . + - name: Run linters + uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0 + with: + version: latest + + generate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version-file: 'go.mod' + cache: true + - run: go generate ./... + - name: git diff + run: | + git diff --compact-summary --exit-code || \ + (echo; echo "Unexpected difference in directories after code generation. Run 'go generate ./...' command and commit."; exit 1) + + # Run acceptance tests in a matrix with Terraform CLI versions + test: + name: Terraform Provider Acceptance Tests + needs: build + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + # list whatever Terraform versions here you would like to support + terraform: + - '1.9.*' + steps: + - uses: actions/checkout # v4.1.0 + - uses: actions/setup-go # v4.1.0 + with: + go-version-file: 'go.mod' + cache: true + - uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2.0.3 + with: + terraform_version: ${{ matrix.terraform }} + terraform_wrapper: false + - run: go mod download + - env: + TF_ACC: "1" + run: go test -v -cover ./internal/provider/ + timeout-minutes: 10 \ No newline at end of file diff --git a/go.mod b/go.mod index 5e585cd..da3439e 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.22.1 require ( github.com/hashicorp/terraform-plugin-framework v1.10.0 github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 - github.com/quantcdn/quant-admin-go v0.0.0-20240531233223-66fb14a9f0d3 + github.com/quantcdn/quant-admin-go v0.0.0-20240909055315-7af0c1f67581 github.com/stretchr/testify v1.7.2 ) diff --git a/internal/provider/crawler_resource.go b/internal/provider/crawler_resource.go index 2236897..5594c9b 100644 --- a/internal/provider/crawler_resource.go +++ b/internal/provider/crawler_resource.go @@ -180,7 +180,7 @@ func callCrawlerReadAPI(ctx context.Context, r *crawlerResource, crawler *resour api, _, err := r.client.Instance.CrawlersAPI.CrawlersRead(ctx, org, crawler.Project.ValueString(), crawler.Uuid.ValueString()).Execute() if err != nil { - diags.AddError("Unable to load crawler", fmt.Sprintf("Error: ", err.Error())) + diags.AddError("Unable to load crawler", fmt.Sprintf("Error: %s", err.Error())) return } diff --git a/internal/provider/project_resource_test.go b/internal/provider/project_resource_test.go index 43422fe..7d639c7 100644 --- a/internal/provider/project_resource_test.go +++ b/internal/provider/project_resource_test.go @@ -25,24 +25,3 @@ func TestReadProject(t *testing.T) { assert.Equal(t, "api-test", project.GetName()) assert.Equal(t, res.StatusCode, 200) } - -// func TestCreateProject(t *testing.T) { -// bearer := os.Getenv("QUANT_BEARER") - -// cfg := openapi.NewConfiguration() -// client := openapi.NewAPIClient(cfg) -// ctx := context.WithValue(context.Background(), openapi.ContextAccessToken, bearer) - -// req := *openapi.NewProjectRequestWithDefaults() -// req.SetName("tf test 7") - -// p, res, err := client.ProjectsAPI.ProjectsCreate(ctx, "quant").ProjectRequest(req).Execute() - -// t.Logf("Response: %v", res) - -// if err != nil { -// t.Fatalf("unexpected error, %v", err) -// } - -// t.Logf("Project: %v", p) -// } diff --git a/internal/provider/rule_auth_resource_test.go b/internal/provider/rule_auth_resource_test.go deleted file mode 100644 index 9584902..0000000 --- a/internal/provider/rule_auth_resource_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package provider_test - -import ( - "context" - "fmt" - "os" - "testing" - "time" - - openapi "github.com/quantcdn/quant-admin-go" - "github.com/stretchr/testify/assert" -) - - -func TestListAuthResource(t *testing.T) { - bearer := os.Getenv("QUANT_BEARER") - cfg := openapi.NewConfiguration() - client := openapi.NewAPIClient(cfg) - ctx := context.WithValue(context.Background(), openapi.ContextAccessToken, bearer) - - rules, _, err := client.RulesAuthAPI.RulesAuthList(ctx, "quant", "api-test").Execute() - - if err != nil { - t.Fatalf("Error: %v", err) - } - - for _, rule := range rules { - assert.Equal(t, "auth", rule.GetAction()) - } -} - -func TestCreateAuthResource(t *testing.T) { - bearer := os.Getenv("QUANT_BEARER") - cfg := openapi.NewConfiguration() - client := openapi.NewAPIClient(cfg) - ctx := context.WithValue(context.Background(), openapi.ContextAccessToken, bearer) - - req := *openapi.NewRuleAuthRequestWithDefaults() - req.SetName(fmt.Sprintf("SDK-TF: Auth Rule %v", time.Now().Unix())) - - req.SetAuthUser("test") - req.SetAuthPass("test") - - rule, _, err := client.RulesAuthAPI.RulesAuthCreate(ctx, "quant", "api-test").RuleAuthRequest(req).Execute() - - if err != nil { - t.Fatalf("Error: %v", err) - } - - assert.Equal(t, "auth", rule.GetAction()) -} diff --git a/internal/provider/rule_custom_response_resource_test.go b/internal/provider/rule_custom_response_resource_test.go deleted file mode 100644 index 62192c4..0000000 --- a/internal/provider/rule_custom_response_resource_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package provider_test - -import ( - "context" - "fmt" - "os" - "testing" - "time" - - openapi "github.com/quantcdn/quant-admin-go" - "github.com/stretchr/testify/assert" -) - -func TestListCustomResponseResource(t *testing.T) { - bearer := os.Getenv("QUANT_BEARER") - cfg := openapi.NewConfiguration() - client := openapi.NewAPIClient(cfg) - ctx := context.WithValue(context.Background(), openapi.ContextAccessToken, bearer) - - rules, _, err := client.RulesCustomResponseAPI.RulesCustomResponseList(ctx, "quant", "api-test").Execute() - - if err != nil { - t.Fatalf("Error: %v", err) - } - - for _, rule := range rules { - assert.Equal(t, "custom_response", rule.GetAction()) - } -} - -func TestCreateCustomResponseResource(t *testing.T) { - bearer := os.Getenv("QUANT_BEARER") - cfg := openapi.NewConfiguration() - client := openapi.NewAPIClient(cfg) - ctx := context.WithValue(context.Background(), openapi.ContextAccessToken, bearer) - - req := *openapi.NewRuleCustomResponseRequestWithDefaults() - req.SetName(fmt.Sprintf("SDK-TF: custom response rule %v", time.Now().Unix())) - req.SetCustomResponseBody("

Test

") - req.SetCustomResponseStatusCode(200) - - rule, _, err := client.RulesCustomResponseAPI.RulesCustomResponseCreate(ctx, "quant", "api-test").RuleCustomResponseRequest(req).Execute() - - if err != nil { - t.Fatalf("Error: %v", err) - } - - assert.Equal(t, "custom_response", rule.GetAction()) -} diff --git a/internal/provider/rule_header_resource_test.go b/internal/provider/rule_header_resource_test.go deleted file mode 100644 index 262dc59..0000000 --- a/internal/provider/rule_header_resource_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package provider_test - -import ( - "context" - "fmt" - "os" - "testing" - "time" - - openapi "github.com/quantcdn/quant-admin-go" - "github.com/stretchr/testify/assert" -) - -func TestListHeaderResources(t *testing.T) { - bearer := os.Getenv("QUANT_BEARER") - cfg := openapi.NewConfiguration() - client := openapi.NewAPIClient(cfg) - ctx := context.WithValue(context.Background(), openapi.ContextAccessToken, bearer) - - rules, _, err := client.RulesHeadersAPI.RulesHeadersList(ctx, "quant", "api-test").Execute() - - if err != nil { - t.Fatalf("Error: %v", err) - } - - for _, rule := range rules { - assert.Equal(t, "headers", rule.GetAction()) - } -} - -func TestCreateHeaderResource(t *testing.T) { - bearer := os.Getenv("QUANT_BEARER") - cfg := openapi.NewConfiguration() - client := openapi.NewAPIClient(cfg) - ctx := context.WithValue(context.Background(), openapi.ContextAccessToken, bearer) - - req := *openapi.NewRuleHeaderRequestWithDefaults() - req.SetName(fmt.Sprintf("SDK-TF: Header Rule %v", time.Now().Unix())) - - var headers = map[string] string{ - "X-Test-Header": "test", - } - - req.SetHeaders(headers) - - rule, _, err := client.RulesHeadersAPI.RulesHeadersCreate(ctx, "quant", "api-test").RuleHeaderRequest(req).Execute() - - if err != nil { - t.Fatalf("Error: %v", err) - } - - assert.Equal(t, "headers", rule.GetAction()) -} diff --git a/internal/provider/rule_proxy_resource_test.go b/internal/provider/rule_proxy_resource_test.go index 06c9b6f..9bcb718 100644 --- a/internal/provider/rule_proxy_resource_test.go +++ b/internal/provider/rule_proxy_resource_test.go @@ -9,119 +9,99 @@ import ( ) // Static schema from an API request -// curl --request GET \ -// --url http://localhost:8001/api/v2/organizations/quant/projects/api-test/rules/proxy \ -// --header 'Authorization: Bearer $QUANT_BEARER' \ -// --header 'User-Agent: insomnia/8.3.0' +// +// curl --request GET \ +// --url http://localhost:8001/api/v2/organizations/quant/projects/api-test/rules/proxy \ +// --header 'Authorization: Bearer $QUANT_BEARER' \ +// --header 'User-Agent: insomnia/8.3.0' func TestProxySchema(t *testing.T) { jsonData := []byte(`[ { - "country_is_not": [], - "country": "country_is", - "ip_is_not": [], "action_config": { - "disable_ssl_verify": "true", + "notify_config": { + "period": 60, + "slack_webhook": 60, + "origin_status_codes": [] + }, + "origin_timeout": "30000", "proxy_strip_headers": [], - "auth_pass": "", - "origin_timeout": "15000", + "proxy_alert_enabled": true, + "failover_mode": false, + "failover_lifetime": "300", + "inject_headers": null, + "notify": "none", + "host": "www.example.com", + "cache_lifetime": null, "waf_config": { - "notify_email": [], "block_lists": { "user_agent": false, "ip": false, - "referer": false + "referer": false, + "ai": false }, + "notify_email": [], + "block_referer": [], + "notify_slack_hits_rpm": null, "thresholds": [ { - "cooldown": 30, - "type": "ip", - "rps": 10, - "notify_slack": "", - "mode": "disabled" - }, - { - "value": "", - "cooldown": 30, "mode": "disabled", "rps": 5, + "cooldown": 30, "notify_slack": "", - "type": "header" - }, - { - "minutes": 5, - "cooldown": 300, - "hits": 10, - "type": "waf_hit_by_ip", - "notify_slack": "", - "mode": "disabled" + "type": "ip" } ], - "notify_slack_hits_rpm": "", "mode": "report", - "allow_rules": [], "httpbl": { + "api_key": "", "block_suspicious": false, "block_harvester": false, "block_spam": false, "block_search_engine": false, "httpbl_enabled": false }, - "block_ua": [], - "block_referer": [], + "block_ua": [ + "Mozilla\/5.0 (compatible; Googlebot\/2.1; +http:\/\/www.google.com\/bot.html)" + ], "block_ip": [], - "paranoia_level": 1, + "allow_rules": [], + "paranoia_level": 0, "notify_slack": "", - "allow_ip": [] + "allow_ip": [ + "10.0.0.1" + ] }, - "proxy_alert_enabled": false, "waf_enabled": true, - "cache_lifetime": "", - "failover_mode": false, - "failover_lifetime": "", - "proxy_strip_request_headers": [], - "failover_origin_ttfb": "", - "only_proxy_404": false, + "to": "http:\/\/origin.example.com", "auth_user": "", - "inject_headers": null, - "notify_config": { - "slack_webhook": "", - "period": "60", - "origin_status_codes": [ - "200", - "404", - "301", - "302", - "304" - ] - }, - "failover_origin_status_codes": [ - "200", - "404", - "301", - "302", - "304" - ], - "to": "https:\/\/sdp3.amazee.io$1", - "host": "content.premier.vic.gov.au", - "notify": "none" + "auth_pass": "", + "only_proxy_404": false, + "failover_origin_ttfb": "2000", + "disable_ssl_verify": true, + "failover_origin_status_codes": [] }, - "name": "[drupal] authenticated traffic (WAF to report mode in AU)", - "domain": "content.premier.vic.gov.au", - "disabled": false, - "url": [ + "country_is_not": [], + "domain": [ "*" ], - "uuid": "2a254c9b-b275-4755-9dc5-30fca699032a", + "ip": "", + "action": "proxy", + "name": "proxyrule", + "uuid": "5da31117-7fbd-48bd-8fdf-08ab8218ae87", + "url": [ + "\/test-proxy" + ], + "disabled": false, + "method_is_not": [], + "ip_is": [], + "ip_is_not": [], "country_is": [ "AU" ], - "method": "any", + "method": "", + "country": "country_is", "method_is": [], - "method_is_not": [], - "ip_is": [], - "action": "proxy", - "only_with_cookie": "SSESS*", - "ip": "any" + "only_with_cookie": "" } ]`) @@ -136,120 +116,3 @@ func TestProxySchema(t *testing.T) { // from the API can be unmarhslled correctlly into openapi.RuleProxy. assert.Equal(t, 1, len(rules)) } - -// func TestListRules(t *testing.T) { -// bearer := os.Getenv("QUANT_BEARER") -// cfg := openapi.NewConfiguration() -// client := openapi.NewAPIClient(cfg) -// ctx := context.WithValue(context.Background(), openapi.ContextAccessToken, bearer) - -// rules, _, err := client.RulesProxyAPI.RulesProxyList(ctx, "quant", "tf-test-6").Execute() - -// if err != nil { -// t.Fatalf("Error: %v", err) -// } - -// // This is a live API call to the Quant API, it will return the rules for `tf-test-6` -// // the rule names may change, but this is to validate that a live API call returns -// // values as we're expecting and the SDK is able to unmarshal the response. -// for _, rule := range rules { -// assert.Equal(t, "proxy", rule.GetAction()) -// } -// } - -// func TestRuleAfterCreate(t *testing.T) { -// jsonData := []byte(`[{"uuid":"baf1c2a5-3274-4746-be7b-80487ba62906","domain":"any","country":"country_is","country_is":["AU"],"country_is_not":[],"method":"method_is","method_is":["GET"],"method_is_not":[],"ip":"any","ip_is":[],"ip_is_not":[],"only_with_cookie":"","url":["\/*"],"name":"SDK-TF proxy rule 1719533575","disabled":false,"action":"proxy","action_config":{"to":"http:\/\/origin.com","host":"test.com","auth_user":"test","auth_pass":"test","disable_ssl_verify":true,"cache_lifetime":100,"only_proxy_404":false,"proxy_strip_headers":["x-strip-me"],"waf_enabled":true,"proxy_alert_enabled":true,"origin_timeout":"30000","failover_mode":false,"failover_origin_ttfb":"2000","failover_lifetime":300,"notify":"none","notify_config":{"period":"120","slack_webhook":"https:\/\/slack.com","origin_status_codes":[]},"inject_headers":null,"failover_origin_status_codes":null}}]`) -// var rules []openapi.RuleProxy -// err := json.Unmarshal(jsonData, &rules) - -// if err != nil { -// t.Fatalf("Error: %v", err) -// } - -// assert.Equal(t, "SDK-TF proxy rule 1719533575", rules[0].GetName()) -// } - -// func TestCreateProxyRule(t *testing.T) { -// bearer := os.Getenv("QUANT_BEARER") -// project := "api-test" -// ts := time.Now().Unix() - -// cfg := openapi.NewConfiguration() -// client := openapi.NewAPIClient(cfg) -// ctx := context.WithValue(context.Background(), openapi.ContextAccessToken, bearer) - -// req := *openapi.NewRuleProxyRequestWithDefaults() - -// req.SetName(fmt.Sprintf("SDK-TF proxy rule %v", ts)) -// req.SetDomain("any") - -// urls := []string{"/*"} -// req.SetUrl(urls) - -// req.SetCountry("country_is") -// req.SetCountryIs([]string{"AU"}) - -// // req.SetIp() -// // req.SetIpIs() -// // req.SetIpIsNot() - -// req.SetMethod("method_is") -// req.SetMethodIs([]string{"GET"}) - -// req.SetTo("http://origin.com") -// req.SetHost("test.com") -// // req.SetHost() # Override the host header. -// req.SetCacheLifetime("100") - -// req.SetAuthPass("test") -// req.SetAuthUser("test") - -// req.SetDisableSslVerify(true) -// req.SetOnlyProxy404(false) -// req.SetFailoverMode("false") - -// // Wrong type; needs to be { key : value } -// req.SetProxyStripHeaders([]string{"x-strip-me"}) -// req.SetProxyStripRequestHeaders([]string{"x-custom-header"}) - -// req.SetWafEnabled(true) - -// waf := *openapi.NewWAFConfigWithDefaults() -// waf.SetMode("block") -// waf.SetParanoiaLevel(1) -// waf.SetAllowRules([]string{"10001"}) -// waf.SetAllowIp([]string{"10.0.0.1"}) -// waf.SetBlockIp([]string{"127.0.0.1"}) -// waf.SetBlockUa([]string{"python-requests"}) -// waf.SetBlockReferer([]string{"illegal-referrer.com"}) - -// // Dictionary support. - -// httpbl := *openapi.NewHttpblWithDefaults() -// httpbl.SetHttpblEnabled(false) -// httpbl.SetBlockHarvester(true) -// httpbl.SetBlockSearchEngine(true) -// httpbl.SetBlockSuspicious(true) -// httpbl.SetBlockSpam(true) -// // Add API key support. - -// waf.SetHttpbl(httpbl) - -// notify := *openapi.NewNotifyConfigWithDefaults() -// notify.SetPeriod("120") -// notify.SetSlackWebhook("https://slack.com") -// notify.SetOriginStatusCodes([]string{"200"}) - -// req.SetNotifyConfig(notify) - -// req.SetWafConfig(waf) - -// r, _, err := client.RulesProxyAPI.RulesProxyCreate(ctx, "quant", project).RuleProxyRequest(req).Execute() - -// if err != nil { -// t.Logf("Error: %v", err.Error()) -// t.Fatalf("Unable to add rule") -// } - -// t.Logf("Success %v", r) -// } diff --git a/internal/provider/rule_redirect_resource_test.go b/internal/provider/rule_redirect_resource_test.go index 0d28715..b4e57a7 100644 --- a/internal/provider/rule_redirect_resource_test.go +++ b/internal/provider/rule_redirect_resource_test.go @@ -12,27 +12,11 @@ import ( func TestListRedirectResource(t *testing.T) { bearer := os.Getenv("QUANT_BEARER") + cfg := openapi.NewConfiguration() client := openapi.NewAPIClient(cfg) ctx := context.WithValue(context.Background(), openapi.ContextAccessToken, bearer) - rules, _, err := client.RulesRedirectAPI.RulesRedirectList(ctx, "quant", "terraform-project-17-02-2024-3").Execute() + rules, _, err := client.RulesRedirectAPI.RulesRedirectList(ctx, "quant", "api-test").Execute() assert.Nil(t, err) - assert.Equal(t, 2, len(rules)) + assert.Equal(t, 4, len(rules)) } - -// func TestCreateRedirectResource(t *testing.T) { -// bearer := os.Getenv("QUANT_BEARER") -// cfg := openapi.NewConfiguration() -// client := openapi.NewAPIClient(cfg) -// ctx := context.WithValue(context.Background(), openapi.ContextAccessToken, bearer) - -// req := *openapi.NewRuleRedirectRequestWithDefaults() -// req.SetName("SDK-TF: Redirect Rule") -// req.SetRedirectTo("https://www.google.com") -// req.SetRedirectCode("301") -// req.SetUrl([]string{"/test"}) - -// rule, _, err := client.RulesRedirectAPI.RulesRedirectCreate(ctx, "quant", "terraform-project-17-02-2024-3").RuleRedirectRequest(req).Execute() -// assert.Nil(t, err) -// assert.Equal(t, "redirect", rule.GetAction()) -// }