github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/builtin/providers/heroku/resource_heroku_app.go (about)

     1  package heroku
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/cyberdelia/heroku-go/v3"
     8  	"github.com/hashicorp/go-multierror"
     9  	"github.com/hashicorp/terraform/helper/schema"
    10  )
    11  
    12  // type application is used to store all the details of a heroku app
    13  type application struct {
    14  	Id string // Id of the resource
    15  
    16  	App    *heroku.App       // The heroku application
    17  	Client *heroku.Service   // Client to interact with the heroku API
    18  	Vars   map[string]string // The vars on the application
    19  }
    20  
    21  // Updates the application to have the latest from remote
    22  func (a *application) Update() error {
    23  	var errs []error
    24  	var err error
    25  
    26  	a.App, err = a.Client.AppInfo(a.Id)
    27  	if err != nil {
    28  		errs = append(errs, err)
    29  	}
    30  
    31  	a.Vars, err = retrieveConfigVars(a.Id, a.Client)
    32  	if err != nil {
    33  		errs = append(errs, err)
    34  	}
    35  
    36  	if len(errs) > 0 {
    37  		return &multierror.Error{Errors: errs}
    38  	}
    39  
    40  	return nil
    41  }
    42  
    43  func resourceHerokuApp() *schema.Resource {
    44  	return &schema.Resource{
    45  		Create: switchHerokuAppCreate,
    46  		Read:   resourceHerokuAppRead,
    47  		Update: resourceHerokuAppUpdate,
    48  		Delete: resourceHerokuAppDelete,
    49  
    50  		Schema: map[string]*schema.Schema{
    51  			"name": &schema.Schema{
    52  				Type:     schema.TypeString,
    53  				Required: true,
    54  			},
    55  
    56  			"region": &schema.Schema{
    57  				Type:     schema.TypeString,
    58  				Required: true,
    59  				ForceNew: true,
    60  			},
    61  
    62  			"stack": &schema.Schema{
    63  				Type:     schema.TypeString,
    64  				Optional: true,
    65  				Computed: true,
    66  				ForceNew: true,
    67  			},
    68  
    69  			"config_vars": &schema.Schema{
    70  				Type:     schema.TypeList,
    71  				Optional: true,
    72  				Elem: &schema.Schema{
    73  					Type: schema.TypeMap,
    74  				},
    75  			},
    76  
    77  			"all_config_vars": &schema.Schema{
    78  				Type:     schema.TypeMap,
    79  				Computed: true,
    80  			},
    81  
    82  			"git_url": &schema.Schema{
    83  				Type:     schema.TypeString,
    84  				Computed: true,
    85  			},
    86  
    87  			"web_url": &schema.Schema{
    88  				Type:     schema.TypeString,
    89  				Computed: true,
    90  			},
    91  
    92  			"heroku_hostname": &schema.Schema{
    93  				Type:     schema.TypeString,
    94  				Computed: true,
    95  			},
    96  
    97  			"organization": &schema.Schema{
    98  				Description: "Name of Organization to create application in. Leave blank for personal apps.",
    99  				Type:        schema.TypeList,
   100  				Optional:    true,
   101  				ForceNew:    true,
   102  				Elem: &schema.Resource{
   103  					Schema: map[string]*schema.Schema{
   104  						"name": &schema.Schema{
   105  							Type:     schema.TypeString,
   106  							Required: true,
   107  						},
   108  
   109  						"locked": &schema.Schema{
   110  							Type:     schema.TypeBool,
   111  							Optional: true,
   112  						},
   113  
   114  						"personal": &schema.Schema{
   115  							Type:     schema.TypeBool,
   116  							Optional: true,
   117  						},
   118  					},
   119  				},
   120  			},
   121  		},
   122  	}
   123  }
   124  
   125  func switchHerokuAppCreate(d *schema.ResourceData, meta interface{}) error {
   126  	orgCount := d.Get("organization.#").(int)
   127  	if orgCount > 1 {
   128  		return fmt.Errorf("Error Creating Heroku App: Only 1 Heroku Organization is permitted")
   129  	}
   130  
   131  	if _, ok := d.GetOk("organization.0.name"); ok {
   132  		return resourceHerokuOrgAppCreate(d, meta)
   133  	} else {
   134  		return resourceHerokuAppCreate(d, meta)
   135  	}
   136  }
   137  
   138  func resourceHerokuAppCreate(d *schema.ResourceData, meta interface{}) error {
   139  	client := meta.(*heroku.Service)
   140  
   141  	// Build up our creation options
   142  	opts := heroku.AppCreateOpts{}
   143  
   144  	if v, ok := d.GetOk("name"); ok {
   145  		vs := v.(string)
   146  		log.Printf("[DEBUG] App name: %s", vs)
   147  		opts.Name = &vs
   148  	}
   149  	if v, ok := d.GetOk("region"); ok {
   150  		vs := v.(string)
   151  		log.Printf("[DEBUG] App region: %s", vs)
   152  		opts.Region = &vs
   153  	}
   154  	if v, ok := d.GetOk("stack"); ok {
   155  		vs := v.(string)
   156  		log.Printf("[DEBUG] App stack: %s", vs)
   157  		opts.Stack = &vs
   158  	}
   159  
   160  	log.Printf("[DEBUG] Creating Heroku app...")
   161  	a, err := client.AppCreate(opts)
   162  	if err != nil {
   163  		return err
   164  	}
   165  
   166  	d.SetId(a.Name)
   167  	log.Printf("[INFO] App ID: %s", d.Id())
   168  
   169  	if v, ok := d.GetOk("config_vars"); ok {
   170  		err = updateConfigVars(d.Id(), client, nil, v.([]interface{}))
   171  		if err != nil {
   172  			return err
   173  		}
   174  	}
   175  
   176  	return resourceHerokuAppRead(d, meta)
   177  }
   178  
   179  func resourceHerokuOrgAppCreate(d *schema.ResourceData, meta interface{}) error {
   180  	client := meta.(*heroku.Service)
   181  	// Build up our creation options
   182  	opts := heroku.OrganizationAppCreateOpts{}
   183  
   184  	if v := d.Get("organization.0.name"); v != nil {
   185  		vs := v.(string)
   186  		log.Printf("[DEBUG] Organization name: %s", vs)
   187  		opts.Organization = &vs
   188  	}
   189  
   190  	if v := d.Get("organization.0.personal"); v != nil {
   191  		vs := v.(bool)
   192  		log.Printf("[DEBUG] Organization Personal: %t", vs)
   193  		opts.Personal = &vs
   194  	}
   195  
   196  	if v := d.Get("organization.0.locked"); v != nil {
   197  		vs := v.(bool)
   198  		log.Printf("[DEBUG] Organization locked: %t", vs)
   199  		opts.Locked = &vs
   200  	}
   201  
   202  	if v := d.Get("name"); v != nil {
   203  		vs := v.(string)
   204  		log.Printf("[DEBUG] App name: %s", vs)
   205  		opts.Name = &vs
   206  	}
   207  	if v, ok := d.GetOk("region"); ok {
   208  		vs := v.(string)
   209  		log.Printf("[DEBUG] App region: %s", vs)
   210  		opts.Region = &vs
   211  	}
   212  	if v, ok := d.GetOk("stack"); ok {
   213  		vs := v.(string)
   214  		log.Printf("[DEBUG] App stack: %s", vs)
   215  		opts.Stack = &vs
   216  	}
   217  
   218  	log.Printf("[DEBUG] Creating Heroku app...")
   219  	a, err := client.OrganizationAppCreate(opts)
   220  	if err != nil {
   221  		return err
   222  	}
   223  
   224  	d.SetId(a.Name)
   225  	log.Printf("[INFO] App ID: %s", d.Id())
   226  
   227  	if v, ok := d.GetOk("config_vars"); ok {
   228  		err = updateConfigVars(d.Id(), client, nil, v.([]interface{}))
   229  		if err != nil {
   230  			return err
   231  		}
   232  	}
   233  
   234  	return resourceHerokuAppRead(d, meta)
   235  }
   236  
   237  func resourceHerokuAppRead(d *schema.ResourceData, meta interface{}) error {
   238  	client := meta.(*heroku.Service)
   239  	app, err := resourceHerokuAppRetrieve(d.Id(), client)
   240  	if err != nil {
   241  		return err
   242  	}
   243  
   244  	// Only set the config_vars that we have set in the configuration.
   245  	// The "all_config_vars" field has all of them.
   246  	configVars := make(map[string]string)
   247  	care := make(map[string]struct{})
   248  	for _, v := range d.Get("config_vars").([]interface{}) {
   249  		for k, _ := range v.(map[string]interface{}) {
   250  			care[k] = struct{}{}
   251  		}
   252  	}
   253  	for k, v := range app.Vars {
   254  		if _, ok := care[k]; ok {
   255  			configVars[k] = v
   256  		}
   257  	}
   258  	var configVarsValue []map[string]string
   259  	if len(configVars) > 0 {
   260  		configVarsValue = []map[string]string{configVars}
   261  	}
   262  
   263  	d.Set("name", app.App.Name)
   264  	d.Set("stack", app.App.Stack.Name)
   265  	d.Set("region", app.App.Region.Name)
   266  	d.Set("git_url", app.App.GitURL)
   267  	d.Set("web_url", app.App.WebURL)
   268  	d.Set("config_vars", configVarsValue)
   269  	d.Set("all_config_vars", app.Vars)
   270  
   271  	// We know that the hostname on heroku will be the name+herokuapp.com
   272  	// You need this to do things like create DNS CNAME records
   273  	d.Set("heroku_hostname", fmt.Sprintf("%s.herokuapp.com", app.App.Name))
   274  
   275  	return nil
   276  }
   277  
   278  func resourceHerokuAppUpdate(d *schema.ResourceData, meta interface{}) error {
   279  	client := meta.(*heroku.Service)
   280  
   281  	// If name changed, update it
   282  	if d.HasChange("name") {
   283  		v := d.Get("name").(string)
   284  		opts := heroku.AppUpdateOpts{
   285  			Name: &v,
   286  		}
   287  
   288  		renamedApp, err := client.AppUpdate(d.Id(), opts)
   289  		if err != nil {
   290  			return err
   291  		}
   292  
   293  		// Store the new ID
   294  		d.SetId(renamedApp.Name)
   295  	}
   296  
   297  	// If the config vars changed, then recalculate those
   298  	if d.HasChange("config_vars") {
   299  		o, n := d.GetChange("config_vars")
   300  		if o == nil {
   301  			o = []interface{}{}
   302  		}
   303  		if n == nil {
   304  			n = []interface{}{}
   305  		}
   306  
   307  		err := updateConfigVars(
   308  			d.Id(), client, o.([]interface{}), n.([]interface{}))
   309  		if err != nil {
   310  			return err
   311  		}
   312  	}
   313  
   314  	return resourceHerokuAppRead(d, meta)
   315  }
   316  
   317  func resourceHerokuAppDelete(d *schema.ResourceData, meta interface{}) error {
   318  	client := meta.(*heroku.Service)
   319  
   320  	log.Printf("[INFO] Deleting App: %s", d.Id())
   321  	err := client.AppDelete(d.Id())
   322  	if err != nil {
   323  		return fmt.Errorf("Error deleting App: %s", err)
   324  	}
   325  
   326  	d.SetId("")
   327  	return nil
   328  }
   329  
   330  func resourceHerokuAppRetrieve(id string, client *heroku.Service) (*application, error) {
   331  	app := application{Id: id, Client: client}
   332  
   333  	err := app.Update()
   334  
   335  	if err != nil {
   336  		return nil, fmt.Errorf("Error retrieving app: %s", err)
   337  	}
   338  
   339  	return &app, nil
   340  }
   341  
   342  func retrieveConfigVars(id string, client *heroku.Service) (map[string]string, error) {
   343  	vars, err := client.ConfigVarInfo(id)
   344  
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  
   349  	return vars, nil
   350  }
   351  
   352  // Updates the config vars for from an expanded configuration.
   353  func updateConfigVars(
   354  	id string,
   355  	client *heroku.Service,
   356  	o []interface{},
   357  	n []interface{}) error {
   358  	vars := make(map[string]*string)
   359  
   360  	for _, v := range o {
   361  		if v != nil {
   362  			for k, _ := range v.(map[string]interface{}) {
   363  				vars[k] = nil
   364  			}
   365  		}
   366  	}
   367  	for _, v := range n {
   368  		if v != nil {
   369  			for k, v := range v.(map[string]interface{}) {
   370  				val := v.(string)
   371  				vars[k] = &val
   372  			}
   373  		}
   374  	}
   375  
   376  	log.Printf("[INFO] Updating config vars: *%#v", vars)
   377  	if _, err := client.ConfigVarUpdate(id, vars); err != nil {
   378  		return fmt.Errorf("Error updating config vars: %s", err)
   379  	}
   380  
   381  	return nil
   382  }