diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go new file mode 100644 index 000000000..e0a3ae910 --- /dev/null +++ b/builtin/providers/aws/provider.go @@ -0,0 +1,22 @@ +package aws + +import ( + "github.com/hashicorp/terraform/helper/schema" +) + +// Provider returns a schema.Provider for AWS. +// +// NOTE: schema.Provider became available long after the AWS provider +// was started, so resources may not be converted to this new structure +// yet. This is a WIP. To assist with the migration, make sure any resources +// you migrate are acceptance tested, then perform the migration. +func Provider() *schema.Provider { + // TODO: Move the validation to this, requires conditional schemas + // TODO: Move the configuration to this, requires validation + + return &schema.Provider{ + ResourcesMap: map[string]*schema.Resource{ + "aws_eip": resourceAwsEip(), + }, + } +} diff --git a/builtin/providers/aws/provider_test.go b/builtin/providers/aws/provider_test.go new file mode 100644 index 000000000..480c88bf8 --- /dev/null +++ b/builtin/providers/aws/provider_test.go @@ -0,0 +1,11 @@ +package aws + +import ( + "testing" +) + +func TestProvider(t *testing.T) { + if err := Provider().InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} diff --git a/builtin/providers/aws/resource_aws_eip.go b/builtin/providers/aws/resource_aws_eip.go index bb8e9afc1..67e5106a6 100644 --- a/builtin/providers/aws/resource_aws_eip.go +++ b/builtin/providers/aws/resource_aws_eip.go @@ -5,27 +5,51 @@ import ( "log" "strings" - "github.com/hashicorp/terraform/helper/config" - "github.com/hashicorp/terraform/helper/diff" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/helper/schema" + //"github.com/hashicorp/terraform/terraform" "github.com/mitchellh/goamz/ec2" ) -func resource_aws_eip_create( - s *terraform.ResourceState, - d *terraform.ResourceDiff, - meta interface{}) (*terraform.ResourceState, error) { +func resourceAwsEip() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEipCreate, + Read: resourceAwsEipRead, + Update: resourceAwsEipUpdate, + Delete: resourceAwsEipDelete, + + Schema: map[string]*schema.Schema{ + "vpc": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "instance": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "public_ip": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "private_ip": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error { p := meta.(*ResourceProvider) ec2conn := p.ec2conn - // Merge the diff into the state so that we have all the attributes - // properly. - rs := s.MergeDiff(d) - // By default, we're not in a VPC vpc := false domainOpt := "" - if rs.Attributes["vpc"] == "true" { + if v := d.Get("vpc"); v != nil && v.(bool) { vpc = true domainOpt = "vpc" } @@ -37,7 +61,7 @@ func resource_aws_eip_create( log.Printf("[DEBUG] EIP create configuration: %#v", allocOpts) allocResp, err := ec2conn.AllocateAddress(&allocOpts) if err != nil { - return nil, fmt.Errorf("Error creating EIP: %s", err) + return fmt.Errorf("Error creating EIP: %s", err) } // Assign the eips (unique) allocation id for use later @@ -46,46 +70,37 @@ func resource_aws_eip_create( // it defaults to using the public IP log.Printf("[DEBUG] EIP Allocate: %#v", allocResp) if allocResp.AllocationId != "" { - rs.ID = allocResp.AllocationId - rs.Attributes["vpc"] = "true" - + d.SetId(allocResp.AllocationId) + d.Set("vpc", true) } else { - rs.ID = allocResp.PublicIp + d.SetId(allocResp.PublicIp) + d.Set("vpc", false) } - log.Printf("[INFO] EIP ID: %s (vpc: %v)", rs.ID, vpc) - - return resource_aws_eip_update(rs, d, meta) + log.Printf("[INFO] EIP ID: %s (vpc: %v)", d.Id(), vpc) + return resourceAwsEipRead(d, meta) } -func resource_aws_eip_update( - s *terraform.ResourceState, - d *terraform.ResourceDiff, - meta interface{}) (*terraform.ResourceState, error) { +func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error { p := meta.(*ResourceProvider) ec2conn := p.ec2conn - // Merge the diff into the state so that we have all the attributes - // properly. - rs := s.MergeDiff(d) - - vpc := strings.Contains(rs.ID, "eipalloc") - - // If we have an instance to register, do it - instanceId := rs.Attributes["instance"] + vpc := strings.Contains(d.Id(), "eipalloc") // Only register with an instance if we have one - if instanceId != "" { + if v := d.Get("instance"); v != nil { + instanceId := v.(string) + assocOpts := ec2.AssociateAddress{ InstanceId: instanceId, - PublicIp: rs.ID, + PublicIp: d.Id(), } // more unique ID conditionals if vpc { assocOpts = ec2.AssociateAddress{ InstanceId: instanceId, - AllocationId: rs.ID, + AllocationId: d.Id(), PublicIp: "", } } @@ -93,93 +108,42 @@ func resource_aws_eip_update( log.Printf("[DEBUG] EIP associate configuration: %#v (vpc: %v)", assocOpts, vpc) _, err := ec2conn.AssociateAddress(&assocOpts) if err != nil { - return rs, fmt.Errorf("Failure associating instances: %s", err) + return fmt.Errorf("Failure associating instances: %s", err) } } - address, err := resource_aws_eip_retrieve_address(rs.ID, vpc, ec2conn) - if err != nil { - return rs, err - } - - return resource_aws_eip_update_state(rs, address) + return resourceAwsEipRead(d, meta) } -func resource_aws_eip_destroy( - s *terraform.ResourceState, - meta interface{}) error { +func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error { p := meta.(*ResourceProvider) ec2conn := p.ec2conn var err error - if strings.Contains(s.ID, "eipalloc") { - log.Printf("[DEBUG] EIP release (destroy) address allocation: %v", s.ID) - _, err = ec2conn.ReleaseAddress(s.ID) + if strings.Contains(d.Id(), "eipalloc") { + log.Printf("[DEBUG] EIP release (destroy) address allocation: %v", d.Id()) + _, err = ec2conn.ReleaseAddress(d.Id()) return err } else { - log.Printf("[DEBUG] EIP release (destroy) address: %v", s.ID) - _, err = ec2conn.ReleasePublicAddress(s.ID) + log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id()) + _, err = ec2conn.ReleasePublicAddress(d.Id()) return err } return nil } -func resource_aws_eip_refresh( - s *terraform.ResourceState, - meta interface{}) (*terraform.ResourceState, error) { +func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error { p := meta.(*ResourceProvider) ec2conn := p.ec2conn vpc := false - if s.Attributes["vpc"] == "true" { + if d.Get("vpc").(bool) { vpc = true } - address, err := resource_aws_eip_retrieve_address(s.ID, vpc, ec2conn) + id := d.Id() - if err != nil { - return s, err - } - - return resource_aws_eip_update_state(s, address) -} - -func resource_aws_eip_diff( - s *terraform.ResourceState, - c *terraform.ResourceConfig, - meta interface{}) (*terraform.ResourceDiff, error) { - - b := &diff.ResourceBuilder{ - Attrs: map[string]diff.AttrType{ - "vpc": diff.AttrTypeCreate, - "instance": diff.AttrTypeUpdate, - }, - - ComputedAttrs: []string{ - "public_ip", - "private_ip", - }, - } - - return b.Diff(s, c) -} - -func resource_aws_eip_update_state( - s *terraform.ResourceState, - address *ec2.Address) (*terraform.ResourceState, error) { - - s.Attributes["private_ip"] = address.PrivateIpAddress - s.Attributes["public_ip"] = address.PublicIp - s.Attributes["instance"] = address.InstanceId - - return s, nil -} - -// Returns a single address by its ID -func resource_aws_eip_retrieve_address(id string, vpc bool, ec2conn *ec2.EC2) (*ec2.Address, error) { - // Get the full address description for saving to state for - // use in other resources assocIds := []string{} publicIps := []string{} if vpc { @@ -188,12 +152,13 @@ func resource_aws_eip_retrieve_address(id string, vpc bool, ec2conn *ec2.EC2) (* publicIps = []string{id} } - log.Printf("[DEBUG] EIP describe configuration: %#v, %#v (vpc: %v)", assocIds, publicIps, vpc) + log.Printf( + "[DEBUG] EIP describe configuration: %#v, %#v (vpc: %v)", + assocIds, publicIps, vpc) describeAddresses, err := ec2conn.Addresses(publicIps, assocIds, nil) - if err != nil { - return nil, fmt.Errorf("Error retrieving EIP: %s", err) + return fmt.Errorf("Error retrieving EIP: %s", err) } // Verify AWS returned our EIP @@ -201,20 +166,15 @@ func resource_aws_eip_retrieve_address(id string, vpc bool, ec2conn *ec2.EC2) (* describeAddresses.Addresses[0].AllocationId != id || describeAddresses.Addresses[0].PublicIp != id { if err != nil { - return nil, fmt.Errorf("Unable to find EIP: %#v", describeAddresses.Addresses) + return fmt.Errorf("Unable to find EIP: %#v", describeAddresses.Addresses) } } address := describeAddresses.Addresses[0] - return &address, nil -} + d.Set("instance", address.InstanceId) + d.Set("public_ip", address.PublicIp) + d.Set("private_ip", address.PrivateIpAddress) -func resource_aws_eip_validation() *config.Validator { - return &config.Validator{ - Optional: []string{ - "vpc", - "instance", - }, - } + return nil } diff --git a/builtin/providers/aws/resource_provider.go b/builtin/providers/aws/resource_provider.go index 92a7b544c..edbe58dc3 100644 --- a/builtin/providers/aws/resource_provider.go +++ b/builtin/providers/aws/resource_provider.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform/helper/config" "github.com/hashicorp/terraform/helper/multierror" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/goamz/autoscaling" "github.com/mitchellh/goamz/ec2" @@ -24,6 +25,10 @@ type ResourceProvider struct { s3conn *s3.S3 rdsconn *rds.Rds route53 *route53.Route53 + + // This is the schema.Provider. Eventually this will replace much + // of this structure. For now it is an element of it for compatiblity. + p *schema.Provider } func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) { @@ -56,6 +61,11 @@ func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []er func (p *ResourceProvider) ValidateResource( t string, c *terraform.ResourceConfig) ([]string, []error) { + prov := Provider() + if _, ok := prov.ResourcesMap[t]; ok { + return prov.ValidateResource(t, c) + } + return resourceMap.Validate(t, c) } @@ -98,26 +108,44 @@ func (p *ResourceProvider) Configure(c *terraform.ResourceConfig) error { return &multierror.Error{Errors: errs} } + // Create the provider, set the meta + p.p = Provider() + p.p.SetMeta(p) + return nil } func (p *ResourceProvider) Apply( s *terraform.ResourceState, d *terraform.ResourceDiff) (*terraform.ResourceState, error) { + if _, ok := p.p.ResourcesMap[s.Type]; ok { + return p.p.Apply(s, d) + } + return resourceMap.Apply(s, d, p) } func (p *ResourceProvider) Diff( s *terraform.ResourceState, c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) { + if _, ok := p.p.ResourcesMap[s.Type]; ok { + return p.p.Diff(s, c) + } + return resourceMap.Diff(s, c, p) } func (p *ResourceProvider) Refresh( s *terraform.ResourceState) (*terraform.ResourceState, error) { + if _, ok := p.p.ResourcesMap[s.Type]; ok { + return p.p.Refresh(s) + } + return resourceMap.Refresh(s, p) } func (p *ResourceProvider) Resources() []terraform.ResourceType { - return resourceMap.Resources() + result := resourceMap.Resources() + result = append(result, Provider().Resources()...) + return result } diff --git a/builtin/providers/aws/resource_provider_test.go b/builtin/providers/aws/resource_provider_test.go index d3393caeb..b376f62b8 100644 --- a/builtin/providers/aws/resource_provider_test.go +++ b/builtin/providers/aws/resource_provider_test.go @@ -52,6 +52,13 @@ func TestResourceProvider_Configure(t *testing.T) { if !reflect.DeepEqual(rp.Config, expected) { t.Fatalf("bad: %#v", rp.Config) } + + if rp.p == nil { + t.Fatal("provider should be set") + } + if !reflect.DeepEqual(rp, rp.p.Meta()) { + t.Fatalf("meta should be set") + } } func TestResourceProvider_ConfigureBadRegion(t *testing.T) { diff --git a/builtin/providers/aws/resources.go b/builtin/providers/aws/resources.go index 48465cb12..2cf1ed4bc 100644 --- a/builtin/providers/aws/resources.go +++ b/builtin/providers/aws/resources.go @@ -38,15 +38,6 @@ func init() { Refresh: resource_aws_db_security_group_refresh, }, - "aws_eip": resource.Resource{ - ConfigValidator: resource_aws_eip_validation(), - Create: resource_aws_eip_create, - Destroy: resource_aws_eip_destroy, - Diff: resource_aws_eip_diff, - Refresh: resource_aws_eip_refresh, - Update: resource_aws_eip_update, - }, - "aws_elb": resource.Resource{ ConfigValidator: resource_aws_elb_validation(), Create: resource_aws_elb_create, diff --git a/helper/schema/resource_data.go b/helper/schema/resource_data.go index b00c4b154..ba234dc61 100644 --- a/helper/schema/resource_data.go +++ b/helper/schema/resource_data.go @@ -319,6 +319,17 @@ func (d *ResourceData) getPrimitive( } switch schema.Type { + case TypeBool: + if result == "" { + return false + } + + v, err := strconv.ParseBool(result) + if err != nil { + panic(err) + } + + return v case TypeString: // Use the value as-is. We just put this case here to be explicit. return result @@ -503,6 +514,13 @@ func (d *ResourceData) setPrimitive( var set string switch schema.Type { + case TypeBool: + var b bool + if err := mapstructure.Decode(v, &b); err != nil { + return fmt.Errorf("%s: %s", k, err) + } + + set = strconv.FormatBool(b) case TypeString: if err := mapstructure.Decode(v, &set); err != nil { return fmt.Errorf("%s: %s", k, err) @@ -602,6 +620,8 @@ func (d *ResourceData) statePrimitive( var vs string switch schema.Type { + case TypeBool: + vs = strconv.FormatBool(v.(bool)) case TypeString: vs = v.(string) case TypeInt: diff --git a/helper/schema/resource_data_test.go b/helper/schema/resource_data_test.go index e07daab08..b4550f68b 100644 --- a/helper/schema/resource_data_test.go +++ b/helper/schema/resource_data_test.go @@ -680,6 +680,45 @@ func TestResourceDataSet(t *testing.T) { GetValue: 80, }, + // Basic bool + { + Schema: map[string]*Schema{ + "vpc": &Schema{ + Type: TypeBool, + Optional: true, + }, + }, + + State: nil, + + Diff: nil, + + Key: "vpc", + Value: true, + + GetKey: "vpc", + GetValue: true, + }, + + { + Schema: map[string]*Schema{ + "vpc": &Schema{ + Type: TypeBool, + Optional: true, + }, + }, + + State: nil, + + Diff: nil, + + Key: "vpc", + Value: false, + + GetKey: "vpc", + GetValue: false, + }, + // Invalid type { Schema: map[string]*Schema{ @@ -1076,6 +1115,29 @@ func TestResourceDataState(t *testing.T) { }, }, + { + Schema: map[string]*Schema{ + "vpc": &Schema{ + Type: TypeBool, + Optional: true, + }, + }, + + State: nil, + + Diff: nil, + + Set: map[string]interface{}{ + "vpc": true, + }, + + Result: &terraform.ResourceState{ + Attributes: map[string]string{ + "vpc": "true", + }, + }, + }, + // List { Schema: map[string]*Schema{