Merge pull request #13910 from bernerdschaefer/bs-heroku-buildpacks

provider/heroku: set app buildpacks from config
This commit is contained in:
Jake Champlin 2017-04-25 10:40:19 -04:00 committed by GitHub
commit c839a7b2ff
3 changed files with 237 additions and 0 deletions

View File

@ -30,6 +30,7 @@ type application struct {
App *herokuApplication // The heroku application
Client *heroku.Service // Client to interact with the heroku API
Vars map[string]string // The vars on the application
Buildpacks []string // The application's buildpack names or URLs
Organization bool // is the application organization app
}
@ -71,6 +72,11 @@ func (a *application) Update() error {
}
}
a.Buildpacks, err = retrieveBuildpacks(a.Id, a.Client)
if err != nil {
errs = append(errs, err)
}
a.Vars, err = retrieveConfigVars(a.Id, a.Client)
if err != nil {
errs = append(errs, err)
@ -109,6 +115,14 @@ func resourceHerokuApp() *schema.Resource {
ForceNew: true,
},
"buildpacks": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"config_vars": {
Type: schema.TypeList,
Optional: true,
@ -215,6 +229,10 @@ func resourceHerokuAppCreate(d *schema.ResourceData, meta interface{}) error {
}
}
if v, ok := d.GetOk("buildpacks"); ok {
err = updateBuildpacks(d.Id(), client, v.([]interface{}))
}
return resourceHerokuAppRead(d, meta)
}
@ -293,6 +311,9 @@ func resourceHerokuAppRead(d *schema.ResourceData, meta interface{}) error {
}
}
// Only track buildpacks when set in the configuration.
_, buildpacksConfigured := d.GetOk("buildpacks")
organizationApp := isOrganizationApp(d)
// Only set the config_vars that we have set in the configuration.
@ -317,6 +338,9 @@ func resourceHerokuAppRead(d *schema.ResourceData, meta interface{}) error {
d.Set("region", app.App.Region)
d.Set("git_url", app.App.GitURL)
d.Set("web_url", app.App.WebURL)
if buildpacksConfigured {
d.Set("buildpacks", app.Buildpacks)
}
d.Set("config_vars", configVarsValue)
d.Set("all_config_vars", app.Vars)
if organizationApp {
@ -374,6 +398,13 @@ func resourceHerokuAppUpdate(d *schema.ResourceData, meta interface{}) error {
}
}
if d.HasChange("buildpacks") {
err := updateBuildpacks(d.Id(), client, d.Get("buildpacks").([]interface{}))
if err != nil {
return err
}
}
return resourceHerokuAppRead(d, meta)
}
@ -402,6 +433,21 @@ func resourceHerokuAppRetrieve(id string, organization bool, client *heroku.Serv
return &app, nil
}
func retrieveBuildpacks(id string, client *heroku.Service) ([]string, error) {
results, err := client.BuildpackInstallationList(context.TODO(), id, nil)
if err != nil {
return nil, err
}
buildpacks := []string{}
for _, installation := range results {
buildpacks = append(buildpacks, installation.Buildpack.Name)
}
return buildpacks, nil
}
func retrieveConfigVars(id string, client *heroku.Service) (map[string]string, error) {
vars, err := client.ConfigVarInfoForApp(context.TODO(), id)
@ -450,3 +496,24 @@ func updateConfigVars(
return nil
}
func updateBuildpacks(id string, client *heroku.Service, v []interface{}) error {
opts := heroku.BuildpackInstallationUpdateOpts{
Updates: []struct {
Buildpack string `json:"buildpack" url:"buildpack,key"`
}{}}
for _, buildpack := range v {
opts.Updates = append(opts.Updates, struct {
Buildpack string `json:"buildpack" url:"buildpack,key"`
}{
Buildpack: buildpack.(string),
})
}
if _, err := client.BuildpackInstallationUpdate(context.TODO(), id, opts); err != nil {
return fmt.Errorf("Error updating buildpacks: %s", err)
}
return nil
}

View File

@ -109,6 +109,75 @@ func TestAccHerokuApp_NukeVars(t *testing.T) {
})
}
func TestAccHerokuApp_Buildpacks(t *testing.T) {
var app heroku.AppInfoResult
appName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckHerokuAppDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckHerokuAppConfig_go(appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
testAccCheckHerokuAppBuildpacks(appName, false),
resource.TestCheckResourceAttr("heroku_app.foobar", "buildpacks.0", "heroku/go"),
),
},
{
Config: testAccCheckHerokuAppConfig_multi(appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
testAccCheckHerokuAppBuildpacks(appName, true),
resource.TestCheckResourceAttr(
"heroku_app.foobar", "buildpacks.0", "https://github.com/heroku/heroku-buildpack-multi-procfile"),
resource.TestCheckResourceAttr("heroku_app.foobar", "buildpacks.1", "heroku/go"),
),
},
{
Config: testAccCheckHerokuAppConfig_no_vars(appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
testAccCheckHerokuAppNoBuildpacks(appName),
resource.TestCheckNoResourceAttr("heroku_app.foobar", "buildpacks.0"),
),
},
},
})
}
func TestAccHerokuApp_ExternallySetBuildpacks(t *testing.T) {
var app heroku.AppInfoResult
appName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckHerokuAppDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckHerokuAppConfig_no_vars(appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
testAccCheckHerokuAppNoBuildpacks(appName),
resource.TestCheckNoResourceAttr("heroku_app.foobar", "buildpacks.0"),
),
},
{
PreConfig: testAccInstallUnconfiguredBuildpack(t, appName),
Config: testAccCheckHerokuAppConfig_no_vars(appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
testAccCheckHerokuAppBuildpacks(appName, false),
resource.TestCheckNoResourceAttr("heroku_app.foobar", "buildpacks.0"),
),
},
},
})
}
func TestAccHerokuApp_Organization(t *testing.T) {
var app heroku.OrganizationApp
appName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
@ -230,6 +299,59 @@ func testAccCheckHerokuAppAttributesNoVars(app *heroku.AppInfoResult, appName st
}
}
func testAccCheckHerokuAppBuildpacks(appName string, multi bool) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*heroku.Service)
results, err := client.BuildpackInstallationList(context.TODO(), appName, nil)
if err != nil {
return err
}
buildpacks := []string{}
for _, installation := range results {
buildpacks = append(buildpacks, installation.Buildpack.Name)
}
if multi {
herokuMulti := "https://github.com/heroku/heroku-buildpack-multi-procfile"
if len(buildpacks) != 2 || buildpacks[0] != herokuMulti || buildpacks[1] != "heroku/go" {
return fmt.Errorf("Bad buildpacks: %v", buildpacks)
}
return nil
}
if len(buildpacks) != 1 || buildpacks[0] != "heroku/go" {
return fmt.Errorf("Bad buildpacks: %v", buildpacks)
}
return nil
}
}
func testAccCheckHerokuAppNoBuildpacks(appName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*heroku.Service)
results, err := client.BuildpackInstallationList(context.TODO(), appName, nil)
if err != nil {
return err
}
buildpacks := []string{}
for _, installation := range results {
buildpacks = append(buildpacks, installation.Buildpack.Name)
}
if len(buildpacks) != 0 {
return fmt.Errorf("Bad buildpacks: %v", buildpacks)
}
return nil
}
}
func testAccCheckHerokuAppAttributesOrg(app *heroku.OrganizationApp, appName string, org string) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*heroku.Service)
@ -323,6 +445,25 @@ func testAccCheckHerokuAppExistsOrg(n string, app *heroku.OrganizationApp) resou
}
}
func testAccInstallUnconfiguredBuildpack(t *testing.T, appName string) func() {
return func() {
client := testAccProvider.Meta().(*heroku.Service)
opts := heroku.BuildpackInstallationUpdateOpts{
Updates: []struct {
Buildpack string `json:"buildpack" url:"buildpack,key"`
}{
{Buildpack: "heroku/go"},
},
}
_, err := client.BuildpackInstallationUpdate(context.TODO(), appName, opts)
if err != nil {
t.Fatalf("Error updating buildpacks: %s", err)
}
}
}
func testAccCheckHerokuAppConfig_basic(appName string) string {
return fmt.Sprintf(`
resource "heroku_app" "foobar" {
@ -335,6 +476,29 @@ resource "heroku_app" "foobar" {
}`, appName)
}
func testAccCheckHerokuAppConfig_go(appName string) string {
return fmt.Sprintf(`
resource "heroku_app" "foobar" {
name = "%s"
region = "us"
buildpacks = ["heroku/go"]
}`, appName)
}
func testAccCheckHerokuAppConfig_multi(appName string) string {
return fmt.Sprintf(`
resource "heroku_app" "foobar" {
name = "%s"
region = "us"
buildpacks = [
"https://github.com/heroku/heroku-buildpack-multi-procfile",
"heroku/go"
]
}`, appName)
}
func testAccCheckHerokuAppConfig_updated(appName string) string {
return fmt.Sprintf(`
resource "heroku_app" "foobar" {

View File

@ -22,6 +22,10 @@ resource "heroku_app" "default" {
config_vars {
FOOBAR = "baz"
}
buildpacks = [
"heroku/go"
]
}
```
@ -34,6 +38,8 @@ The following arguments are supported:
* `region` - (Required) The region that the app should be deployed in.
* `stack` - (Optional) The application stack is what platform to run the application
in.
* `buildpacks` - (Optional) Buildpack names or URLs for the application.
Buildpacks configured externally won't be altered if this is not present.
* `config_vars` - (Optional) Configuration variables for the application.
The config variables in this map are not the final set of configuration
variables, but rather variables you want present. That is, other