terraform/builtin/providers/heroku/resource_heroku_addon.go

212 lines
5.0 KiB
Go

package heroku
import (
"context"
"fmt"
"log"
"strings"
"sync"
"time"
"github.com/cyberdelia/heroku-go/v3"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
// 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": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"plan": {
Type: schema.TypeString,
Required: true,
},
"config": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeMap,
},
},
"provider_id": {
Type: schema.TypeString,
Computed: true,
},
"config_vars": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}
func resourceHerokuAddonCreate(d *schema.ResourceData, meta interface{}) error {
addonLock.Lock()
defer addonLock.Unlock()
client := meta.(*heroku.Service)
app := d.Get("app").(string)
opts := heroku.AddOnCreateOpts{Plan: d.Get("plan").(string)}
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", app, opts)
a, err := client.AddOnCreate(context.TODO(), app, opts)
if err != nil {
return err
}
d.SetId(a.ID)
log.Printf("[INFO] Addon ID: %s", d.Id())
// Wait for the Addon to be provisioned
log.Printf("[DEBUG] Waiting for Addon (%s) to be provisioned", d.Id())
stateConf := &resource.StateChangeConf{
Pending: []string{"provisioning"},
Target: []string{"provisioned"},
Refresh: AddOnStateRefreshFunc(client, app, d.Id()),
Timeout: 20 * time.Minute,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf("Error waiting for Addon (%s) to be provisioned: %s", d.Id(), err)
}
log.Printf("[INFO] Addon provisioned: %s", d.Id())
return resourceHerokuAddonRead(d, meta)
}
func resourceHerokuAddonRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*heroku.Service)
addon, err := resourceHerokuAddonRetrieveByApp(
d.Get("app").(string), d.Id(), client)
if err != nil {
return err
}
// Determine the plan. If we were configured without a specific plan,
// then just avoid the plan altogether (accepting anything that
// Heroku sends down).
plan := addon.Plan.Name
if v := d.Get("plan").(string); v != "" {
if idx := strings.IndexRune(v, ':'); idx == -1 {
idx = strings.IndexRune(plan, ':')
if idx > -1 {
plan = plan[:idx]
}
}
}
d.Set("name", addon.Name)
d.Set("plan", plan)
d.Set("provider_id", addon.ProviderID)
if err := d.Set("config_vars", addon.ConfigVars); err != nil {
return err
}
return nil
}
func resourceHerokuAddonUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*heroku.Service)
app := d.Get("app").(string)
if d.HasChange("plan") {
ad, err := client.AddOnUpdate(
context.TODO(), app, d.Id(), heroku.AddOnUpdateOpts{Plan: 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.Service)
log.Printf("[INFO] Deleting Addon: %s", d.Id())
// Destroy the app
_, err := client.AddOnDelete(context.TODO(), d.Get("app").(string), d.Id())
if err != nil {
return fmt.Errorf("Error deleting addon: %s", err)
}
d.SetId("")
return nil
}
func resourceHerokuAddonRetrieve(id string, client *heroku.Service) (*heroku.AddOn, error) {
addon, err := client.AddOnInfo(context.TODO(), id)
if err != nil {
return nil, fmt.Errorf("Error retrieving addon: %s", err)
}
return addon, nil
}
func resourceHerokuAddonRetrieveByApp(app string, id string, client *heroku.Service) (*heroku.AddOn, error) {
addon, err := client.AddOnInfoByApp(context.TODO(), app, id)
if err != nil {
return nil, fmt.Errorf("Error retrieving addon: %s", err)
}
return addon, nil
}
// AddOnStateRefreshFunc returns a resource.StateRefreshFunc that is used to
// watch an AddOn.
func AddOnStateRefreshFunc(client *heroku.Service, appID, addOnID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
addon, err := resourceHerokuAddonRetrieveByApp(appID, addOnID, client)
if err != nil {
return nil, "", err
}
// The type conversion here can be dropped when the vendored version of
// heroku-go is updated.
return (*heroku.AddOn)(addon), addon.State, nil
}
}