provider/heroku: set app buildpacks from config
Many apps deployed to Heroku require that multiple buildpacks be configured in a particular order to operate correctly. This updates the builtin Heroku provider's app resource to support configuring buildpacks and the related documentation in the website. Similar to config vars, externally set buildpacks will not be altered if the config is not set.
This commit is contained in:
parent
fe15c68aa9
commit
acdb5c659a
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue