Skip to content

Commit

Permalink
Add ability to define pipeline owner for heroku_pipeline resource
Browse files Browse the repository at this point in the history
  • Loading branch information
davidji99 committed Apr 20, 2020
1 parent 5c598a3 commit 49c38c3
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 9 deletions.
6 changes: 6 additions & 0 deletions helper/test/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
TestConfigSlugIDKey
TestConfigEmail
TestConfigTeam
TestConfigUserID
)

var testConfigKeyToEnvName = map[TestConfigKey]string{
Expand All @@ -31,6 +32,7 @@ var testConfigKeyToEnvName = map[TestConfigKey]string{
TestConfigSlugIDKey: "HEROKU_SLUG_ID",
TestConfigEmail: "HEROKU_EMAIL",
TestConfigTeam: "HEROKU_TEAM",
TestConfigUserID: "HEROKU_USER_ID",
TestConfigAcceptanceTestKey: resource.TestEnvVar,
}

Expand Down Expand Up @@ -125,3 +127,7 @@ func (t *TestConfig) GetEmailOrSkip(testing *testing.T) (val string) {
func (t *TestConfig) GetTeamOrSkip(testing *testing.T) (val string) {
return t.GetOrSkip(testing, TestConfigTeam)
}

func (t *TestConfig) GetUserIDOrSkip(testing *testing.T) (val string) {
return t.GetOrSkip(testing, TestConfigUserID)
}
3 changes: 2 additions & 1 deletion heroku/import_heroku_pipeline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import (

func TestAccHerokuPipeline_importBasic(t *testing.T) {
pName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
ownerID := testAccConfig.GetUserIDOrSkip(t)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckHerokuPipelineDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckHerokuPipeline_basic(pName),
Config: testAccCheckHerokuPipeline_basic(pName, ownerID, "user"),
},
{
ResourceName: "heroku_pipeline.foobar",
Expand Down
65 changes: 61 additions & 4 deletions heroku/resource_heroku_pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package heroku
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"log"
"regexp"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
heroku "github.com/heroku/heroku-go/v5"
Expand All @@ -24,6 +26,31 @@ func resourceHerokuPipeline() *schema.Resource {
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[a-z][a-z0-9-]{2,29}$`),
"invalid pipeline name"),
},

"owner": {
Type: schema.TypeList,
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.IsUUID,
},

"type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{"team", "user"}, false),
},
},
},
},
},
}
Expand All @@ -45,8 +72,32 @@ func resourceHerokuPipelineImport(d *schema.ResourceData, meta interface{}) ([]*
func resourceHerokuPipelineCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Config).Api

opts := heroku.PipelineCreateOpts{
Name: d.Get("name").(string),
opts := heroku.PipelineCreateOpts{}

if v, ok := d.GetOk("name"); ok {
vs := v.(string)
log.Printf("[DEBUG] New pipeline name: %s", vs)
opts.Name = vs
}

if v, ok := d.GetOk("owner"); ok {
vi := v.([]interface{})
ownerInfo := vi[0].(map[string]interface{})

ownerID := ownerInfo["id"].(string)
log.Printf("[DEBUG] New pipeline owner id: %s", ownerID)

ownerType := ownerInfo["type"].(string)
log.Printf("[DEBUG] New pipeline owner type: %s", ownerType)

opts.Owner = (*struct {
ID string `json:"id" url:"id,key"`
Type string `json:"type" url:"type,key"`
})(&struct {
ID string
Type string
}{ID: ownerID, Type: ownerType})

}

log.Printf("[DEBUG] Pipeline create configuration: %#v", opts)
Expand All @@ -57,11 +108,10 @@ func resourceHerokuPipelineCreate(d *schema.ResourceData, meta interface{}) erro
}

d.SetId(p.ID)
d.Set("name", p.Name)

log.Printf("[INFO] Pipeline ID: %s", d.Id())

return resourceHerokuPipelineUpdate(d, meta)
return resourceHerokuPipelineRead(d, meta)
}

func resourceHerokuPipelineUpdate(d *schema.ResourceData, meta interface{}) error {
Expand Down Expand Up @@ -92,6 +142,8 @@ func resourceHerokuPipelineDelete(d *schema.ResourceData, meta interface{}) erro
return fmt.Errorf("Error deleting pipeline: %s", err)
}

d.SetId("")

return nil
}

Expand All @@ -105,5 +157,10 @@ func resourceHerokuPipelineRead(d *schema.ResourceData, meta interface{}) error

d.Set("name", p.Name)

ownerInfo := make(map[string]string)
ownerInfo["id"] = p.Owner.ID
ownerInfo["type"] = p.Owner.Type
d.Set("owner", []interface{}{ownerInfo})

return nil
}
50 changes: 46 additions & 4 deletions heroku/resource_heroku_pipeline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package heroku
import (
"context"
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
Expand All @@ -15,37 +16,78 @@ func TestAccHerokuPipeline_Basic(t *testing.T) {
var pipeline heroku.Pipeline
pipelineName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
pipelineName2 := fmt.Sprintf("%s-2", pipelineName)
ownerID := testAccConfig.GetUserIDOrSkip(t)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckHerokuPipelineDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckHerokuPipeline_basic(pipelineName),
Config: testAccCheckHerokuPipeline_basic(pipelineName, ownerID, "user"),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuPipelineExists("heroku_pipeline.foobar", &pipeline),
resource.TestCheckResourceAttr(
"heroku_pipeline.foobar", "name", pipelineName),
resource.TestCheckResourceAttr(
"heroku_pipeline.foobar", "owner.0.id", ownerID),
),
},
{
Config: testAccCheckHerokuPipeline_basic(pipelineName2),
Config: testAccCheckHerokuPipeline_basic(pipelineName2, ownerID, "user"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"heroku_pipeline.foobar", "name", pipelineName2),
resource.TestCheckResourceAttr(
"heroku_pipeline.foobar", "owner.0.id", ownerID),
),
},
},
})
}

func testAccCheckHerokuPipeline_basic(pipelineName string) string {
func TestAccHerokuPipeline_InvalidOwnerID(t *testing.T) {
pipelineName := fmt.Sprintf("tftest-%s", acctest.RandString(10))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckHerokuPipelineDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckHerokuPipeline_basic(pipelineName, "im-an-invalid-owner-id", "user"),
ExpectError: regexp.MustCompile(`expected "owner.0.id" to be a valid UUID`),
},
},
})
}

func TestAccHerokuPipeline_InvalidOwnerType(t *testing.T) {
pipelineName := fmt.Sprintf("tftest-%s", acctest.RandString(10))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckHerokuPipelineDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckHerokuPipeline_basic(pipelineName, "16d1c25f-d879-4f4d-ad1b-d807169aaa1c", "invalid"), // not real UUID
ExpectError: regexp.MustCompile(`expected owner.0.type to be one of \[team user], got invalid`),
},
},
})
}

func testAccCheckHerokuPipeline_basic(pipelineName, pipelineOwnerID, pipelineOwnerType string) string {
return fmt.Sprintf(`
resource "heroku_pipeline" "foobar" {
name = "%s"
owner {
id = "%s"
type = "%s"
}
}
`, pipelineName)
`, pipelineName, pipelineOwnerID, pipelineOwnerType)
}

func testAccCheckHerokuPipelineExists(n string, pipeline *heroku.Pipeline) resource.TestCheckFunc {
Expand Down
16 changes: 16 additions & 0 deletions website/docs/r/pipeline.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ resource "heroku_app" "production" {
# Create a Heroku pipeline
resource "heroku_pipeline" "test-app" {
name = "test-app"
owner {
id = "16d1c25f-d879-4f4d-ad1b-d807169aaa1c"
type = "user"
}
}
# Couple apps to different pipeline stages
Expand All @@ -54,6 +59,17 @@ The following arguments are supported:

* `name` - (Required) The name of the pipeline.

* `owner` - (Required) The owner of the pipeline. This block as the following required attributes:
* `id` - (Required) The unique identifier (UUID) of a pipeline owner.
* `type` - (Required) The type of pipeline owner. Can be either `user` or `team`.


Regarding the `owner` attribute block, please note the following:
* The Heroku Platform API allows a pipeline to be created without an owner. However, the UI indicates pipelines require an owner.
Therefore to enforce a best practice, this provider will require this attribute be set in your configuration(s).
* While the UI allows users to change pipeline ownership, the Platform API does not. Therefore to prevent config drift,
the provider will force a resource creation should you change the `owner` in your configuration(s).

## Attributes Reference

The following attributes are exported:
Expand Down

0 comments on commit 49c38c3

Please sign in to comment.