From c1840c41bf4b937c03ad80ff79d8edc65980e364 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Mon, 5 Nov 2018 16:21:00 -0500 Subject: [PATCH 1/2] service/datasync: Initial Resources for DataSync Service FEATURES * New Resource: `aws_datasync_agent` * New Resource: `aws_datasync_location_efs` * New Resource: `aws_datasync_location_nfs` * New Resource: `aws_datasync_location_s3` * New Resource: `aws_datasync_task` --- aws/config.go | 3 + aws/datasync.go | 144 +++ aws/datasync_tags.go | 63 ++ aws/datasync_tags_test.go | 57 ++ aws/datasync_test.go | 67 ++ aws/provider.go | 5 + aws/resource_aws_datasync_agent.go | 270 +++++ aws/resource_aws_datasync_agent_test.go | 425 ++++++++ aws/resource_aws_datasync_location_efs.go | 211 ++++ ...resource_aws_datasync_location_efs_test.go | 383 ++++++++ aws/resource_aws_datasync_location_nfs.go | 204 ++++ ...resource_aws_datasync_location_nfs_test.go | 511 ++++++++++ aws/resource_aws_datasync_location_s3.go | 204 ++++ aws/resource_aws_datasync_location_s3_test.go | 338 +++++++ aws/resource_aws_datasync_task.go | 339 +++++++ aws/resource_aws_datasync_task_test.go | 926 ++++++++++++++++++ website/aws.erb | 27 + website/docs/r/datasync_agent.html.markdown | 52 + .../r/datasync_location_efs.html.markdown | 59 ++ .../r/datasync_location_nfs.html.markdown | 56 ++ .../docs/r/datasync_location_s3.html.markdown | 54 + website/docs/r/datasync_task.html.markdown | 73 ++ 22 files changed, 4471 insertions(+) create mode 100644 aws/datasync.go create mode 100644 aws/datasync_tags.go create mode 100644 aws/datasync_tags_test.go create mode 100644 aws/datasync_test.go create mode 100644 aws/resource_aws_datasync_agent.go create mode 100644 aws/resource_aws_datasync_agent_test.go create mode 100644 aws/resource_aws_datasync_location_efs.go create mode 100644 aws/resource_aws_datasync_location_efs_test.go create mode 100644 aws/resource_aws_datasync_location_nfs.go create mode 100644 aws/resource_aws_datasync_location_nfs_test.go create mode 100644 aws/resource_aws_datasync_location_s3.go create mode 100644 aws/resource_aws_datasync_location_s3_test.go create mode 100644 aws/resource_aws_datasync_task.go create mode 100644 aws/resource_aws_datasync_task_test.go create mode 100644 website/docs/r/datasync_agent.html.markdown create mode 100644 website/docs/r/datasync_location_efs.html.markdown create mode 100644 website/docs/r/datasync_location_nfs.html.markdown create mode 100644 website/docs/r/datasync_location_s3.html.markdown create mode 100644 website/docs/r/datasync_task.html.markdown diff --git a/aws/config.go b/aws/config.go index 8380a90dede..6b9d7ddecff 100644 --- a/aws/config.go +++ b/aws/config.go @@ -40,6 +40,7 @@ import ( "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" "github.com/aws/aws-sdk-go/service/configservice" "github.com/aws/aws-sdk-go/service/databasemigrationservice" + "github.com/aws/aws-sdk-go/service/datasync" "github.com/aws/aws-sdk-go/service/dax" "github.com/aws/aws-sdk-go/service/devicefarm" "github.com/aws/aws-sdk-go/service/directconnect" @@ -172,6 +173,7 @@ type AWSClient struct { cognitoconn *cognitoidentity.CognitoIdentity cognitoidpconn *cognitoidentityprovider.CognitoIdentityProvider configconn *configservice.ConfigService + datasyncconn *datasync.DataSync daxconn *dax.DAX devicefarmconn *devicefarm.DeviceFarm dlmconn *dlm.DLM @@ -519,6 +521,7 @@ func (c *Config) Client() (interface{}, error) { client.cognitoconn = cognitoidentity.New(sess) client.cognitoidpconn = cognitoidentityprovider.New(sess) client.codepipelineconn = codepipeline.New(sess) + client.datasyncconn = datasync.New(sess) client.daxconn = dax.New(awsDynamoSess) client.dlmconn = dlm.New(sess) client.dmsconn = databasemigrationservice.New(sess) diff --git a/aws/datasync.go b/aws/datasync.go new file mode 100644 index 00000000000..058fea2e3e1 --- /dev/null +++ b/aws/datasync.go @@ -0,0 +1,144 @@ +package aws + +import ( + "net/url" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSyncParseLocationURI(uri string) (string, string, string, error) { + parsedURL, err := url.ParseRequestURI(uri) + + if err != nil { + return "", "", "", err + } + + return parsedURL.Scheme, parsedURL.Host, parsedURL.Path, nil +} + +func expandDataSyncEc2Config(l []interface{}) *datasync.Ec2Config { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + ec2Config := &datasync.Ec2Config{ + SecurityGroupArns: expandStringSet(m["security_group_arns"].(*schema.Set)), + SubnetArn: aws.String(m["subnet_arn"].(string)), + } + + return ec2Config +} + +func expandDataSyncOnPremConfig(l []interface{}) *datasync.OnPremConfig { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + onPremConfig := &datasync.OnPremConfig{ + AgentArns: expandStringSet(m["agent_arns"].(*schema.Set)), + } + + return onPremConfig +} + +func expandDataSyncOptions(l []interface{}) *datasync.Options { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + options := &datasync.Options{ + Atime: aws.String(m["atime"].(string)), + Gid: aws.String(m["gid"].(string)), + Mtime: aws.String(m["mtime"].(string)), + PreserveDeletedFiles: aws.String(m["preserve_deleted_files"].(string)), + PreserveDevices: aws.String(m["preserve_devices"].(string)), + PosixPermissions: aws.String(m["posix_permissions"].(string)), + Uid: aws.String(m["uid"].(string)), + VerifyMode: aws.String(m["verify_mode"].(string)), + } + + if v, ok := m["bytes_per_second"]; ok && v.(int) > 0 { + options.BytesPerSecond = aws.Int64(int64(v.(int))) + } + + return options +} + +func expandDataSyncS3Config(l []interface{}) *datasync.S3Config { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + s3Config := &datasync.S3Config{ + BucketAccessRoleArn: aws.String(m["bucket_access_role_arn"].(string)), + } + + return s3Config +} + +func flattenDataSyncEc2Config(ec2Config *datasync.Ec2Config) []interface{} { + if ec2Config == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "security_group_arns": schema.NewSet(schema.HashString, flattenStringList(ec2Config.SecurityGroupArns)), + "subnet_arn": aws.StringValue(ec2Config.SubnetArn), + } + + return []interface{}{m} +} + +func flattenDataSyncOnPremConfig(onPremConfig *datasync.OnPremConfig) []interface{} { + if onPremConfig == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "agent_arns": schema.NewSet(schema.HashString, flattenStringList(onPremConfig.AgentArns)), + } + + return []interface{}{m} +} + +func flattenDataSyncOptions(options *datasync.Options) []interface{} { + if options == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "atime": aws.StringValue(options.Atime), + "bytes_per_second": aws.Int64Value(options.BytesPerSecond), + "gid": aws.StringValue(options.Gid), + "mtime": aws.StringValue(options.Mtime), + "posix_permissions": aws.StringValue(options.PosixPermissions), + "preserve_deleted_files": aws.StringValue(options.PreserveDeletedFiles), + "preserve_devices": aws.StringValue(options.PreserveDevices), + "uid": aws.StringValue(options.Uid), + "verify_mode": aws.StringValue(options.VerifyMode), + } + + return []interface{}{m} +} + +func flattenDataSyncS3Config(s3Config *datasync.S3Config) []interface{} { + if s3Config == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "bucket_access_role_arn": aws.StringValue(s3Config.BucketAccessRoleArn), + } + + return []interface{}{m} +} diff --git a/aws/datasync_tags.go b/aws/datasync_tags.go new file mode 100644 index 00000000000..037d29d6dcc --- /dev/null +++ b/aws/datasync_tags.go @@ -0,0 +1,63 @@ +package aws + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/datasync" +) + +// dataSyncTagsDiff takes our tags locally and the ones remotely and returns +// the set of tags that must be created, and the set of tags that must +// be destroyed. +func dataSyncTagsDiff(oldTags, newTags []*datasync.TagListEntry) ([]*datasync.TagListEntry, []*datasync.TagListEntry) { + // First, we're creating everything we have + create := make(map[string]interface{}) + for _, t := range newTags { + create[aws.StringValue(t.Key)] = aws.StringValue(t.Value) + } + + // Build the list of what to remove + var remove []*datasync.TagListEntry + for _, t := range oldTags { + old, ok := create[aws.StringValue(t.Key)] + if !ok || old != aws.StringValue(t.Value) { + // Delete it! + remove = append(remove, t) + } + } + + return expandDataSyncTagListEntry(create), remove +} + +func dataSyncTagsKeys(tags []*datasync.TagListEntry) []*string { + keys := make([]*string, 0) + + for _, tag := range tags { + if tag == nil { + continue + } + keys = append(keys, tag.Key) + } + + return keys +} + +func expandDataSyncTagListEntry(m map[string]interface{}) []*datasync.TagListEntry { + result := []*datasync.TagListEntry{} + for k, v := range m { + result = append(result, &datasync.TagListEntry{ + Key: aws.String(k), + Value: aws.String(v.(string)), + }) + } + + return result +} + +func flattenDataSyncTagListEntry(ts []*datasync.TagListEntry) map[string]string { + result := map[string]string{} + for _, t := range ts { + result[aws.StringValue(t.Key)] = aws.StringValue(t.Value) + } + + return result +} diff --git a/aws/datasync_tags_test.go b/aws/datasync_tags_test.go new file mode 100644 index 00000000000..c5a6e9d2886 --- /dev/null +++ b/aws/datasync_tags_test.go @@ -0,0 +1,57 @@ +package aws + +import ( + "reflect" + "testing" +) + +func TestDataSyncTagsDiff(t *testing.T) { + cases := []struct { + Old, New map[string]interface{} + Create, Remove map[string]string + }{ + // Basic add/remove + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "bar": "baz", + }, + Create: map[string]string{ + "bar": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + + // Modify + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "foo": "baz", + }, + Create: map[string]string{ + "foo": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + } + + for i, tc := range cases { + create, remove := dataSyncTagsDiff(expandDataSyncTagListEntry(tc.Old), expandDataSyncTagListEntry(tc.New)) + createMap := flattenDataSyncTagListEntry(create) + removeMap := flattenDataSyncTagListEntry(remove) + if !reflect.DeepEqual(createMap, tc.Create) { + t.Fatalf("%d: bad create: %#v", i, createMap) + } + if !reflect.DeepEqual(removeMap, tc.Remove) { + t.Fatalf("%d: bad remove: %#v", i, removeMap) + } + } +} diff --git a/aws/datasync_test.go b/aws/datasync_test.go new file mode 100644 index 00000000000..cdf8cfa9b49 --- /dev/null +++ b/aws/datasync_test.go @@ -0,0 +1,67 @@ +package aws + +import ( + "testing" +) + +func TestDataSyncParseLocationURI(t *testing.T) { + testCases := []struct { + LocationURI string + LocationType string + GlobalID string + Subdirectory string + }{ + { + LocationURI: "efs://us-east-2.fs-abcd1234/", + LocationType: "efs", + GlobalID: "us-east-2.fs-abcd1234", + Subdirectory: "/", + }, + { + LocationURI: "efs://us-east-2.fs-abcd1234/path", + LocationType: "efs", + GlobalID: "us-east-2.fs-abcd1234", + Subdirectory: "/path", + }, + { + LocationURI: "nfs://example.com/", + LocationType: "nfs", + GlobalID: "example.com", + Subdirectory: "/", + }, + { + LocationURI: "nfs://example.com/path", + LocationType: "nfs", + GlobalID: "example.com", + Subdirectory: "/path", + }, + { + LocationURI: "s3://myBucket/", + LocationType: "s3", + GlobalID: "myBucket", + Subdirectory: "/", + }, + { + LocationURI: "s3://myBucket/path", + LocationType: "s3", + GlobalID: "myBucket", + Subdirectory: "/path", + }, + } + + for i, tc := range testCases { + locationType, globalID, subdirectory, err := dataSyncParseLocationURI(tc.LocationURI) + if err != nil { + t.Fatalf("%d: received error parsing (%s): %s", i, tc.LocationURI, err) + } + if locationType != tc.LocationType { + t.Fatalf("%d: expected type (%s), received: %s", i, tc.LocationType, locationType) + } + if globalID != tc.GlobalID { + t.Fatalf("%d: expected global ID (%s), received: %s", i, tc.GlobalID, globalID) + } + if subdirectory != tc.Subdirectory { + t.Fatalf("%d: expected subdirectory (%s), received: %s", i, tc.Subdirectory, subdirectory) + } + } +} diff --git a/aws/provider.go b/aws/provider.go index 73ebd59291b..a9c8b502851 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -372,6 +372,11 @@ func Provider() terraform.ResourceProvider { "aws_codepipeline": resourceAwsCodePipeline(), "aws_codepipeline_webhook": resourceAwsCodePipelineWebhook(), "aws_customer_gateway": resourceAwsCustomerGateway(), + "aws_datasync_agent": resourceAwsDataSyncAgent(), + "aws_datasync_location_efs": resourceAwsDataSyncLocationEfs(), + "aws_datasync_location_nfs": resourceAwsDataSyncLocationNfs(), + "aws_datasync_location_s3": resourceAwsDataSyncLocationS3(), + "aws_datasync_task": resourceAwsDataSyncTask(), "aws_dax_cluster": resourceAwsDaxCluster(), "aws_dax_parameter_group": resourceAwsDaxParameterGroup(), "aws_dax_subnet_group": resourceAwsDaxSubnetGroup(), diff --git a/aws/resource_aws_datasync_agent.go b/aws/resource_aws_datasync_agent.go new file mode 100644 index 00000000000..d79a35da83a --- /dev/null +++ b/aws/resource_aws_datasync_agent.go @@ -0,0 +1,270 @@ +package aws + +import ( + "fmt" + "log" + "net" + "net/http" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsDataSyncAgent() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsDataSyncAgentCreate, + Read: resourceAwsDataSyncAgentRead, + Update: resourceAwsDataSyncAgentUpdate, + Delete: resourceAwsDataSyncAgentDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "activation_key": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"ip_address"}, + }, + "ip_address": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"activation_key"}, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "tags": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func resourceAwsDataSyncAgentCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + region := meta.(*AWSClient).region + + activationKey := d.Get("activation_key").(string) + agentIpAddress := d.Get("ip_address").(string) + + // Perform one time fetch of activation key from gateway IP address + if activationKey == "" { + if agentIpAddress == "" { + return fmt.Errorf("either activation_key or ip_address must be provided") + } + + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + Timeout: time.Second * 10, + } + + requestURL := fmt.Sprintf("http://%s/?gatewayType=SYNC&activationRegion=%s", agentIpAddress, region) + log.Printf("[DEBUG] Creating HTTP request: %s", requestURL) + request, err := http.NewRequest("GET", requestURL, nil) + if err != nil { + return fmt.Errorf("error creating HTTP request: %s", err) + } + + err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { + log.Printf("[DEBUG] Making HTTP request: %s", request.URL.String()) + response, err := client.Do(request) + if err != nil { + if err, ok := err.(net.Error); ok { + errMessage := fmt.Errorf("error making HTTP request: %s", err) + log.Printf("[DEBUG] retryable %s", errMessage) + return resource.RetryableError(errMessage) + } + return resource.NonRetryableError(fmt.Errorf("error making HTTP request: %s", err)) + } + + log.Printf("[DEBUG] Received HTTP response: %#v", response) + if response.StatusCode != 302 { + return resource.NonRetryableError(fmt.Errorf("expected HTTP status code 302, received: %d", response.StatusCode)) + } + + redirectURL, err := response.Location() + if err != nil { + return resource.NonRetryableError(fmt.Errorf("error extracting HTTP Location header: %s", err)) + } + + activationKey = redirectURL.Query().Get("activationKey") + + return nil + }) + if err != nil { + return fmt.Errorf("error retrieving activation key from IP Address (%s): %s", agentIpAddress, err) + } + if activationKey == "" { + return fmt.Errorf("empty activationKey received from IP Address: %s", agentIpAddress) + } + } + + input := &datasync.CreateAgentInput{ + ActivationKey: aws.String(activationKey), + Tags: expandDataSyncTagListEntry(d.Get("tags").(map[string]interface{})), + } + + if v, ok := d.GetOk("name"); ok { + input.AgentName = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Creating DataSync Agent: %s", input) + output, err := conn.CreateAgent(input) + if err != nil { + return fmt.Errorf("error creating DataSync Agent: %s", err) + } + + d.SetId(aws.StringValue(output.AgentArn)) + + // Agent activations can take a few minutes + err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { + _, err := conn.DescribeAgent(&datasync.DescribeAgentInput{ + AgentArn: aws.String(d.Id()), + }) + + if isAWSErr(err, "InvalidRequestException", "not found") { + return resource.RetryableError(err) + } + + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + if err != nil { + return fmt.Errorf("error waiting for DataSync Agent (%s) creation: %s", d.Id(), err) + } + + return resourceAwsDataSyncAgentRead(d, meta) +} + +func resourceAwsDataSyncAgentRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + input := &datasync.DescribeAgentInput{ + AgentArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Reading DataSync Agent: %s", input) + output, err := conn.DescribeAgent(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + log.Printf("[WARN] DataSync Agent %q not found - removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading DataSync Agent (%s): %s", d.Id(), err) + } + + tagsInput := &datasync.ListTagsForResourceInput{ + ResourceArn: output.AgentArn, + } + + log.Printf("[DEBUG] Reading DataSync Agent tags: %s", tagsInput) + tagsOutput, err := conn.ListTagsForResource(tagsInput) + + if err != nil { + return fmt.Errorf("error reading DataSync Agent (%s) tags: %s", d.Id(), err) + } + + d.Set("arn", output.AgentArn) + d.Set("name", output.Name) + + if err := d.Set("tags", flattenDataSyncTagListEntry(tagsOutput.Tags)); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + return nil +} + +func resourceAwsDataSyncAgentUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + if d.HasChange("name") { + input := &datasync.UpdateAgentInput{ + AgentArn: aws.String(d.Id()), + Name: aws.String(d.Get("name").(string)), + } + + log.Printf("[DEBUG] Updating DataSync Agent: %s", input) + _, err := conn.UpdateAgent(input) + if err != nil { + return fmt.Errorf("error updating DataSync Agent (%s): %s", d.Id(), err) + } + } + + if d.HasChange("tags") { + oldRaw, newRaw := d.GetChange("tags") + createTags, removeTags := dataSyncTagsDiff(expandDataSyncTagListEntry(oldRaw.(map[string]interface{})), expandDataSyncTagListEntry(newRaw.(map[string]interface{}))) + + if len(removeTags) > 0 { + input := &datasync.UntagResourceInput{ + Keys: dataSyncTagsKeys(removeTags), + ResourceArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Untagging DataSync Agent: %s", input) + if _, err := conn.UntagResource(input); err != nil { + return fmt.Errorf("error untagging DataSync Agent (%s): %s", d.Id(), err) + } + } + + if len(createTags) > 0 { + input := &datasync.TagResourceInput{ + ResourceArn: aws.String(d.Id()), + Tags: createTags, + } + + log.Printf("[DEBUG] Tagging DataSync Agent: %s", input) + if _, err := conn.TagResource(input); err != nil { + return fmt.Errorf("error tagging DataSync Agent (%s): %s", d.Id(), err) + } + } + } + + return resourceAwsDataSyncAgentRead(d, meta) +} + +func resourceAwsDataSyncAgentDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + input := &datasync.DeleteAgentInput{ + AgentArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting DataSync Agent: %s", input) + _, err := conn.DeleteAgent(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting DataSync Agent (%s): %s", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_datasync_agent_test.go b/aws/resource_aws_datasync_agent_test.go new file mode 100644 index 00000000000..65d7a4640eb --- /dev/null +++ b/aws/resource_aws_datasync_agent_test.go @@ -0,0 +1,425 @@ +package aws + +import ( + "errors" + "fmt" + "log" + "regexp" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func init() { + resource.AddTestSweepers("aws_datasync_agent", &resource.Sweeper{ + Name: "aws_datasync_agent", + F: testSweepDataSyncAgents, + }) +} + +func testSweepDataSyncAgents(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).datasyncconn + + input := &datasync.ListAgentsInput{} + for { + output, err := conn.ListAgents(input) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping DataSync Agent sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("Error retrieving DataSync Agents: %s", err) + } + + if len(output.Agents) == 0 { + log.Print("[DEBUG] No DataSync Agents to sweep") + return nil + } + + for _, agent := range output.Agents { + name := aws.StringValue(agent.Name) + if !strings.HasPrefix(name, "tf-acc-test-") { + log.Printf("[INFO] Skipping DataSync Agent: %s", name) + continue + } + log.Printf("[INFO] Deleting DataSync Agent: %s", name) + input := &datasync.DeleteAgentInput{ + AgentArn: agent.AgentArn, + } + + _, err := conn.DeleteAgent(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + continue + } + + if err != nil { + log.Printf("[ERROR] Failed to delete DataSync Agent (%s): %s", name, err) + } + } + + if aws.StringValue(output.NextToken) == "" { + break + } + + input.NextToken = output.NextToken + } + + return nil +} + +func TestAccAWSDataSyncAgent_basic(t *testing.T) { + var agent1 datasync.DescribeAgentOutput + resourceName := "aws_datasync_agent.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncAgentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncAgentConfig(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncAgentExists(resourceName, &agent1), + resource.TestCheckResourceAttr(resourceName, "name", ""), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "datasync", regexp.MustCompile(`agent/agent-.+`)), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"activation_key", "ip_address"}, + }, + }, + }) +} + +func TestAccAWSDataSyncAgent_disappears(t *testing.T) { + var agent1 datasync.DescribeAgentOutput + resourceName := "aws_datasync_agent.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncAgentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncAgentConfig(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncAgentExists(resourceName, &agent1), + testAccCheckAWSDataSyncAgentDisappears(&agent1), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSDataSyncAgent_AgentName(t *testing.T) { + var agent1, agent2 datasync.DescribeAgentOutput + rName1 := acctest.RandomWithPrefix("tf-acc-test") + rName2 := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_agent.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncAgentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncAgentConfigName(rName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncAgentExists(resourceName, &agent1), + resource.TestCheckResourceAttr(resourceName, "name", rName1), + ), + }, + { + Config: testAccAWSDataSyncAgentConfigName(rName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncAgentExists(resourceName, &agent2), + resource.TestCheckResourceAttr(resourceName, "name", rName2), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"activation_key", "ip_address"}, + }, + }, + }) +} + +func TestAccAWSDataSyncAgent_Tags(t *testing.T) { + var agent1, agent2, agent3 datasync.DescribeAgentOutput + resourceName := "aws_datasync_agent.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncAgentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncAgentConfigTags1("key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncAgentExists(resourceName, &agent1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"activation_key", "ip_address"}, + }, + { + Config: testAccAWSDataSyncAgentConfigTags2("key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncAgentExists(resourceName, &agent2), + testAccCheckAWSDataSyncAgentNotRecreated(&agent1, &agent2), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSDataSyncAgentConfigTags1("key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncAgentExists(resourceName, &agent3), + testAccCheckAWSDataSyncAgentNotRecreated(&agent2, &agent3), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + }, + }) +} + +func testAccCheckAWSDataSyncAgentDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_datasync_agent" { + continue + } + + input := &datasync.DescribeAgentInput{ + AgentArn: aws.String(rs.Primary.ID), + } + + _, err := conn.DescribeAgent(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + return nil + } + + if err != nil { + return err + } + } + + return nil +} + +func testAccCheckAWSDataSyncAgentExists(resourceName string, agent *datasync.DescribeAgentOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + input := &datasync.DescribeAgentInput{ + AgentArn: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeAgent(input) + + if err != nil { + return err + } + + if output == nil { + return fmt.Errorf("Agent %q does not exist", rs.Primary.ID) + } + + *agent = *output + + return nil + } +} + +func testAccCheckAWSDataSyncAgentDisappears(agent *datasync.DescribeAgentOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + + input := &datasync.DeleteAgentInput{ + AgentArn: agent.AgentArn, + } + + _, err := conn.DeleteAgent(input) + + return err + } +} + +func testAccCheckAWSDataSyncAgentNotRecreated(i, j *datasync.DescribeAgentOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.TimeValue(i.CreationTime) != aws.TimeValue(j.CreationTime) { + return errors.New("DataSync Agent was recreated") + } + + return nil + } +} + +// testAccAWSDataSyncAgentConfigAgentBase uses the "thinstaller" AMI +func testAccAWSDataSyncAgentConfigAgentBase() string { + return fmt.Sprintf(` +data "aws_ami" "aws-thinstaller" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["aws-thinstaller-*"] + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags { + Name = "tf-acc-test-datasync-agent" + } +} + +resource "aws_subnet" "test" { + cidr_block = "10.0.0.0/24" + vpc_id = "${aws_vpc.test.id}" + + tags { + Name = "tf-acc-test-datasync-agent" + } +} + +resource "aws_internet_gateway" "test" { + vpc_id = "${aws_vpc.test.id}" + + tags { + Name = "tf-acc-test-datasync-agent" + } +} + +resource "aws_route_table" "test" { + vpc_id = "${aws_vpc.test.id}" + + route { + cidr_block = "0.0.0.0/0" + gateway_id = "${aws_internet_gateway.test.id}" + } + + tags { + Name = "tf-acc-test-datasync-agent" + } +} + +resource "aws_route_table_association" "test" { + subnet_id = "${aws_subnet.test.id}" + route_table_id = "${aws_route_table.test.id}" +} + +resource "aws_security_group" "test" { + vpc_id = "${aws_vpc.test.id}" + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags { + Name = "tf-acc-test-datasync-agent" + } +} + +resource "aws_instance" "test" { + depends_on = ["aws_internet_gateway.test"] + + ami = "${data.aws_ami.aws-thinstaller.id}" + associate_public_ip_address = true + # Default instance type from sync.sh + instance_type = "c5.2xlarge" + vpc_security_group_ids = ["${aws_security_group.test.id}"] + subnet_id = "${aws_subnet.test.id}" + + tags { + Name = "tf-acc-test-datasync-agent" + } +} +`) +} + +func testAccAWSDataSyncAgentConfig() string { + return testAccAWSDataSyncAgentConfigAgentBase() + fmt.Sprintf(` +resource "aws_datasync_agent" "test" { + ip_address = "${aws_instance.test.public_ip}" +} +`) +} + +func testAccAWSDataSyncAgentConfigName(rName string) string { + return testAccAWSDataSyncAgentConfigAgentBase() + fmt.Sprintf(` +resource "aws_datasync_agent" "test" { + ip_address = "${aws_instance.test.public_ip}" + name = %q +} +`, rName) +} + +func testAccAWSDataSyncAgentConfigTags1(key1, value1 string) string { + return testAccAWSDataSyncAgentConfigAgentBase() + fmt.Sprintf(` +resource "aws_datasync_agent" "test" { + ip_address = "${aws_instance.test.public_ip}" + + tags { + %q = %q + } +} +`, key1, value1) +} + +func testAccAWSDataSyncAgentConfigTags2(key1, value1, key2, value2 string) string { + return testAccAWSDataSyncAgentConfigAgentBase() + fmt.Sprintf(` +resource "aws_datasync_agent" "test" { + ip_address = "${aws_instance.test.public_ip}" + + tags { + %q = %q + %q = %q + } +} +`, key1, value1, key2, value2) +} diff --git a/aws/resource_aws_datasync_location_efs.go b/aws/resource_aws_datasync_location_efs.go new file mode 100644 index 00000000000..8c87d73450a --- /dev/null +++ b/aws/resource_aws_datasync_location_efs.go @@ -0,0 +1,211 @@ +package aws + +import ( + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceAwsDataSyncLocationEfs() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsDataSyncLocationEfsCreate, + Read: resourceAwsDataSyncLocationEfsRead, + Update: resourceAwsDataSyncLocationEfsUpdate, + Delete: resourceAwsDataSyncLocationEfsDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "ec2_config": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "security_group_arns": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "subnet_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + "efs_file_system_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + "subdirectory": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "/", + // Ignore missing trailing slash + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if new == "/" { + return false + } + if strings.TrimSuffix(old, "/") == strings.TrimSuffix(new, "/") { + return true + } + return false + }, + }, + "tags": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "uri": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsDataSyncLocationEfsCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + input := &datasync.CreateLocationEfsInput{ + Ec2Config: expandDataSyncEc2Config(d.Get("ec2_config").([]interface{})), + EfsFilesystemArn: aws.String(d.Get("efs_file_system_arn").(string)), + Subdirectory: aws.String(d.Get("subdirectory").(string)), + Tags: expandDataSyncTagListEntry(d.Get("tags").(map[string]interface{})), + } + + log.Printf("[DEBUG] Creating DataSync Location EFS: %s", input) + output, err := conn.CreateLocationEfs(input) + if err != nil { + return fmt.Errorf("error creating DataSync Location EFS: %s", err) + } + + d.SetId(aws.StringValue(output.LocationArn)) + + return resourceAwsDataSyncLocationEfsRead(d, meta) +} + +func resourceAwsDataSyncLocationEfsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + input := &datasync.DescribeLocationEfsInput{ + LocationArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Reading DataSync Location EFS: %s", input) + output, err := conn.DescribeLocationEfs(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + log.Printf("[WARN] DataSync Location EFS %q not found - removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading DataSync Location EFS (%s): %s", d.Id(), err) + } + + tagsInput := &datasync.ListTagsForResourceInput{ + ResourceArn: output.LocationArn, + } + + log.Printf("[DEBUG] Reading DataSync Location EFS tags: %s", tagsInput) + tagsOutput, err := conn.ListTagsForResource(tagsInput) + + if err != nil { + return fmt.Errorf("error reading DataSync Location EFS (%s) tags: %s", d.Id(), err) + } + + _, _, subdirectory, err := dataSyncParseLocationURI(aws.StringValue(output.LocationUri)) + + d.Set("arn", output.LocationArn) + + if err := d.Set("ec2_config", flattenDataSyncEc2Config(output.Ec2Config)); err != nil { + return fmt.Errorf("error setting ec2_config: %s", err) + } + + d.Set("subdirectory", subdirectory) + + if err := d.Set("tags", flattenDataSyncTagListEntry(tagsOutput.Tags)); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + d.Set("uri", output.LocationUri) + + return nil +} + +func resourceAwsDataSyncLocationEfsUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + if d.HasChange("tags") { + oldRaw, newRaw := d.GetChange("tags") + createTags, removeTags := dataSyncTagsDiff(expandDataSyncTagListEntry(oldRaw.(map[string]interface{})), expandDataSyncTagListEntry(newRaw.(map[string]interface{}))) + + if len(removeTags) > 0 { + input := &datasync.UntagResourceInput{ + Keys: dataSyncTagsKeys(removeTags), + ResourceArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Untagging DataSync Location EFS: %s", input) + if _, err := conn.UntagResource(input); err != nil { + return fmt.Errorf("error untagging DataSync Location EFS (%s): %s", d.Id(), err) + } + } + + if len(createTags) > 0 { + input := &datasync.TagResourceInput{ + ResourceArn: aws.String(d.Id()), + Tags: createTags, + } + + log.Printf("[DEBUG] Tagging DataSync Location EFS: %s", input) + if _, err := conn.TagResource(input); err != nil { + return fmt.Errorf("error tagging DataSync Location EFS (%s): %s", d.Id(), err) + } + } + } + + return resourceAwsDataSyncLocationEfsRead(d, meta) +} + +func resourceAwsDataSyncLocationEfsDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + input := &datasync.DeleteLocationInput{ + LocationArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting DataSync Location EFS: %s", input) + _, err := conn.DeleteLocation(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting DataSync Location EFS (%s): %s", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_datasync_location_efs_test.go b/aws/resource_aws_datasync_location_efs_test.go new file mode 100644 index 00000000000..4b1ca63883c --- /dev/null +++ b/aws/resource_aws_datasync_location_efs_test.go @@ -0,0 +1,383 @@ +package aws + +import ( + "errors" + "fmt" + "log" + "regexp" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func init() { + resource.AddTestSweepers("aws_datasync_location_efs", &resource.Sweeper{ + Name: "aws_datasync_location_efs", + F: testSweepDataSyncLocationEfss, + }) +} + +func testSweepDataSyncLocationEfss(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).datasyncconn + + input := &datasync.ListLocationsInput{} + for { + output, err := conn.ListLocations(input) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping DataSync Location EFS sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("Error retrieving DataSync Location EFSs: %s", err) + } + + if len(output.Locations) == 0 { + log.Print("[DEBUG] No DataSync Location EFSs to sweep") + return nil + } + + for _, location := range output.Locations { + uri := aws.StringValue(location.LocationUri) + if !strings.HasPrefix(uri, "efs://") { + log.Printf("[INFO] Skipping DataSync Location EFS: %s", uri) + continue + } + log.Printf("[INFO] Deleting DataSync Location EFS: %s", uri) + input := &datasync.DeleteLocationInput{ + LocationArn: location.LocationArn, + } + + _, err := conn.DeleteLocation(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + continue + } + + if err != nil { + log.Printf("[ERROR] Failed to delete DataSync Location EFS (%s): %s", uri, err) + } + } + + if aws.StringValue(output.NextToken) == "" { + break + } + + input.NextToken = output.NextToken + } + + return nil +} + +func TestAccAWSDataSyncLocationEfs_basic(t *testing.T) { + var locationEfs1 datasync.DescribeLocationEfsOutput + efsFileSystemResourceName := "aws_efs_file_system.test" + resourceName := "aws_datasync_location_efs.test" + subnetResourceName := "aws_subnet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationEfsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationEfsConfig(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationEfsExists(resourceName, &locationEfs1), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "datasync", regexp.MustCompile(`location/loc-.+`)), + resource.TestCheckResourceAttr(resourceName, "ec2_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ec2_config.0.security_group_arns.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "ec2_config.0.subnet_arn", subnetResourceName, "arn"), + resource.TestCheckResourceAttrPair(resourceName, "efs_file_system_arn", efsFileSystemResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "subdirectory", "/"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestMatchResourceAttr(resourceName, "uri", regexp.MustCompile(`^efs://.+/`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"efs_file_system_arn"}, + }, + }, + }) +} + +func TestAccAWSDataSyncLocationEfs_disappears(t *testing.T) { + var locationEfs1 datasync.DescribeLocationEfsOutput + resourceName := "aws_datasync_location_efs.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationEfsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationEfsConfig(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationEfsExists(resourceName, &locationEfs1), + testAccCheckAWSDataSyncLocationEfsDisappears(&locationEfs1), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSDataSyncLocationEfs_Subdirectory(t *testing.T) { + var locationEfs1 datasync.DescribeLocationEfsOutput + resourceName := "aws_datasync_location_efs.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationEfsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationEfsConfigSubdirectory("/subdirectory1/"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationEfsExists(resourceName, &locationEfs1), + resource.TestCheckResourceAttr(resourceName, "subdirectory", "/subdirectory1/"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"efs_file_system_arn"}, + }, + }, + }) +} + +func TestAccAWSDataSyncLocationEfs_Tags(t *testing.T) { + var locationEfs1, locationEfs2, locationEfs3 datasync.DescribeLocationEfsOutput + resourceName := "aws_datasync_location_efs.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationEfsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationEfsConfigTags1("key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationEfsExists(resourceName, &locationEfs1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"efs_file_system_arn"}, + }, + { + Config: testAccAWSDataSyncLocationEfsConfigTags2("key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationEfsExists(resourceName, &locationEfs2), + testAccCheckAWSDataSyncLocationEfsNotRecreated(&locationEfs1, &locationEfs2), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSDataSyncLocationEfsConfigTags1("key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationEfsExists(resourceName, &locationEfs3), + testAccCheckAWSDataSyncLocationEfsNotRecreated(&locationEfs2, &locationEfs3), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + }, + }) +} + +func testAccCheckAWSDataSyncLocationEfsDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_datasync_location_efs" { + continue + } + + input := &datasync.DescribeLocationEfsInput{ + LocationArn: aws.String(rs.Primary.ID), + } + + _, err := conn.DescribeLocationEfs(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + return nil + } + + if err != nil { + return err + } + } + + return nil +} + +func testAccCheckAWSDataSyncLocationEfsExists(resourceName string, locationEfs *datasync.DescribeLocationEfsOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + input := &datasync.DescribeLocationEfsInput{ + LocationArn: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeLocationEfs(input) + + if err != nil { + return err + } + + if output == nil { + return fmt.Errorf("Location %q does not exist", rs.Primary.ID) + } + + *locationEfs = *output + + return nil + } +} + +func testAccCheckAWSDataSyncLocationEfsDisappears(location *datasync.DescribeLocationEfsOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + + input := &datasync.DeleteLocationInput{ + LocationArn: location.LocationArn, + } + + _, err := conn.DeleteLocation(input) + + return err + } +} + +func testAccCheckAWSDataSyncLocationEfsNotRecreated(i, j *datasync.DescribeLocationEfsOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.TimeValue(i.CreationTime) != aws.TimeValue(j.CreationTime) { + return errors.New("DataSync Location EFS was recreated") + } + + return nil + } +} + +func testAccAWSDataSyncLocationEfsConfigBase() string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags { + Name = "tf-acc-test-datasync-location-efs" + } +} + +resource "aws_subnet" "test" { + cidr_block = "10.0.0.0/24" + vpc_id = "${aws_vpc.test.id}" + + tags { + Name = "tf-acc-test-datasync-location-efs" + } +} + +resource "aws_security_group" "test" { + vpc_id = "${aws_vpc.test.id}" + + tags { + Name = "tf-acc-test-datasync-location-efs" + } +} + +resource "aws_efs_file_system" "test" {} + +resource "aws_efs_mount_target" "test" { + file_system_id = "${aws_efs_file_system.test.id}" + subnet_id = "${aws_subnet.test.id}" +} +`) +} + +func testAccAWSDataSyncLocationEfsConfig() string { + return testAccAWSDataSyncLocationEfsConfigBase() + fmt.Sprintf(` +resource "aws_datasync_location_efs" "test" { + efs_file_system_arn = "${aws_efs_mount_target.test.file_system_arn}" + + ec2_config { + security_group_arns = ["${aws_security_group.test.arn}"] + subnet_arn = "${aws_subnet.test.arn}" + } +} +`) +} + +func testAccAWSDataSyncLocationEfsConfigSubdirectory(subdirectory string) string { + return testAccAWSDataSyncLocationEfsConfigBase() + fmt.Sprintf(` +resource "aws_datasync_location_efs" "test" { + efs_file_system_arn = "${aws_efs_mount_target.test.file_system_arn}" + subdirectory = %q + + ec2_config { + security_group_arns = ["${aws_security_group.test.arn}"] + subnet_arn = "${aws_subnet.test.arn}" + } +} +`, subdirectory) +} + +func testAccAWSDataSyncLocationEfsConfigTags1(key1, value1 string) string { + return testAccAWSDataSyncLocationEfsConfigBase() + fmt.Sprintf(` +resource "aws_datasync_location_efs" "test" { + efs_file_system_arn = "${aws_efs_mount_target.test.file_system_arn}" + + ec2_config { + security_group_arns = ["${aws_security_group.test.arn}"] + subnet_arn = "${aws_subnet.test.arn}" + } + + tags { + %q = %q + } +} +`, key1, value1) +} + +func testAccAWSDataSyncLocationEfsConfigTags2(key1, value1, key2, value2 string) string { + return testAccAWSDataSyncLocationEfsConfigBase() + fmt.Sprintf(` +resource "aws_datasync_location_efs" "test" { + efs_file_system_arn = "${aws_efs_mount_target.test.file_system_arn}" + + ec2_config { + security_group_arns = ["${aws_security_group.test.arn}"] + subnet_arn = "${aws_subnet.test.arn}" + } + + tags { + %q = %q + %q = %q + } +} +`, key1, value1, key2, value2) +} diff --git a/aws/resource_aws_datasync_location_nfs.go b/aws/resource_aws_datasync_location_nfs.go new file mode 100644 index 00000000000..8eeb42b34c9 --- /dev/null +++ b/aws/resource_aws_datasync_location_nfs.go @@ -0,0 +1,204 @@ +package aws + +import ( + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceAwsDataSyncLocationNfs() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsDataSyncLocationNfsCreate, + Read: resourceAwsDataSyncLocationNfsRead, + Update: resourceAwsDataSyncLocationNfsUpdate, + Delete: resourceAwsDataSyncLocationNfsDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "on_prem_config": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "agent_arns": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "server_hostname": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + "subdirectory": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + // Ignore missing trailing slash + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if new == "/" { + return false + } + if strings.TrimSuffix(old, "/") == strings.TrimSuffix(new, "/") { + return true + } + return false + }, + }, + "tags": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "uri": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsDataSyncLocationNfsCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + input := &datasync.CreateLocationNfsInput{ + OnPremConfig: expandDataSyncOnPremConfig(d.Get("on_prem_config").([]interface{})), + ServerHostname: aws.String(d.Get("server_hostname").(string)), + Subdirectory: aws.String(d.Get("subdirectory").(string)), + Tags: expandDataSyncTagListEntry(d.Get("tags").(map[string]interface{})), + } + + log.Printf("[DEBUG] Creating DataSync Location NFS: %s", input) + output, err := conn.CreateLocationNfs(input) + if err != nil { + return fmt.Errorf("error creating DataSync Location NFS: %s", err) + } + + d.SetId(aws.StringValue(output.LocationArn)) + + return resourceAwsDataSyncLocationNfsRead(d, meta) +} + +func resourceAwsDataSyncLocationNfsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + input := &datasync.DescribeLocationNfsInput{ + LocationArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Reading DataSync Location NFS: %s", input) + output, err := conn.DescribeLocationNfs(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + log.Printf("[WARN] DataSync Location NFS %q not found - removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading DataSync Location NFS (%s): %s", d.Id(), err) + } + + tagsInput := &datasync.ListTagsForResourceInput{ + ResourceArn: output.LocationArn, + } + + log.Printf("[DEBUG] Reading DataSync Location NFS tags: %s", tagsInput) + tagsOutput, err := conn.ListTagsForResource(tagsInput) + + if err != nil { + return fmt.Errorf("error reading DataSync Location NFS (%s) tags: %s", d.Id(), err) + } + + _, _, subdirectory, err := dataSyncParseLocationURI(aws.StringValue(output.LocationUri)) + + d.Set("arn", output.LocationArn) + + if err := d.Set("on_prem_config", flattenDataSyncOnPremConfig(output.OnPremConfig)); err != nil { + return fmt.Errorf("error setting on_prem_config: %s", err) + } + + d.Set("subdirectory", subdirectory) + + if err := d.Set("tags", flattenDataSyncTagListEntry(tagsOutput.Tags)); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + d.Set("uri", output.LocationUri) + + return nil +} + +func resourceAwsDataSyncLocationNfsUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + if d.HasChange("tags") { + oldRaw, newRaw := d.GetChange("tags") + createTags, removeTags := dataSyncTagsDiff(expandDataSyncTagListEntry(oldRaw.(map[string]interface{})), expandDataSyncTagListEntry(newRaw.(map[string]interface{}))) + + if len(removeTags) > 0 { + input := &datasync.UntagResourceInput{ + Keys: dataSyncTagsKeys(removeTags), + ResourceArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Untagging DataSync Location NFS: %s", input) + if _, err := conn.UntagResource(input); err != nil { + return fmt.Errorf("error untagging DataSync Location NFS (%s): %s", d.Id(), err) + } + } + + if len(createTags) > 0 { + input := &datasync.TagResourceInput{ + ResourceArn: aws.String(d.Id()), + Tags: createTags, + } + + log.Printf("[DEBUG] Tagging DataSync Location NFS: %s", input) + if _, err := conn.TagResource(input); err != nil { + return fmt.Errorf("error tagging DataSync Location NFS (%s): %s", d.Id(), err) + } + } + } + + return resourceAwsDataSyncLocationNfsRead(d, meta) +} + +func resourceAwsDataSyncLocationNfsDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + input := &datasync.DeleteLocationInput{ + LocationArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting DataSync Location NFS: %s", input) + _, err := conn.DeleteLocation(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting DataSync Location NFS (%s): %s", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_datasync_location_nfs_test.go b/aws/resource_aws_datasync_location_nfs_test.go new file mode 100644 index 00000000000..d89da8b8691 --- /dev/null +++ b/aws/resource_aws_datasync_location_nfs_test.go @@ -0,0 +1,511 @@ +package aws + +import ( + "errors" + "fmt" + "log" + "regexp" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func init() { + resource.AddTestSweepers("aws_datasync_location_nfs", &resource.Sweeper{ + Name: "aws_datasync_location_nfs", + F: testSweepDataSyncLocationNfss, + }) +} + +func testSweepDataSyncLocationNfss(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).datasyncconn + + input := &datasync.ListLocationsInput{} + for { + output, err := conn.ListLocations(input) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping DataSync Location EFS sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("Error retrieving DataSync Location EFSs: %s", err) + } + + if len(output.Locations) == 0 { + log.Print("[DEBUG] No DataSync Location EFSs to sweep") + return nil + } + + for _, location := range output.Locations { + uri := aws.StringValue(location.LocationUri) + if !strings.HasPrefix(uri, "nfs://") { + log.Printf("[INFO] Skipping DataSync Location EFS: %s", uri) + continue + } + log.Printf("[INFO] Deleting DataSync Location EFS: %s", uri) + input := &datasync.DeleteLocationInput{ + LocationArn: location.LocationArn, + } + + _, err := conn.DeleteLocation(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + continue + } + + if err != nil { + log.Printf("[ERROR] Failed to delete DataSync Location EFS (%s): %s", uri, err) + } + } + + if aws.StringValue(output.NextToken) == "" { + break + } + + input.NextToken = output.NextToken + } + + return nil +} + +func TestAccAWSDataSyncLocationNfs_basic(t *testing.T) { + var locationNfs1 datasync.DescribeLocationNfsOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_location_nfs.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationNfsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationNfsConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationNfsExists(resourceName, &locationNfs1), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "datasync", regexp.MustCompile(`location/loc-.+`)), + resource.TestCheckResourceAttr(resourceName, "on_prem_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_prem_config.0.agent_arns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "server_hostname", "example.com"), + resource.TestCheckResourceAttr(resourceName, "subdirectory", "/"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestMatchResourceAttr(resourceName, "uri", regexp.MustCompile(`^nfs://.+/`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"server_hostname"}, + }, + }, + }) +} + +func TestAccAWSDataSyncLocationNfs_disappears(t *testing.T) { + var locationNfs1 datasync.DescribeLocationNfsOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_location_nfs.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationNfsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationNfsConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationNfsExists(resourceName, &locationNfs1), + testAccCheckAWSDataSyncLocationNfsDisappears(&locationNfs1), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSDataSyncLocationNfs_AgentARNs_Multple(t *testing.T) { + var locationNfs1 datasync.DescribeLocationNfsOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_location_nfs.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationNfsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationNfsConfigAgentArnsMultiple(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationNfsExists(resourceName, &locationNfs1), + resource.TestCheckResourceAttr(resourceName, "on_prem_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_prem_config.0.agent_arns.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"server_hostname"}, + }, + }, + }) +} + +func TestAccAWSDataSyncLocationNfs_Subdirectory(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + var locationNfs1 datasync.DescribeLocationNfsOutput + resourceName := "aws_datasync_location_nfs.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationNfsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationNfsConfigSubdirectory(rName, "/subdirectory1/"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationNfsExists(resourceName, &locationNfs1), + resource.TestCheckResourceAttr(resourceName, "subdirectory", "/subdirectory1/"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"server_hostname"}, + }, + }, + }) +} + +func TestAccAWSDataSyncLocationNfs_Tags(t *testing.T) { + var locationNfs1, locationNfs2, locationNfs3 datasync.DescribeLocationNfsOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_location_nfs.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationNfsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationNfsConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationNfsExists(resourceName, &locationNfs1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"server_hostname"}, + }, + { + Config: testAccAWSDataSyncLocationNfsConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationNfsExists(resourceName, &locationNfs2), + testAccCheckAWSDataSyncLocationNfsNotRecreated(&locationNfs1, &locationNfs2), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSDataSyncLocationNfsConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationNfsExists(resourceName, &locationNfs3), + testAccCheckAWSDataSyncLocationNfsNotRecreated(&locationNfs2, &locationNfs3), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + }, + }) +} + +func testAccCheckAWSDataSyncLocationNfsDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_datasync_location_nfs" { + continue + } + + input := &datasync.DescribeLocationNfsInput{ + LocationArn: aws.String(rs.Primary.ID), + } + + _, err := conn.DescribeLocationNfs(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + return nil + } + + if err != nil { + return err + } + } + + return nil +} + +func testAccCheckAWSDataSyncLocationNfsExists(resourceName string, locationNfs *datasync.DescribeLocationNfsOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + input := &datasync.DescribeLocationNfsInput{ + LocationArn: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeLocationNfs(input) + + if err != nil { + return err + } + + if output == nil { + return fmt.Errorf("Location %q does not exist", rs.Primary.ID) + } + + *locationNfs = *output + + return nil + } +} + +func testAccCheckAWSDataSyncLocationNfsDisappears(location *datasync.DescribeLocationNfsOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + + input := &datasync.DeleteLocationInput{ + LocationArn: location.LocationArn, + } + + _, err := conn.DeleteLocation(input) + + return err + } +} + +func testAccCheckAWSDataSyncLocationNfsNotRecreated(i, j *datasync.DescribeLocationNfsOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.TimeValue(i.CreationTime) != aws.TimeValue(j.CreationTime) { + return errors.New("DataSync Location EFS was recreated") + } + + return nil + } +} + +func testAccAWSDataSyncLocationNfsConfigBase(rName string) string { + return fmt.Sprintf(` +data "aws_ami" "aws-thinstaller" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["aws-thinstaller-*"] + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags { + Name = "tf-acc-test-datasync-location-nfs" + } +} + +resource "aws_subnet" "test" { + cidr_block = "10.0.0.0/24" + vpc_id = "${aws_vpc.test.id}" + + tags { + Name = "tf-acc-test-datasync-location-nfs" + } +} + +resource "aws_internet_gateway" "test" { + vpc_id = "${aws_vpc.test.id}" + + tags { + Name = "tf-acc-test-datasync-location-nfs" + } +} + +resource "aws_route_table" "test" { + vpc_id = "${aws_vpc.test.id}" + + route { + cidr_block = "0.0.0.0/0" + gateway_id = "${aws_internet_gateway.test.id}" + } + + tags { + Name = "tf-acc-test-datasync-location-nfs" + } +} + +resource "aws_route_table_association" "test" { + subnet_id = "${aws_subnet.test.id}" + route_table_id = "${aws_route_table.test.id}" +} + +resource "aws_security_group" "test" { + vpc_id = "${aws_vpc.test.id}" + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags { + Name = "tf-acc-test-datasync-location-nfs" + } +} + +resource "aws_instance" "test" { + depends_on = ["aws_internet_gateway.test"] + + ami = "${data.aws_ami.aws-thinstaller.id}" + associate_public_ip_address = true + # Default instance type from sync.sh + instance_type = "c5.2xlarge" + vpc_security_group_ids = ["${aws_security_group.test.id}"] + subnet_id = "${aws_subnet.test.id}" + + tags { + Name = "tf-acc-test-datasync-location-nfs" + } +} + +resource "aws_datasync_agent" "test" { + ip_address = "${aws_instance.test.public_ip}" + name = %q +} +`, rName) +} + +func testAccAWSDataSyncLocationNfsConfig(rName string) string { + return testAccAWSDataSyncLocationNfsConfigBase(rName) + fmt.Sprintf(` +resource "aws_datasync_location_nfs" "test" { + server_hostname = "example.com" + subdirectory = "/" + + on_prem_config { + agent_arns = ["${aws_datasync_agent.test.arn}"] + } +} +`) +} + +func testAccAWSDataSyncLocationNfsConfigAgentArnsMultiple(rName string) string { + return testAccAWSDataSyncLocationNfsConfigBase(rName) + fmt.Sprintf(` +resource "aws_instance" "test2" { + depends_on = ["aws_internet_gateway.test"] + + ami = "${data.aws_ami.aws-thinstaller.id}" + associate_public_ip_address = true + # Default instance type from sync.sh + instance_type = "c5.2xlarge" + vpc_security_group_ids = ["${aws_security_group.test.id}"] + subnet_id = "${aws_subnet.test.id}" + + tags { + Name = "tf-acc-test-datasync-location-nfs" + } +} + +resource "aws_datasync_agent" "test2" { + ip_address = "${aws_instance.test2.public_ip}" + name = "%s2" +} + +resource "aws_datasync_location_nfs" "test" { + server_hostname = "example.com" + subdirectory = "/" + + on_prem_config { + agent_arns = [ + "${aws_datasync_agent.test.arn}", + "${aws_datasync_agent.test2.arn}", + ] + } +} +`, rName) +} + +func testAccAWSDataSyncLocationNfsConfigSubdirectory(rName, subdirectory string) string { + return testAccAWSDataSyncLocationNfsConfigBase(rName) + fmt.Sprintf(` +resource "aws_datasync_location_nfs" "test" { + server_hostname = "example.com" + subdirectory = %q + + on_prem_config { + agent_arns = ["${aws_datasync_agent.test.arn}"] + } +} +`, subdirectory) +} + +func testAccAWSDataSyncLocationNfsConfigTags1(rName, key1, value1 string) string { + return testAccAWSDataSyncLocationNfsConfigBase(rName) + fmt.Sprintf(` +resource "aws_datasync_location_nfs" "test" { + server_hostname = "example.com" + subdirectory = "/" + + on_prem_config { + agent_arns = ["${aws_datasync_agent.test.arn}"] + } + + tags { + %q = %q + } +} +`, key1, value1) +} + +func testAccAWSDataSyncLocationNfsConfigTags2(rName, key1, value1, key2, value2 string) string { + return testAccAWSDataSyncLocationNfsConfigBase(rName) + fmt.Sprintf(` +resource "aws_datasync_location_nfs" "test" { + server_hostname = "example.com" + subdirectory = "/" + + on_prem_config { + agent_arns = ["${aws_datasync_agent.test.arn}"] + } + + tags { + %q = %q + %q = %q + } +} +`, key1, value1, key2, value2) +} diff --git a/aws/resource_aws_datasync_location_s3.go b/aws/resource_aws_datasync_location_s3.go new file mode 100644 index 00000000000..a4a3bc293b3 --- /dev/null +++ b/aws/resource_aws_datasync_location_s3.go @@ -0,0 +1,204 @@ +package aws + +import ( + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceAwsDataSyncLocationS3() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsDataSyncLocationS3Create, + Read: resourceAwsDataSyncLocationS3Read, + Update: resourceAwsDataSyncLocationS3Update, + Delete: resourceAwsDataSyncLocationS3Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "s3_bucket_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + "s3_config": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bucket_access_role_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + "subdirectory": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + // Ignore missing trailing slash + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if new == "/" { + return false + } + if strings.TrimSuffix(old, "/") == strings.TrimSuffix(new, "/") { + return true + } + return false + }, + }, + "tags": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "uri": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsDataSyncLocationS3Create(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + input := &datasync.CreateLocationS3Input{ + S3BucketArn: aws.String(d.Get("s3_bucket_arn").(string)), + S3Config: expandDataSyncS3Config(d.Get("s3_config").([]interface{})), + Subdirectory: aws.String(d.Get("subdirectory").(string)), + Tags: expandDataSyncTagListEntry(d.Get("tags").(map[string]interface{})), + } + + log.Printf("[DEBUG] Creating DataSync Location S3: %s", input) + output, err := conn.CreateLocationS3(input) + if err != nil { + return fmt.Errorf("error creating DataSync Location S3: %s", err) + } + + d.SetId(aws.StringValue(output.LocationArn)) + + return resourceAwsDataSyncLocationS3Read(d, meta) +} + +func resourceAwsDataSyncLocationS3Read(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + input := &datasync.DescribeLocationS3Input{ + LocationArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Reading DataSync Location S3: %s", input) + output, err := conn.DescribeLocationS3(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + log.Printf("[WARN] DataSync Location S3 %q not found - removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading DataSync Location S3 (%s): %s", d.Id(), err) + } + + tagsInput := &datasync.ListTagsForResourceInput{ + ResourceArn: output.LocationArn, + } + + log.Printf("[DEBUG] Reading DataSync Location S3 tags: %s", tagsInput) + tagsOutput, err := conn.ListTagsForResource(tagsInput) + + if err != nil { + return fmt.Errorf("error reading DataSync Location S3 (%s) tags: %s", d.Id(), err) + } + + _, _, subdirectory, err := dataSyncParseLocationURI(aws.StringValue(output.LocationUri)) + + d.Set("arn", output.LocationArn) + + if err := d.Set("s3_config", flattenDataSyncS3Config(output.S3Config)); err != nil { + return fmt.Errorf("error setting s3_config: %s", err) + } + + d.Set("subdirectory", subdirectory) + + if err := d.Set("tags", flattenDataSyncTagListEntry(tagsOutput.Tags)); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + d.Set("uri", output.LocationUri) + + return nil +} + +func resourceAwsDataSyncLocationS3Update(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + if d.HasChange("tags") { + oldRaw, newRaw := d.GetChange("tags") + createTags, removeTags := dataSyncTagsDiff(expandDataSyncTagListEntry(oldRaw.(map[string]interface{})), expandDataSyncTagListEntry(newRaw.(map[string]interface{}))) + + if len(removeTags) > 0 { + input := &datasync.UntagResourceInput{ + Keys: dataSyncTagsKeys(removeTags), + ResourceArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Untagging DataSync Location S3: %s", input) + if _, err := conn.UntagResource(input); err != nil { + return fmt.Errorf("error untagging DataSync Location S3 (%s): %s", d.Id(), err) + } + } + + if len(createTags) > 0 { + input := &datasync.TagResourceInput{ + ResourceArn: aws.String(d.Id()), + Tags: createTags, + } + + log.Printf("[DEBUG] Tagging DataSync Location S3: %s", input) + if _, err := conn.TagResource(input); err != nil { + return fmt.Errorf("error tagging DataSync Location S3 (%s): %s", d.Id(), err) + } + } + } + + return resourceAwsDataSyncLocationS3Read(d, meta) +} + +func resourceAwsDataSyncLocationS3Delete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + input := &datasync.DeleteLocationInput{ + LocationArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting DataSync Location S3: %s", input) + _, err := conn.DeleteLocation(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting DataSync Location S3 (%s): %s", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_datasync_location_s3_test.go b/aws/resource_aws_datasync_location_s3_test.go new file mode 100644 index 00000000000..790311d0992 --- /dev/null +++ b/aws/resource_aws_datasync_location_s3_test.go @@ -0,0 +1,338 @@ +package aws + +import ( + "errors" + "fmt" + "log" + "regexp" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func init() { + resource.AddTestSweepers("aws_datasync_location_s3", &resource.Sweeper{ + Name: "aws_datasync_location_s3", + F: testSweepDataSyncLocationS3s, + }) +} + +func testSweepDataSyncLocationS3s(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).datasyncconn + + input := &datasync.ListLocationsInput{} + for { + output, err := conn.ListLocations(input) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping DataSync Location S3 sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("Error retrieving DataSync Location S3s: %s", err) + } + + if len(output.Locations) == 0 { + log.Print("[DEBUG] No DataSync Location S3s to sweep") + return nil + } + + for _, location := range output.Locations { + uri := aws.StringValue(location.LocationUri) + if !strings.HasPrefix(uri, "s3://") { + log.Printf("[INFO] Skipping DataSync Location S3: %s", uri) + continue + } + log.Printf("[INFO] Deleting DataSync Location S3: %s", uri) + input := &datasync.DeleteLocationInput{ + LocationArn: location.LocationArn, + } + + _, err := conn.DeleteLocation(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + continue + } + + if err != nil { + log.Printf("[ERROR] Failed to delete DataSync Location S3 (%s): %s", uri, err) + } + } + + if aws.StringValue(output.NextToken) == "" { + break + } + + input.NextToken = output.NextToken + } + + return nil +} + +func TestAccAWSDataSyncLocationS3_basic(t *testing.T) { + var locationS31 datasync.DescribeLocationS3Output + rName := acctest.RandomWithPrefix("tf-acc-test") + iamRoleResourceName := "aws_iam_role.test" + resourceName := "aws_datasync_location_s3.test" + s3BucketResourceName := "aws_s3_bucket.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationS3Destroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationS3Config(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationS3Exists(resourceName, &locationS31), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "datasync", regexp.MustCompile(`location/loc-.+`)), + resource.TestCheckResourceAttrPair(resourceName, "s3_bucket_arn", s3BucketResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "s3_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "s3_config.0.bucket_access_role_arn", iamRoleResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "subdirectory", "/test/"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestMatchResourceAttr(resourceName, "uri", regexp.MustCompile(`^s3://.+/`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"s3_bucket_arn"}, + }, + }, + }) +} + +func TestAccAWSDataSyncLocationS3_disappears(t *testing.T) { + var locationS31 datasync.DescribeLocationS3Output + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_location_s3.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationS3Destroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationS3Config(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationS3Exists(resourceName, &locationS31), + testAccCheckAWSDataSyncLocationS3Disappears(&locationS31), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSDataSyncLocationS3_Tags(t *testing.T) { + var locationS31, locationS32, locationS33 datasync.DescribeLocationS3Output + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_location_s3.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationS3Destroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationS3ConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationS3Exists(resourceName, &locationS31), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"s3_bucket_arn"}, + }, + { + Config: testAccAWSDataSyncLocationS3ConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationS3Exists(resourceName, &locationS32), + testAccCheckAWSDataSyncLocationS3NotRecreated(&locationS31, &locationS32), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSDataSyncLocationS3ConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationS3Exists(resourceName, &locationS33), + testAccCheckAWSDataSyncLocationS3NotRecreated(&locationS32, &locationS33), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + }, + }) +} + +func testAccCheckAWSDataSyncLocationS3Destroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_datasync_location_s3" { + continue + } + + input := &datasync.DescribeLocationS3Input{ + LocationArn: aws.String(rs.Primary.ID), + } + + _, err := conn.DescribeLocationS3(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + return nil + } + + if err != nil { + return err + } + } + + return nil +} + +func testAccCheckAWSDataSyncLocationS3Exists(resourceName string, locationS3 *datasync.DescribeLocationS3Output) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + input := &datasync.DescribeLocationS3Input{ + LocationArn: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeLocationS3(input) + + if err != nil { + return err + } + + if output == nil { + return fmt.Errorf("Location %q does not exist", rs.Primary.ID) + } + + *locationS3 = *output + + return nil + } +} + +func testAccCheckAWSDataSyncLocationS3Disappears(location *datasync.DescribeLocationS3Output) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + + input := &datasync.DeleteLocationInput{ + LocationArn: location.LocationArn, + } + + _, err := conn.DeleteLocation(input) + + return err + } +} + +func testAccCheckAWSDataSyncLocationS3NotRecreated(i, j *datasync.DescribeLocationS3Output) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.TimeValue(i.CreationTime) != aws.TimeValue(j.CreationTime) { + return errors.New("DataSync Location S3 was recreated") + } + + return nil + } +} + +func testAccAWSDataSyncLocationS3ConfigBase(rName string) string { + return fmt.Sprintf(` +resource "aws_iam_role" "test" { + name = %q + + assume_role_policy = < 0 { + input := &datasync.UntagResourceInput{ + Keys: dataSyncTagsKeys(removeTags), + ResourceArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Untagging DataSync Task: %s", input) + if _, err := conn.UntagResource(input); err != nil { + return fmt.Errorf("error untagging DataSync Task (%s): %s", d.Id(), err) + } + } + + if len(createTags) > 0 { + input := &datasync.TagResourceInput{ + ResourceArn: aws.String(d.Id()), + Tags: createTags, + } + + log.Printf("[DEBUG] Tagging DataSync Task: %s", input) + if _, err := conn.TagResource(input); err != nil { + return fmt.Errorf("error tagging DataSync Task (%s): %s", d.Id(), err) + } + } + } + + return resourceAwsDataSyncTaskRead(d, meta) +} + +func resourceAwsDataSyncTaskDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + input := &datasync.DeleteTaskInput{ + TaskArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting DataSync Task: %s", input) + _, err := conn.DeleteTask(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting DataSync Task (%s): %s", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_datasync_task_test.go b/aws/resource_aws_datasync_task_test.go new file mode 100644 index 00000000000..3df057a43c9 --- /dev/null +++ b/aws/resource_aws_datasync_task_test.go @@ -0,0 +1,926 @@ +package aws + +import ( + "errors" + "fmt" + "log" + "regexp" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func init() { + resource.AddTestSweepers("aws_datasync_task", &resource.Sweeper{ + Name: "aws_datasync_task", + F: testSweepDataSyncTasks, + }) +} + +func testSweepDataSyncTasks(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).datasyncconn + + input := &datasync.ListTasksInput{} + for { + output, err := conn.ListTasks(input) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping DataSync Task sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("Error retrieving DataSync Tasks: %s", err) + } + + if len(output.Tasks) == 0 { + log.Print("[DEBUG] No DataSync Tasks to sweep") + return nil + } + + for _, task := range output.Tasks { + name := aws.StringValue(task.Name) + if !strings.HasPrefix(name, "tf-acc-test") { + log.Printf("[INFO] Skipping DataSync Task: %s", name) + continue + } + log.Printf("[INFO] Deleting DataSync Task: %s", name) + input := &datasync.DeleteTaskInput{ + TaskArn: task.TaskArn, + } + + _, err := conn.DeleteTask(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + continue + } + + if err != nil { + log.Printf("[ERROR] Failed to delete DataSync Task (%s): %s", name, err) + } + } + + if aws.StringValue(output.NextToken) == "" { + break + } + + input.NextToken = output.NextToken + } + + return nil +} + +func TestAccAWSDataSyncTask_basic(t *testing.T) { + var task1 datasync.DescribeTaskOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + dataSyncDestinationLocationResourceName := "aws_datasync_location_s3.destination" + dataSyncSourceLocationResourceName := "aws_datasync_location_nfs.source" + resourceName := "aws_datasync_task.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncTaskConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task1), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "datasync", regexp.MustCompile(`task/task-.+`)), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_log_group_arn", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.atime", "BEST_EFFORT"), + resource.TestCheckResourceAttr(resourceName, "options.0.bytes_per_second", "-1"), + resource.TestCheckResourceAttr(resourceName, "options.0.gid", "INT_VALUE"), + resource.TestCheckResourceAttr(resourceName, "options.0.mtime", "PRESERVE"), + resource.TestCheckResourceAttr(resourceName, "options.0.posix_permissions", "PRESERVE"), + resource.TestCheckResourceAttr(resourceName, "options.0.preserve_deleted_files", "PRESERVE"), + resource.TestCheckResourceAttr(resourceName, "options.0.preserve_devices", "NONE"), + resource.TestCheckResourceAttr(resourceName, "options.0.uid", "INT_VALUE"), + resource.TestCheckResourceAttr(resourceName, "options.0.verify_mode", "POINT_IN_TIME_CONSISTENT"), + resource.TestCheckResourceAttrPair(resourceName, "destination_location_arn", dataSyncDestinationLocationResourceName, "arn"), + resource.TestCheckResourceAttrPair(resourceName, "source_location_arn", dataSyncSourceLocationResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSDataSyncTask_disappears(t *testing.T) { + var task1 datasync.DescribeTaskOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_task.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncTaskConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task1), + testAccCheckAWSDataSyncTaskDisappears(&task1), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSDataSyncTask_CloudWatchLogGroupARN(t *testing.T) { + var task1 datasync.DescribeTaskOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_task.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncTaskConfigCloudWatchLogGroupArn(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task1), + resource.TestCheckResourceAttrSet(resourceName, "cloudwatch_log_group_arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSDataSyncTask_DefaultSyncOptions_AtimeMtime(t *testing.T) { + var task1, task2 datasync.DescribeTaskOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_task.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsAtimeMtime(rName, "NONE", "NONE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task1), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.atime", "NONE"), + resource.TestCheckResourceAttr(resourceName, "options.0.mtime", "NONE"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsAtimeMtime(rName, "BEST_EFFORT", "PRESERVE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task2), + testAccCheckAWSDataSyncTaskNotRecreated(&task1, &task2), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.atime", "BEST_EFFORT"), + resource.TestCheckResourceAttr(resourceName, "options.0.mtime", "PRESERVE"), + ), + }, + }, + }) +} + +func TestAccAWSDataSyncTask_DefaultSyncOptions_BytesPerSecond(t *testing.T) { + var task1, task2 datasync.DescribeTaskOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_task.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsBytesPerSecond(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task1), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.bytes_per_second", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsBytesPerSecond(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task2), + testAccCheckAWSDataSyncTaskNotRecreated(&task1, &task2), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.bytes_per_second", "2"), + ), + }, + }, + }) +} + +func TestAccAWSDataSyncTask_DefaultSyncOptions_Gid(t *testing.T) { + var task1, task2 datasync.DescribeTaskOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_task.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsGid(rName, "NONE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task1), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.gid", "NONE"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsGid(rName, "INT_VALUE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task2), + testAccCheckAWSDataSyncTaskNotRecreated(&task1, &task2), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.gid", "INT_VALUE"), + ), + }, + }, + }) +} + +func TestAccAWSDataSyncTask_DefaultSyncOptions_PosixPermissions(t *testing.T) { + var task1, task2 datasync.DescribeTaskOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_task.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsPosixPermissions(rName, "NONE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task1), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.posix_permissions", "NONE"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsPosixPermissions(rName, "PRESERVE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task2), + testAccCheckAWSDataSyncTaskNotRecreated(&task1, &task2), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.posix_permissions", "PRESERVE"), + ), + }, + }, + }) +} + +func TestAccAWSDataSyncTask_DefaultSyncOptions_PreserveDeletedFiles(t *testing.T) { + var task1, task2 datasync.DescribeTaskOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_task.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsPreserveDeletedFiles(rName, "REMOVE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task1), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.preserve_deleted_files", "REMOVE"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsPreserveDeletedFiles(rName, "PRESERVE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task2), + testAccCheckAWSDataSyncTaskNotRecreated(&task1, &task2), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.preserve_deleted_files", "PRESERVE"), + ), + }, + }, + }) +} + +func TestAccAWSDataSyncTask_DefaultSyncOptions_PreserveDevices(t *testing.T) { + var task1, task2 datasync.DescribeTaskOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_task.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsPreserveDevices(rName, "PRESERVE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task1), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.preserve_devices", "PRESERVE"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsPreserveDevices(rName, "NONE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task2), + testAccCheckAWSDataSyncTaskNotRecreated(&task1, &task2), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.preserve_devices", "NONE"), + ), + }, + }, + }) +} + +func TestAccAWSDataSyncTask_DefaultSyncOptions_Uid(t *testing.T) { + var task1, task2 datasync.DescribeTaskOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_task.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsUid(rName, "NONE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task1), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.uid", "NONE"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsUid(rName, "INT_VALUE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task2), + testAccCheckAWSDataSyncTaskNotRecreated(&task1, &task2), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.uid", "INT_VALUE"), + ), + }, + }, + }) +} + +func TestAccAWSDataSyncTask_DefaultSyncOptions_VerifyMode(t *testing.T) { + var task1, task2 datasync.DescribeTaskOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_task.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsVerifyMode(rName, "NONE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task1), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.verify_mode", "NONE"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSDataSyncTaskConfigDefaultSyncOptionsVerifyMode(rName, "POINT_IN_TIME_CONSISTENT"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task2), + testAccCheckAWSDataSyncTaskNotRecreated(&task1, &task2), + resource.TestCheckResourceAttr(resourceName, "options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "options.0.verify_mode", "POINT_IN_TIME_CONSISTENT"), + ), + }, + }, + }) +} + +func TestAccAWSDataSyncTask_Tags(t *testing.T) { + t.Skip("Tagging on creation is inconsistent") + var task1, task2, task3 datasync.DescribeTaskOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_datasync_task.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncTaskConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSDataSyncTaskConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task2), + testAccCheckAWSDataSyncTaskNotRecreated(&task1, &task2), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSDataSyncTaskConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncTaskExists(resourceName, &task3), + testAccCheckAWSDataSyncTaskNotRecreated(&task2, &task3), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + }, + }) +} + +func testAccCheckAWSDataSyncTaskDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_datasync_task" { + continue + } + + input := &datasync.DescribeTaskInput{ + TaskArn: aws.String(rs.Primary.ID), + } + + _, err := conn.DescribeTask(input) + + if isAWSErr(err, "InvalidRequestException", "not found") { + return nil + } + + if err != nil { + return err + } + } + + return nil +} + +func testAccCheckAWSDataSyncTaskExists(resourceName string, task *datasync.DescribeTaskOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + input := &datasync.DescribeTaskInput{ + TaskArn: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeTask(input) + + if err != nil { + return err + } + + if output == nil { + return fmt.Errorf("Task %q does not exist", rs.Primary.ID) + } + + if aws.StringValue(output.Status) != datasync.TaskStatusAvailable && aws.StringValue(output.Status) != datasync.TaskStatusRunning { + return fmt.Errorf("Task %q not available or running: last status (%s), error code (%s), error detail: %s", + rs.Primary.ID, aws.StringValue(output.Status), aws.StringValue(output.ErrorCode), aws.StringValue(output.ErrorDetail)) + } + + *task = *output + + return nil + } +} + +func testAccCheckAWSDataSyncTaskDisappears(location *datasync.DescribeTaskOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + + input := &datasync.DeleteTaskInput{ + TaskArn: location.TaskArn, + } + + _, err := conn.DeleteTask(input) + + return err + } +} + +func testAccCheckAWSDataSyncTaskNotRecreated(i, j *datasync.DescribeTaskOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.StringValue(i.TaskArn) != aws.StringValue(j.TaskArn) { + return errors.New("DataSync Task was recreated") + } + + return nil + } +} + +func testAccAWSDataSyncTaskConfigDestinationLocationS3Base(rName string) string { + return fmt.Sprintf(` +resource "aws_iam_role" "destination" { + name = "%sdestination" + + assume_role_policy = < + > + DataSync Resources + + + > Device Farm Resources