Skip to content

Commit

Permalink
Merge pull request #4495 from hashicorp/plan3-fixing_heroku_organizat…
Browse files Browse the repository at this point in the history
…ion_app_read

Heroku Organization App Improvements
  • Loading branch information
phinze committed Jan 6, 2016
2 parents e705780 + c527654 commit b351524
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 44 deletions.
124 changes: 93 additions & 31 deletions builtin/providers/heroku/resource_heroku_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,65 @@ import (
"github.com/hashicorp/terraform/helper/schema"
)

// herokuApplication is a value type used to hold the details of an
// application. We use this for common storage of values needed for the
// heroku.App and heroku.OrganizationApp types
type herokuApplication struct {
Name string
Region string
Stack string
GitURL string
WebURL string
OrganizationName string
Locked bool
}

// type application is used to store all the details of a heroku app
type application struct {
Id string // Id of the resource

App *heroku.App // The heroku application
Client *heroku.Service // Client to interact with the heroku API
Vars map[string]string // The vars on the application
App *herokuApplication // The heroku application
Client *heroku.Service // Client to interact with the heroku API
Vars map[string]string // The vars on the application
Organization bool // is the application organization app
}

// Updates the application to have the latest from remote
func (a *application) Update() error {
var errs []error
var err error

a.App, err = a.Client.AppInfo(a.Id)
if err != nil {
errs = append(errs, err)
if !a.Organization {
app, err := a.Client.AppInfo(a.Id)
if err != nil {
errs = append(errs, err)
} else {
a.App = &herokuApplication{}
a.App.Name = app.Name
a.App.Region = app.Region.Name
a.App.Stack = app.Stack.Name
a.App.GitURL = app.GitURL
a.App.WebURL = app.WebURL
}
} else {
app, err := a.Client.OrganizationAppInfo(a.Id)
if err != nil {
errs = append(errs, err)
} else {
// No inheritance between OrganizationApp and App is killing it :/
a.App = &herokuApplication{}
a.App.Name = app.Name
a.App.Region = app.Region.Name
a.App.Stack = app.Stack.Name
a.App.GitURL = app.GitURL
a.App.WebURL = app.WebURL
if app.Organization != nil {
a.App.OrganizationName = app.Organization.Name
} else {
log.Println("[DEBUG] Something is wrong - didn't get information about organization name, while the app is marked as being so")
}
a.App.Locked = app.Locked
}
}

a.Vars, err = retrieveConfigVars(a.Id, a.Client)
Expand Down Expand Up @@ -95,10 +137,9 @@ func resourceHerokuApp() *schema.Resource {
},

"organization": &schema.Schema{
Description: "Name of Organization to create application in. Leave blank for personal apps.",
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Expand All @@ -122,17 +163,17 @@ func resourceHerokuApp() *schema.Resource {
}
}

func switchHerokuAppCreate(d *schema.ResourceData, meta interface{}) error {
orgCount := d.Get("organization.#").(int)
if orgCount > 1 {
return fmt.Errorf("Error Creating Heroku App: Only 1 Heroku Organization is permitted")
}
func isOrganizationApp(d *schema.ResourceData) bool {
v := d.Get("organization").([]interface{})
return len(v) > 0 && v[0] != nil
}

if _, ok := d.GetOk("organization.0.name"); ok {
func switchHerokuAppCreate(d *schema.ResourceData, meta interface{}) error {
if isOrganizationApp(d) {
return resourceHerokuOrgAppCreate(d, meta)
} else {
return resourceHerokuAppCreate(d, meta)
}

return resourceHerokuAppCreate(d, meta)
}

func resourceHerokuAppCreate(d *schema.ResourceData, meta interface{}) error {
Expand Down Expand Up @@ -181,19 +222,25 @@ func resourceHerokuOrgAppCreate(d *schema.ResourceData, meta interface{}) error
// Build up our creation options
opts := heroku.OrganizationAppCreateOpts{}

if v := d.Get("organization.0.name"); v != nil {
v := d.Get("organization").([]interface{})
if len(v) > 1 {
return fmt.Errorf("Error Creating Heroku App: Only 1 Heroku Organization is permitted")
}
orgDetails := v[0].(map[string]interface{})

if v := orgDetails["name"]; v != nil {
vs := v.(string)
log.Printf("[DEBUG] Organization name: %s", vs)
opts.Organization = &vs
}

if v := d.Get("organization.0.personal"); v != nil {
if v := orgDetails["personal"]; v != nil {
vs := v.(bool)
log.Printf("[DEBUG] Organization Personal: %t", vs)
opts.Personal = &vs
}

if v := d.Get("organization.0.locked"); v != nil {
if v := orgDetails["locked"]; v != nil {
vs := v.(bool)
log.Printf("[DEBUG] Organization locked: %t", vs)
opts.Locked = &vs
Expand Down Expand Up @@ -236,20 +283,24 @@ func resourceHerokuOrgAppCreate(d *schema.ResourceData, meta interface{}) error

func resourceHerokuAppRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*heroku.Service)
app, err := resourceHerokuAppRetrieve(d.Id(), client)
if err != nil {
return err
}

// Only set the config_vars that we have set in the configuration.
// The "all_config_vars" field has all of them.
configVars := make(map[string]string)
care := make(map[string]struct{})
for _, v := range d.Get("config_vars").([]interface{}) {
for k, _ := range v.(map[string]interface{}) {
care[k] = struct{}{}
}
}

organizationApp := isOrganizationApp(d)

// Only set the config_vars that we have set in the configuration.
// The "all_config_vars" field has all of them.
app, err := resourceHerokuAppRetrieve(d.Id(), organizationApp, client)
if err != nil {
return err
}

for k, v := range app.Vars {
if _, ok := care[k]; ok {
configVars[k] = v
Expand All @@ -261,12 +312,23 @@ func resourceHerokuAppRead(d *schema.ResourceData, meta interface{}) error {
}

d.Set("name", app.App.Name)
d.Set("stack", app.App.Stack.Name)
d.Set("region", app.App.Region.Name)
d.Set("stack", app.App.Stack)
d.Set("region", app.App.Region)
d.Set("git_url", app.App.GitURL)
d.Set("web_url", app.App.WebURL)
d.Set("config_vars", configVarsValue)
d.Set("all_config_vars", app.Vars)
if organizationApp {
orgDetails := map[string]interface{}{
"name": app.App.OrganizationName,
"locked": app.App.Locked,
"private": false,
}
err := d.Set("organization", []interface{}{orgDetails})
if err != nil {
return err
}
}

// We know that the hostname on heroku will be the name+herokuapp.com
// You need this to do things like create DNS CNAME records
Expand Down Expand Up @@ -327,8 +389,8 @@ func resourceHerokuAppDelete(d *schema.ResourceData, meta interface{}) error {
return nil
}

func resourceHerokuAppRetrieve(id string, client *heroku.Service) (*application, error) {
app := application{Id: id, Client: client}
func resourceHerokuAppRetrieve(id string, organization bool, client *heroku.Service) (*application, error) {
app := application{Id: id, Client: client, Organization: organization}

err := app.Update()

Expand Down
129 changes: 116 additions & 13 deletions builtin/providers/heroku/resource_heroku_app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package heroku

import (
"fmt"
"os"
"testing"

"github.com/cyberdelia/heroku-go/v3"
Expand Down Expand Up @@ -102,6 +103,31 @@ func TestAccHerokuApp_NukeVars(t *testing.T) {
})
}

func TestAccHerokuApp_Organization(t *testing.T) {
var app heroku.OrganizationApp
org := os.Getenv("HEROKU_ORGANIZATION")

resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
if org == "" {
t.Skip("HEROKU_ORGANIZATION is not set; skipping test.")
}
},
Providers: testAccProviders,
CheckDestroy: testAccCheckHerokuAppDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(testAccCheckHerokuAppConfig_organization, org),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuAppExistsOrg("heroku_app.foobar", &app),
testAccCheckHerokuAppAttributesOrg(&app, org),
),
},
},
})
}

func testAccCheckHerokuAppDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*heroku.Service)

Expand Down Expand Up @@ -197,6 +223,39 @@ func testAccCheckHerokuAppAttributesNoVars(app *heroku.App) resource.TestCheckFu
}
}

func testAccCheckHerokuAppAttributesOrg(app *heroku.OrganizationApp, org string) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*heroku.Service)

if app.Region.Name != "us" {
return fmt.Errorf("Bad region: %s", app.Region.Name)
}

if app.Stack.Name != "cedar-14" {
return fmt.Errorf("Bad stack: %s", app.Stack.Name)
}

if app.Name != "terraform-test-app" {
return fmt.Errorf("Bad name: %s", app.Name)
}

if app.Organization == nil || app.Organization.Name != org {
return fmt.Errorf("Bad org: %v", app.Organization)
}

vars, err := client.ConfigVarInfo(app.Name)
if err != nil {
return err
}

if vars["FOO"] != "bar" {
return fmt.Errorf("Bad config vars: %v", vars)
}

return nil
}
}

func testAccCheckHerokuAppExists(n string, app *heroku.App) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
Expand Down Expand Up @@ -227,29 +286,73 @@ func testAccCheckHerokuAppExists(n string, app *heroku.App) resource.TestCheckFu
}
}

func testAccCheckHerokuAppExistsOrg(n string, app *heroku.OrganizationApp) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]

if !ok {
return fmt.Errorf("Not found: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No App Name is set")
}

client := testAccProvider.Meta().(*heroku.Service)

foundApp, err := client.OrganizationAppInfo(rs.Primary.ID)

if err != nil {
return err
}

if foundApp.Name != rs.Primary.ID {
return fmt.Errorf("App not found")
}

*app = *foundApp

return nil
}
}

const testAccCheckHerokuAppConfig_basic = `
resource "heroku_app" "foobar" {
name = "terraform-test-app"
region = "us"
name = "terraform-test-app"
region = "us"
config_vars {
FOO = "bar"
}
config_vars {
FOO = "bar"
}
}`

const testAccCheckHerokuAppConfig_updated = `
resource "heroku_app" "foobar" {
name = "terraform-test-renamed"
region = "us"
name = "terraform-test-renamed"
region = "us"
config_vars {
FOO = "bing"
BAZ = "bar"
}
config_vars {
FOO = "bing"
BAZ = "bar"
}
}`

const testAccCheckHerokuAppConfig_no_vars = `
resource "heroku_app" "foobar" {
name = "terraform-test-app"
region = "us"
name = "terraform-test-app"
region = "us"
}`

const testAccCheckHerokuAppConfig_organization = `
resource "heroku_app" "foobar" {
name = "terraform-test-app"
region = "us"
organization {
name = "%s"
}
config_vars {
FOO = "bar"
}
}`

0 comments on commit b351524

Please sign in to comment.