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 }