From a270bc9771b9f9ad90c232a69fab8796678683c8 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Mon, 17 Nov 2014 12:01:22 +0100 Subject: [PATCH 1/2] Refactoring the CloudFlare provider With this refactor the CloudFlare provider is updated to use the schema.Provider approach released with TF 0.2. --- builtin/bins/provider-cloudflare/main.go | 5 +- builtin/providers/cloudflare/config.go | 3 +- builtin/providers/cloudflare/provider.go | 54 ++++ builtin/providers/cloudflare/provider_test.go | 43 +++ .../cloudflare/resource_cloudflare_record.go | 245 ++++++++---------- .../resource_cloudflare_record_test.go | 4 +- .../providers/cloudflare/resource_provider.go | 77 ------ .../cloudflare/resource_provider_test.go | 80 ------ builtin/providers/cloudflare/resources.go | 24 -- 9 files changed, 206 insertions(+), 329 deletions(-) create mode 100644 builtin/providers/cloudflare/provider.go create mode 100644 builtin/providers/cloudflare/provider_test.go delete mode 100644 builtin/providers/cloudflare/resource_provider.go delete mode 100644 builtin/providers/cloudflare/resource_provider_test.go delete mode 100644 builtin/providers/cloudflare/resources.go diff --git a/builtin/bins/provider-cloudflare/main.go b/builtin/bins/provider-cloudflare/main.go index c81c552e7..fdce8e7a8 100644 --- a/builtin/bins/provider-cloudflare/main.go +++ b/builtin/bins/provider-cloudflare/main.go @@ -3,13 +3,10 @@ package main import ( "github.com/hashicorp/terraform/builtin/providers/cloudflare" "github.com/hashicorp/terraform/plugin" - "github.com/hashicorp/terraform/terraform" ) func main() { plugin.Serve(&plugin.ServeOpts{ - ProviderFunc: func() terraform.ResourceProvider { - return new(cloudflare.ResourceProvider) - }, + ProviderFunc: cloudflare.Provider, }) } diff --git a/builtin/providers/cloudflare/config.go b/builtin/providers/cloudflare/config.go index 2aa020b62..c1433ede2 100644 --- a/builtin/providers/cloudflare/config.go +++ b/builtin/providers/cloudflare/config.go @@ -8,12 +8,11 @@ import ( ) type Config struct { - Token string `mapstructure:"token"` Email string `mapstructure:"email"` + Token string `mapstructure:"token"` } // Client() returns a new client for accessing cloudflare. -// func (c *Config) Client() (*cloudflare.Client, error) { client, err := cloudflare.NewClient(c.Email, c.Token) diff --git a/builtin/providers/cloudflare/provider.go b/builtin/providers/cloudflare/provider.go new file mode 100644 index 000000000..f92e67673 --- /dev/null +++ b/builtin/providers/cloudflare/provider.go @@ -0,0 +1,54 @@ +package cloudflare + +import ( + "os" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +// Provider returns a terraform.ResourceProvider. +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "email": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: envDefaultFunc("CLOUDFLARE_EMAIL"), + Description: "A registered CloudFlare email address.", + }, + + "token": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: envDefaultFunc("CLOUDFLARE_TOKEN"), + Description: "The token key for API operations.", + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + "cloudflare_record": resourceCloudFlareRecord(), + }, + + ConfigureFunc: providerConfigure, + } +} + +func envDefaultFunc(k string) schema.SchemaDefaultFunc { + return func() (interface{}, error) { + if v := os.Getenv(k); v != "" { + return v, nil + } + + return nil, nil + } +} + +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + config := Config{ + Email: d.Get("email").(string), + Token: d.Get("token").(string), + } + + return config.Client() +} diff --git a/builtin/providers/cloudflare/provider_test.go b/builtin/providers/cloudflare/provider_test.go new file mode 100644 index 000000000..3306633cf --- /dev/null +++ b/builtin/providers/cloudflare/provider_test.go @@ -0,0 +1,43 @@ +package cloudflare + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +var testAccProviders map[string]terraform.ResourceProvider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider().(*schema.Provider) + testAccProviders = map[string]terraform.ResourceProvider{ + "cloudflare": testAccProvider, + } +} + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestProvider_impl(t *testing.T) { + var _ terraform.ResourceProvider = Provider() +} + +func testAccPreCheck(t *testing.T) { + if v := os.Getenv("CLOUDFLARE_EMAIL"); v == "" { + t.Fatal("CLOUDFLARE_EMAIL must be set for acceptance tests") + } + + if v := os.Getenv("CLOUDFLARE_TOKEN"); v == "" { + t.Fatal("CLOUDFLARE_TOKEN must be set for acceptance tests") + } + + if v := os.Getenv("CLOUDFLARE_DOMAIN"); v == "" { + t.Fatal("CLOUDFLARE_DOMAIN must be set for acceptance tests. The domain is used to ` and destroy record against.") + } +} diff --git a/builtin/providers/cloudflare/resource_cloudflare_record.go b/builtin/providers/cloudflare/resource_cloudflare_record.go index 8f9b964a5..c1a547959 100644 --- a/builtin/providers/cloudflare/resource_cloudflare_record.go +++ b/builtin/providers/cloudflare/resource_cloudflare_record.go @@ -4,96 +4,140 @@ import ( "fmt" "log" - "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/pearkes/cloudflare" ) -func resource_cloudflare_record_create( - s *terraform.InstanceState, - d *terraform.InstanceDiff, - meta interface{}) (*terraform.InstanceState, error) { - p := meta.(*ResourceProvider) - client := p.client +func resourceCloudFlareRecord() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudFlareRecordCreate, + Read: resourceCloudFlareRecordRead, + Update: resourceCloudFlareRecordUpdate, + Delete: resourceCloudFlareRecordDelete, - // Merge the diff into the state so that we have all the attributes - // properly. - rs := s.MergeDiff(d) + Schema: map[string]*schema.Schema{ + "domain": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, - var err error + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, - newRecord := cloudflare.CreateRecord{ - Name: rs.Attributes["name"], - Priority: rs.Attributes["priority"], - Type: rs.Attributes["type"], - Content: rs.Attributes["value"], - Ttl: rs.Attributes["ttl"], + "hostname": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "value": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "ttl": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "priority": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func resourceCloudFlareRecordCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cloudflare.Client) + + // Create the new record + newRecord := &cloudflare.CreateRecord{ + Name: d.Get("name").(string), + Type: d.Get("type").(string), + Content: d.Get("value").(string), + } + + if ttl, ok := d.GetOk("ttl"); ok { + newRecord.Ttl = ttl.(string) + } + + if priority, ok := d.GetOk("priority"); ok { + newRecord.Priority = priority.(string) } log.Printf("[DEBUG] record create configuration: %#v", newRecord) - rec, err := client.CreateRecord(rs.Attributes["domain"], &newRecord) + rec, err := client.CreateRecord(d.Get("domain").(string), newRecord) if err != nil { - return nil, fmt.Errorf("Failed to create record: %s", err) + return fmt.Errorf("Failed to create record: %s", err) } - rs.ID = rec.Id - log.Printf("[INFO] record ID: %s", rs.ID) + d.SetId(rec.Id) + log.Printf("[INFO] record ID: %s", d.Id()) - record, err := resource_cloudflare_record_retrieve(rs.Attributes["domain"], rs.ID, client) - if err != nil { - return nil, fmt.Errorf("Couldn't find record: %s", err) - } - - return resource_cloudflare_record_update_state(rs, record) + return resourceCloudFlareRecordRead(d, meta) } -func resource_cloudflare_record_update( - s *terraform.InstanceState, - d *terraform.InstanceDiff, - meta interface{}) (*terraform.InstanceState, error) { - p := meta.(*ResourceProvider) - client := p.client - rs := s.MergeDiff(d) +func resourceCloudFlareRecordRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cloudflare.Client) - // Cloudflare requires we send all values - // for an update request, so we just - // merge out diff and send the current - // state of affairs to them - updateRecord := cloudflare.UpdateRecord{ - Name: rs.Attributes["name"], - Content: rs.Attributes["value"], - Type: rs.Attributes["type"], - Ttl: rs.Attributes["ttl"], - Priority: rs.Attributes["priority"], + rec, err := client.RetrieveRecord(d.Get("domain").(string), d.Id()) + if err != nil { + return fmt.Errorf("Couldn't find record: %s", err) + } + + d.Set("name", rec.Name) + d.Set("hostname", rec.FullName) + d.Set("type", rec.Type) + d.Set("value", rec.Value) + d.Set("ttl", rec.Ttl) + d.Set("priority", rec.Priority) + + return nil +} + +func resourceCloudFlareRecordUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cloudflare.Client) + + // CloudFlare requires we send all values for an update request + updateRecord := &cloudflare.UpdateRecord{ + Name: d.Get("name").(string), + Type: d.Get("type").(string), + Content: d.Get("value").(string), + } + + if ttl, ok := d.GetOk("ttl"); ok { + updateRecord.Ttl = ttl.(string) + } + + if priority, ok := d.GetOk("priority"); ok { + updateRecord.Priority = priority.(string) } log.Printf("[DEBUG] record update configuration: %#v", updateRecord) - err := client.UpdateRecord(rs.Attributes["domain"], rs.ID, &updateRecord) + err := client.UpdateRecord(d.Get("domain").(string), d.Id(), updateRecord) if err != nil { - return rs, fmt.Errorf("Failed to update record: %s", err) + return fmt.Errorf("Failed to update record: %s", err) } - record, err := resource_cloudflare_record_retrieve(rs.Attributes["domain"], rs.ID, client) - if err != nil { - return rs, fmt.Errorf("Couldn't find record: %s", err) - } - - return resource_cloudflare_record_update_state(rs, record) + return resourceCloudFlareRecordRead(d, meta) } -func resource_cloudflare_record_destroy( - s *terraform.InstanceState, - meta interface{}) error { - p := meta.(*ResourceProvider) - client := p.client +func resourceCloudFlareRecordDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cloudflare.Client) - log.Printf("[INFO] Deleting record: %s, %s", s.Attributes["domain"], s.ID) + log.Printf("[INFO] Deleting record: %s, %s", d.Get("domain").(string), d.Id()) - err := client.DestroyRecord(s.Attributes["domain"], s.ID) + err := client.DestroyRecord(d.Get("domain").(string), d.Id()) if err != nil { return fmt.Errorf("Error deleting record: %s", err) @@ -101,82 +145,3 @@ func resource_cloudflare_record_destroy( return nil } - -func resource_cloudflare_record_refresh( - s *terraform.InstanceState, - meta interface{}) (*terraform.InstanceState, error) { - p := meta.(*ResourceProvider) - client := p.client - - rec, err := resource_cloudflare_record_retrieve(s.Attributes["domain"], s.ID, client) - if err != nil { - return nil, err - } - - return resource_cloudflare_record_update_state(s, rec) -} - -func resource_cloudflare_record_diff( - s *terraform.InstanceState, - c *terraform.ResourceConfig, - meta interface{}) (*terraform.InstanceDiff, error) { - - b := &diff.ResourceBuilder{ - Attrs: map[string]diff.AttrType{ - "domain": diff.AttrTypeCreate, - "name": diff.AttrTypeUpdate, - "value": diff.AttrTypeUpdate, - "ttl": diff.AttrTypeUpdate, - "type": diff.AttrTypeUpdate, - "priority": diff.AttrTypeUpdate, - }, - - ComputedAttrs: []string{ - "priority", - "ttl", - "hostname", - }, - - ComputedAttrsUpdate: []string{}, - } - - return b.Diff(s, c) -} - -func resource_cloudflare_record_update_state( - s *terraform.InstanceState, - rec *cloudflare.Record) (*terraform.InstanceState, error) { - - s.Attributes["name"] = rec.Name - s.Attributes["value"] = rec.Value - s.Attributes["type"] = rec.Type - s.Attributes["ttl"] = rec.Ttl - s.Attributes["priority"] = rec.Priority - s.Attributes["hostname"] = rec.FullName - - return s, nil -} - -func resource_cloudflare_record_retrieve(domain string, id string, client *cloudflare.Client) (*cloudflare.Record, error) { - record, err := client.RetrieveRecord(domain, id) - if err != nil { - return nil, err - } - - return record, nil -} - -func resource_cloudflare_record_validation() *config.Validator { - return &config.Validator{ - Required: []string{ - "domain", - "name", - "value", - "type", - }, - Optional: []string{ - "ttl", - "priority", - }, - } -} diff --git a/builtin/providers/cloudflare/resource_cloudflare_record_test.go b/builtin/providers/cloudflare/resource_cloudflare_record_test.go index 9ebfaa5a5..8a15fdca1 100644 --- a/builtin/providers/cloudflare/resource_cloudflare_record_test.go +++ b/builtin/providers/cloudflare/resource_cloudflare_record_test.go @@ -76,7 +76,7 @@ func TestAccCLOudflareRecord_Updated(t *testing.T) { } func testAccCheckCLOudflareRecordDestroy(s *terraform.State) error { - client := testAccProvider.client + client := testAccProvider.Meta().(*cloudflare.Client) for _, rs := range s.RootModule().Resources { if rs.Type != "cloudflare_record" { @@ -127,7 +127,7 @@ func testAccCheckCLOudflareRecordExists(n string, record *cloudflare.Record) res return fmt.Errorf("No Record ID is set") } - client := testAccProvider.client + client := testAccProvider.Meta().(*cloudflare.Client) foundRecord, err := client.RetrieveRecord(rs.Primary.Attributes["domain"], rs.Primary.ID) diff --git a/builtin/providers/cloudflare/resource_provider.go b/builtin/providers/cloudflare/resource_provider.go deleted file mode 100644 index 307498b4d..000000000 --- a/builtin/providers/cloudflare/resource_provider.go +++ /dev/null @@ -1,77 +0,0 @@ -package cloudflare - -import ( - "log" - - "github.com/hashicorp/terraform/helper/config" - "github.com/hashicorp/terraform/terraform" - "github.com/pearkes/cloudflare" -) - -type ResourceProvider struct { - Config Config - - client *cloudflare.Client -} - -func (p *ResourceProvider) Input( - input terraform.UIInput, - c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { - return c, nil -} - -func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) { - v := &config.Validator{ - Required: []string{ - "token", - "email", - }, - } - - return v.Validate(c) -} - -func (p *ResourceProvider) ValidateResource( - t string, c *terraform.ResourceConfig) ([]string, []error) { - return resourceMap.Validate(t, c) -} - -func (p *ResourceProvider) Configure(c *terraform.ResourceConfig) error { - if _, err := config.Decode(&p.Config, c.Config); err != nil { - return err - } - - log.Println("[INFO] Initializing CloudFlare client") - var err error - p.client, err = p.Config.Client() - - if err != nil { - return err - } - - return nil -} - -func (p *ResourceProvider) Apply( - info *terraform.InstanceInfo, - s *terraform.InstanceState, - d *terraform.InstanceDiff) (*terraform.InstanceState, error) { - return resourceMap.Apply(info, s, d, p) -} - -func (p *ResourceProvider) Diff( - info *terraform.InstanceInfo, - s *terraform.InstanceState, - c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { - return resourceMap.Diff(info, s, c, p) -} - -func (p *ResourceProvider) Refresh( - info *terraform.InstanceInfo, - s *terraform.InstanceState) (*terraform.InstanceState, error) { - return resourceMap.Refresh(info, s, p) -} - -func (p *ResourceProvider) Resources() []terraform.ResourceType { - return resourceMap.Resources() -} diff --git a/builtin/providers/cloudflare/resource_provider_test.go b/builtin/providers/cloudflare/resource_provider_test.go deleted file mode 100644 index ab2d7f995..000000000 --- a/builtin/providers/cloudflare/resource_provider_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package cloudflare - -import ( - "os" - "reflect" - "testing" - - "github.com/hashicorp/terraform/config" - "github.com/hashicorp/terraform/terraform" -) - -var testAccProviders map[string]terraform.ResourceProvider -var testAccProvider *ResourceProvider - -func init() { - testAccProvider = new(ResourceProvider) - testAccProviders = map[string]terraform.ResourceProvider{ - "cloudflare": testAccProvider, - } -} - -func TestResourceProvider_impl(t *testing.T) { - var _ terraform.ResourceProvider = new(ResourceProvider) -} - -func TestResourceProvider_Configure(t *testing.T) { - rp := new(ResourceProvider) - var expectedToken string - var expectedEmail string - - if v := os.Getenv("CLOUDFLARE_EMAIL"); v != "" { - expectedEmail = v - } else { - expectedEmail = "foo" - } - - if v := os.Getenv("CLOUDFLARE_TOKEN"); v != "" { - expectedToken = v - } else { - expectedToken = "foo" - } - - raw := map[string]interface{}{ - "token": expectedToken, - "email": expectedEmail, - } - - rawConfig, err := config.NewRawConfig(raw) - if err != nil { - t.Fatalf("err: %s", err) - } - - err = rp.Configure(terraform.NewResourceConfig(rawConfig)) - if err != nil { - t.Fatalf("err: %s", err) - } - - expected := Config{ - Token: expectedToken, - Email: expectedEmail, - } - - if !reflect.DeepEqual(rp.Config, expected) { - t.Fatalf("bad: %#v", rp.Config) - } -} - -func testAccPreCheck(t *testing.T) { - if v := os.Getenv("CLOUDFLARE_EMAIL"); v == "" { - t.Fatal("CLOUDFLARE_EMAIL must be set for acceptance tests") - } - - if v := os.Getenv("CLOUDFLARE_TOKEN"); v == "" { - t.Fatal("CLOUDFLARE_TOKEN must be set for acceptance tests") - } - - if v := os.Getenv("CLOUDFLARE_DOMAIN"); v == "" { - t.Fatal("CLOUDFLARE_DOMAIN must be set for acceptance tests. The domain is used to ` and destroy record against.") - } -} diff --git a/builtin/providers/cloudflare/resources.go b/builtin/providers/cloudflare/resources.go deleted file mode 100644 index 3701f6273..000000000 --- a/builtin/providers/cloudflare/resources.go +++ /dev/null @@ -1,24 +0,0 @@ -package cloudflare - -import ( - "github.com/hashicorp/terraform/helper/resource" -) - -// resourceMap is the mapping of resources we support to their basic -// operations. This makes it easy to implement new resource types. -var resourceMap *resource.Map - -func init() { - resourceMap = &resource.Map{ - Mapping: map[string]resource.Resource{ - "cloudflare_record": resource.Resource{ - ConfigValidator: resource_cloudflare_record_validation(), - Create: resource_cloudflare_record_create, - Destroy: resource_cloudflare_record_destroy, - Diff: resource_cloudflare_record_diff, - Update: resource_cloudflare_record_update, - Refresh: resource_cloudflare_record_refresh, - }, - }, - } -} From 11f024a8e793ce88db440e95bfa3cb767fae9b3c Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Mon, 17 Nov 2014 18:57:41 +0100 Subject: [PATCH 2/2] Removing obsolete struct tags --- builtin/providers/cloudflare/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/providers/cloudflare/config.go b/builtin/providers/cloudflare/config.go index c1433ede2..4e4bb6d2f 100644 --- a/builtin/providers/cloudflare/config.go +++ b/builtin/providers/cloudflare/config.go @@ -8,8 +8,8 @@ import ( ) type Config struct { - Email string `mapstructure:"email"` - Token string `mapstructure:"token"` + Email string + Token string } // Client() returns a new client for accessing cloudflare.