github.com/ezbercih/terraform@v0.1.1-0.20140729011846-3c33865e0839/builtin/providers/heroku/resource_heroku_app.go (about)

     1  package heroku
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/bgentry/heroku-go"
     8  	"github.com/hashicorp/terraform/flatmap"
     9  	"github.com/hashicorp/terraform/helper/config"
    10  	"github.com/hashicorp/terraform/helper/diff"
    11  	"github.com/hashicorp/terraform/helper/multierror"
    12  	"github.com/hashicorp/terraform/terraform"
    13  )
    14  
    15  // type application is used to store all the details of a heroku app
    16  type application struct {
    17  	Id string // Id of the resource
    18  
    19  	App    *heroku.App       // The heroku application
    20  	Client *heroku.Client    // Client to interact with the heroku API
    21  	Vars   map[string]string // The vars on the application
    22  }
    23  
    24  // Updates the application to have the latest from remote
    25  func (a *application) Update() error {
    26  	var errs []error
    27  	var err error
    28  
    29  	a.App, err = a.Client.AppInfo(a.Id)
    30  	if err != nil {
    31  		errs = append(errs, err)
    32  	}
    33  
    34  	a.Vars, err = retrieve_config_vars(a.Id, a.Client)
    35  	if err != nil {
    36  		errs = append(errs, err)
    37  	}
    38  
    39  	if len(errs) > 0 {
    40  		return &multierror.Error{Errors: errs}
    41  	}
    42  
    43  	return nil
    44  }
    45  
    46  func resource_heroku_app_create(
    47  	s *terraform.ResourceState,
    48  	d *terraform.ResourceDiff,
    49  	meta interface{}) (*terraform.ResourceState, error) {
    50  	p := meta.(*ResourceProvider)
    51  	client := p.client
    52  
    53  	// Merge the diff into the state so that we have all the attributes
    54  	// properly.
    55  	rs := s.MergeDiff(d)
    56  
    57  	// Build up our creation options
    58  	opts := heroku.AppCreateOpts{}
    59  
    60  	if attr := rs.Attributes["name"]; attr != "" {
    61  		opts.Name = &attr
    62  	}
    63  
    64  	if attr := rs.Attributes["region"]; attr != "" {
    65  		opts.Region = &attr
    66  	}
    67  
    68  	if attr := rs.Attributes["stack"]; attr != "" {
    69  		opts.Stack = &attr
    70  	}
    71  
    72  	log.Printf("[DEBUG] App create configuration: %#v", opts)
    73  
    74  	a, err := client.AppCreate(&opts)
    75  	if err != nil {
    76  		return s, err
    77  	}
    78  
    79  	rs.ID = a.Name
    80  	log.Printf("[INFO] App ID: %s", rs.ID)
    81  
    82  	if attr, ok := rs.Attributes["config_vars.#"]; ok && attr == "1" {
    83  		vs := flatmap.Expand(
    84  			rs.Attributes, "config_vars").([]interface{})
    85  
    86  		err = update_config_vars(rs.ID, vs, client)
    87  		if err != nil {
    88  			return rs, err
    89  		}
    90  	}
    91  
    92  	app, err := resource_heroku_app_retrieve(rs.ID, client)
    93  	if err != nil {
    94  		return rs, err
    95  	}
    96  
    97  	return resource_heroku_app_update_state(rs, app)
    98  }
    99  
   100  func resource_heroku_app_update(
   101  	s *terraform.ResourceState,
   102  	d *terraform.ResourceDiff,
   103  	meta interface{}) (*terraform.ResourceState, error) {
   104  	p := meta.(*ResourceProvider)
   105  	client := p.client
   106  	rs := s.MergeDiff(d)
   107  
   108  	if attr, ok := d.Attributes["name"]; ok {
   109  		opts := heroku.AppUpdateOpts{
   110  			Name: &attr.New,
   111  		}
   112  
   113  		renamedApp, err := client.AppUpdate(rs.ID, &opts)
   114  
   115  		if err != nil {
   116  			return s, err
   117  		}
   118  
   119  		// Store the new ID
   120  		rs.ID = renamedApp.Name
   121  	}
   122  
   123  	attr, ok := s.Attributes["config_vars.#"]
   124  
   125  	// If the config var block was removed, nuke all config vars
   126  	if ok && attr == "1" {
   127  		vs := flatmap.Expand(
   128  			rs.Attributes, "config_vars").([]interface{})
   129  
   130  		err := update_config_vars(rs.ID, vs, client)
   131  		if err != nil {
   132  			return rs, err
   133  		}
   134  	} else if ok && attr == "0" {
   135  		log.Println("[INFO] Config vars removed, removing all vars")
   136  
   137  		err := update_config_vars(rs.ID, make([]interface{}, 0), client)
   138  
   139  		if err != nil {
   140  			return rs, err
   141  		}
   142  	}
   143  
   144  	app, err := resource_heroku_app_retrieve(rs.ID, client)
   145  	if err != nil {
   146  		return rs, err
   147  	}
   148  
   149  	return resource_heroku_app_update_state(rs, app)
   150  }
   151  
   152  func resource_heroku_app_destroy(
   153  	s *terraform.ResourceState,
   154  	meta interface{}) error {
   155  	p := meta.(*ResourceProvider)
   156  	client := p.client
   157  
   158  	log.Printf("[INFO] Deleting App: %s", s.ID)
   159  
   160  	// Destroy the app
   161  	err := client.AppDelete(s.ID)
   162  
   163  	if err != nil {
   164  		return fmt.Errorf("Error deleting App: %s", err)
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  func resource_heroku_app_refresh(
   171  	s *terraform.ResourceState,
   172  	meta interface{}) (*terraform.ResourceState, error) {
   173  	p := meta.(*ResourceProvider)
   174  	client := p.client
   175  
   176  	app, err := resource_heroku_app_retrieve(s.ID, client)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	return resource_heroku_app_update_state(s, app)
   182  }
   183  
   184  func resource_heroku_app_diff(
   185  	s *terraform.ResourceState,
   186  	c *terraform.ResourceConfig,
   187  	meta interface{}) (*terraform.ResourceDiff, error) {
   188  
   189  	b := &diff.ResourceBuilder{
   190  		Attrs: map[string]diff.AttrType{
   191  			"name":        diff.AttrTypeUpdate,
   192  			"region":      diff.AttrTypeUpdate,
   193  			"stack":       diff.AttrTypeCreate,
   194  			"config_vars": diff.AttrTypeUpdate,
   195  		},
   196  
   197  		ComputedAttrs: []string{
   198  			"name",
   199  			"region",
   200  			"stack",
   201  			"git_url",
   202  			"web_url",
   203  			"id",
   204  			"config_vars",
   205  		},
   206  
   207  		ComputedAttrsUpdate: []string{
   208  			"heroku_hostname",
   209  		},
   210  	}
   211  
   212  	return b.Diff(s, c)
   213  }
   214  
   215  func resource_heroku_app_update_state(
   216  	s *terraform.ResourceState,
   217  	app *application) (*terraform.ResourceState, error) {
   218  
   219  	s.Attributes["name"] = app.App.Name
   220  	s.Attributes["stack"] = app.App.Stack.Name
   221  	s.Attributes["region"] = app.App.Region.Name
   222  	s.Attributes["git_url"] = app.App.GitURL
   223  	s.Attributes["web_url"] = app.App.WebURL
   224  
   225  	// We know that the hostname on heroku will be the name+herokuapp.com
   226  	// You need this to do things like create DNS CNAME records
   227  	s.Attributes["heroku_hostname"] = fmt.Sprintf("%s.herokuapp.com", app.App.Name)
   228  
   229  	toFlatten := make(map[string]interface{})
   230  
   231  	if len(app.Vars) > 0 {
   232  		toFlatten["config_vars"] = []map[string]string{app.Vars}
   233  	}
   234  
   235  	for k, v := range flatmap.Flatten(toFlatten) {
   236  		s.Attributes[k] = v
   237  	}
   238  
   239  	return s, nil
   240  }
   241  
   242  func resource_heroku_app_retrieve(id string, client *heroku.Client) (*application, error) {
   243  	app := application{Id: id, Client: client}
   244  
   245  	err := app.Update()
   246  
   247  	if err != nil {
   248  		return nil, fmt.Errorf("Error retrieving app: %s", err)
   249  	}
   250  
   251  	return &app, nil
   252  }
   253  
   254  func resource_heroku_app_validation() *config.Validator {
   255  	return &config.Validator{
   256  		Required: []string{},
   257  		Optional: []string{
   258  			"name",
   259  			"region",
   260  			"stack",
   261  			"config_vars.*",
   262  		},
   263  	}
   264  }
   265  
   266  func retrieve_config_vars(id string, client *heroku.Client) (map[string]string, error) {
   267  	vars, err := client.ConfigVarInfo(id)
   268  
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  
   273  	return vars, nil
   274  }
   275  
   276  // Updates the config vars for from an expanded (prior to assertion)
   277  // []map[string]string config
   278  func update_config_vars(id string, vs []interface{}, client *heroku.Client) error {
   279  	vars := make(map[string]*string)
   280  
   281  	for k, v := range vs[0].(map[string]interface{}) {
   282  		val := v.(string)
   283  		vars[k] = &val
   284  	}
   285  
   286  	log.Printf("[INFO] Updating config vars: *%#v", vars)
   287  
   288  	_, err := client.ConfigVarUpdate(id, vars)
   289  
   290  	if err != nil {
   291  		return fmt.Errorf("Error updating config vars: %s", err)
   292  	}
   293  
   294  	return nil
   295  }