From 5fc308d934dce2cf8da69c8b633d6207acc864e3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 18 Aug 2014 15:49:54 -0700 Subject: [PATCH] providers/heroku: addon --- builtin/providers/heroku/provider.go | 3 +- .../providers/heroku/resource_heroku_addon.go | 155 ++++++++++++++++++ .../heroku/resource_heroku_addon_test.go | 107 ++++++++++++ 3 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/heroku/resource_heroku_addon.go create mode 100644 builtin/providers/heroku/resource_heroku_addon_test.go diff --git a/builtin/providers/heroku/provider.go b/builtin/providers/heroku/provider.go index 1a64525cd..28445a2bb 100644 --- a/builtin/providers/heroku/provider.go +++ b/builtin/providers/heroku/provider.go @@ -23,7 +23,8 @@ func Provider() *schema.Provider { }, ResourcesMap: map[string]*schema.Resource{ - "heroku_app": resourceHerokuApp(), + "heroku_app": resourceHerokuApp(), + "heroku_addon": resourceHerokuAddon(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/heroku/resource_heroku_addon.go b/builtin/providers/heroku/resource_heroku_addon.go new file mode 100644 index 000000000..4c0f14f30 --- /dev/null +++ b/builtin/providers/heroku/resource_heroku_addon.go @@ -0,0 +1,155 @@ +package heroku + +import ( + "fmt" + "log" + "sync" + + "github.com/bgentry/heroku-go" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +// Global lock to prevent parallelism for heroku_addon since +// the Heroku API cannot handle a single application requesting +// multiple addons simultaneously. +var addonLock sync.Mutex + +func resourceHerokuAddon() *schema.Resource { + return &schema.Resource{ + Create: resourceHerokuAddonCreate, + Read: resourceHerokuAddonRead, + Update: resourceHerokuAddonUpdate, + Delete: resourceHerokuAddonDelete, + + Schema: map[string]*schema.Schema{ + "app": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "plan": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "config": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeMap, + }, + }, + + "provider_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "config_vars": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeMap}, + }, + }, + } +} + +func resourceHerokuAddonCreate(d *schema.ResourceData, meta interface{}) error { + addonLock.Lock() + defer addonLock.Unlock() + + client := meta.(*heroku.Client) + + app := d.Get("app").(string) + plan := d.Get("plan").(string) + opts := heroku.AddonCreateOpts{} + + if v := d.Get("config"); v != nil { + config := make(map[string]string) + for _, v := range v.([]interface{}) { + for k, v := range v.(map[string]interface{}) { + config[k] = v.(string) + } + } + + opts.Config = &config + } + + log.Printf("[DEBUG] Addon create configuration: %#v, %#v, %#v", app, plan, opts) + a, err := client.AddonCreate(app, plan, &opts) + if err != nil { + return err + } + + d.SetId(a.Id) + log.Printf("[INFO] Addon ID: %s", d.Id()) + + return resourceHerokuAddonRead(d, meta) +} + +func resourceHerokuAddonRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Client) + + addon, err := resource_heroku_addon_retrieve( + d.Get("app").(string), d.Id(), client) + if err != nil { + return err + } + + d.Set("name", addon.Name) + d.Set("plan", addon.Plan.Name) + d.Set("provider_id", addon.ProviderId) + d.Set("config_vars", []interface{}{addon.ConfigVars}) + d.SetDependencies([]terraform.ResourceDependency{ + terraform.ResourceDependency{ID: d.Get("app").(string)}, + }) + + return nil +} + +func resourceHerokuAddonUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Client) + + app := d.Get("app").(string) + + if d.HasChange("plan") { + ad, err := client.AddonUpdate( + app, d.Id(), d.Get("plan").(string)) + if err != nil { + return err + } + + // Store the new ID + d.SetId(ad.Id) + } + + return resourceHerokuAddonRead(d, meta) +} + +func resourceHerokuAddonDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Client) + + log.Printf("[INFO] Deleting Addon: %s", d.Id()) + + // Destroy the app + err := client.AddonDelete(d.Get("app").(string), d.Id()) + if err != nil { + return fmt.Errorf("Error deleting addon: %s", err) + } + + d.SetId("") + return nil +} + +func resource_heroku_addon_retrieve(app string, id string, client *heroku.Client) (*heroku.Addon, error) { + addon, err := client.AddonInfo(app, id) + + if err != nil { + return nil, fmt.Errorf("Error retrieving addon: %s", err) + } + + return addon, nil +} diff --git a/builtin/providers/heroku/resource_heroku_addon_test.go b/builtin/providers/heroku/resource_heroku_addon_test.go new file mode 100644 index 000000000..9765c6e19 --- /dev/null +++ b/builtin/providers/heroku/resource_heroku_addon_test.go @@ -0,0 +1,107 @@ +package heroku + +import ( + "fmt" + "testing" + + "github.com/bgentry/heroku-go" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccHerokuAddon_Basic(t *testing.T) { + var addon heroku.Addon + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckHerokuAddonDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckHerokuAddonConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckHerokuAddonExists("heroku_addon.foobar", &addon), + testAccCheckHerokuAddonAttributes(&addon), + resource.TestCheckResourceAttr( + "heroku_addon.foobar", "config.0.url", "http://google.com"), + resource.TestCheckResourceAttr( + "heroku_addon.foobar", "app", "terraform-test-app"), + resource.TestCheckResourceAttr( + "heroku_addon.foobar", "plan", "deployhooks:http"), + ), + }, + }, + }) +} + +func testAccCheckHerokuAddonDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*heroku.Client) + + for _, rs := range s.Resources { + if rs.Type != "heroku_addon" { + continue + } + + _, err := client.AddonInfo(rs.Attributes["app"], rs.ID) + + if err == nil { + return fmt.Errorf("Addon still exists") + } + } + + return nil +} + +func testAccCheckHerokuAddonAttributes(addon *heroku.Addon) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if addon.Plan.Name != "deployhooks:http" { + return fmt.Errorf("Bad plan: %s", addon.Plan) + } + + return nil + } +} + +func testAccCheckHerokuAddonExists(n string, addon *heroku.Addon) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.Resources[n] + + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.ID == "" { + return fmt.Errorf("No Addon ID is set") + } + + client := testAccProvider.Meta().(*heroku.Client) + + foundAddon, err := client.AddonInfo(rs.Attributes["app"], rs.ID) + + if err != nil { + return err + } + + if foundAddon.Id != rs.ID { + return fmt.Errorf("Addon not found") + } + + *addon = *foundAddon + + return nil + } +} + +const testAccCheckHerokuAddonConfig_basic = ` +resource "heroku_app" "foobar" { + name = "terraform-test-app" +} + +resource "heroku_addon" "foobar" { + app = "${heroku_app.foobar.name}" + plan = "deployhooks:http" + config { + url = "http://google.com" + } +}`