Skip to content

Commit 3517ad8

Browse files
committed
Add promoted_release_ids attribute to pipeline promotion resource
Add new promoted_release_ids computed attribute that returns the list of release IDs created by a promotion (one for each target app). The existing promoted_release_id attribute incorrectly returned the source release ID instead of the promoted release IDs. This attribute is now deprecated and returns the first value from promoted_release_ids for backwards compatibility. Changes: - Add promoted_release_ids as computed list attribute - Fetch promotion targets via PipelinePromotionTargetList API - Extract release IDs from each target - Deprecate promoted_release_id with clear messaging - Set promoted_release_id to first value of promoted_release_ids - Update documentation with examples - Add schema tests for new attribute and deprecation Testing: - Schema test validates both attributes exist - Schema test validates promoted_release_id is deprecated - Schema test validates promoted_release_ids is a List - All unit tests pass - Manual test confirmed correct release IDs returned
1 parent c1b9d3e commit 3517ad8

File tree

3 files changed

+102
-9
lines changed

3 files changed

+102
-9
lines changed

docs/resources/pipeline_promotion.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ resource "heroku_pipeline_promotion" "staging_to_multiple" {
4646
heroku_app.demo.id
4747
]
4848
}
49+
50+
# Access the promoted release information
51+
output "promoted_releases" {
52+
value = heroku_pipeline_promotion.staging_to_multiple.promoted_release_ids
53+
}
54+
55+
# Access a specific target's release ID
56+
output "production_release_id" {
57+
value = [
58+
for release in heroku_pipeline_promotion.staging_to_multiple.promoted_release_ids :
59+
release.release_id if release.app_id == heroku_app.production.id
60+
][0]
61+
}
4962
```
5063

5164
## Argument Reference
@@ -65,4 +78,7 @@ The following attributes are exported:
6578
* `id`: The UUID of the pipeline promotion.
6679
* `status`: The status of the promotion (`pending`, `completed`).
6780
* `created_at`: When the promotion was created.
68-
* `promoted_release_id`: The UUID of the release that was promoted.
81+
* `promoted_release_ids`: List of objects containing information about each promoted release. Each object has:
82+
* `app_id`: The UUID of the target app that received the promotion.
83+
* `release_id`: The UUID of the release created on that target app.
84+
* `promoted_release_id`: **(Deprecated)** The UUID of the first promoted release. Use `promoted_release_ids` instead. This attribute is maintained for backwards compatibility and will be removed in a future major version.

heroku/resource_heroku_pipeline_promotion.go

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,28 @@ func resourceHerokuPipelinePromotion() *schema.Resource {
7272
"promoted_release_id": {
7373
Type: schema.TypeString,
7474
Computed: true,
75-
Description: "ID of the release that was actually promoted",
75+
Deprecated: "Use promoted_release_ids instead. This attribute contains the first release ID from promoted_release_ids for backwards compatibility.",
76+
Description: "ID of the first promoted release (deprecated, use promoted_release_ids)",
77+
},
78+
79+
"promoted_release_ids": {
80+
Type: schema.TypeList,
81+
Computed: true,
82+
Description: "List of promoted releases with their associated target app IDs",
83+
Elem: &schema.Resource{
84+
Schema: map[string]*schema.Schema{
85+
"app_id": {
86+
Type: schema.TypeString,
87+
Computed: true,
88+
Description: "ID of the target app that received the promotion",
89+
},
90+
"release_id": {
91+
Type: schema.TypeString,
92+
Computed: true,
93+
Description: "ID of the release created on the target app",
94+
},
95+
},
96+
},
7697
},
7798
},
7899
}
@@ -142,16 +163,48 @@ func resourceHerokuPipelinePromotionRead(d *schema.ResourceData, meta interface{
142163
d.Set("status", promotion.Status)
143164
d.Set("created_at", promotion.CreatedAt.String())
144165

145-
// Set the release that was actually promoted
146-
if promotion.Source.Release.ID != "" {
147-
d.Set("promoted_release_id", promotion.Source.Release.ID)
148-
}
149-
150166
// Set configuration from API response
151167
d.Set("pipeline", promotion.Pipeline.ID)
152168
d.Set("source_app_id", promotion.Source.App.ID)
153169

154-
log.Printf("[DEBUG] Pipeline promotion read completed for: %s", d.Id())
170+
// Fetch promotion targets to get the resulting release IDs
171+
targets, err := client.PipelinePromotionTargetList(context.TODO(), d.Id(), nil)
172+
if err != nil {
173+
return fmt.Errorf("error retrieving pipeline promotion targets: %s", err)
174+
}
175+
176+
// Build list of promoted releases with app associations
177+
var promotedReleases []map[string]interface{}
178+
var firstReleaseID string
179+
for _, target := range targets {
180+
if target.Release != nil && target.Release.ID != "" {
181+
promotedRelease := map[string]interface{}{
182+
"app_id": target.App.ID,
183+
"release_id": target.Release.ID,
184+
}
185+
promotedReleases = append(promotedReleases, promotedRelease)
186+
187+
// Capture first release ID for backwards compatibility
188+
if firstReleaseID == "" {
189+
firstReleaseID = target.Release.ID
190+
}
191+
192+
log.Printf("[DEBUG] Found promoted release ID: %s for app: %s", target.Release.ID, target.App.ID)
193+
}
194+
}
195+
196+
// Set promoted_release_ids (new structured attribute)
197+
if err := d.Set("promoted_release_ids", promotedReleases); err != nil {
198+
return fmt.Errorf("error setting promoted_release_ids: %s", err)
199+
}
200+
201+
// Set promoted_release_id (deprecated, for backwards compatibility)
202+
// Contains the first release ID from the list
203+
if firstReleaseID != "" {
204+
d.Set("promoted_release_id", firstReleaseID)
205+
}
206+
207+
log.Printf("[DEBUG] Pipeline promotion read completed for: %s with %d promoted releases", d.Id(), len(promotedReleases))
155208
return nil
156209
}
157210

heroku/resource_heroku_pipeline_promotion_test.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestResourceHerokuPipelinePromotion_Schema(t *testing.T) {
2424
}
2525

2626
// Test computed fields
27-
computedFields := []string{"status", "created_at", "promoted_release_id"}
27+
computedFields := []string{"status", "created_at", "promoted_release_id", "promoted_release_ids"}
2828
for _, field := range computedFields {
2929
if _, ok := resource.Schema[field]; !ok {
3030
t.Errorf("Computed field %s not found in schema", field)
@@ -34,6 +34,30 @@ func TestResourceHerokuPipelinePromotion_Schema(t *testing.T) {
3434
}
3535
}
3636

37+
// Test promoted_release_id is deprecated
38+
if resource.Schema["promoted_release_id"].Deprecated == "" {
39+
t.Errorf("promoted_release_id should be marked as deprecated")
40+
}
41+
42+
// Test promoted_release_ids is a List of objects
43+
if resource.Schema["promoted_release_ids"].Type != schema.TypeList {
44+
t.Errorf("promoted_release_ids field should be TypeList")
45+
}
46+
47+
// Test promoted_release_ids contains objects with app_id and release_id
48+
promotedReleaseIdsElem, ok := resource.Schema["promoted_release_ids"].Elem.(*schema.Resource)
49+
if !ok {
50+
t.Fatal("Expected promoted_release_ids.Elem to be a Resource (object)")
51+
}
52+
53+
if _, hasAppID := promotedReleaseIdsElem.Schema["app_id"]; !hasAppID {
54+
t.Fatal("Expected promoted_release_ids objects to have app_id field")
55+
}
56+
57+
if _, hasReleaseID := promotedReleaseIdsElem.Schema["release_id"]; !hasReleaseID {
58+
t.Fatal("Expected promoted_release_ids objects to have release_id field")
59+
}
60+
3761
// Test targets field is a Set
3862
if resource.Schema["targets"].Type != schema.TypeSet {
3963
t.Errorf("targets field should be TypeSet")

0 commit comments

Comments
 (0)