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 }