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