github.com/bradfeehan/terraform@v0.7.0-rc3.0.20170529055808-34b45c5ad841/builtin/providers/heroku/resource_heroku_addon.go (about) 1 package heroku 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/cyberdelia/heroku-go/v3" 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 ) 15 16 // Global lock to prevent parallelism for heroku_addon since 17 // the Heroku API cannot handle a single application requesting 18 // multiple addons simultaneously. 19 var addonLock sync.Mutex 20 21 func resourceHerokuAddon() *schema.Resource { 22 return &schema.Resource{ 23 Create: resourceHerokuAddonCreate, 24 Read: resourceHerokuAddonRead, 25 Update: resourceHerokuAddonUpdate, 26 Delete: resourceHerokuAddonDelete, 27 28 Importer: &schema.ResourceImporter{ 29 State: schema.ImportStatePassthrough, 30 }, 31 32 Schema: map[string]*schema.Schema{ 33 "app": { 34 Type: schema.TypeString, 35 Required: true, 36 ForceNew: true, 37 }, 38 39 "plan": { 40 Type: schema.TypeString, 41 Required: true, 42 }, 43 44 "config": { 45 Type: schema.TypeList, 46 Optional: true, 47 ForceNew: true, 48 Elem: &schema.Schema{ 49 Type: schema.TypeMap, 50 }, 51 }, 52 53 "provider_id": { 54 Type: schema.TypeString, 55 Computed: true, 56 }, 57 58 "config_vars": { 59 Type: schema.TypeList, 60 Computed: true, 61 Elem: &schema.Schema{ 62 Type: schema.TypeString, 63 }, 64 }, 65 }, 66 } 67 } 68 69 func resourceHerokuAddonCreate(d *schema.ResourceData, meta interface{}) error { 70 addonLock.Lock() 71 defer addonLock.Unlock() 72 73 client := meta.(*heroku.Service) 74 75 app := d.Get("app").(string) 76 opts := heroku.AddOnCreateOpts{Plan: d.Get("plan").(string)} 77 78 if v := d.Get("config"); v != nil { 79 config := make(map[string]string) 80 for _, v := range v.([]interface{}) { 81 for k, v := range v.(map[string]interface{}) { 82 config[k] = v.(string) 83 } 84 } 85 86 opts.Config = &config 87 } 88 89 log.Printf("[DEBUG] Addon create configuration: %#v, %#v", app, opts) 90 a, err := client.AddOnCreate(context.TODO(), app, opts) 91 if err != nil { 92 return err 93 } 94 95 d.SetId(a.ID) 96 log.Printf("[INFO] Addon ID: %s", d.Id()) 97 98 // Wait for the Addon to be provisioned 99 log.Printf("[DEBUG] Waiting for Addon (%s) to be provisioned", d.Id()) 100 stateConf := &resource.StateChangeConf{ 101 Pending: []string{"provisioning"}, 102 Target: []string{"provisioned"}, 103 Refresh: AddOnStateRefreshFunc(client, app, d.Id()), 104 Timeout: 20 * time.Minute, 105 } 106 107 if _, err := stateConf.WaitForState(); err != nil { 108 return fmt.Errorf("Error waiting for Addon (%s) to be provisioned: %s", d.Id(), err) 109 } 110 log.Printf("[INFO] Addon provisioned: %s", d.Id()) 111 112 return resourceHerokuAddonRead(d, meta) 113 } 114 115 func resourceHerokuAddonRead(d *schema.ResourceData, meta interface{}) error { 116 client := meta.(*heroku.Service) 117 118 addon, err := resourceHerokuAddonRetrieve(d.Id(), client) 119 if err != nil { 120 return err 121 } 122 123 // Determine the plan. If we were configured without a specific plan, 124 // then just avoid the plan altogether (accepting anything that 125 // Heroku sends down). 126 plan := addon.Plan.Name 127 if v := d.Get("plan").(string); v != "" { 128 if idx := strings.IndexRune(v, ':'); idx == -1 { 129 idx = strings.IndexRune(plan, ':') 130 if idx > -1 { 131 plan = plan[:idx] 132 } 133 } 134 } 135 136 d.Set("name", addon.Name) 137 d.Set("plan", plan) 138 d.Set("provider_id", addon.ProviderID) 139 if err := d.Set("config_vars", addon.ConfigVars); err != nil { 140 return err 141 } 142 143 return nil 144 } 145 146 func resourceHerokuAddonUpdate(d *schema.ResourceData, meta interface{}) error { 147 client := meta.(*heroku.Service) 148 149 app := d.Get("app").(string) 150 151 if d.HasChange("plan") { 152 ad, err := client.AddOnUpdate( 153 context.TODO(), app, d.Id(), heroku.AddOnUpdateOpts{Plan: d.Get("plan").(string)}) 154 if err != nil { 155 return err 156 } 157 158 // Store the new ID 159 d.SetId(ad.ID) 160 } 161 162 return resourceHerokuAddonRead(d, meta) 163 } 164 165 func resourceHerokuAddonDelete(d *schema.ResourceData, meta interface{}) error { 166 client := meta.(*heroku.Service) 167 168 log.Printf("[INFO] Deleting Addon: %s", d.Id()) 169 170 // Destroy the app 171 _, err := client.AddOnDelete(context.TODO(), d.Get("app").(string), d.Id()) 172 if err != nil { 173 return fmt.Errorf("Error deleting addon: %s", err) 174 } 175 176 d.SetId("") 177 return nil 178 } 179 180 func resourceHerokuAddonRetrieve(id string, client *heroku.Service) (*heroku.AddOn, error) { 181 addon, err := client.AddOnInfo(context.TODO(), id) 182 183 if err != nil { 184 return nil, fmt.Errorf("Error retrieving addon: %s", err) 185 } 186 187 return addon, nil 188 } 189 190 func resourceHerokuAddonRetrieveByApp(app string, id string, client *heroku.Service) (*heroku.AddOn, error) { 191 addon, err := client.AddOnInfoByApp(context.TODO(), app, id) 192 193 if err != nil { 194 return nil, fmt.Errorf("Error retrieving addon: %s", err) 195 } 196 197 return addon, nil 198 } 199 200 // AddOnStateRefreshFunc returns a resource.StateRefreshFunc that is used to 201 // watch an AddOn. 202 func AddOnStateRefreshFunc(client *heroku.Service, appID, addOnID string) resource.StateRefreshFunc { 203 return func() (interface{}, string, error) { 204 addon, err := resourceHerokuAddonRetrieveByApp(appID, addOnID, client) 205 206 if err != nil { 207 return nil, "", err 208 } 209 210 // The type conversion here can be dropped when the vendored version of 211 // heroku-go is updated. 212 return (*heroku.AddOn)(addon), addon.State, nil 213 } 214 }