github.com/meteor/terraform@v0.6.15-0.20210412225145-79ec4bc057c6/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 Schema: map[string]*schema.Schema{ 29 "app": { 30 Type: schema.TypeString, 31 Required: true, 32 ForceNew: true, 33 }, 34 35 "plan": { 36 Type: schema.TypeString, 37 Required: true, 38 }, 39 40 "config": { 41 Type: schema.TypeList, 42 Optional: true, 43 ForceNew: true, 44 Elem: &schema.Schema{ 45 Type: schema.TypeMap, 46 }, 47 }, 48 49 "provider_id": { 50 Type: schema.TypeString, 51 Computed: true, 52 }, 53 54 "config_vars": { 55 Type: schema.TypeList, 56 Computed: true, 57 Elem: &schema.Schema{ 58 Type: schema.TypeString, 59 }, 60 }, 61 }, 62 } 63 } 64 65 func resourceHerokuAddonCreate(d *schema.ResourceData, meta interface{}) error { 66 addonLock.Lock() 67 defer addonLock.Unlock() 68 69 client := meta.(*heroku.Service) 70 71 app := d.Get("app").(string) 72 opts := heroku.AddOnCreateOpts{Plan: d.Get("plan").(string)} 73 74 if v := d.Get("config"); v != nil { 75 config := make(map[string]string) 76 for _, v := range v.([]interface{}) { 77 for k, v := range v.(map[string]interface{}) { 78 config[k] = v.(string) 79 } 80 } 81 82 opts.Config = &config 83 } 84 85 log.Printf("[DEBUG] Addon create configuration: %#v, %#v", app, opts) 86 a, err := client.AddOnCreate(context.TODO(), app, opts) 87 if err != nil { 88 return err 89 } 90 91 d.SetId(a.ID) 92 log.Printf("[INFO] Addon ID: %s", d.Id()) 93 94 // Wait for the Addon to be provisioned 95 log.Printf("[DEBUG] Waiting for Addon (%s) to be provisioned", d.Id()) 96 stateConf := &resource.StateChangeConf{ 97 Pending: []string{"provisioning"}, 98 Target: []string{"provisioned"}, 99 Refresh: AddOnStateRefreshFunc(client, app, d.Id()), 100 Timeout: 20 * time.Minute, 101 } 102 103 if _, err := stateConf.WaitForState(); err != nil { 104 return fmt.Errorf("Error waiting for Addon (%s) to be provisioned: %s", d.Id(), err) 105 } 106 log.Printf("[INFO] Addon provisioned: %s", d.Id()) 107 108 return resourceHerokuAddonRead(d, meta) 109 } 110 111 func resourceHerokuAddonRead(d *schema.ResourceData, meta interface{}) error { 112 client := meta.(*heroku.Service) 113 114 addon, err := resourceHerokuAddonRetrieve( 115 d.Get("app").(string), d.Id(), client) 116 if err != nil { 117 return err 118 } 119 120 // Determine the plan. If we were configured without a specific plan, 121 // then just avoid the plan altogether (accepting anything that 122 // Heroku sends down). 123 plan := addon.Plan.Name 124 if v := d.Get("plan").(string); v != "" { 125 if idx := strings.IndexRune(v, ':'); idx == -1 { 126 idx = strings.IndexRune(plan, ':') 127 if idx > -1 { 128 plan = plan[:idx] 129 } 130 } 131 } 132 133 d.Set("name", addon.Name) 134 d.Set("plan", plan) 135 d.Set("provider_id", addon.ProviderID) 136 if err := d.Set("config_vars", addon.ConfigVars); err != nil { 137 return err 138 } 139 140 return nil 141 } 142 143 func resourceHerokuAddonUpdate(d *schema.ResourceData, meta interface{}) error { 144 client := meta.(*heroku.Service) 145 146 app := d.Get("app").(string) 147 148 if d.HasChange("plan") { 149 ad, err := client.AddOnUpdate( 150 context.TODO(), app, d.Id(), heroku.AddOnUpdateOpts{Plan: d.Get("plan").(string)}) 151 if err != nil { 152 return err 153 } 154 155 // Store the new ID 156 d.SetId(ad.ID) 157 } 158 159 return resourceHerokuAddonRead(d, meta) 160 } 161 162 func resourceHerokuAddonDelete(d *schema.ResourceData, meta interface{}) error { 163 client := meta.(*heroku.Service) 164 165 log.Printf("[INFO] Deleting Addon: %s", d.Id()) 166 167 // Destroy the app 168 _, err := client.AddOnDelete(context.TODO(), d.Get("app").(string), d.Id()) 169 if err != nil { 170 return fmt.Errorf("Error deleting addon: %s", err) 171 } 172 173 d.SetId("") 174 return nil 175 } 176 177 func resourceHerokuAddonRetrieve(app string, id string, client *heroku.Service) (*heroku.AddOnInfoResult, error) { 178 addon, err := client.AddOnInfo(context.TODO(), app, id) 179 180 if err != nil { 181 return nil, fmt.Errorf("Error retrieving addon: %s", err) 182 } 183 184 return addon, nil 185 } 186 187 // AddOnStateRefreshFunc returns a resource.StateRefreshFunc that is used to 188 // watch an AddOn. 189 func AddOnStateRefreshFunc(client *heroku.Service, appID, addOnID string) resource.StateRefreshFunc { 190 return func() (interface{}, string, error) { 191 addon, err := client.AddOnInfo(context.TODO(), appID, addOnID) 192 if err != nil { 193 return nil, "", err 194 } 195 196 // The type conversion here can be dropped when the vendored version of 197 // heroku-go is updated. 198 return (*heroku.AddOn)(addon), addon.State, nil 199 } 200 }