Skip to content
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

Feat: Update the Plans API to be able to fetch & return resource changes #854

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package tfe
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/url"
Expand All @@ -28,6 +29,9 @@ type Plans interface {

// Retrieve the JSON execution plan
ReadJSONOutput(ctx context.Context, planID string) ([]byte, error)

// ReadResourceChanges fetch plan changed resources
ReadResourceChanges(ctx context.Context, planID string) (*PlanResourceChanges, error)
}

// plans implements Plans.
Expand Down Expand Up @@ -78,6 +82,32 @@ type PlanStatusTimestamps struct {
StartedAt time.Time `jsonapi:"attr,started-at,rfc3339"`
}

// ResourceChange details changes made to a specific resource within a plan.
type ResourceChange struct {
Address string `json:"address"` // Resource address in the configuration
Change Change `json:"change"` // Describes the change applied to the resource
Index interface{} `json:"index"` // Resource index, can be a string or number
Mode string `json:"mode"` // Resource management mode (managed or data)
Name string `json:"name"` // Resource name
ProviderName string `json:"provider_name"` // Name of the provider managing the resource
Type string `json:"type"` // Type of the resource
}

// Change captures the before and after states of a resource, including actions taken.
type Change struct {
Actions []string `json:"actions"` // Actions performed on the resource
After interface{} `json:"after"` // State of the resource after the change
AfterSensitive interface{} `json:"after_sensitive"` // Indicates if the "after" state includes sensitive values
AfterUnknown interface{} `json:"after_unknown"` // Parts of the "after" state that are unknown
Before interface{} `json:"before"` // State of the resource before the change
BeforeSensitive interface{} `json:"before_sensitive"` // Indicates if the "before" state includes sensitive values
}

// PlanResourceChanges encapsulates all resource changes within a plan.
type PlanResourceChanges struct {
ResourceChanges []ResourceChange `json:"resource_changes"` // Collection of resource changes
}

// Read a plan by its ID.
func (s *plans) Read(ctx context.Context, planID string) (*Plan, error) {
if !validStringID(&planID) {
Expand Down Expand Up @@ -163,3 +193,29 @@ func (s *plans) ReadJSONOutput(ctx context.Context, planID string) ([]byte, erro

return buf.Bytes(), nil
}

// ReadResourceChanges fetch plan changed resources
func (s *plans) ReadResourceChanges(ctx context.Context, planID string) (*PlanResourceChanges, error) {
if !validStringID(&planID) {
return nil, ErrInvalidPlanID
}

u := fmt.Sprintf("plans/%s/json-output-redacted", url.QueryEscape(planID))
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}

var buf bytes.Buffer
err = req.Do(ctx, &buf)
if err != nil {
return nil, err
}

var resourceChanges PlanResourceChanges
if err := json.Unmarshal(buf.Bytes(), &resourceChanges); err != nil {
return nil, err
}

return &resourceChanges, nil
}
19 changes: 19 additions & 0 deletions plan_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,22 @@ func TestPlansJSONOutput(t *testing.T) {
assert.Error(t, err)
})
}

func TestPlansReadResourceChanges(t *testing.T) {
client := testClient(t)
ctx := context.Background()
rTest, rTestCleanup := createPlannedRun(t, client, nil)
defer rTestCleanup()

t.Run("when resource changes exist for the plan", func(t *testing.T) {
resourceChanges, err := client.Plans.ReadResourceChanges(ctx, rTest.Plan.ID)
require.NoError(t, err)
assert.NotEmpty(t, resourceChanges.ResourceChanges)
})

t.Run("when the plan does not exist", func(t *testing.T) {
resourceChanges, err := client.Plans.ReadResourceChanges(ctx, "nonexisting")
assert.Nil(t, resourceChanges)
assert.Error(t, err)
})
}