Skip to content

Commit

Permalink
providers/heroku: add heroku_app_feature resource (#14035)
Browse files Browse the repository at this point in the history
  • Loading branch information
danp authored and catsby committed Apr 27, 2017
1 parent 2af1bd9 commit e495c6b
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 6 deletions.
24 changes: 18 additions & 6 deletions builtin/providers/heroku/provider.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package heroku

import (
"fmt"
"log"
"strings"

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
Expand All @@ -25,12 +27,13 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"heroku_addon": resourceHerokuAddon(),
"heroku_app": resourceHerokuApp(),
"heroku_cert": resourceHerokuCert(),
"heroku_domain": resourceHerokuDomain(),
"heroku_drain": resourceHerokuDrain(),
"heroku_space": resourceHerokuSpace(),
"heroku_addon": resourceHerokuAddon(),
"heroku_app": resourceHerokuApp(),
"heroku_app_feature": resourceHerokuAppFeature(),
"heroku_cert": resourceHerokuCert(),
"heroku_domain": resourceHerokuDomain(),
"heroku_drain": resourceHerokuDrain(),
"heroku_space": resourceHerokuSpace(),
},

ConfigureFunc: providerConfigure,
Expand All @@ -46,3 +49,12 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
log.Println("[INFO] Initializing Heroku client")
return config.Client()
}

func buildCompositeID(a, b string) string {
return fmt.Sprintf("%s:%s", a, b)
}

func parseCompositeID(id string) (string, string) {
parts := strings.SplitN(id, ":", 2)
return parts[0], parts[1]
}
101 changes: 101 additions & 0 deletions builtin/providers/heroku/resource_heroku_app_feature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package heroku

import (
"context"
"log"

heroku "github.com/cyberdelia/heroku-go/v3"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceHerokuAppFeature() *schema.Resource {
return &schema.Resource{
Create: resourceAppFeatureCreate,
Update: resourceAppFeatureUpdate,
Read: resourceAppFeatureRead,
Delete: resourceAppFeatureDelete,

Schema: map[string]*schema.Schema{
"app": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"enabled": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
},
}
}

func resourceAppFeatureRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*heroku.Service)

app, id := parseCompositeID(d.Id())

feature, err := client.AppFeatureInfo(context.TODO(), app, id)
if err != nil {
return err
}

d.Set("app", app)
d.Set("name", feature.Name)
d.Set("enabled", feature.Enabled)

return nil
}

func resourceAppFeatureCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*heroku.Service)

app := d.Get("app").(string)
featureName := d.Get("name").(string)
enabled := d.Get("enabled").(bool)

opts := heroku.AppFeatureUpdateOpts{Enabled: enabled}

log.Printf("[DEBUG] Feature set configuration: %#v, %#v", featureName, opts)

feature, err := client.AppFeatureUpdate(context.TODO(), app, featureName, opts)
if err != nil {
return err
}

d.SetId(buildCompositeID(app, feature.ID))

return resourceAppFeatureRead(d, meta)
}

func resourceAppFeatureUpdate(d *schema.ResourceData, meta interface{}) error {
if d.HasChange("enabled") {
return resourceAppFeatureCreate(d, meta)
}

return resourceAppFeatureRead(d, meta)
}

func resourceAppFeatureDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*heroku.Service)

app, id := parseCompositeID(d.Id())
featureName := d.Get("name").(string)

log.Printf("[INFO] Deleting app feature %s (%s) for app %s", featureName, id, app)
opts := heroku.AppFeatureUpdateOpts{Enabled: false}
_, err := client.AppFeatureUpdate(context.TODO(), app, id, opts)
if err != nil {
return err
}

d.SetId("")
return nil
}
135 changes: 135 additions & 0 deletions builtin/providers/heroku/resource_heroku_app_feature_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package heroku

import (
"context"
"fmt"
"testing"

heroku "github.com/cyberdelia/heroku-go/v3"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccHerokuAppFeature(t *testing.T) {
var feature heroku.AppFeatureInfoResult
appName := fmt.Sprintf("tftest-%s", acctest.RandString(10))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckHerokuFeatureDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckHerokuFeature_basic(appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuFeatureExists("heroku_app_feature.runtime_metrics", &feature),
testAccCheckHerokuFeatureEnabled(&feature, true),
resource.TestCheckResourceAttr(
"heroku_app_feature.runtime_metrics", "enabled", "true",
),
),
},
{
Config: testAccCheckHerokuFeature_disabled(appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuFeatureExists("heroku_app_feature.runtime_metrics", &feature),
testAccCheckHerokuFeatureEnabled(&feature, false),
resource.TestCheckResourceAttr(
"heroku_app_feature.runtime_metrics", "enabled", "false",
),
),
},
},
})
}

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

for _, rs := range s.RootModule().Resources {
if rs.Type != "heroku_app_feature" {
continue
}

_, err := client.AppFeatureInfo(context.TODO(), rs.Primary.Attributes["app"], rs.Primary.ID)

if err == nil {
return fmt.Errorf("Feature still exists")
}
}

return nil
}

func testAccCheckHerokuFeatureExists(n string, feature *heroku.AppFeatureInfoResult) 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 feature ID is set")
}

app, id := parseCompositeID(rs.Primary.ID)
if app != rs.Primary.Attributes["app"] {
return fmt.Errorf("Bad app: %s", app)
}

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

foundFeature, err := client.AppFeatureInfo(context.TODO(), app, id)
if err != nil {
return err
}

if foundFeature.ID != id {
return fmt.Errorf("Feature not found")
}

*feature = *foundFeature
return nil
}
}

func testAccCheckHerokuFeatureEnabled(feature *heroku.AppFeatureInfoResult, enabled bool) resource.TestCheckFunc {
return func(s *terraform.State) error {
if feature.Enabled != enabled {
return fmt.Errorf("Bad enabled: %v", feature.Enabled)
}

return nil
}
}

func testAccCheckHerokuFeature_basic(appName string) string {
return fmt.Sprintf(`
resource "heroku_app" "example" {
name = "%s"
region = "us"
}
resource "heroku_app_feature" "runtime_metrics" {
app = "${heroku_app.example.name}"
name = "log-runtime-metrics"
}
`, appName)
}

func testAccCheckHerokuFeature_disabled(appName string) string {
return fmt.Sprintf(`
resource "heroku_app" "example" {
name = "%s"
region = "us"
}
resource "heroku_app_feature" "runtime_metrics" {
app = "${heroku_app.example.name}"
name = "log-runtime-metrics"
enabled = false
}
`, appName)
}
28 changes: 28 additions & 0 deletions website/source/docs/providers/heroku/r/app_feature.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
layout: "heroku"
page_title: "Heroku: heroku_app_feature"
sidebar_current: "docs-heroku-resource-app-feature"
description: |-
Provides a Heroku App Feature resource. This can be used to create and manage App Features on Heroku.
---

# heroku\_app\_feature

Provides a Heroku App Feature resource. This can be used to create and manage App Features on Heroku.

## Example Usage

```hcl
resource "heroku_app_feature" "log_runtime_metrics" {
app = "test-app"
name = "log-runtime-metrics"
}
```

## Argument Reference

The following arguments are supported:

* `app` - (Required) The Heroku app to link to.
* `name` - (Required) The name of the App Feature to manage.
* `enabled` - (Optional) Whether to enable or disable the App Feature. The default value is true.

0 comments on commit e495c6b

Please sign in to comment.