provider/aws: Add Sweeper setup, Sweepers for DB Option Group, Key Pair (#14773)
* provider/aws: Add Sweeper setup, Sweepers for DB Option Group, Key Pair * provider/google: Add sweeper for any leaked databases * more recursion and added LC sweeper, to test out the Dependency path * implement a dependency example * implement sweep-run flag to filter runs * stub a test for TestMain * test for multiple -sweep-run list
This commit is contained in:
parent
3d1e60b504
commit
372a80bc42
|
@ -0,0 +1,37 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
resource.TestMain(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sharedClientForRegion returns a common AWSClient setup needed for the sweeper
|
||||||
|
// functions for a given region
|
||||||
|
func sharedClientForRegion(region string) (interface{}, error) {
|
||||||
|
if os.Getenv("AWS_ACCESS_KEY_ID") == "" {
|
||||||
|
return nil, fmt.Errorf("empty AWS_ACCESS_KEY_ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("AWS_SECRET_ACCESS_KEY") == "" {
|
||||||
|
return nil, fmt.Errorf("empty AWS_SECRET_ACCESS_KEY")
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := &Config{
|
||||||
|
Region: region,
|
||||||
|
}
|
||||||
|
|
||||||
|
// configures a default client for the region, using the above env vars
|
||||||
|
client, err := conf.Client()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting AWS client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
|
@ -3,11 +3,13 @@ package aws
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
@ -18,6 +20,72 @@ import (
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
resource.AddTestSweepers("aws_autoscaling_group", &resource.Sweeper{
|
||||||
|
Name: "aws_autoscaling_group",
|
||||||
|
F: testSweepAutoscalingGroups,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSweepAutoscalingGroups(region string) error {
|
||||||
|
client, err := sharedClientForRegion(region)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting client: %s", err)
|
||||||
|
}
|
||||||
|
conn := client.(*AWSClient).autoscalingconn
|
||||||
|
|
||||||
|
resp, err := conn.DescribeAutoScalingGroups(&autoscaling.DescribeAutoScalingGroupsInput{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error retrieving launch configuration: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.AutoScalingGroups) == 0 {
|
||||||
|
log.Print("[DEBUG] No aws autoscaling groups to sweep")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, asg := range resp.AutoScalingGroups {
|
||||||
|
var testOptGroup bool
|
||||||
|
for _, testName := range []string{"foobar", "terraform-"} {
|
||||||
|
if strings.HasPrefix(*asg.AutoScalingGroupName, testName) {
|
||||||
|
testOptGroup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testOptGroup {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteopts := autoscaling.DeleteAutoScalingGroupInput{
|
||||||
|
AutoScalingGroupName: asg.AutoScalingGroupName,
|
||||||
|
ForceDelete: aws.Bool(true),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = resource.Retry(5*time.Minute, func() *resource.RetryError {
|
||||||
|
if _, err := conn.DeleteAutoScalingGroup(&deleteopts); err != nil {
|
||||||
|
if awserr, ok := err.(awserr.Error); ok {
|
||||||
|
switch awserr.Code() {
|
||||||
|
case "InvalidGroup.NotFound":
|
||||||
|
return nil
|
||||||
|
case "ResourceInUse", "ScalingActivityInProgress":
|
||||||
|
return resource.RetryableError(awserr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Didn't recognize the error, so shouldn't retry.
|
||||||
|
return resource.NonRetryableError(err)
|
||||||
|
}
|
||||||
|
// Successful delete
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccAWSAutoScalingGroup_basic(t *testing.T) {
|
func TestAccAWSAutoScalingGroup_basic(t *testing.T) {
|
||||||
var group autoscaling.Group
|
var group autoscaling.Group
|
||||||
var lc autoscaling.LaunchConfiguration
|
var lc autoscaling.LaunchConfiguration
|
||||||
|
|
|
@ -2,8 +2,11 @@ package aws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
@ -13,6 +16,64 @@ import (
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
resource.AddTestSweepers("aws_db_option_group", &resource.Sweeper{
|
||||||
|
Name: "aws_db_option_group",
|
||||||
|
F: testSweepDbOptionGroups,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSweepDbOptionGroups(region string) error {
|
||||||
|
client, err := sharedClientForRegion(region)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := client.(*AWSClient).rdsconn
|
||||||
|
|
||||||
|
opts := rds.DescribeOptionGroupsInput{}
|
||||||
|
resp, err := conn.DescribeOptionGroups(&opts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error describing DB Option Groups in Sweeper: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, og := range resp.OptionGroupsList {
|
||||||
|
var testOptGroup bool
|
||||||
|
for _, testName := range []string{"option-group-test-terraform-", "tf-test"} {
|
||||||
|
if strings.HasPrefix(*og.OptionGroupName, testName) {
|
||||||
|
testOptGroup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testOptGroup {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteOpts := &rds.DeleteOptionGroupInput{
|
||||||
|
OptionGroupName: og.OptionGroupName,
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := resource.Retry(1*time.Minute, func() *resource.RetryError {
|
||||||
|
_, err := conn.DeleteOptionGroup(deleteOpts)
|
||||||
|
if err != nil {
|
||||||
|
if awsErr, ok := err.(awserr.Error); ok {
|
||||||
|
if awsErr.Code() == "InvalidOptionGroupStateFault" {
|
||||||
|
log.Printf("[DEBUG] AWS believes the RDS Option Group is still in use, retrying")
|
||||||
|
return resource.RetryableError(awsErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resource.NonRetryableError(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if ret != nil {
|
||||||
|
return fmt.Errorf("Error Deleting DB Option Group (%s) in Sweeper: %s", *og.OptionGroupName, ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccAWSDBOptionGroup_basic(t *testing.T) {
|
func TestAccAWSDBOptionGroup_basic(t *testing.T) {
|
||||||
var v rds.OptionGroup
|
var v rds.OptionGroup
|
||||||
rName := fmt.Sprintf("option-group-test-terraform-%s", acctest.RandString(5))
|
rName := fmt.Sprintf("option-group-test-terraform-%s", acctest.RandString(5))
|
||||||
|
|
|
@ -2,6 +2,7 @@ package aws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -12,6 +13,47 @@ import (
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
resource.AddTestSweepers("aws_key_pair", &resource.Sweeper{
|
||||||
|
Name: "aws_key_pair",
|
||||||
|
F: testSweepKeyPairs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSweepKeyPairs(region string) error {
|
||||||
|
client, err := sharedClientForRegion(region)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting client: %s", err)
|
||||||
|
}
|
||||||
|
ec2conn := client.(*AWSClient).ec2conn
|
||||||
|
|
||||||
|
log.Printf("Destroying the tmp keys in (%s)", client.(*AWSClient).region)
|
||||||
|
|
||||||
|
resp, err := ec2conn.DescribeKeyPairs(&ec2.DescribeKeyPairsInput{
|
||||||
|
Filters: []*ec2.Filter{
|
||||||
|
&ec2.Filter{
|
||||||
|
Name: aws.String("key-name"),
|
||||||
|
Values: []*string{aws.String("tmp-key*")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error describing key pairs in Sweeper: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPairs := resp.KeyPairs
|
||||||
|
for _, d := range keyPairs {
|
||||||
|
_, err := ec2conn.DeleteKeyPair(&ec2.DeleteKeyPairInput{
|
||||||
|
KeyName: d.KeyName,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting key pairs in Sweeper: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccAWSKeyPair_basic(t *testing.T) {
|
func TestAccAWSKeyPair_basic(t *testing.T) {
|
||||||
var conf ec2.KeyPairInfo
|
var conf ec2.KeyPairInfo
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package aws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -16,6 +17,61 @@ import (
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
resource.AddTestSweepers("aws_launch_configuration", &resource.Sweeper{
|
||||||
|
Name: "aws_launch_configuration",
|
||||||
|
Dependencies: []string{"aws_autoscaling_group"},
|
||||||
|
F: testSweepLaunchConfigurations,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSweepLaunchConfigurations(region string) error {
|
||||||
|
client, err := sharedClientForRegion(region)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting client: %s", err)
|
||||||
|
}
|
||||||
|
autoscalingconn := client.(*AWSClient).autoscalingconn
|
||||||
|
|
||||||
|
resp, err := autoscalingconn.DescribeLaunchConfigurations(&autoscaling.DescribeLaunchConfigurationsInput{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error retrieving launch configuration: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.LaunchConfigurations) == 0 {
|
||||||
|
log.Print("[DEBUG] No aws launch configurations to sweep")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, lc := range resp.LaunchConfigurations {
|
||||||
|
var testOptGroup bool
|
||||||
|
for _, testName := range []string{"terraform-", "foobar"} {
|
||||||
|
if strings.HasPrefix(*lc.LaunchConfigurationName, testName) {
|
||||||
|
testOptGroup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testOptGroup {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := autoscalingconn.DeleteLaunchConfiguration(
|
||||||
|
&autoscaling.DeleteLaunchConfigurationInput{
|
||||||
|
LaunchConfigurationName: lc.LaunchConfigurationName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
autoscalingerr, ok := err.(awserr.Error)
|
||||||
|
if ok && (autoscalingerr.Code() == "InvalidConfiguration.NotFound" || autoscalingerr.Code() == "ValidationError") {
|
||||||
|
log.Printf("[DEBUG] Launch configuration (%s) not found", *lc.LaunchConfigurationName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccAWSLaunchConfiguration_basic(t *testing.T) {
|
func TestAccAWSLaunchConfiguration_basic(t *testing.T) {
|
||||||
var conf autoscaling.LaunchConfiguration
|
var conf autoscaling.LaunchConfiguration
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
resource.TestMain(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sharedConfigForRegion returns a common config setup needed for the sweeper
|
||||||
|
// functions for a given region
|
||||||
|
func sharedConfigForRegion(region string) (*Config, error) {
|
||||||
|
project := os.Getenv("GOOGLE_PROJECT")
|
||||||
|
if project == "" {
|
||||||
|
return nil, fmt.Errorf("empty GOOGLE_PROJECT")
|
||||||
|
}
|
||||||
|
|
||||||
|
creds := os.Getenv("GOOGLE_CREDENTIALS")
|
||||||
|
if creds == "" {
|
||||||
|
return nil, fmt.Errorf("empty GOOGLE_CREDENTIALS")
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := &Config{
|
||||||
|
Credentials: creds,
|
||||||
|
Region: region,
|
||||||
|
Project: project,
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf, nil
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ package google
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -20,6 +21,105 @@ import (
|
||||||
"google.golang.org/api/sqladmin/v1beta4"
|
"google.golang.org/api/sqladmin/v1beta4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
resource.AddTestSweepers("gcp_sql_db_instance", &resource.Sweeper{
|
||||||
|
Name: "gcp_sql_db_instance",
|
||||||
|
F: testSweepDatabases,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSweepDatabases(region string) error {
|
||||||
|
config, err := sharedConfigForRegion(region)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting shared config for region: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = config.loadAndValidate()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error loading: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := config.clientSqlAdmin.Instances.List(config.Project).Do()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error listing databases: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(found.Items) == 0 {
|
||||||
|
log.Printf("No databases found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range found.Items {
|
||||||
|
var testDbInstance bool
|
||||||
|
for _, testName := range []string{"tf-lw-", "sqldatabasetest"} {
|
||||||
|
// only destroy instances we know to fit our test naming pattern
|
||||||
|
if strings.HasPrefix(d.Name, testName) {
|
||||||
|
testDbInstance = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testDbInstance {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Destroying SQL Instance (%s)", d.Name)
|
||||||
|
|
||||||
|
// replicas need to be stopped and destroyed before destroying a master
|
||||||
|
// instance. The ordering slice tracks replica databases for a given master
|
||||||
|
// and we call destroy on them before destroying the master
|
||||||
|
var ordering []string
|
||||||
|
for _, replicaName := range d.ReplicaNames {
|
||||||
|
// need to stop replication before being able to destroy a database
|
||||||
|
op, err := config.clientSqlAdmin.Instances.StopReplica(config.Project, replicaName).Do()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error, failed to stop replica instance (%s) for instance (%s): %s", replicaName, d.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sqladminOperationWait(config, op, "Stop Replica")
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "does not exist") {
|
||||||
|
log.Printf("Replication operation not found")
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ordering = append(ordering, replicaName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ordering has a list of replicas (or none), now add the primary to the end
|
||||||
|
ordering = append(ordering, d.Name)
|
||||||
|
|
||||||
|
for _, db := range ordering {
|
||||||
|
// destroy instances, replicas first
|
||||||
|
op, err := config.clientSqlAdmin.Instances.Delete(config.Project, db).Do()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "409") {
|
||||||
|
// the GCP api can return a 409 error after the delete operation
|
||||||
|
// reaches a successful end
|
||||||
|
log.Printf("Operation not found, got 409 response")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Error, failed to delete instance %s: %s", db, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sqladminOperationWait(config, op, "Delete Instance")
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "does not exist") {
|
||||||
|
log.Printf("SQL instance not found")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccGoogleSqlDatabaseInstance_basic(t *testing.T) {
|
func TestAccGoogleSqlDatabaseInstance_basic(t *testing.T) {
|
||||||
var instance sqladmin.DatabaseInstance
|
var instance sqladmin.DatabaseInstance
|
||||||
databaseID := acctest.RandInt()
|
databaseID := acctest.RandInt()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -20,6 +21,153 @@ import (
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// flagSweep is a flag available when running tests on the command line. It
|
||||||
|
// contains a comma seperated list of regions to for the sweeper functions to
|
||||||
|
// run in. This flag bypasses the normal Test path and instead runs functions designed to
|
||||||
|
// clean up any leaked resources a testing environment could have created. It is
|
||||||
|
// a best effort attempt, and relies on Provider authors to implement "Sweeper"
|
||||||
|
// methods for resources.
|
||||||
|
|
||||||
|
// Adding Sweeper methods with AddTestSweepers will
|
||||||
|
// construct a list of sweeper funcs to be called here. We iterate through
|
||||||
|
// regions provided by the sweep flag, and for each region we iterate through the
|
||||||
|
// tests, and exit on any errors. At time of writing, sweepers are ran
|
||||||
|
// sequentially, however they can list dependencies to be ran first. We track
|
||||||
|
// the sweepers that have been ran, so as to not run a sweeper twice for a given
|
||||||
|
// region.
|
||||||
|
//
|
||||||
|
// WARNING:
|
||||||
|
// Sweepers are designed to be destructive. You should not use the -sweep flag
|
||||||
|
// in any environment that is not strictly a test environment. Resources will be
|
||||||
|
// destroyed.
|
||||||
|
|
||||||
|
var flagSweep = flag.String("sweep", "", "List of Regions to run available Sweepers")
|
||||||
|
var flagSweepRun = flag.String("sweep-run", "", "Comma seperated list of Sweeper Tests to run")
|
||||||
|
var sweeperFuncs map[string]*Sweeper
|
||||||
|
|
||||||
|
// map of sweepers that have ran, and the success/fail status based on any error
|
||||||
|
// raised
|
||||||
|
var sweeperRunList map[string]bool
|
||||||
|
|
||||||
|
// type SweeperFunc is a signature for a function that acts as a sweeper. It
|
||||||
|
// accepts a string for the region that the sweeper is to be ran in. This
|
||||||
|
// function must be able to construct a valid client for that region.
|
||||||
|
type SweeperFunc func(r string) error
|
||||||
|
|
||||||
|
type Sweeper struct {
|
||||||
|
// Name for sweeper. Must be unique to be ran by the Sweeper Runner
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Dependencies list the const names of other Sweeper functions that must be ran
|
||||||
|
// prior to running this Sweeper. This is an ordered list that will be invoked
|
||||||
|
// recursively at the helper/resource level
|
||||||
|
Dependencies []string
|
||||||
|
|
||||||
|
// Sweeper function that when invoked sweeps the Provider of specific
|
||||||
|
// resources
|
||||||
|
F SweeperFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sweeperFuncs = make(map[string]*Sweeper)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTestSweepers function adds a given name and Sweeper configuration
|
||||||
|
// pair to the internal sweeperFuncs map. Invoke this function to register a
|
||||||
|
// resource sweeper to be available for running when the -sweep flag is used
|
||||||
|
// with `go test`. Sweeper names must be unique to help ensure a given sweeper
|
||||||
|
// is only ran once per run.
|
||||||
|
func AddTestSweepers(name string, s *Sweeper) {
|
||||||
|
if _, ok := sweeperFuncs[name]; ok {
|
||||||
|
log.Fatalf("[ERR] Error adding (%s) to sweeperFuncs: function already exists in map", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
sweeperFuncs[name] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
flag.Parse()
|
||||||
|
if *flagSweep != "" {
|
||||||
|
// parse flagSweep contents for regions to run
|
||||||
|
regions := strings.Split(*flagSweep, ",")
|
||||||
|
|
||||||
|
// get filtered list of sweepers to run based on sweep-run flag
|
||||||
|
sweepers := filterSweepers(*flagSweepRun, sweeperFuncs)
|
||||||
|
for _, region := range regions {
|
||||||
|
region = strings.TrimSpace(region)
|
||||||
|
// reset sweeperRunList for each region
|
||||||
|
sweeperRunList = map[string]bool{}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Running Sweepers for region (%s):\n", region)
|
||||||
|
for _, sweeper := range sweepers {
|
||||||
|
if err := runSweeperWithRegion(region, sweeper); err != nil {
|
||||||
|
log.Fatalf("[ERR] error running (%s): %s", sweeper.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Sweeper Tests ran:\n")
|
||||||
|
for s, _ := range sweeperRunList {
|
||||||
|
fmt.Printf("\t- %s\n", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterSweepers takes a comma seperated string listing the names of sweepers
|
||||||
|
// to be ran, and returns a filtered set from the list of all of sweepers to
|
||||||
|
// run based on the names given.
|
||||||
|
func filterSweepers(f string, source map[string]*Sweeper) map[string]*Sweeper {
|
||||||
|
filterSlice := strings.Split(strings.ToLower(f), ",")
|
||||||
|
if len(filterSlice) == 1 && filterSlice[0] == "" {
|
||||||
|
// if the filter slice is a single element of "" then no sweeper list was
|
||||||
|
// given, so just return the full list
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
sweepers := make(map[string]*Sweeper)
|
||||||
|
for name, sweeper := range source {
|
||||||
|
for _, s := range filterSlice {
|
||||||
|
if strings.Contains(strings.ToLower(name), s) {
|
||||||
|
sweepers[name] = sweeper
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sweepers
|
||||||
|
}
|
||||||
|
|
||||||
|
// runSweeperWithRegion recieves a sweeper and a region, and recursively calls
|
||||||
|
// itself with that region for every dependency found for that sweeper. If there
|
||||||
|
// are no dependencies, invoke the contained sweeper fun with the region, and
|
||||||
|
// add the success/fail status to the sweeperRunList.
|
||||||
|
func runSweeperWithRegion(region string, s *Sweeper) error {
|
||||||
|
for _, dep := range s.Dependencies {
|
||||||
|
if depSweeper, ok := sweeperFuncs[dep]; ok {
|
||||||
|
log.Printf("[DEBUG] Sweeper (%s) has dependency (%s), running..", s.Name, dep)
|
||||||
|
if err := runSweeperWithRegion(region, depSweeper); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("[DEBUG] Sweeper (%s) has dependency (%s), but that sweeper was not found", s.Name, dep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := sweeperRunList[s.Name]; ok {
|
||||||
|
log.Printf("[DEBUG] Sweeper (%s) already ran in region (%s)", s.Name, region)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
runE := s.F(region)
|
||||||
|
if runE == nil {
|
||||||
|
sweeperRunList[s.Name] = true
|
||||||
|
} else {
|
||||||
|
sweeperRunList[s.Name] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return runE
|
||||||
|
}
|
||||||
|
|
||||||
const TestEnvVar = "TF_ACC"
|
const TestEnvVar = "TF_ACC"
|
||||||
|
|
||||||
// TestProvider can be implemented by any ResourceProvider to provide custom
|
// TestProvider can be implemented by any ResourceProvider to provide custom
|
||||||
|
|
|
@ -2,9 +2,12 @@ package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
@ -619,3 +622,158 @@ func testProvider() *terraform.MockResourceProvider {
|
||||||
const testConfigStr = `
|
const testConfigStr = `
|
||||||
resource "test_instance" "foo" {}
|
resource "test_instance" "foo" {}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
func TestTest_Main(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
if *flagSweep == "" {
|
||||||
|
// Tests for the TestMain method used for Sweepers will panic without the -sweep
|
||||||
|
// flag specified. Mock the value for now
|
||||||
|
*flagSweep = "us-east-1"
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
Name string
|
||||||
|
Sweepers map[string]*Sweeper
|
||||||
|
ExpectedRunList []string
|
||||||
|
SweepRun string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "normal",
|
||||||
|
Sweepers: map[string]*Sweeper{
|
||||||
|
"aws_dummy": &Sweeper{
|
||||||
|
Name: "aws_dummy",
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedRunList: []string{"aws_dummy"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "with dep",
|
||||||
|
Sweepers: map[string]*Sweeper{
|
||||||
|
"aws_dummy": &Sweeper{
|
||||||
|
Name: "aws_dummy",
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
"aws_top": &Sweeper{
|
||||||
|
Name: "aws_top",
|
||||||
|
Dependencies: []string{"aws_sub"},
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
"aws_sub": &Sweeper{
|
||||||
|
Name: "aws_sub",
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedRunList: []string{"aws_dummy", "aws_sub", "aws_top"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "with filter",
|
||||||
|
Sweepers: map[string]*Sweeper{
|
||||||
|
"aws_dummy": &Sweeper{
|
||||||
|
Name: "aws_dummy",
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
"aws_top": &Sweeper{
|
||||||
|
Name: "aws_top",
|
||||||
|
Dependencies: []string{"aws_sub"},
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
"aws_sub": &Sweeper{
|
||||||
|
Name: "aws_sub",
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedRunList: []string{"aws_dummy"},
|
||||||
|
SweepRun: "aws_dummy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "with two filters",
|
||||||
|
Sweepers: map[string]*Sweeper{
|
||||||
|
"aws_dummy": &Sweeper{
|
||||||
|
Name: "aws_dummy",
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
"aws_top": &Sweeper{
|
||||||
|
Name: "aws_top",
|
||||||
|
Dependencies: []string{"aws_sub"},
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
"aws_sub": &Sweeper{
|
||||||
|
Name: "aws_sub",
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedRunList: []string{"aws_dummy", "aws_sub"},
|
||||||
|
SweepRun: "aws_dummy,aws_sub",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "with dep and filter",
|
||||||
|
Sweepers: map[string]*Sweeper{
|
||||||
|
"aws_dummy": &Sweeper{
|
||||||
|
Name: "aws_dummy",
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
"aws_top": &Sweeper{
|
||||||
|
Name: "aws_top",
|
||||||
|
Dependencies: []string{"aws_sub"},
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
"aws_sub": &Sweeper{
|
||||||
|
Name: "aws_sub",
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedRunList: []string{"aws_top", "aws_sub"},
|
||||||
|
SweepRun: "aws_top",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "filter and none",
|
||||||
|
Sweepers: map[string]*Sweeper{
|
||||||
|
"aws_dummy": &Sweeper{
|
||||||
|
Name: "aws_dummy",
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
"aws_top": &Sweeper{
|
||||||
|
Name: "aws_top",
|
||||||
|
Dependencies: []string{"aws_sub"},
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
"aws_sub": &Sweeper{
|
||||||
|
Name: "aws_sub",
|
||||||
|
F: mockSweeperFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SweepRun: "none",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
// reset sweepers
|
||||||
|
sweeperFuncs = map[string]*Sweeper{}
|
||||||
|
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
for n, s := range tc.Sweepers {
|
||||||
|
AddTestSweepers(n, s)
|
||||||
|
}
|
||||||
|
*flagSweepRun = tc.SweepRun
|
||||||
|
|
||||||
|
TestMain(&testing.M{})
|
||||||
|
|
||||||
|
// get list of tests ran from sweeperRunList keys
|
||||||
|
var keys []string
|
||||||
|
for k, _ := range sweeperRunList {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
sort.Strings(tc.ExpectedRunList)
|
||||||
|
if !reflect.DeepEqual(keys, tc.ExpectedRunList) {
|
||||||
|
t.Fatalf("Expected keys mismatch, expected:\n%#v\ngot:\n%#v\n", tc.ExpectedRunList, keys)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockSweeperFunc(s string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue