provider/aws: Add efs_file_system

This commit is contained in:
Radek Simko 2015-06-02 21:19:50 +01:00 committed by Radek Simko
parent 53f1edc28c
commit cd96b8a7fc
6 changed files with 483 additions and 0 deletions

View File

@ -16,6 +16,7 @@ import (
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/efs"
"github.com/aws/aws-sdk-go/service/elasticache"
"github.com/aws/aws-sdk-go/service/elb"
"github.com/aws/aws-sdk-go/service/iam"
@ -47,6 +48,7 @@ type AWSClient struct {
dynamodbconn *dynamodb.DynamoDB
ec2conn *ec2.EC2
ecsconn *ecs.ECS
efsconn *efs.EFS
elbconn *elb.ELB
autoscalingconn *autoscaling.AutoScaling
s3conn *s3.S3
@ -140,6 +142,9 @@ func (c *Config) Client() (interface{}, error) {
log.Println("[INFO] Initializing ECS Connection")
client.ecsconn = ecs.New(awsConfig)
log.Println("[INFO] Initializing EFS Connection")
client.efsconn = efs.New(awsConfig)
// aws-sdk-go uses v4 for signing requests, which requires all global
// endpoints to use 'us-east-1'.
// See http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html

View File

@ -175,6 +175,7 @@ func Provider() terraform.ResourceProvider {
"aws_ecs_cluster": resourceAwsEcsCluster(),
"aws_ecs_service": resourceAwsEcsService(),
"aws_ecs_task_definition": resourceAwsEcsTaskDefinition(),
"aws_efs_file_system": resourceAwsEfsFileSystem(),
"aws_eip": resourceAwsEip(),
"aws_elasticache_cluster": resourceAwsElasticacheCluster(),
"aws_elasticache_parameter_group": resourceAwsElasticacheParameterGroup(),

View File

@ -0,0 +1,165 @@
package aws
import (
"fmt"
"log"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/efs"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsEfsFileSystem() *schema.Resource {
return &schema.Resource{
Create: resourceAwsEfsFileSystemCreate,
Read: resourceAwsEfsFileSystemRead,
Update: resourceAwsEfsFileSystemUpdate,
Delete: resourceAwsEfsFileSystemDelete,
Schema: map[string]*schema.Schema{
"reference_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"tags": tagsSchema(),
},
}
}
func resourceAwsEfsFileSystemCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).efsconn
referenceName := ""
if v, ok := d.GetOk("reference_name"); ok {
referenceName = v.(string) + "-"
}
token := referenceName + resource.UniqueId()
fs, err := conn.CreateFileSystem(&efs.CreateFileSystemInput{
CreationToken: aws.String(token),
})
if err != nil {
return err
}
log.Printf("[DEBUG] Creating EFS file system: %s", *fs)
d.SetId(*fs.FileSystemId)
stateConf := &resource.StateChangeConf{
Pending: []string{"creating"},
Target: "available",
Refresh: func() (interface{}, string, error) {
resp, err := conn.DescribeFileSystems(&efs.DescribeFileSystemsInput{
FileSystemId: aws.String(d.Id()),
})
if err != nil {
return nil, "error", err
}
if len(resp.FileSystems) < 1 {
return nil, "not-found", fmt.Errorf("EFS file system %q not found", d.Id())
}
fs := resp.FileSystems[0]
log.Printf("[DEBUG] current status of %q: %q", *fs.FileSystemId, *fs.LifeCycleState)
return fs, *fs.LifeCycleState, nil
},
Timeout: 10 * time.Minute,
Delay: 2 * time.Second,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for EFS file system (%q) to create: %q",
d.Id(), err.Error())
}
log.Printf("[DEBUG] EFS file system created: %q", *fs.FileSystemId)
return resourceAwsEfsFileSystemUpdate(d, meta)
}
func resourceAwsEfsFileSystemUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).efsconn
err := setTagsEFS(conn, d)
if err != nil {
return err
}
return resourceAwsEfsFileSystemRead(d, meta)
}
func resourceAwsEfsFileSystemRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).efsconn
resp, err := conn.DescribeFileSystems(&efs.DescribeFileSystemsInput{
FileSystemId: aws.String(d.Id()),
})
if err != nil {
return err
}
if len(resp.FileSystems) < 1 {
return fmt.Errorf("EFS file system %q not found", d.Id())
}
tagsResp, err := conn.DescribeTags(&efs.DescribeTagsInput{
FileSystemId: aws.String(d.Id()),
})
if err != nil {
return err
}
d.Set("tags", tagsToMapEFS(tagsResp.Tags))
return nil
}
func resourceAwsEfsFileSystemDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).efsconn
log.Printf("[DEBUG] Deleting EFS file system %s", d.Id())
_, err := conn.DeleteFileSystem(&efs.DeleteFileSystemInput{
FileSystemId: aws.String(d.Id()),
})
stateConf := &resource.StateChangeConf{
Pending: []string{"available", "deleting"},
Target: "",
Refresh: func() (interface{}, string, error) {
resp, err := conn.DescribeFileSystems(&efs.DescribeFileSystemsInput{
FileSystemId: aws.String(d.Id()),
})
if err != nil {
efsErr, ok := err.(awserr.Error)
if ok && efsErr.Code() == "FileSystemNotFound" {
return nil, "", nil
}
return nil, "error", err
}
if len(resp.FileSystems) < 1 {
return nil, "", nil
}
fs := resp.FileSystems[0]
log.Printf("[DEBUG] current status of %q: %q",
*fs.FileSystemId, *fs.LifeCycleState)
return fs, *fs.LifeCycleState, nil
},
Timeout: 10 * time.Minute,
Delay: 2 * time.Second,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return err
}
log.Printf("[DEBUG] EFS file system %q deleted.", d.Id())
return nil
}

View File

@ -0,0 +1,133 @@
package aws
import (
"fmt"
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/efs"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSEFSFileSystem(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckEfsFileSystemDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSEFSFileSystemConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckEfsFileSystem(
"aws_efs_file_system.foo",
),
),
},
resource.TestStep{
Config: testAccAWSEFSFileSystemConfigWithTags,
Check: resource.ComposeTestCheckFunc(
testAccCheckEfsFileSystem(
"aws_efs_file_system.foo-with-tags",
),
testAccCheckEfsFileSystemTags(
"aws_efs_file_system.foo-with-tags",
map[string]string{
"Name": "foo-efs",
"Another": "tag",
},
),
),
},
},
})
}
func testAccCheckEfsFileSystemDestroy(s *terraform.State) error {
if len(s.RootModule().Resources) > 0 {
return fmt.Errorf("Expected all resources to be gone, but found: %#v", s.RootModule().Resources)
}
return nil
}
func testAccCheckEfsFileSystem(resourceID string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceID]
if !ok {
return fmt.Errorf("Not found: %s", resourceID)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
fs, ok := s.RootModule().Resources[resourceID]
if !ok {
return fmt.Errorf("Not found: %s", resourceID)
}
conn := testAccProvider.Meta().(*AWSClient).efsconn
_, err := conn.DescribeFileSystems(&efs.DescribeFileSystemsInput{
FileSystemId: aws.String(fs.Primary.ID),
})
if err != nil {
return err
}
return nil
}
}
func testAccCheckEfsFileSystemTags(resourceID string, expectedTags map[string]string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceID]
if !ok {
return fmt.Errorf("Not found: %s", resourceID)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
fs, ok := s.RootModule().Resources[resourceID]
if !ok {
return fmt.Errorf("Not found: %s", resourceID)
}
conn := testAccProvider.Meta().(*AWSClient).efsconn
resp, err := conn.DescribeTags(&efs.DescribeTagsInput{
FileSystemId: aws.String(fs.Primary.ID),
})
if !reflect.DeepEqual(expectedTags, tagsToMapEFS(resp.Tags)) {
return fmt.Errorf("Tags mismatch.\nExpected: %#v\nGiven: %#v",
expectedTags, resp.Tags)
}
if err != nil {
return err
}
return nil
}
}
const testAccAWSEFSFileSystemConfig = `
resource "aws_efs_file_system" "foo" {
reference_name = "radeksimko"
}
`
const testAccAWSEFSFileSystemConfigWithTags = `
resource "aws_efs_file_system" "foo-with-tags" {
reference_name = "yada_yada"
tags {
Name = "foo-efs"
Another = "tag"
}
}
`

View File

@ -0,0 +1,94 @@
package aws
import (
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/efs"
"github.com/hashicorp/terraform/helper/schema"
)
// setTags is a helper to set the tags for a resource. It expects the
// tags field to be named "tags"
func setTagsEFS(conn *efs.EFS, d *schema.ResourceData) error {
if d.HasChange("tags") {
oraw, nraw := d.GetChange("tags")
o := oraw.(map[string]interface{})
n := nraw.(map[string]interface{})
create, remove := diffTagsEFS(tagsFromMapEFS(o), tagsFromMapEFS(n))
// Set tags
if len(remove) > 0 {
log.Printf("[DEBUG] Removing tags: %#v", remove)
k := make([]*string, 0, len(remove))
for _, t := range remove {
k = append(k, t.Key)
}
_, err := conn.DeleteTags(&efs.DeleteTagsInput{
FileSystemId: aws.String(d.Id()),
TagKeys: k,
})
if err != nil {
return err
}
}
if len(create) > 0 {
log.Printf("[DEBUG] Creating tags: %#v", create)
_, err := conn.CreateTags(&efs.CreateTagsInput{
FileSystemId: aws.String(d.Id()),
Tags: create,
})
if err != nil {
return err
}
}
}
return nil
}
// diffTags 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 diffTagsEFS(oldTags, newTags []*efs.Tag) ([]*efs.Tag, []*efs.Tag) {
// First, we're creating everything we have
create := make(map[string]interface{})
for _, t := range newTags {
create[*t.Key] = *t.Value
}
// Build the list of what to remove
var remove []*efs.Tag
for _, t := range oldTags {
old, ok := create[*t.Key]
if !ok || old != *t.Value {
// Delete it!
remove = append(remove, t)
}
}
return tagsFromMapEFS(create), remove
}
// tagsFromMap returns the tags for the given map of data.
func tagsFromMapEFS(m map[string]interface{}) []*efs.Tag {
var result []*efs.Tag
for k, v := range m {
result = append(result, &efs.Tag{
Key: aws.String(k),
Value: aws.String(v.(string)),
})
}
return result
}
// tagsToMap turns the list of tags into a map.
func tagsToMapEFS(ts []*efs.Tag) map[string]string {
result := make(map[string]string)
for _, t := range ts {
result[*t.Key] = *t.Value
}
return result
}

View File

@ -0,0 +1,85 @@
package aws
import (
"fmt"
"reflect"
"testing"
"github.com/aws/aws-sdk-go/service/efs"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestDiffEFSTags(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 {
c, r := diffTagsEFS(tagsFromMapEFS(tc.Old), tagsFromMapEFS(tc.New))
cm := tagsToMapEFS(c)
rm := tagsToMapEFS(r)
if !reflect.DeepEqual(cm, tc.Create) {
t.Fatalf("%d: bad create: %#v", i, cm)
}
if !reflect.DeepEqual(rm, tc.Remove) {
t.Fatalf("%d: bad remove: %#v", i, rm)
}
}
}
// testAccCheckTags can be used to check the tags on a resource.
func testAccCheckEFSTags(
ts *[]*efs.Tag, key string, value string) resource.TestCheckFunc {
return func(s *terraform.State) error {
m := tagsToMapEFS(*ts)
v, ok := m[key]
if value != "" && !ok {
return fmt.Errorf("Missing tag: %s", key)
} else if value == "" && ok {
return fmt.Errorf("Extra tag: %s", key)
}
if value == "" {
return nil
}
if v != value {
return fmt.Errorf("%s: bad value: %s", key, v)
}
return nil
}
}