diff --git a/.env.example b/.env.example index bb3aaa8..eead8e0 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,8 @@ TF_ACC=1 BINARYLANE_API_TOKEN= + +# Uncomment to enable debug logging (will dump request bodies) +# TF_LOG=DEBUG + +# Uncomment to disable the Go cache when testing, useful for flaky tests +# GOCACHE=off diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1b336e1..6d23266 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,6 +44,12 @@ jobs: env: TF_ACC: '1' BINARYLANE_API_TOKEN: ${{ secrets.BINARYLANE_API_TOKEN }} + - name: Run sweepers + if: failure() + run: go test -v ./internal/provider/... -sweep=all + env: + TF_ACC: '1' + BINARYLANE_API_TOKEN: ${{ secrets.BINARYLANE_API_TOKEN }} # unit: # name: Unit Tests # runs-on: ubuntu-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5828523..69ddcfa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,6 +41,12 @@ eval export $(cat .env) go test -v ./internal/provider/... ``` +To run Sweepers: + +```sh +go test -v ./internal/provider/... -sweep=all +``` + ### Update modules ```sh diff --git a/internal/binarylane/client.go b/internal/binarylane/client.go new file mode 100644 index 0000000..0c60855 --- /dev/null +++ b/internal/binarylane/client.go @@ -0,0 +1,49 @@ +package binarylane + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/http/httputil" + + "github.com/deepmap/oapi-codegen/pkg/securityprovider" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +func NewClientWithAuth(endpoint string, token string) (*ClientWithResponses, error) { + if token == "" { + return nil, errors.New("missing or empty value for the Binary Lane API " + + "token. Set the `api_token` value in the configuration or use the " + + "BINARYLANE_API_TOKEN environment variable. If either is already set, " + + "ensure the value is not empty") + } + + auth, err := securityprovider.NewSecurityProviderBearerToken(token) + if err != nil { + return nil, fmt.Errorf("failed to create API client with supplied API token: %w", err) + } + + if endpoint == "" { + endpoint = "https://api.binarylane.com.au/v2" + } + + client, err := NewClientWithResponses( + endpoint, + WithRequestEditorFn(func(ctx context.Context, req *http.Request) error { + dump, err := httputil.DumpRequestOut(req, true) + if err != nil { + return err + } + tflog.Debug(ctx, fmt.Sprintf("%q\n", dump)) + return nil + }), + WithRequestEditorFn(auth.Intercept), // include auth AFTER the request logger + ) + + if err != nil { + return nil, fmt.Errorf("failed to create Binary Lane API client: %w", err) + } + + return client, nil +} diff --git a/internal/provider/load_balancer_resource.go b/internal/provider/load_balancer_resource.go index 61056bd..ef9f88b 100644 --- a/internal/provider/load_balancer_resource.go +++ b/internal/provider/load_balancer_resource.go @@ -128,29 +128,54 @@ func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRe tflog.Info(ctx, fmt.Sprintf("Creating Load Balancer: name=%s", data.Name.ValueString())) - lbResp, err := r.bc.client.PostLoadBalancersWithResponse(ctx, body) - if err != nil { - tflog.Info(ctx, fmt.Sprintf("Attempted to create new load balancer: request=%+v", body)) - resp.Diagnostics.AddError( - fmt.Sprintf("Error sending request to create load balancer: name=%s", data.Name.ValueString()), - err.Error(), - ) - return + const maxRetries = 3 + var lbResp *binarylane.PostLoadBalancersResponse + +retryLoop: + for i := 0; i < maxRetries; i++ { + var err error + lbResp, err = r.bc.client.PostLoadBalancersWithResponse(ctx, body) + if err != nil { + tflog.Info(ctx, fmt.Sprintf("Attempted to create new load balancer: request=%+v", body)) + resp.Diagnostics.AddError( + fmt.Sprintf("Error sending request to create load balancer: name=%s", data.Name.ValueString()), + err.Error(), + ) + return + } + + switch lbResp.StatusCode() { + + case http.StatusOK: + break retryLoop + + case http.StatusInternalServerError: + if i < maxRetries-1 { + tflog.Warn(ctx, "Received 500 creating load balancer, retrying...") + time.Sleep(time.Second * 5) + continue + } + + default: + tflog.Info(ctx, fmt.Sprintf("Attempted to create new load balancer: request=%+v", body)) + resp.Diagnostics.AddError( + "Unexpected HTTP status code creating load balancer", + fmt.Sprintf("Received %s creating new load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body), + ) + return + } } + // Check if retries exceeded if lbResp.StatusCode() != http.StatusOK { - tflog.Info(ctx, fmt.Sprintf("Attempted to create new load balancer: request=%+v", body)) resp.Diagnostics.AddError( - "Unexpected HTTP status code creating load balancer", - fmt.Sprintf("Received %s creating new load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body)) + "Failed to create load balancer after retries", + fmt.Sprintf("Final status code: %d", lbResp.StatusCode()), + ) return } - diags = SetLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer) - resp.Diagnostics.Append(diags...) - if diags.HasError() { - return - } + data.Id = types.Int64Value(*lbResp.JSON200.LoadBalancer.Id) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) diff --git a/internal/provider/load_balancer_resource_test.go b/internal/provider/load_balancer_resource_test.go index dfb3810..798973e 100644 --- a/internal/provider/load_balancer_resource_test.go +++ b/internal/provider/load_balancer_resource_test.go @@ -1,8 +1,15 @@ package provider import ( + "context" "crypto/rand" "encoding/base64" + "fmt" + "log" + "net/http" + "os" + "strings" + "terraform-provider-binarylane/internal/binarylane" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -84,3 +91,69 @@ data "binarylane_load_balancer" "test" { }, }) } + +func init() { + resource.AddTestSweepers("load_balancer", &resource.Sweeper{ + Name: "load_balancer", + Dependencies: []string{"server"}, + F: func(_ string) error { + endpoint := os.Getenv("BINARYLANE_API_ENDPOINT") + if endpoint == "" { + endpoint = "https://api.binarylane.com.au/v2" + } + token := os.Getenv("BINARYLANE_API_TOKEN") + + client, err := binarylane.NewClientWithAuth( + endpoint, + token, + ) + + if err != nil { + return fmt.Errorf("Error creating Binary Lane API client: %w", err) + } + + ctx := context.Background() + + var page int32 = 1 + perPage := int32(200) + var nextPage bool = true + + for nextPage { + params := binarylane.GetLoadBalancersParams{ + Page: &page, + PerPage: &perPage, + } + + lbResp, err := client.GetLoadBalancersWithResponse(ctx, ¶ms) + if err != nil { + return fmt.Errorf("Error getting load balancers for test sweep: %w", err) + } + + if lbResp.StatusCode() != http.StatusOK { + return fmt.Errorf("Unexpected status code getting load balancers for test sweep: %s", lbResp.Body) + } + + loadBalancers := *lbResp.JSON200.LoadBalancers + for _, lb := range loadBalancers { + if strings.HasPrefix(*lb.Name, "tf-test-") { + lbResp, err := client.DeleteLoadBalancersLoadBalancerIdWithResponse(ctx, *lb.Id) + if err != nil { + return fmt.Errorf("Error deleting load balancer %d for test sweep: %w", *lb.Id, err) + } + if lbResp.StatusCode() != http.StatusNoContent { + return fmt.Errorf("Unexpected status %d deleting load balancer %d in test sweep: %s", lbResp.StatusCode(), *lb.Id, lbResp.Body) + } + log.Println("Deleted load balancer during test sweep:", *lb.Id) + } + } + if lbResp.JSON200.Links == nil || lbResp.JSON200.Links.Pages == nil || lbResp.JSON200.Links.Pages.Next == nil { + nextPage = false + break + } + + page++ + } + return nil + }, + }) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 45e9f91..3ea8da4 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -5,9 +5,7 @@ import ( "os" "terraform-provider-binarylane/internal/binarylane" - "github.com/deepmap/oapi-codegen/pkg/securityprovider" "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -67,60 +65,24 @@ func (p *binarylaneProvider) Configure(ctx context.Context, req provider.Configu return } - if config.Endpoint.IsUnknown() { - resp.Diagnostics.AddAttributeError( - path.Root("api_endpoint"), - "Unknown Binary Lane API endpoint", - "The provider cannot create the Binary Lane API client as there is an unknown configuration value for the "+ - "Binary Lane API endpoint. Either target apply the source of the value first, set the value statically in "+ - "the configuration, or use the BINARYLANE_API_ENDPOINT environment variable.", - ) - } - if config.Token.IsUnknown() { - resp.Diagnostics.AddAttributeError( - path.Root("api_token"), - "Unknown Binary Lane token", - "The provider cannot create the Binary Lane API client as there is an unknown configuration value for the "+ - "Binary Lane API token. Either target apply the source of the value first, set the value statically in the "+ - "configuration, or use the BINARYLANE_API_TOKEN environment variable.", - ) - } - if resp.Diagnostics.HasError() { - return - } - - // Default values to environment variables, but override - // with Terraform configuration value if set. - endpoint := os.Getenv("BINARYLANE_API_ENDPOINT") - token := os.Getenv("BINARYLANE_API_TOKEN") - if !config.Endpoint.IsNull() { - endpoint = config.Endpoint.ValueString() - } - if !config.Token.IsNull() { - token = config.Token.ValueString() - } + endpoint := config.Endpoint.ValueString() if endpoint == "" { - endpoint = "https://api.binarylane.com.au/v2" + endpoint = os.Getenv("BINARYLANE_API_ENDPOINT") + if endpoint == "" { + endpoint = "https://api.binarylane.com.au/v2" + } } + + token := config.Token.ValueString() if token == "" { - resp.Diagnostics.AddAttributeError( - path.Root("api_token"), - "Missing Binary Lane API token", - "The provider cannot create the Binary Lane API client as there is a missing or empty value for the Binary "+ - "Lane API token. Set the token value in the configuration or use the BINARYLANE_API_TOKEN environment "+ - "variable. If either is already set, ensure the value is not empty.", - ) - } - if resp.Diagnostics.HasError() { - return - } - auth, err := securityprovider.NewSecurityProviderBearerToken(token) - if err != nil { - resp.Diagnostics.AddError("Failed to create security provider with supplied token", err.Error()) - return + token = os.Getenv("BINARYLANE_API_TOKEN") } - client, err := binarylane.NewClientWithResponses(endpoint, binarylane.WithRequestEditorFn(auth.Intercept)) + client, err := binarylane.NewClientWithAuth( + endpoint, + token, + ) + if err != nil { resp.Diagnostics.AddError("Failed to create Binary Lane API client", err.Error()) return diff --git a/internal/provider/server_resource.go b/internal/provider/server_resource.go index 310f1bc..9bbd898 100644 --- a/internal/provider/server_resource.go +++ b/internal/provider/server_resource.go @@ -255,21 +255,51 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest, data.Password = types.StringNull() } else { body.Password = data.Password.ValueStringPointer() + ctx = tflog.MaskMessageStrings(ctx, data.Password.String()) } - serverResp, err := r.bc.client.PostServersWithResponse(ctx, body) - if err != nil { - resp.Diagnostics.AddError( - fmt.Sprintf("Error creating server: name=%s", data.Name.ValueString()), - err.Error(), - ) - return + const maxRetries = 3 + var serverResp *binarylane.PostServersResponse + +retryLoop: + for i := 0; i < maxRetries; i++ { + var err error + serverResp, err = r.bc.client.PostServersWithResponse(ctx, body) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error creating server: name=%s", data.Name.ValueString()), + err.Error(), + ) + return + } + + switch serverResp.StatusCode() { + + case http.StatusOK: + break retryLoop + + case http.StatusInternalServerError: + if i < maxRetries-1 { + tflog.Warn(ctx, "Received 500 creating server, retrying...") + time.Sleep(time.Second * 5) + continue + } + + default: + resp.Diagnostics.AddError( + "Unexpected HTTP status code creating server", + fmt.Sprintf("Received %s creating new server: name=%s. Details: %s", serverResp.Status(), data.Name.ValueString(), serverResp.Body), + ) + return + } } + // Check if retries exceeded if serverResp.StatusCode() != http.StatusOK { resp.Diagnostics.AddError( - "Unexpected HTTP status code creating server", - fmt.Sprintf("Received %s creating new server: name=%s. Details: %s", serverResp.Status(), data.Name.ValueString(), serverResp.Body)) + "Failed to create server after retries", + fmt.Sprintf("Final status code: %d", serverResp.StatusCode()), + ) return } diff --git a/internal/provider/server_resource_test.go b/internal/provider/server_resource_test.go index 7a7cee2..7a80f3e 100644 --- a/internal/provider/server_resource_test.go +++ b/internal/provider/server_resource_test.go @@ -1,8 +1,15 @@ package provider import ( + "context" "crypto/rand" "encoding/base64" + "fmt" + "log" + "net/http" + "os" + "strings" + "terraform-provider-binarylane/internal/binarylane" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -109,3 +116,74 @@ echo "Hello World" > /var/tmp/output.txt }, }) } + +func init() { + resource.AddTestSweepers("server", &resource.Sweeper{ + Name: "server", + F: func(_ string) error { + endpoint := os.Getenv("BINARYLANE_API_ENDPOINT") + if endpoint == "" { + endpoint = "https://api.binarylane.com.au/v2" + } + token := os.Getenv("BINARYLANE_API_TOKEN") + + client, err := binarylane.NewClientWithAuth( + endpoint, + token, + ) + + if err != nil { + return fmt.Errorf("Error creating Binary Lane API client: %w", err) + } + + ctx := context.Background() + + var page int32 = 1 + perPage := int32(200) + nextPage := true + + for nextPage { + params := binarylane.GetServersParams{ + Page: &page, + PerPage: &perPage, + } + + serverResp, err := client.GetServersWithResponse(ctx, ¶ms) + if err != nil { + return fmt.Errorf("Error getting servers for test sweep: %w", err) + } + + if serverResp.StatusCode() != http.StatusOK { + return fmt.Errorf("Unexpected status code getting servers in test sweep: %s", serverResp.Body) + } + + servers := *serverResp.JSON200.Servers + for _, s := range servers { + if strings.HasPrefix(*s.Name, "tf-test-") { + reason := "Terraform deletion" + params := binarylane.DeleteServersServerIdParams{ + Reason: &reason, + } + + serverResp, err := client.DeleteServersServerIdWithResponse(ctx, *s.Id, ¶ms) + if err != nil { + return fmt.Errorf("Error deleting server %d during test sweep: %w", *s.Id, err) + } + if serverResp.StatusCode() != http.StatusNoContent { + return fmt.Errorf("Unexpected status %d deleting server %d in test sweep: %s", serverResp.StatusCode(), *s.Id, serverResp.Body) + } + log.Println("Deleted server during test sweep:", *s.Id) + } + } + if serverResp.JSON200.Links == nil || serverResp.JSON200.Links.Pages == nil || serverResp.JSON200.Links.Pages.Next == nil { + nextPage = false + break + } + + page++ + } + + return nil + }, + }) +} diff --git a/internal/provider/ssh_key_resource.go b/internal/provider/ssh_key_resource.go index 9e87735..d849163 100644 --- a/internal/provider/ssh_key_resource.go +++ b/internal/provider/ssh_key_resource.go @@ -7,6 +7,7 @@ import ( "strconv" "terraform-provider-binarylane/internal/binarylane" "terraform-provider-binarylane/internal/resources" + "time" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -15,6 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" ) // Ensure the implementation satisfies the expected interfaces. @@ -103,22 +105,51 @@ func (r *sshKeyResource) Create(ctx context.Context, req resource.CreateRequest, } // Create API call logic - sshResp, err := r.bc.client.PostAccountKeysWithResponse(ctx, binarylane.SshKeyRequest{ - Name: data.Name.ValueString(), - Default: data.Default.ValueBoolPointer(), - PublicKey: data.PublicKey.ValueString(), - }) - if err != nil { - resp.Diagnostics.AddError( - fmt.Sprintf("Error creating SSH Key: name=%s", data.Name.ValueString()), - err.Error(), - ) - return + const maxRetries = 3 + var sshResp *binarylane.PostAccountKeysResponse + +retryLoop: + for i := 0; i < maxRetries; i++ { + var err error + sshResp, err = r.bc.client.PostAccountKeysWithResponse(ctx, binarylane.SshKeyRequest{ + Name: data.Name.ValueString(), + Default: data.Default.ValueBoolPointer(), + PublicKey: data.PublicKey.ValueString(), + }) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error creating SSH Key: name=%s", data.Name.ValueString()), + err.Error(), + ) + return + } + + switch sshResp.StatusCode() { + + case http.StatusOK: + break retryLoop + + case http.StatusInternalServerError: + if i < maxRetries-1 { + tflog.Warn(ctx, "Received 500 creating SSH key, retrying...") + time.Sleep(time.Second * 5) + continue + } + + default: + resp.Diagnostics.AddError( + "Unexpected HTTP status code creating SSH key", + fmt.Sprintf("Received %s creating SSH key: name=%s. Details: %s", sshResp.Status(), data.Name.ValueString(), sshResp.Body), + ) + return + } } + + // Check if retries exceeded if sshResp.StatusCode() != http.StatusOK { resp.Diagnostics.AddError( - "Unexpected HTTP status code creating new SSH key", - fmt.Sprintf("Received %s creating new SSH key: name=%s. Details: %s", sshResp.Status(), data.Name.ValueString(), sshResp.Body), + "Failed to create SSH key after retries", + fmt.Sprintf("Final status code: %d", sshResp.StatusCode()), ) return } diff --git a/internal/provider/ssh_key_resource_test.go b/internal/provider/ssh_key_resource_test.go index 0e04f50..d6d348c 100644 --- a/internal/provider/ssh_key_resource_test.go +++ b/internal/provider/ssh_key_resource_test.go @@ -1,9 +1,15 @@ package provider import ( + "context" "crypto/ed25519" "encoding/base64" "fmt" + "log" + "net/http" + "os" + "strings" + "terraform-provider-binarylane/internal/binarylane" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -20,7 +26,7 @@ func TestSshKeyResource(t *testing.T) { { Config: providerConfig + ` resource "binarylane_ssh_key" "test" { - name = "tf_ssh_key_resource_test" + name = "tf-test-key-resource-test" public_key = "` + publicKey + `" } @@ -32,13 +38,13 @@ data "binarylane_ssh_key" "test" { `, Check: resource.ComposeAggregateTestCheckFunc( // Verify resource values - resource.TestCheckResourceAttr("binarylane_ssh_key.test", "name", "tf_ssh_key_resource_test"), + resource.TestCheckResourceAttr("binarylane_ssh_key.test", "name", "tf-test-key-resource-test"), resource.TestCheckResourceAttr("binarylane_ssh_key.test", "public_key", publicKey), resource.TestCheckResourceAttr("binarylane_ssh_key.test", "default", "false"), resource.TestCheckResourceAttrSet("binarylane_ssh_key.test", "id"), // Verify data source values - resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "name", "tf_ssh_key_resource_test"), + resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "name", "tf-test-key-resource-test"), resource.TestCheckResourceAttrSet("data.binarylane_ssh_key.test", "public_key"), // Ideally would check this is identical, but whitespace is not preserved resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "default", "false"), resource.TestCheckResourceAttrSet("data.binarylane_ssh_key.test", "id"), @@ -62,14 +68,14 @@ data "binarylane_ssh_key" "test" { // { // Config: providerConfig + ` // resource "binarylane_ssh_key" "test" { - // name = "tf_ssh_key_resource_test" + // name = "tf-test-key-resource-test" // public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJCsuosklP0T4fJcQDgkeVh7dQu+eV+vev1CfwdUkj7h test@company.internal" // default = true // } // `, // Check: resource.ComposeAggregateTestCheckFunc( // // Verify resource values - // resource.TestCheckResourceAttr("binarylane_ssh_key.test", "name", "tf_ssh_key_resource_test"), + // resource.TestCheckResourceAttr("binarylane_ssh_key.test", "name", "tf-test-key-resource-test"), // resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "default", "true"), // ), // }, @@ -106,3 +112,70 @@ func ImportByFingerprint(state *terraform.State) (fingerprint string, err error) return rawState["fingerprint"], nil } + +func init() { + resource.AddTestSweepers("ssh_key", &resource.Sweeper{ + Name: "ssh_key", + F: func(_ string) error { + endpoint := os.Getenv("BINARYLANE_API_ENDPOINT") + if endpoint == "" { + endpoint = "https://api.binarylane.com.au/v2" + } + token := os.Getenv("BINARYLANE_API_TOKEN") + + client, err := binarylane.NewClientWithAuth( + endpoint, + token, + ) + + if err != nil { + return fmt.Errorf("Error creating Binary Lane API client: %w", err) + } + + ctx := context.Background() + + var page int32 = 1 + perPage := int32(200) + nextPage := true + + for nextPage { + params := binarylane.GetAccountKeysParams{ + Page: &page, + PerPage: &perPage, + } + + keyResp, err := client.GetAccountKeysWithResponse(ctx, ¶ms) + if err != nil { + return fmt.Errorf("Error getting SSH keys for test sweep: %w", err) + } + + if keyResp.StatusCode() != http.StatusOK { + return fmt.Errorf("Unexpected status code getting SSH keys for test sweep: %s", keyResp.Body) + } + + keys := keyResp.JSON200.SshKeys + for _, k := range keys { + if strings.HasPrefix(*k.Name, "tf-test-") { + + keyResp, err := client.DeleteAccountKeysKeyIdWithResponse(ctx, int(*k.Id)) + if err != nil { + return fmt.Errorf("Error deleting SSH key %d for test sweep: %w", *k.Id, err) + } + if keyResp.StatusCode() != http.StatusNoContent { + return fmt.Errorf("Unexpected status %d deleting SSH key %d for test sweep: %s", keyResp.StatusCode(), *k.Id, keyResp.Body) + } + log.Println("Deleted SSH key for test sweep:", *k.Id) + } + } + if keyResp.JSON200.Links == nil || keyResp.JSON200.Links.Pages == nil || keyResp.JSON200.Links.Pages.Next == nil { + nextPage = false + break + } + + page++ + } + + return nil + }, + }) +} diff --git a/internal/provider/sweeper_test.go b/internal/provider/sweeper_test.go new file mode 100644 index 0000000..fc7c71d --- /dev/null +++ b/internal/provider/sweeper_test.go @@ -0,0 +1,11 @@ +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestMain(m *testing.M) { + resource.TestMain(m) +}