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