From 40805bb80c5322304f15c18acbd5bf937143e16d Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Fri, 28 Apr 2017 12:25:56 -0400 Subject: [PATCH 1/8] provider/heroku: Add heroku_pipeline resource --- builtin/providers/heroku/provider.go | 1 + .../heroku/resource_heroku_pipeline.go | 73 ++++++++++++++ .../heroku/resource_heroku_pipeline_test.go | 97 +++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 builtin/providers/heroku/resource_heroku_pipeline.go create mode 100644 builtin/providers/heroku/resource_heroku_pipeline_test.go diff --git a/builtin/providers/heroku/provider.go b/builtin/providers/heroku/provider.go index 08432ac98b..8afbc80ebe 100644 --- a/builtin/providers/heroku/provider.go +++ b/builtin/providers/heroku/provider.go @@ -33,6 +33,7 @@ func Provider() terraform.ResourceProvider { "heroku_cert": resourceHerokuCert(), "heroku_domain": resourceHerokuDomain(), "heroku_drain": resourceHerokuDrain(), + "heroku_pipeline": resourceHerokuPipeline(), "heroku_space": resourceHerokuSpace(), }, diff --git a/builtin/providers/heroku/resource_heroku_pipeline.go b/builtin/providers/heroku/resource_heroku_pipeline.go new file mode 100644 index 0000000000..8b23dd7d39 --- /dev/null +++ b/builtin/providers/heroku/resource_heroku_pipeline.go @@ -0,0 +1,73 @@ +package heroku + +import ( + "context" + "fmt" + "log" + + "github.com/cyberdelia/heroku-go/v3" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceHerokuPipeline() *schema.Resource { + return &schema.Resource{ + Create: resourceHerokuPipelineCreate, + Read: resourceHerokuPipelineRead, + Delete: resourceHerokuPipelineDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, // TODO does it? + }, + }, + } +} + +func resourceHerokuPipelineCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Service) + + opts := heroku.PipelineCreateOpts{} + opts.Name = d.Get("name").(string) + + log.Printf("[DEBUG] Pipeline create configuration: %#v", opts) + + p, err := client.PipelineCreate(context.TODO(), opts) + if err != nil { + return fmt.Errorf("Error creating pipeline: %s", err) + } + + d.SetId(p.ID) + d.Set("name", p.Name) + + log.Printf("[INFO] Pipeline ID: %s", d.Id()) + + return resourceHerokuPipelineRead(d, meta) +} + +func resourceHerokuPipelineDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Service) + + log.Printf("[INFO] Deleting pipeline: %s", d.Id()) + + _, err := client.PipelineDelete(context.TODO(), d.Id()) + if err != nil { + return fmt.Errorf("Error deleting pipeline: %s", err) + } + + return nil +} + +func resourceHerokuPipelineRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Service) + + p, err := client.PipelineInfo(context.TODO(), d.Id()) + if err != nil { + return fmt.Errorf("Error retrieving pipeline: %s", err) + } + + d.Set("name", p.Name) + + return nil +} diff --git a/builtin/providers/heroku/resource_heroku_pipeline_test.go b/builtin/providers/heroku/resource_heroku_pipeline_test.go new file mode 100644 index 0000000000..7ce7f0707a --- /dev/null +++ b/builtin/providers/heroku/resource_heroku_pipeline_test.go @@ -0,0 +1,97 @@ +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 TestAccHerokuPipeline_Basic(t *testing.T) { + var pipeline heroku.PipelineInfoResult + 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: testAccCheckHerokuPipelineConfig_basic(pipelineName), + Check: resource.ComposeTestCheckFunc( + testAccCheckHerokuPipelineExists("heroku_pipeline.foobar", &pipeline), + testAccCheckHerokuPipelineAttributes(&pipeline, pipelineName), + ), + }, + }, + }) +} + +func testAccCheckHerokuPipelineConfig_basic(pipelineName string) string { + return fmt.Sprintf(` +resource "heroku_pipeline" "foobar" { + name = "%s" +} +`, pipelineName) +} + +func testAccCheckHerokuPipelineExists(n string, pipeline *heroku.PipelineInfoResult) 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 pipeline name set") + } + + client := testAccProvider.Meta().(*heroku.Service) + + foundPipeline, err := client.PipelineInfo(context.TODO(), rs.Primary.ID) + if err != nil { + return err + } + + if foundPipeline.ID != rs.Primary.ID { + return fmt.Errorf("Pipeline not found") + } + + *pipeline = *foundPipeline + + return nil + } +} + +func testAccCheckHerokuPipelineAttributes(pipeline *heroku.PipelineInfoResult, pipelineName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if pipeline.Name != pipelineName { + return fmt.Errorf("Bad name: %s", pipeline.Name) + } + + return nil + } +} + +func testAccCheckHerokuPipelineDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*heroku.Service) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "heroku_pipeline" { + continue + } + + _, err := client.PipelineInfo(context.TODO(), rs.Primary.ID) + + if err == nil { + return fmt.Errorf("Pipeline still exists") + } + } + + return nil +} From dd48b5b7e74a4837465ffbe4ecd52f0ad1c9e8d4 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Fri, 28 Apr 2017 15:12:54 -0400 Subject: [PATCH 2/8] provider/heroku: heroku_pipeline_coupling Also adds validators for UUID and pipeline stage names --- builtin/providers/heroku/provider.go | 17 +-- .../resource_heroku_pipeline_coupling.go | 88 +++++++++++++ .../resource_heroku_pipeline_coupling_test.go | 124 ++++++++++++++++++ builtin/providers/heroku/validators.go | 38 ++++++ builtin/providers/heroku/validators_test.go | 53 ++++++++ 5 files changed, 312 insertions(+), 8 deletions(-) create mode 100644 builtin/providers/heroku/resource_heroku_pipeline_coupling.go create mode 100644 builtin/providers/heroku/resource_heroku_pipeline_coupling_test.go create mode 100644 builtin/providers/heroku/validators.go create mode 100644 builtin/providers/heroku/validators_test.go diff --git a/builtin/providers/heroku/provider.go b/builtin/providers/heroku/provider.go index 8afbc80ebe..fec57ca595 100644 --- a/builtin/providers/heroku/provider.go +++ b/builtin/providers/heroku/provider.go @@ -27,14 +27,15 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "heroku_addon": resourceHerokuAddon(), - "heroku_app": resourceHerokuApp(), - "heroku_app_feature": resourceHerokuAppFeature(), - "heroku_cert": resourceHerokuCert(), - "heroku_domain": resourceHerokuDomain(), - "heroku_drain": resourceHerokuDrain(), - "heroku_pipeline": resourceHerokuPipeline(), - "heroku_space": resourceHerokuSpace(), + "heroku_addon": resourceHerokuAddon(), + "heroku_app": resourceHerokuApp(), + "heroku_app_feature": resourceHerokuAppFeature(), + "heroku_cert": resourceHerokuCert(), + "heroku_domain": resourceHerokuDomain(), + "heroku_drain": resourceHerokuDrain(), + "heroku_pipeline": resourceHerokuPipeline(), + "heroku_pipeline_coupling": resourceHerokuPipelineCoupling(), + "heroku_space": resourceHerokuSpace(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/heroku/resource_heroku_pipeline_coupling.go b/builtin/providers/heroku/resource_heroku_pipeline_coupling.go new file mode 100644 index 0000000000..7fb4548a4b --- /dev/null +++ b/builtin/providers/heroku/resource_heroku_pipeline_coupling.go @@ -0,0 +1,88 @@ +package heroku + +import ( + "context" + "fmt" + "log" + + "github.com/cyberdelia/heroku-go/v3" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceHerokuPipelineCoupling() *schema.Resource { + return &schema.Resource{ + Create: resourceHerokuPipelineCouplingCreate, + Read: resourceHerokuPipelineCouplingRead, + Delete: resourceHerokuPipelineCouplingDelete, + + Schema: map[string]*schema.Schema{ + "app": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "pipeline": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateUUID, + }, + "stage": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validatePipelineStageName, + }, + }, + } +} + +func resourceHerokuPipelineCouplingCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Service) + + opts := heroku.PipelineCouplingCreateOpts{} + opts.App = d.Get("app").(string) + opts.Pipeline = d.Get("pipeline").(string) + opts.Stage = d.Get("stage").(string) + + log.Printf("[DEBUG] PipelineCoupling create configuration: %#v", opts) + + p, err := client.PipelineCouplingCreate(context.TODO(), opts) + if err != nil { + return fmt.Errorf("Error creating pipeline: %s", err) + } + + d.SetId(p.ID) + + log.Printf("[INFO] PipelineCoupling ID: %s", d.Id()) + + return resourceHerokuPipelineCouplingRead(d, meta) +} + +func resourceHerokuPipelineCouplingDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Service) + + log.Printf("[INFO] Deleting pipeline: %s", d.Id()) + + _, err := client.PipelineCouplingDelete(context.TODO(), d.Id()) + if err != nil { + return fmt.Errorf("Error deleting pipeline: %s", err) + } + + return nil +} + +func resourceHerokuPipelineCouplingRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Service) + + p, err := client.PipelineCouplingInfo(context.TODO(), d.Id()) + if err != nil { + return fmt.Errorf("Error retrieving pipeline: %s", err) + } + + d.Set("app", p.App) + d.Set("pipeline", p.Pipeline) + d.Set("stage", p.Stage) + + return nil +} diff --git a/builtin/providers/heroku/resource_heroku_pipeline_coupling_test.go b/builtin/providers/heroku/resource_heroku_pipeline_coupling_test.go new file mode 100644 index 0000000000..9e600d1e34 --- /dev/null +++ b/builtin/providers/heroku/resource_heroku_pipeline_coupling_test.go @@ -0,0 +1,124 @@ +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 TestAccHerokuPipelineCoupling_Basic(t *testing.T) { + var coupling heroku.PipelineCouplingInfoResult + + appName := fmt.Sprintf("tftest-%s", acctest.RandString(10)) + pipelineName := fmt.Sprintf("tftest-%s", acctest.RandString(10)) + stageName := "development" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckHerokuPipelineCouplingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckHerokuPipelineCouplingConfig_basic(appName, pipelineName, stageName), + Check: resource.ComposeTestCheckFunc( + testAccCheckHerokuPipelineCouplingExists("heroku_pipeline_coupling.default", &coupling), + testAccCheckHerokuPipelineCouplingAttributes( + &coupling, + "heroku_app.default", + "heroku_pipeline.default", + stageName, + ), + ), + }, + }, + }) +} + +func testAccCheckHerokuPipelineCouplingConfig_basic(appName, pipelineName, stageName string) string { + return fmt.Sprintf(` +resource "heroku_app" "default" { + name = "%s" + region = "us" +} + +resource "heroku_pipeline" "default" { + name = "%s" +} + +resource "heroku_pipeline_coupling" "default" { + app = "${heroku_app.default.id}" + pipeline = "${heroku_pipeline.default.id}" + stage = "%s" +} +`, appName, pipelineName, stageName) +} + +func testAccCheckHerokuPipelineCouplingExists(n string, pipeline *heroku.PipelineCouplingInfoResult) 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 coupling ID set") + } + + client := testAccProvider.Meta().(*heroku.Service) + + foundPipelineCoupling, err := client.PipelineCouplingInfo(context.TODO(), rs.Primary.ID) + if err != nil { + return err + } + + if foundPipelineCoupling.ID != rs.Primary.ID { + return fmt.Errorf("PipelineCoupling not found") + } + + *pipeline = *foundPipelineCoupling + + return nil + } +} + +func testAccCheckHerokuPipelineCouplingAttributes(coupling *heroku.PipelineCouplingInfoResult, _, pipelineResource, stageName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + pipeline, ok := s.RootModule().Resources[pipelineResource] + if !ok { + return fmt.Errorf("Pipeline not found: %s", pipelineResource) + } + + if coupling.Pipeline.ID != pipeline.Primary.ID { + return fmt.Errorf("Bad pipeline ID: %v != %v", coupling.Pipeline.ID, pipeline.Primary.ID) + } + if coupling.Stage != stageName { + return fmt.Errorf("Bad stage: %s", coupling.Stage) + } + + return nil + } +} + +func testAccCheckHerokuPipelineCouplingDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*heroku.Service) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "heroku_pipeline_coupling" { + continue + } + + _, err := client.PipelineCouplingInfo(context.TODO(), rs.Primary.ID) + + if err == nil { + return fmt.Errorf("PipelineCoupling still exists") + } + } + + return nil +} diff --git a/builtin/providers/heroku/validators.go b/builtin/providers/heroku/validators.go new file mode 100644 index 0000000000..35ac264776 --- /dev/null +++ b/builtin/providers/heroku/validators.go @@ -0,0 +1,38 @@ +package heroku + +import ( + "fmt" + "strings" + + "github.com/satori/uuid" +) + +var validPipelineStageNames = []string{ + "review", + "development", + "staging", + "production", +} + +func validatePipelineStageName(v interface{}, k string) (ws []string, errors []error) { + for _, s := range validPipelineStageNames { + if v == s { + return + } + } + + err := fmt.Errorf( + "%s is an invalid pipeline stage, must be one of [%s]", + v, + strings.Join(validPipelineStageNames, ", "), + ) + errors = append(errors, err) + return +} + +func validateUUID(v interface{}, k string) (ws []string, errors []error) { + if _, err := uuid.FromString(v.(string)); err != nil { + errors = append(errors, fmt.Errorf("%q is an invalid UUID: %s", k, err)) + } + return +} diff --git a/builtin/providers/heroku/validators_test.go b/builtin/providers/heroku/validators_test.go new file mode 100644 index 0000000000..6131be8bc3 --- /dev/null +++ b/builtin/providers/heroku/validators_test.go @@ -0,0 +1,53 @@ +package heroku + +import "testing" + +func TestPipelineStage(t *testing.T) { + valid := []string{ + "review", + "development", + "staging", + "production", + } + for _, v := range valid { + _, errors := validatePipelineStageName(v, "stage") + if len(errors) != 0 { + t.Fatalf("%q should be a valid stage: %q", v, errors) + } + } + + invalid := []string{ + "foobarbaz", + "another-stage", + "", + } + for _, v := range invalid { + _, errors := validatePipelineStageName(v, "stage") + if len(errors) == 0 { + t.Fatalf("%q should be an invalid stage", v) + } + } +} + +func TestValidateUUID(t *testing.T) { + valid := []string{ + "4812ccbc-2a2e-4c6c-bae4-a3d04ed51c0e", + } + for _, v := range valid { + _, errors := validateUUID(v, "id") + if len(errors) != 0 { + t.Fatalf("%q should be a valid UUID: %q", v, errors) + } + } + + invalid := []string{ + "foobarbaz", + "my-app-name", + } + for _, v := range invalid { + _, errors := validateUUID(v, "id") + if len(errors) == 0 { + t.Fatalf("%q should be an invalid UUID", v) + } + } +} From e2b361f603c7991632c4196659c0c1b1dd8d0c1d Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Fri, 28 Apr 2017 15:46:06 -0400 Subject: [PATCH 3/8] provider/heroku: Allow renaming a pipeline --- .../heroku/resource_heroku_pipeline.go | 20 +++++++++++++++++- .../heroku/resource_heroku_pipeline_test.go | 21 +++++++++---------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/builtin/providers/heroku/resource_heroku_pipeline.go b/builtin/providers/heroku/resource_heroku_pipeline.go index 8b23dd7d39..1293aef46c 100644 --- a/builtin/providers/heroku/resource_heroku_pipeline.go +++ b/builtin/providers/heroku/resource_heroku_pipeline.go @@ -12,6 +12,7 @@ import ( func resourceHerokuPipeline() *schema.Resource { return &schema.Resource{ Create: resourceHerokuPipelineCreate, + Update: resourceHerokuPipelineUpdate, Read: resourceHerokuPipelineRead, Delete: resourceHerokuPipelineDelete, @@ -19,7 +20,6 @@ func resourceHerokuPipeline() *schema.Resource { "name": { Type: schema.TypeString, Required: true, - ForceNew: true, // TODO does it? }, }, } @@ -43,6 +43,24 @@ func resourceHerokuPipelineCreate(d *schema.ResourceData, meta interface{}) erro log.Printf("[INFO] Pipeline ID: %s", d.Id()) + return resourceHerokuPipelineUpdate(d, meta) +} + +func resourceHerokuPipelineUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*heroku.Service) + + if d.HasChange("name") { + name := d.Get("name").(string) + opts := heroku.PipelineUpdateOpts{ + Name: &name, + } + + _, err := client.PipelineUpdate(context.TODO(), d.Id(), opts) + if err != nil { + return err + } + } + return resourceHerokuPipelineRead(d, meta) } diff --git a/builtin/providers/heroku/resource_heroku_pipeline_test.go b/builtin/providers/heroku/resource_heroku_pipeline_test.go index 7ce7f0707a..1c40e14037 100644 --- a/builtin/providers/heroku/resource_heroku_pipeline_test.go +++ b/builtin/providers/heroku/resource_heroku_pipeline_test.go @@ -14,6 +14,7 @@ import ( func TestAccHerokuPipeline_Basic(t *testing.T) { var pipeline heroku.PipelineInfoResult pipelineName := fmt.Sprintf("tftest-%s", acctest.RandString(10)) + pipelineName2 := fmt.Sprintf("%s-2", pipelineName) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -24,7 +25,15 @@ func TestAccHerokuPipeline_Basic(t *testing.T) { Config: testAccCheckHerokuPipelineConfig_basic(pipelineName), Check: resource.ComposeTestCheckFunc( testAccCheckHerokuPipelineExists("heroku_pipeline.foobar", &pipeline), - testAccCheckHerokuPipelineAttributes(&pipeline, pipelineName), + resource.TestCheckResourceAttr( + "heroku_pipeline.foobar", "name", pipelineName), + ), + }, + { + Config: testAccCheckHerokuPipelineConfig_basic(pipelineName2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "heroku_pipeline.foobar", "name", pipelineName2), ), }, }, @@ -68,16 +77,6 @@ func testAccCheckHerokuPipelineExists(n string, pipeline *heroku.PipelineInfoRes } } -func testAccCheckHerokuPipelineAttributes(pipeline *heroku.PipelineInfoResult, pipelineName string) resource.TestCheckFunc { - return func(s *terraform.State) error { - if pipeline.Name != pipelineName { - return fmt.Errorf("Bad name: %s", pipeline.Name) - } - - return nil - } -} - func testAccCheckHerokuPipelineDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*heroku.Service) From ccb1b7ebecc161916c401b270f621be55db71c78 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Fri, 28 Apr 2017 16:12:26 -0400 Subject: [PATCH 4/8] website: Add docs for Heroku Pipeline resources --- .../providers/heroku/r/pipeline.html.markdown | 62 +++++++++++++++++ .../heroku/r/pipeline_coupling.html.markdown | 67 +++++++++++++++++++ website/source/layouts/heroku.erb | 8 +++ 3 files changed, 137 insertions(+) create mode 100644 website/source/docs/providers/heroku/r/pipeline.html.markdown create mode 100644 website/source/docs/providers/heroku/r/pipeline_coupling.html.markdown diff --git a/website/source/docs/providers/heroku/r/pipeline.html.markdown b/website/source/docs/providers/heroku/r/pipeline.html.markdown new file mode 100644 index 0000000000..dcd38ab60d --- /dev/null +++ b/website/source/docs/providers/heroku/r/pipeline.html.markdown @@ -0,0 +1,62 @@ +--- +layout: "heroku" +page_title: "Heroku: heroku_pipeline_" +sidebar_current: "docs-heroku-resource-pipeline-x" +description: |- + Provides a Heroku Pipeline resource. +--- + +# heroku\_pipeline + + +Provides a [Heroku Pipeline](https://devcenter.heroku.com/articles/pipelines) +resource. + +A pipeline is a group of Heroku apps that share the same codebase. Once a +pipeline is created, and apps are added to different stages using +[`heroku_pipeline_coupling`](./pipeline_coupling.html), you can promote app +slugs to the next stage. + +## Example Usage + +```hcl +# Create Heroku apps for staging and production +resource "heroku_app" "staging" { + name = "test-app-staging" +} + +resource "heroku_app" "production" { + name = "test-app-production" +} + +# Create a Heroku pipeline +resource "heroku_pipeline" "test-app" { + name = "test-app" +} + +# Couple apps to different pipeline stages +resource "heroku_pipeline_coupling" "staging" { + app = "${heroku_app.staging.name}" + pipeline = "${heroku_pipeline.test-app.id}" + stage = "staging" +} + +resource "heroku_pipeline_coupling" "production" { + app = "${heroku_app.production.name}" + pipeline = "${heroku_pipeline.test-app.id}" + stage = "production" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the pipeline. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The UUID of the pipeline. +* `name` - The name of the pipeline. diff --git a/website/source/docs/providers/heroku/r/pipeline_coupling.html.markdown b/website/source/docs/providers/heroku/r/pipeline_coupling.html.markdown new file mode 100644 index 0000000000..90a5a9b508 --- /dev/null +++ b/website/source/docs/providers/heroku/r/pipeline_coupling.html.markdown @@ -0,0 +1,67 @@ +--- +layout: "heroku" +page_title: "Heroku: heroku_pipeline_coupling" +sidebar_current: "docs-heroku-resource-pipeline-coupling" +description: |- + Provides a Heroku Pipeline Coupling resource. +--- + +# heroku\_pipeline\_coupling + + +Provides a [Heroku Pipeline Coupling](https://devcenter.heroku.com/articles/pipelines) +resource. + +A pipeline is a group of Heroku apps that share the same codebase. Once a +pipeline is created using [`heroku_pipeline`](./pipeline), and apps are added +to different stages using `heroku_pipeline_coupling`, you can promote app slugs +to the downstream stages. + +## Example Usage + +```hcl +# Create Heroku apps for staging and production +resource "heroku_app" "staging" { + name = "test-app-staging" +} + +resource "heroku_app" "production" { + name = "test-app-production" +} + +# Create a Heroku pipeline +resource "heroku_pipeline" "test-app" { + name = "test-app" +} + +# Couple apps to different pipeline stages +resource "heroku_pipeline_coupling" "staging" { + app = "${heroku_app.staging.name}" + pipeline = "${heroku_pipeline.test-app.id}" + stage = "staging" +} + +resource "heroku_pipeline_coupling" "production" { + app = "${heroku_app.production.name}" + pipeline = "${heroku_pipeline.test-app.id}" + stage = "production" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `app` - (Required) The name of the app for this coupling. +* `pipeline` - (Required) The ID of the pipeline to add this app to. +* `stage` - (Required) The stage to couple this app to. Must be one of +`review`, `development`, `staging`, or `production`. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The UUID of this pipeline coupling. +* `app` - The name of the application. +* `pipeline` - The UUID of the pipeline. +* `stage` - The stage for this coupling. diff --git a/website/source/layouts/heroku.erb b/website/source/layouts/heroku.erb index 6f7211c4f1..0d9d83b050 100644 --- a/website/source/layouts/heroku.erb +++ b/website/source/layouts/heroku.erb @@ -37,6 +37,14 @@ heroku_drain + > + heroku_pipeline + + + > + heroku_pipeline_coupling + + > heroku_space From 3a87ca4a43c38707ffc2eb9b5f504c1183f49233 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Mon, 1 May 2017 10:19:44 -0400 Subject: [PATCH 5/8] provider/heroku: Prefer struct for required attrs --- builtin/providers/heroku/resource_heroku_pipeline.go | 5 +++-- .../heroku/resource_heroku_pipeline_coupling.go | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/builtin/providers/heroku/resource_heroku_pipeline.go b/builtin/providers/heroku/resource_heroku_pipeline.go index 1293aef46c..5aedf33de3 100644 --- a/builtin/providers/heroku/resource_heroku_pipeline.go +++ b/builtin/providers/heroku/resource_heroku_pipeline.go @@ -28,8 +28,9 @@ func resourceHerokuPipeline() *schema.Resource { func resourceHerokuPipelineCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*heroku.Service) - opts := heroku.PipelineCreateOpts{} - opts.Name = d.Get("name").(string) + opts := heroku.PipelineCreateOpts{ + Name: d.Get("name").(string), + } log.Printf("[DEBUG] Pipeline create configuration: %#v", opts) diff --git a/builtin/providers/heroku/resource_heroku_pipeline_coupling.go b/builtin/providers/heroku/resource_heroku_pipeline_coupling.go index 7fb4548a4b..90b70447a1 100644 --- a/builtin/providers/heroku/resource_heroku_pipeline_coupling.go +++ b/builtin/providers/heroku/resource_heroku_pipeline_coupling.go @@ -40,10 +40,11 @@ func resourceHerokuPipelineCoupling() *schema.Resource { func resourceHerokuPipelineCouplingCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*heroku.Service) - opts := heroku.PipelineCouplingCreateOpts{} - opts.App = d.Get("app").(string) - opts.Pipeline = d.Get("pipeline").(string) - opts.Stage = d.Get("stage").(string) + opts := heroku.PipelineCouplingCreateOpts{ + App: d.Get("app").(string), + Pipeline: d.Get("pipeline").(string), + Stage: d.Get("stage").(string), + } log.Printf("[DEBUG] PipelineCoupling create configuration: %#v", opts) From 3d4f1ba4f35c39365c6fc5af63e55dc721cfc772 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Mon, 1 May 2017 10:20:04 -0400 Subject: [PATCH 6/8] provider/heroku: Remove unused argument in test --- .../providers/heroku/resource_heroku_pipeline_coupling_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builtin/providers/heroku/resource_heroku_pipeline_coupling_test.go b/builtin/providers/heroku/resource_heroku_pipeline_coupling_test.go index 9e600d1e34..3835f8e7ae 100644 --- a/builtin/providers/heroku/resource_heroku_pipeline_coupling_test.go +++ b/builtin/providers/heroku/resource_heroku_pipeline_coupling_test.go @@ -29,7 +29,6 @@ func TestAccHerokuPipelineCoupling_Basic(t *testing.T) { testAccCheckHerokuPipelineCouplingExists("heroku_pipeline_coupling.default", &coupling), testAccCheckHerokuPipelineCouplingAttributes( &coupling, - "heroku_app.default", "heroku_pipeline.default", stageName, ), @@ -87,7 +86,7 @@ func testAccCheckHerokuPipelineCouplingExists(n string, pipeline *heroku.Pipelin } } -func testAccCheckHerokuPipelineCouplingAttributes(coupling *heroku.PipelineCouplingInfoResult, _, pipelineResource, stageName string) resource.TestCheckFunc { +func testAccCheckHerokuPipelineCouplingAttributes(coupling *heroku.PipelineCouplingInfoResult, pipelineResource, stageName string) resource.TestCheckFunc { return func(s *terraform.State) error { pipeline, ok := s.RootModule().Resources[pipelineResource] if !ok { From 02bb1e69078dc8256c61d6bc17225f4636a834df Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Mon, 1 May 2017 10:20:28 -0400 Subject: [PATCH 7/8] provider/heroku: Output IDs on failure --- .../providers/heroku/resource_heroku_pipeline_coupling_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/heroku/resource_heroku_pipeline_coupling_test.go b/builtin/providers/heroku/resource_heroku_pipeline_coupling_test.go index 3835f8e7ae..6fd8b51953 100644 --- a/builtin/providers/heroku/resource_heroku_pipeline_coupling_test.go +++ b/builtin/providers/heroku/resource_heroku_pipeline_coupling_test.go @@ -77,7 +77,7 @@ func testAccCheckHerokuPipelineCouplingExists(n string, pipeline *heroku.Pipelin } if foundPipelineCoupling.ID != rs.Primary.ID { - return fmt.Errorf("PipelineCoupling not found") + return fmt.Errorf("PipelineCoupling not found: %s != %s", foundPipelineCoupling.ID, rs.Primary.ID) } *pipeline = *foundPipelineCoupling From 61f45dd585588b2e73205336a3ed79bd3ecc50e6 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Mon, 1 May 2017 10:20:51 -0400 Subject: [PATCH 8/8] provider/heroku: scope valid stage names to func --- builtin/providers/heroku/validators.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/builtin/providers/heroku/validators.go b/builtin/providers/heroku/validators.go index 35ac264776..0b3702247e 100644 --- a/builtin/providers/heroku/validators.go +++ b/builtin/providers/heroku/validators.go @@ -7,14 +7,14 @@ import ( "github.com/satori/uuid" ) -var validPipelineStageNames = []string{ - "review", - "development", - "staging", - "production", -} - func validatePipelineStageName(v interface{}, k string) (ws []string, errors []error) { + validPipelineStageNames := []string{ + "review", + "development", + "staging", + "production", + } + for _, s := range validPipelineStageNames { if v == s { return