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  }