diff --git a/builtin/providers/heroku/provider.go b/builtin/providers/heroku/provider.go new file mode 100644 index 000000000..331d4a1b7 --- /dev/null +++ b/builtin/providers/heroku/provider.go @@ -0,0 +1,42 @@ +package heroku + +import ( + "log" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/mitchellh/mapstructure" +) + +// Provider returns a terraform.ResourceProvider. +func Provider() *schema.Provider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "email": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "api_key": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + "heroku_app": resourceHerokuApp(), + }, + + ConfigureFunc: providerConfigure, + } +} + +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + var config Config + configRaw := d.Get("").(map[string]interface{}) + if err := mapstructure.Decode(configRaw, &config); err != nil { + return nil, err + } + + log.Println("[INFO] Initializing Heroku client") + return config.Client() +} diff --git a/builtin/providers/heroku/resource_provider_test.go b/builtin/providers/heroku/provider_test.go similarity index 65% rename from builtin/providers/heroku/resource_provider_test.go rename to builtin/providers/heroku/provider_test.go index 8585e53cd..f30843e11 100644 --- a/builtin/providers/heroku/resource_provider_test.go +++ b/builtin/providers/heroku/provider_test.go @@ -2,29 +2,35 @@ package heroku import ( "os" - "reflect" "testing" + "github.com/bgentry/heroku-go" "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) var testAccProviders map[string]terraform.ResourceProvider -var testAccProvider *ResourceProvider +var testAccProvider *schema.Provider func init() { - testAccProvider = new(ResourceProvider) + testAccProvider = Provider() testAccProviders = map[string]terraform.ResourceProvider{ "heroku": testAccProvider, } } -func TestResourceProvider_impl(t *testing.T) { - var _ terraform.ResourceProvider = new(ResourceProvider) +func TestProvider(t *testing.T) { + if err := Provider().InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } } -func TestResourceProvider_Configure(t *testing.T) { - rp := new(ResourceProvider) +func TestProvider_impl(t *testing.T) { + var _ terraform.ResourceProvider = Provider() +} + +func TestProviderConfigure(t *testing.T) { var expectedKey string var expectedEmail string @@ -50,18 +56,18 @@ func TestResourceProvider_Configure(t *testing.T) { t.Fatalf("err: %s", err) } + rp := Provider() err = rp.Configure(terraform.NewResourceConfig(rawConfig)) if err != nil { t.Fatalf("err: %s", err) } - expected := Config{ - APIKey: expectedKey, - Email: expectedEmail, + config := rp.Meta().(*heroku.Client) + if config.Username != expectedEmail { + t.Fatalf("bad: %#v", config) } - - if !reflect.DeepEqual(rp.Config, expected) { - t.Fatalf("bad: %#v", rp.Config) + if config.Password != expectedKey { + t.Fatalf("bad: %#v", config) } } diff --git a/builtin/providers/heroku/resource_heroku_addon.go b/builtin/providers/heroku/resource_heroku_addon.go deleted file mode 100644 index 967e8a02f..000000000 --- a/builtin/providers/heroku/resource_heroku_addon.go +++ /dev/null @@ -1,199 +0,0 @@ -package heroku - -import ( - "fmt" - "log" - "sync" - - "github.com/bgentry/heroku-go" - "github.com/hashicorp/terraform/flatmap" - "github.com/hashicorp/terraform/helper/config" - "github.com/hashicorp/terraform/helper/diff" - "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 resource_heroku_addon_create( - s *terraform.ResourceState, - d *terraform.ResourceDiff, - meta interface{}) (*terraform.ResourceState, error) { - addonLock.Lock() - defer addonLock.Unlock() - - p := meta.(*ResourceProvider) - client := p.client - - // Merge the diff into the state so that we have all the attributes - // properly. - rs := s.MergeDiff(d) - - app := rs.Attributes["app"] - plan := rs.Attributes["plan"] - opts := heroku.AddonCreateOpts{} - - if attr, ok := rs.Attributes["config.#"]; ok && attr == "1" { - vs := flatmap.Expand( - rs.Attributes, "config").([]interface{}) - - config := make(map[string]string) - for k, v := range vs[0].(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 s, err - } - - rs.ID = a.Id - log.Printf("[INFO] Addon ID: %s", rs.ID) - - addon, err := resource_heroku_addon_retrieve(app, rs.ID, client) - if err != nil { - return rs, err - } - - return resource_heroku_addon_update_state(rs, addon) -} - -func resource_heroku_addon_update( - s *terraform.ResourceState, - d *terraform.ResourceDiff, - meta interface{}) (*terraform.ResourceState, error) { - p := meta.(*ResourceProvider) - client := p.client - rs := s.MergeDiff(d) - - app := rs.Attributes["app"] - - if attr, ok := d.Attributes["plan"]; ok { - ad, err := client.AddonUpdate( - app, rs.ID, - attr.New) - - if err != nil { - return s, err - } - - // Store the new ID - rs.ID = ad.Id - } - - addon, err := resource_heroku_addon_retrieve(app, rs.ID, client) - - if err != nil { - return rs, err - } - - return resource_heroku_addon_update_state(rs, addon) -} - -func resource_heroku_addon_destroy( - s *terraform.ResourceState, - meta interface{}) error { - p := meta.(*ResourceProvider) - client := p.client - - log.Printf("[INFO] Deleting Addon: %s", s.ID) - - // Destroy the app - err := client.AddonDelete(s.Attributes["app"], s.ID) - - if err != nil { - return fmt.Errorf("Error deleting addon: %s", err) - } - - return nil -} - -func resource_heroku_addon_refresh( - s *terraform.ResourceState, - meta interface{}) (*terraform.ResourceState, error) { - p := meta.(*ResourceProvider) - client := p.client - - app, err := resource_heroku_addon_retrieve(s.Attributes["app"], s.ID, client) - if err != nil { - return nil, err - } - - return resource_heroku_addon_update_state(s, app) -} - -func resource_heroku_addon_diff( - s *terraform.ResourceState, - c *terraform.ResourceConfig, - meta interface{}) (*terraform.ResourceDiff, error) { - - b := &diff.ResourceBuilder{ - Attrs: map[string]diff.AttrType{ - "app": diff.AttrTypeCreate, - "plan": diff.AttrTypeUpdate, - "config": diff.AttrTypeCreate, - }, - - ComputedAttrs: []string{ - "provider_id", - "config_vars", - }, - } - - return b.Diff(s, c) -} - -func resource_heroku_addon_update_state( - s *terraform.ResourceState, - addon *heroku.Addon) (*terraform.ResourceState, error) { - - s.Attributes["name"] = addon.Name - s.Attributes["plan"] = addon.Plan.Name - s.Attributes["provider_id"] = addon.ProviderId - - toFlatten := make(map[string]interface{}) - - if len(addon.ConfigVars) > 0 { - toFlatten["config_vars"] = addon.ConfigVars - } - - for k, v := range flatmap.Flatten(toFlatten) { - s.Attributes[k] = v - } - - s.Dependencies = []terraform.ResourceDependency{ - terraform.ResourceDependency{ID: s.Attributes["app"]}, - } - - return s, 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 -} - -func resource_heroku_addon_validation() *config.Validator { - return &config.Validator{ - Required: []string{ - "app", - "plan", - }, - Optional: []string{ - "config.*", - }, - } -} diff --git a/builtin/providers/heroku/resource_heroku_addon_test.go b/builtin/providers/heroku/resource_heroku_addon_test.go deleted file mode 100644 index 1c099b683..000000000 --- a/builtin/providers/heroku/resource_heroku_addon_test.go +++ /dev/null @@ -1,107 +0,0 @@ -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.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.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" - } -}` diff --git a/builtin/providers/heroku/resource_heroku_app.go b/builtin/providers/heroku/resource_heroku_app.go index 0655daf3a..6f173df5b 100644 --- a/builtin/providers/heroku/resource_heroku_app.go +++ b/builtin/providers/heroku/resource_heroku_app.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform/helper/config" "github.com/hashicorp/terraform/helper/diff" "github.com/hashicorp/terraform/helper/multierror" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) @@ -43,142 +44,165 @@ func (a *application) Update() error { return nil } -func resource_heroku_app_create( - s *terraform.ResourceState, - d *terraform.ResourceDiff, - meta interface{}) (*terraform.ResourceState, error) { - p := meta.(*ResourceProvider) - client := p.client +func resourceHerokuApp() *schema.Resource { + return &schema.Resource{ + Create: resourceHerokuAppCreate, + Read: resourceHerokuAppRead, + Update: resourceHerokuAppUpdate, + Delete: resourceHerokuAppDelete, - // Merge the diff into the state so that we have all the attributes - // properly. - rs := s.MergeDiff(d) + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "region": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "stack": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "config_vars": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + // TODO: make map + Type: schema.TypeString, + }, + }, + + "git_url": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "web_url": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "heroku_hostname": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceHerokuAppCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Client) // Build up our creation options opts := heroku.AppCreateOpts{} - if attr := rs.Attributes["name"]; attr != "" { - opts.Name = &attr + if v := d.Get("name"); v != nil { + vs := v.(string) + opts.Name = &vs } - - if attr := rs.Attributes["region"]; attr != "" { - opts.Region = &attr + if v := d.Get("region"); v != nil { + vs := v.(string) + opts.Region = &vs } - - if attr := rs.Attributes["stack"]; attr != "" { - opts.Stack = &attr + if v := d.Get("stack"); v != nil { + vs := v.(string) + opts.Stack = &vs } log.Printf("[DEBUG] App create configuration: %#v", opts) a, err := client.AppCreate(&opts) if err != nil { - return s, err + return err } - rs.ID = a.Name - log.Printf("[INFO] App ID: %s", rs.ID) + d.SetId(a.Name) + log.Printf("[INFO] App ID: %s", d.Id()) - if attr, ok := rs.Attributes["config_vars.#"]; ok && attr == "1" { - vs := flatmap.Expand( - rs.Attributes, "config_vars").([]interface{}) - - err = update_config_vars(rs.ID, vs, client) + if v := d.Get("config_vars"); v != nil { + err = update_config_vars(d.Id(), v.([]interface{}), client) if err != nil { - return rs, err + return err } } - app, err := resource_heroku_app_retrieve(rs.ID, client) - if err != nil { - return rs, err - } - - return resource_heroku_app_update_state(rs, app) + return resourceHerokuAppRead(d, meta) } -func resource_heroku_app_update( - s *terraform.ResourceState, - d *terraform.ResourceDiff, - meta interface{}) (*terraform.ResourceState, error) { - p := meta.(*ResourceProvider) - client := p.client - rs := s.MergeDiff(d) - - if attr, ok := d.Attributes["name"]; ok { - opts := heroku.AppUpdateOpts{ - Name: &attr.New, - } - - renamedApp, err := client.AppUpdate(rs.ID, &opts) - - if err != nil { - return s, err - } - - // Store the new ID - rs.ID = renamedApp.Name - } - - attr, ok := s.Attributes["config_vars.#"] - - // If the config var block was removed, nuke all config vars - if ok && attr == "1" { - vs := flatmap.Expand( - rs.Attributes, "config_vars").([]interface{}) - - err := update_config_vars(rs.ID, vs, client) - if err != nil { - return rs, err - } - } else if ok && attr == "0" { - log.Println("[INFO] Config vars removed, removing all vars") - - err := update_config_vars(rs.ID, make([]interface{}, 0), client) - - if err != nil { - return rs, err - } - } - - app, err := resource_heroku_app_retrieve(rs.ID, client) +func resourceHerokuAppRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Client) + app, err := resource_heroku_app_retrieve(d.Id(), client) if err != nil { - return rs, err + return err } - return resource_heroku_app_update_state(rs, app) -} + d.Set("name", app.App.Name) + d.Set("stack", app.App.Stack.Name) + d.Set("region", app.App.Region.Name) + d.Set("git_url", app.App.GitURL) + d.Set("web_url", app.App.WebURL) + d.Set("config_vars", []map[string]string{app.Vars}) -func resource_heroku_app_destroy( - s *terraform.ResourceState, - meta interface{}) error { - p := meta.(*ResourceProvider) - client := p.client - - log.Printf("[INFO] Deleting App: %s", s.ID) - - // Destroy the app - err := client.AppDelete(s.ID) - - if err != nil { - return fmt.Errorf("Error deleting App: %s", err) - } + // We know that the hostname on heroku will be the name+herokuapp.com + // You need this to do things like create DNS CNAME records + d.Set("heroku_hostname", fmt.Sprintf("%s.herokuapp.com", app.App.Name)) return nil } -func resource_heroku_app_refresh( - s *terraform.ResourceState, - meta interface{}) (*terraform.ResourceState, error) { - p := meta.(*ResourceProvider) - client := p.client +func resourceHerokuAppUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Client) - app, err := resource_heroku_app_retrieve(s.ID, client) - if err != nil { - return nil, err + // If name changed + // TODO + /* + if attr, ok := d.Attributes["name"]; ok { + opts := heroku.AppUpdateOpts{ + Name: &attr.New, + } + + renamedApp, err := client.AppUpdate(rs.ID, &opts) + + if err != nil { + return s, err + } + + // Store the new ID + rs.ID = renamedApp.Name + } + */ + + v := d.Get("config_vars") + if v == nil { + v = []interface{}{} } - return resource_heroku_app_update_state(s, app) + err := update_config_vars(d.Id(), v.([]interface{}), client) + if err != nil { + return err + } + + return resourceHerokuAppRead(d, meta) +} + +func resourceHerokuAppDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Client) + + log.Printf("[INFO] Deleting App: %s", d.Id()) + err := client.AppDelete(d.Id()) + if err != nil { + return fmt.Errorf("Error deleting App: %s", err) + } + + d.SetId("") + return nil } func resource_heroku_app_diff( @@ -273,21 +297,19 @@ func retrieve_config_vars(id string, client *heroku.Client) (map[string]string, return vars, nil } -// Updates the config vars for from an expanded (prior to assertion) -// []map[string]string config +// Updates the config vars for from an expanded configuration. func update_config_vars(id string, vs []interface{}, client *heroku.Client) error { vars := make(map[string]*string) - for k, v := range vs[0].(map[string]interface{}) { - val := v.(string) - vars[k] = &val + for _, v := range vs { + for k, v := range v.(map[string]interface{}) { + val := v.(string) + vars[k] = &val + } } log.Printf("[INFO] Updating config vars: *%#v", vars) - - _, err := client.ConfigVarUpdate(id, vars) - - if err != nil { + if _, err := client.ConfigVarUpdate(id, vars); err != nil { return fmt.Errorf("Error updating config vars: %s", err) } diff --git a/builtin/providers/heroku/resource_heroku_app_test.go b/builtin/providers/heroku/resource_heroku_app_test.go index ff7c6f110..ba0a08b71 100644 --- a/builtin/providers/heroku/resource_heroku_app_test.go +++ b/builtin/providers/heroku/resource_heroku_app_test.go @@ -103,7 +103,7 @@ func TestAccHerokuApp_NukeVars(t *testing.T) { } func testAccCheckHerokuAppDestroy(s *terraform.State) error { - client := testAccProvider.client + client := testAccProvider.Meta().(*heroku.Client) for _, rs := range s.Resources { if rs.Type != "heroku_app" { @@ -122,7 +122,7 @@ func testAccCheckHerokuAppDestroy(s *terraform.State) error { func testAccCheckHerokuAppAttributes(app *heroku.App) resource.TestCheckFunc { return func(s *terraform.State) error { - client := testAccProvider.client + client := testAccProvider.Meta().(*heroku.Client) if app.Region.Name != "us" { return fmt.Errorf("Bad region: %s", app.Region.Name) @@ -151,7 +151,7 @@ func testAccCheckHerokuAppAttributes(app *heroku.App) resource.TestCheckFunc { func testAccCheckHerokuAppAttributesUpdated(app *heroku.App) resource.TestCheckFunc { return func(s *terraform.State) error { - client := testAccProvider.client + client := testAccProvider.Meta().(*heroku.Client) if app.Name != "terraform-test-renamed" { return fmt.Errorf("Bad name: %s", app.Name) @@ -178,7 +178,7 @@ func testAccCheckHerokuAppAttributesUpdated(app *heroku.App) resource.TestCheckF func testAccCheckHerokuAppAttributesNoVars(app *heroku.App) resource.TestCheckFunc { return func(s *terraform.State) error { - client := testAccProvider.client + client := testAccProvider.Meta().(*heroku.Client) if app.Name != "terraform-test-app" { return fmt.Errorf("Bad name: %s", app.Name) @@ -209,7 +209,7 @@ func testAccCheckHerokuAppExists(n string, app *heroku.App) resource.TestCheckFu return fmt.Errorf("No App Name is set") } - client := testAccProvider.client + client := testAccProvider.Meta().(*heroku.Client) foundApp, err := client.AppInfo(rs.ID) diff --git a/builtin/providers/heroku/resource_heroku_domain.go b/builtin/providers/heroku/resource_heroku_domain.go deleted file mode 100644 index 6f00fdf55..000000000 --- a/builtin/providers/heroku/resource_heroku_domain.go +++ /dev/null @@ -1,126 +0,0 @@ -package heroku - -import ( - "fmt" - "log" - - "github.com/bgentry/heroku-go" - "github.com/hashicorp/terraform/helper/config" - "github.com/hashicorp/terraform/helper/diff" - "github.com/hashicorp/terraform/terraform" -) - -func resource_heroku_domain_create( - s *terraform.ResourceState, - d *terraform.ResourceDiff, - meta interface{}) (*terraform.ResourceState, error) { - p := meta.(*ResourceProvider) - client := p.client - - // Merge the diff into the state so that we have all the attributes - // properly. - rs := s.MergeDiff(d) - - app := rs.Attributes["app"] - hostname := rs.Attributes["hostname"] - - log.Printf("[DEBUG] Domain create configuration: %#v, %#v", app, hostname) - - do, err := client.DomainCreate(app, hostname) - - if err != nil { - return s, err - } - - rs.ID = do.Id - rs.Attributes["hostname"] = do.Hostname - rs.Attributes["cname"] = fmt.Sprintf("%s.herokuapp.com", app) - - log.Printf("[INFO] Domain ID: %s", rs.ID) - - return rs, nil -} - -func resource_heroku_domain_update( - s *terraform.ResourceState, - d *terraform.ResourceDiff, - meta interface{}) (*terraform.ResourceState, error) { - - panic("Cannot update domain") - - return nil, nil -} - -func resource_heroku_domain_destroy( - s *terraform.ResourceState, - meta interface{}) error { - p := meta.(*ResourceProvider) - client := p.client - - log.Printf("[INFO] Deleting Domain: %s", s.ID) - - // Destroy the app - err := client.DomainDelete(s.Attributes["app"], s.ID) - - if err != nil { - return fmt.Errorf("Error deleting domain: %s", err) - } - - return nil -} - -func resource_heroku_domain_refresh( - s *terraform.ResourceState, - meta interface{}) (*terraform.ResourceState, error) { - p := meta.(*ResourceProvider) - client := p.client - - domain, err := resource_heroku_domain_retrieve(s.Attributes["app"], s.ID, client) - if err != nil { - return nil, err - } - - s.Attributes["hostname"] = domain.Hostname - s.Attributes["cname"] = fmt.Sprintf("%s.herokuapp.com", s.Attributes["app"]) - - return s, nil -} - -func resource_heroku_domain_diff( - s *terraform.ResourceState, - c *terraform.ResourceConfig, - meta interface{}) (*terraform.ResourceDiff, error) { - - b := &diff.ResourceBuilder{ - Attrs: map[string]diff.AttrType{ - "hostname": diff.AttrTypeCreate, - "app": diff.AttrTypeCreate, - }, - - ComputedAttrs: []string{ - "cname", - }, - } - - return b.Diff(s, c) -} - -func resource_heroku_domain_retrieve(app string, id string, client *heroku.Client) (*heroku.Domain, error) { - domain, err := client.DomainInfo(app, id) - - if err != nil { - return nil, fmt.Errorf("Error retrieving domain: %s", err) - } - - return domain, nil -} - -func resource_heroku_domain_validation() *config.Validator { - return &config.Validator{ - Required: []string{ - "hostname", - "app", - }, - Optional: []string{}, - } -} diff --git a/builtin/providers/heroku/resource_heroku_domain_test.go b/builtin/providers/heroku/resource_heroku_domain_test.go deleted file mode 100644 index 315881690..000000000 --- a/builtin/providers/heroku/resource_heroku_domain_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package heroku - -import ( - "fmt" - "testing" - - "github.com/bgentry/heroku-go" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" -) - -func TestAccHerokuDomain_Basic(t *testing.T) { - var domain heroku.Domain - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckHerokuDomainDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccCheckHerokuDomainConfig_basic, - Check: resource.ComposeTestCheckFunc( - testAccCheckHerokuDomainExists("heroku_domain.foobar", &domain), - testAccCheckHerokuDomainAttributes(&domain), - resource.TestCheckResourceAttr( - "heroku_domain.foobar", "hostname", "terraform.example.com"), - resource.TestCheckResourceAttr( - "heroku_domain.foobar", "app", "terraform-test-app"), - resource.TestCheckResourceAttr( - "heroku_domain.foobar", "cname", "terraform-test-app.herokuapp.com"), - ), - }, - }, - }) -} - -func testAccCheckHerokuDomainDestroy(s *terraform.State) error { - client := testAccProvider.client - - for _, rs := range s.Resources { - if rs.Type != "heroku_domain" { - continue - } - - _, err := client.DomainInfo(rs.Attributes["app"], rs.ID) - - if err == nil { - return fmt.Errorf("Domain still exists") - } - } - - return nil -} - -func testAccCheckHerokuDomainAttributes(Domain *heroku.Domain) resource.TestCheckFunc { - return func(s *terraform.State) error { - - if Domain.Hostname != "terraform.example.com" { - return fmt.Errorf("Bad hostname: %s", Domain.Hostname) - } - - return nil - } -} - -func testAccCheckHerokuDomainExists(n string, Domain *heroku.Domain) 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 Domain ID is set") - } - - client := testAccProvider.client - - foundDomain, err := client.DomainInfo(rs.Attributes["app"], rs.ID) - - if err != nil { - return err - } - - if foundDomain.Id != rs.ID { - return fmt.Errorf("Domain not found") - } - - *Domain = *foundDomain - - return nil - } -} - -const testAccCheckHerokuDomainConfig_basic = ` -resource "heroku_app" "foobar" { - name = "terraform-test-app" -} - -resource "heroku_domain" "foobar" { - app = "${heroku_app.foobar.name}" - hostname = "terraform.example.com" -}` diff --git a/builtin/providers/heroku/resource_heroku_drain.go b/builtin/providers/heroku/resource_heroku_drain.go deleted file mode 100644 index 908125eea..000000000 --- a/builtin/providers/heroku/resource_heroku_drain.go +++ /dev/null @@ -1,126 +0,0 @@ -package heroku - -import ( - "fmt" - "log" - - "github.com/bgentry/heroku-go" - "github.com/hashicorp/terraform/helper/config" - "github.com/hashicorp/terraform/helper/diff" - "github.com/hashicorp/terraform/terraform" -) - -func resource_heroku_drain_create( - s *terraform.ResourceState, - d *terraform.ResourceDiff, - meta interface{}) (*terraform.ResourceState, error) { - p := meta.(*ResourceProvider) - client := p.client - - // Merge the diff into the state so that we have all the attributes - // properly. - rs := s.MergeDiff(d) - - app := rs.Attributes["app"] - url := rs.Attributes["url"] - - log.Printf("[DEBUG] Drain create configuration: %#v, %#v", app, url) - - dr, err := client.LogDrainCreate(app, url) - - if err != nil { - return s, err - } - - rs.ID = dr.Id - rs.Attributes["url"] = dr.URL - rs.Attributes["token"] = dr.Token - - log.Printf("[INFO] Drain ID: %s", rs.ID) - - return rs, nil -} - -func resource_heroku_drain_update( - s *terraform.ResourceState, - d *terraform.ResourceDiff, - meta interface{}) (*terraform.ResourceState, error) { - - panic("Cannot update drain") - - return nil, nil -} - -func resource_heroku_drain_destroy( - s *terraform.ResourceState, - meta interface{}) error { - p := meta.(*ResourceProvider) - client := p.client - - log.Printf("[INFO] Deleting drain: %s", s.ID) - - // Destroy the app - err := client.LogDrainDelete(s.Attributes["app"], s.ID) - - if err != nil { - return fmt.Errorf("Error deleting drain: %s", err) - } - - return nil -} - -func resource_heroku_drain_refresh( - s *terraform.ResourceState, - meta interface{}) (*terraform.ResourceState, error) { - p := meta.(*ResourceProvider) - client := p.client - - drain, err := resource_heroku_drain_retrieve(s.Attributes["app"], s.ID, client) - if err != nil { - return nil, err - } - - s.Attributes["url"] = drain.URL - s.Attributes["token"] = drain.Token - - return s, nil -} - -func resource_heroku_drain_diff( - s *terraform.ResourceState, - c *terraform.ResourceConfig, - meta interface{}) (*terraform.ResourceDiff, error) { - - b := &diff.ResourceBuilder{ - Attrs: map[string]diff.AttrType{ - "url": diff.AttrTypeCreate, - "app": diff.AttrTypeCreate, - }, - - ComputedAttrs: []string{ - "token", - }, - } - - return b.Diff(s, c) -} - -func resource_heroku_drain_retrieve(app string, id string, client *heroku.Client) (*heroku.LogDrain, error) { - drain, err := client.LogDrainInfo(app, id) - - if err != nil { - return nil, fmt.Errorf("Error retrieving drain: %s", err) - } - - return drain, nil -} - -func resource_heroku_drain_validation() *config.Validator { - return &config.Validator{ - Required: []string{ - "url", - "app", - }, - Optional: []string{}, - } -} diff --git a/builtin/providers/heroku/resource_heroku_drain_test.go b/builtin/providers/heroku/resource_heroku_drain_test.go deleted file mode 100644 index 9a848ac98..000000000 --- a/builtin/providers/heroku/resource_heroku_drain_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package heroku - -import ( - "fmt" - "testing" - - "github.com/bgentry/heroku-go" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" -) - -func TestAccHerokuDrain_Basic(t *testing.T) { - var drain heroku.LogDrain - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckHerokuDrainDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccCheckHerokuDrainConfig_basic, - Check: resource.ComposeTestCheckFunc( - testAccCheckHerokuDrainExists("heroku_drain.foobar", &drain), - testAccCheckHerokuDrainAttributes(&drain), - resource.TestCheckResourceAttr( - "heroku_drain.foobar", "url", "syslog://terraform.example.com:1234"), - resource.TestCheckResourceAttr( - "heroku_drain.foobar", "app", "terraform-test-app"), - ), - }, - }, - }) -} - -func testAccCheckHerokuDrainDestroy(s *terraform.State) error { - client := testAccProvider.client - - for _, rs := range s.Resources { - if rs.Type != "heroku_drain" { - continue - } - - _, err := client.LogDrainInfo(rs.Attributes["app"], rs.ID) - - if err == nil { - return fmt.Errorf("Drain still exists") - } - } - - return nil -} - -func testAccCheckHerokuDrainAttributes(Drain *heroku.LogDrain) resource.TestCheckFunc { - return func(s *terraform.State) error { - - if Drain.URL != "syslog://terraform.example.com:1234" { - return fmt.Errorf("Bad URL: %s", Drain.URL) - } - - if Drain.Token == "" { - return fmt.Errorf("No token: %#v", Drain) - } - - return nil - } -} - -func testAccCheckHerokuDrainExists(n string, Drain *heroku.LogDrain) 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 Drain ID is set") - } - - client := testAccProvider.client - - foundDrain, err := client.LogDrainInfo(rs.Attributes["app"], rs.ID) - - if err != nil { - return err - } - - if foundDrain.Id != rs.ID { - return fmt.Errorf("Drain not found") - } - - *Drain = *foundDrain - - return nil - } -} - -const testAccCheckHerokuDrainConfig_basic = ` -resource "heroku_app" "foobar" { - name = "terraform-test-app" -} - -resource "heroku_drain" "foobar" { - app = "${heroku_app.foobar.name}" - url = "syslog://terraform.example.com:1234" -}` diff --git a/builtin/providers/heroku/resource_provider.go b/builtin/providers/heroku/resource_provider.go deleted file mode 100644 index d15067cc2..000000000 --- a/builtin/providers/heroku/resource_provider.go +++ /dev/null @@ -1,68 +0,0 @@ -package heroku - -import ( - "log" - - "github.com/bgentry/heroku-go" - "github.com/hashicorp/terraform/helper/config" - "github.com/hashicorp/terraform/terraform" -) - -type ResourceProvider struct { - Config Config - - client *heroku.Client -} - -func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) { - v := &config.Validator{ - Required: []string{ - "email", - "api_key", - }, - } - - 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 Heroku client") - var err error - p.client, err = p.Config.Client() - - if err != nil { - return err - } - - return nil -} - -func (p *ResourceProvider) Apply( - s *terraform.ResourceState, - d *terraform.ResourceDiff) (*terraform.ResourceState, error) { - return resourceMap.Apply(s, d, p) -} - -func (p *ResourceProvider) Diff( - s *terraform.ResourceState, - c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) { - return resourceMap.Diff(s, c, p) -} - -func (p *ResourceProvider) Refresh( - s *terraform.ResourceState) (*terraform.ResourceState, error) { - return resourceMap.Refresh(s, p) -} - -func (p *ResourceProvider) Resources() []terraform.ResourceType { - return resourceMap.Resources() -} diff --git a/builtin/providers/heroku/resources.go b/builtin/providers/heroku/resources.go deleted file mode 100644 index 4c47632f8..000000000 --- a/builtin/providers/heroku/resources.go +++ /dev/null @@ -1,49 +0,0 @@ -package heroku - -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{ - "heroku_addon": resource.Resource{ - ConfigValidator: resource_heroku_addon_validation(), - Create: resource_heroku_addon_create, - Destroy: resource_heroku_addon_destroy, - Diff: resource_heroku_addon_diff, - Refresh: resource_heroku_addon_refresh, - Update: resource_heroku_addon_update, - }, - - "heroku_app": resource.Resource{ - ConfigValidator: resource_heroku_app_validation(), - Create: resource_heroku_app_create, - Destroy: resource_heroku_app_destroy, - Diff: resource_heroku_app_diff, - Refresh: resource_heroku_app_refresh, - Update: resource_heroku_app_update, - }, - - "heroku_domain": resource.Resource{ - ConfigValidator: resource_heroku_domain_validation(), - Create: resource_heroku_domain_create, - Destroy: resource_heroku_domain_destroy, - Diff: resource_heroku_domain_diff, - Refresh: resource_heroku_domain_refresh, - }, - - "heroku_drain": resource.Resource{ - ConfigValidator: resource_heroku_drain_validation(), - Create: resource_heroku_drain_create, - Destroy: resource_heroku_drain_destroy, - Diff: resource_heroku_drain_diff, - Refresh: resource_heroku_drain_refresh, - }, - }, - } -} diff --git a/helper/schema/provider.go b/helper/schema/provider.go index 6d196a441..a55ea1f35 100644 --- a/helper/schema/provider.go +++ b/helper/schema/provider.go @@ -1,6 +1,7 @@ package schema import ( + "errors" "fmt" "sort" @@ -27,6 +28,36 @@ type Provider struct { // the subsequent resources as the meta parameter. type ConfigureFunc func(*ResourceData) (interface{}, error) +// InternalValidate should be called to validate the structure +// of the provider. +// +// This should be called in a unit test for any provider to verify +// before release that a provider is properly configured for use with +// this library. +func (p *Provider) InternalValidate() error { + if p == nil { + return errors.New("provider is nil") + } + + if err := schemaMap(p.Schema).InternalValidate(); err != nil { + return err + } + + for k, r := range p.ResourcesMap { + if err := r.InternalValidate(); err != nil { + return fmt.Errorf("%s: %s", k, err) + } + } + + return nil +} + +// Meta returns the metadata associated with this provider that was +// returned by the Configure call. It will be nil until Configure is called. +func (p *Provider) Meta() interface{} { + return p.meta +} + // Validate validates the provider configuration against the schema. func (p *Provider) Validate(c *terraform.ResourceConfig) ([]string, []error) { return schemaMap(p.Schema).Validate(c) diff --git a/helper/schema/resource_data.go b/helper/schema/resource_data.go index 18b81f251..1f2f48e96 100644 --- a/helper/schema/resource_data.go +++ b/helper/schema/resource_data.go @@ -46,6 +46,21 @@ func (d *ResourceData) Set(key string, value interface{}) error { return d.setObject("", parts, d.schema, value) } +// Id returns the ID of the resource. +func (d *ResourceData) Id() string { + var result string + + if d.state != nil { + result = d.state.ID + } + + if d.newState != nil { + result = d.newState.ID + } + + return result +} + // SetId sets the ID of the resource. If the value is blank, then the // resource is destroyed. func (d *ResourceData) SetId(v string) { @@ -60,16 +75,9 @@ func (d *ResourceData) SetId(v string) { // calls. func (d *ResourceData) State() *terraform.ResourceState { var result terraform.ResourceState + result.ID = d.Id() result.Attributes = d.stateObject("", d.schema) - if d.state != nil { - result.ID = d.state.ID - } - - if d.newState != nil { - result.ID = d.newState.ID - } - return &result }