-
Notifications
You must be signed in to change notification settings - Fork 78
Add new Pipeline Promotion resource #410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
53f753a
Add heroku_pipeline_promotion resource
heroku-johnny ef12b74
Update heroku-go API client to add support for Promoting by Release ID
mars dbd04cc
Enable release_id support for pipeline promotions
heroku-johnny 59a0567
Make release_id required for pipeline promotions
heroku-johnny bf95583
Fix flaky VPN connection test - make tunnel count flexible
heroku-johnny File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| --- | ||
| layout: "heroku" | ||
| page_title: "Heroku: heroku_pipeline_promotion" | ||
| sidebar_current: "docs-heroku-resource-pipeline-promotion" | ||
| description: |- | ||
| Provides a Heroku Pipeline Promotion resource. | ||
| --- | ||
|
|
||
| # heroku\_pipeline\_promotion | ||
|
|
||
| Provides a [Heroku Pipeline Promotion](https://devcenter.heroku.com/articles/pipelines) | ||
| resource. | ||
|
|
||
| A pipeline promotion allows you to deploy releases from one app to other apps within the same | ||
| pipeline. This enables infrastructure-as-code workflow for promoting code between pipeline stages | ||
| such as staging to production. | ||
|
|
||
| Currently promotes the latest release from the source app. Support for promoting specific releases | ||
| (`release_id` parameter) requires additional API support from the Heroku platform team. | ||
|
|
||
| ## Example Usage | ||
|
|
||
| ```hcl | ||
| # Basic promotion from staging to production | ||
| resource "heroku_pipeline_promotion" "staging_to_prod" { | ||
| pipeline = heroku_pipeline.my_app.id | ||
| source_app_id = heroku_app.staging.id | ||
| targets = [heroku_app.production.id] | ||
| } | ||
|
|
||
| # Promotion to multiple target apps | ||
| resource "heroku_pipeline_promotion" "staging_to_multiple" { | ||
| pipeline = heroku_pipeline.my_app.id | ||
| source_app_id = heroku_app.staging.id | ||
| targets = [ | ||
| heroku_app.production.id, | ||
| heroku_app.demo.id | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| ## Argument Reference | ||
|
|
||
| The following arguments are supported: | ||
|
|
||
| * `pipeline` - (Required) The UUID of the pipeline containing the apps. | ||
| * `source_app_id` - (Required) The UUID of the source app to promote from. | ||
| * `targets` - (Required) Set of UUIDs of target apps to promote to. | ||
| * `release_id` - (Optional) **Not yet supported**. The UUID of a specific release to promote. | ||
| Currently returns an error as this requires additional Heroku platform API support. | ||
|
|
||
| ## Attributes Reference | ||
|
|
||
| The following attributes are exported: | ||
|
|
||
| * `id` - The UUID of this pipeline promotion. | ||
| * `status` - The status of the promotion (`pending`, `completed`). | ||
| * `created_at` - When the promotion was created. | ||
| * `promoted_release_id` - The UUID of the release that was actually promoted. | ||
|
|
||
| ## Notes | ||
|
|
||
| * Pipeline promotions are immutable - they cannot be updated or modified after creation. | ||
| * All apps (source and targets) must be in the same pipeline. | ||
| * All apps must have the same generation (Cedar or Fir). See [`heroku_pipeline`](./pipeline.html) for generation compatibility requirements. | ||
| * The source app must have at least one release to promote. | ||
| * Promotions copy the latest release from the source app to all target apps. | ||
|
|
||
| ## Future Enhancement | ||
|
|
||
| The `release_id` parameter will be supported once the Heroku platform team adds the necessary API | ||
| functionality. This will enable promoting specific releases rather than just the latest release. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| // Pipeline Promotion Resource | ||
| // | ||
| // This resource allows promoting releases between apps in a Heroku Pipeline. | ||
| // Currently promotes the latest release from the source app to target apps. | ||
| // | ||
| // DEPENDENCY: The 'release_id' field requires Flow team to add Promotion#release_id | ||
| // API support. Until then, only latest release promotion is supported. | ||
| package heroku | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "log" | ||
|
|
||
| "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
| "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" | ||
| heroku "github.com/heroku/heroku-go/v6" | ||
| ) | ||
|
|
||
| func resourceHerokuPipelinePromotion() *schema.Resource { | ||
| return &schema.Resource{ | ||
| Create: resourceHerokuPipelinePromotionCreate, | ||
| Read: resourceHerokuPipelinePromotionRead, | ||
| Delete: resourceHerokuPipelinePromotionDelete, | ||
|
|
||
| Schema: map[string]*schema.Schema{ | ||
| "pipeline": { | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| ForceNew: true, | ||
| ValidateFunc: validation.IsUUID, | ||
| Description: "Pipeline ID for the promotion", | ||
| }, | ||
|
|
||
| "source_app_id": { | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| ForceNew: true, | ||
| ValidateFunc: validation.IsUUID, | ||
| Description: "Source app ID to promote from", | ||
| }, | ||
|
|
||
| "release_id": { | ||
| Type: schema.TypeString, | ||
| Optional: true, | ||
| ForceNew: true, | ||
| ValidateFunc: validation.IsUUID, | ||
| Description: "Specific release ID to promote (requires Flow team API update)", | ||
| }, | ||
|
|
||
| "targets": { | ||
| Type: schema.TypeSet, | ||
| Required: true, | ||
| ForceNew: true, | ||
| Description: "Set of target app IDs to promote to", | ||
| Elem: &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| ValidateFunc: validation.IsUUID, | ||
| }, | ||
| }, | ||
|
|
||
| // Computed fields | ||
| "status": { | ||
| Type: schema.TypeString, | ||
| Computed: true, | ||
| Description: "Status of the promotion (pending, completed)", | ||
| }, | ||
|
|
||
| "created_at": { | ||
| Type: schema.TypeString, | ||
| Computed: true, | ||
| Description: "When the promotion was created", | ||
| }, | ||
|
|
||
| "promoted_release_id": { | ||
| Type: schema.TypeString, | ||
| Computed: true, | ||
| Description: "ID of the release that was actually promoted", | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func resourceHerokuPipelinePromotionCreate(d *schema.ResourceData, meta interface{}) error { | ||
| client := meta.(*Config).Api | ||
|
|
||
| log.Printf("[DEBUG] Creating pipeline promotion") | ||
|
|
||
| // Check if release_id is specified - this requires Flow team API support | ||
| if releaseID, ok := d.GetOk("release_id"); ok { | ||
| return fmt.Errorf("release_id parameter (%s) is not yet supported - waiting for Flow team to add Promotion#release_id API support", releaseID.(string)) | ||
| } | ||
heroku-johnny marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Build promotion options using current API | ||
| pipelineID := d.Get("pipeline").(string) | ||
| sourceAppID := d.Get("source_app_id").(string) | ||
| targets := d.Get("targets").(*schema.Set) | ||
|
|
||
| opts := heroku.PipelinePromotionCreateOpts{} | ||
| opts.Pipeline.ID = pipelineID | ||
| opts.Source.App = &struct { | ||
| ID *string `json:"id,omitempty" url:"id,omitempty,key"` | ||
| }{ID: &sourceAppID} | ||
|
|
||
| // Convert targets set to slice | ||
| for _, target := range targets.List() { | ||
| targetAppID := target.(string) | ||
| targetApp := &struct { | ||
| ID *string `json:"id,omitempty" url:"id,omitempty,key"` | ||
| }{ID: &targetAppID} | ||
|
|
||
| opts.Targets = append(opts.Targets, struct { | ||
| App *struct { | ||
| ID *string `json:"id,omitempty" url:"id,omitempty,key"` | ||
| } `json:"app,omitempty" url:"app,omitempty,key"` | ||
| }{App: targetApp}) | ||
| } | ||
|
|
||
| log.Printf("[DEBUG] Pipeline promotion create configuration: %#v", opts) | ||
|
|
||
| promotion, err := client.PipelinePromotionCreate(context.TODO(), opts) | ||
| if err != nil { | ||
| return fmt.Errorf("error creating pipeline promotion: %s", err) | ||
| } | ||
|
|
||
| log.Printf("[INFO] Created pipeline promotion ID: %s", promotion.ID) | ||
| d.SetId(promotion.ID) | ||
|
|
||
| return resourceHerokuPipelinePromotionRead(d, meta) | ||
| } | ||
|
|
||
| func resourceHerokuPipelinePromotionRead(d *schema.ResourceData, meta interface{}) error { | ||
| client := meta.(*Config).Api | ||
|
|
||
| log.Printf("[DEBUG] Reading pipeline promotion: %s", d.Id()) | ||
|
|
||
| promotion, err := client.PipelinePromotionInfo(context.TODO(), d.Id()) | ||
| if err != nil { | ||
| return fmt.Errorf("error retrieving pipeline promotion: %s", err) | ||
| } | ||
|
|
||
| // Set computed fields | ||
| d.Set("status", promotion.Status) | ||
| d.Set("created_at", promotion.CreatedAt.String()) | ||
|
|
||
| // Set the release that was actually promoted | ||
| if promotion.Source.Release.ID != "" { | ||
| d.Set("promoted_release_id", promotion.Source.Release.ID) | ||
| } | ||
|
|
||
| // Set configuration from API response | ||
| d.Set("pipeline", promotion.Pipeline.ID) | ||
| d.Set("source_app_id", promotion.Source.App.ID) | ||
|
|
||
| log.Printf("[DEBUG] Pipeline promotion read completed for: %s", d.Id()) | ||
| return nil | ||
| } | ||
|
|
||
| func resourceHerokuPipelinePromotionDelete(d *schema.ResourceData, meta interface{}) error { | ||
| log.Printf("[INFO] There is no DELETE for pipeline promotion resource so this is a no-op. Promotion will be removed from state.") | ||
| return nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| package heroku | ||
|
|
||
| import ( | ||
| "strings" | ||
| "testing" | ||
|
|
||
| "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
| ) | ||
|
|
||
| func TestResourceHerokuPipelinePromotion_Schema(t *testing.T) { | ||
| resource := resourceHerokuPipelinePromotion() | ||
|
|
||
| // Test required fields | ||
| requiredFields := []string{"pipeline", "source_app_id", "targets"} | ||
| for _, field := range requiredFields { | ||
| if _, ok := resource.Schema[field]; !ok { | ||
| t.Errorf("Required field %s not found in schema", field) | ||
| } | ||
| if !resource.Schema[field].Required { | ||
| t.Errorf("Field %s should be required", field) | ||
| } | ||
| if !resource.Schema[field].ForceNew { | ||
| t.Errorf("Field %s should be ForceNew", field) | ||
| } | ||
| } | ||
|
|
||
| // Test optional fields | ||
| optionalFields := []string{"release_id"} | ||
| for _, field := range optionalFields { | ||
| if _, ok := resource.Schema[field]; !ok { | ||
| t.Errorf("Optional field %s not found in schema", field) | ||
| } | ||
| if !resource.Schema[field].Optional { | ||
| t.Errorf("Field %s should be optional", field) | ||
| } | ||
| if !resource.Schema[field].ForceNew { | ||
| t.Errorf("Field %s should be ForceNew", field) | ||
| } | ||
| } | ||
|
|
||
| // Test computed fields | ||
| computedFields := []string{"status", "created_at", "promoted_release_id"} | ||
| for _, field := range computedFields { | ||
| if _, ok := resource.Schema[field]; !ok { | ||
| t.Errorf("Computed field %s not found in schema", field) | ||
| } | ||
| if !resource.Schema[field].Computed { | ||
| t.Errorf("Field %s should be computed", field) | ||
| } | ||
| } | ||
|
|
||
| // Test targets field is a Set | ||
| if resource.Schema["targets"].Type != schema.TypeSet { | ||
| t.Errorf("targets field should be TypeSet") | ||
| } | ||
| } | ||
|
|
||
| func TestResourceHerokuPipelinePromotion_ReleaseIdValidation(t *testing.T) { | ||
| // This test validates that release_id parameter currently returns an error | ||
| // Once Flow team adds API support, this test should be updated | ||
|
|
||
| d := schema.TestResourceDataRaw(t, resourceHerokuPipelinePromotion().Schema, map[string]interface{}{ | ||
| "pipeline": "01234567-89ab-cdef-0123-456789abcdef", | ||
| "source_app_id": "01234567-89ab-cdef-0123-456789abcdef", | ||
| "release_id": "01234567-89ab-cdef-0123-456789abcdef", | ||
| "targets": []interface{}{"01234567-89ab-cdef-0123-456789abcdef"}, | ||
| }) | ||
|
|
||
| meta := &Config{} // Mock config | ||
|
|
||
| err := resourceHerokuPipelinePromotionCreate(d, meta) | ||
| if err == nil { | ||
| t.Error("Expected error when release_id is provided, but got none") | ||
| } | ||
|
|
||
| expectedError := "release_id parameter" | ||
| if err != nil && !strings.Contains(err.Error(), expectedError) { | ||
| t.Errorf("Expected error to contain '%s', got: %s", expectedError, err.Error()) | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.