mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Emit warnings for certain run events in cloud backend (#33020)
The cloud backend, which communicates with TFC like APIs, can create runs which may have one more configuration parameters altered. These alterations are emitted as run-events on the run so that API clients can consume and display them to users. This commit adds a step in plan operation to query the run-events once a run is created and then emit specific run-event descriptions to the console as warnings for the user.
This commit is contained in:
parent
c10a07f3b2
commit
7e2e834aff
@ -144,6 +144,7 @@ func testBackend(t *testing.T, obj cty.Value) (*Remote, func()) {
|
||||
b.client.Plans = mc.Plans
|
||||
b.client.PolicyChecks = mc.PolicyChecks
|
||||
b.client.Runs = mc.Runs
|
||||
b.client.RunEvents = mc.RunEvents
|
||||
b.client.StateVersions = mc.StateVersions
|
||||
b.client.Variables = mc.Variables
|
||||
b.client.Workspaces = mc.Workspaces
|
||||
|
@ -294,6 +294,11 @@ in order to capture the filesystem context the remote workspace expects:
|
||||
runHeader, b.hostname, b.organization, op.Workspace, r.ID)) + "\n"))
|
||||
}
|
||||
|
||||
// Render any warnings that were raised during run creation
|
||||
if err := b.renderRunWarnings(stopCtx, b.client, r.ID); err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
// Retrieve the run to get task stages.
|
||||
// Task Stages are calculated upfront so we only need to call this once for the run.
|
||||
taskStages, err := b.runTaskStages(stopCtx, b.client, r.ID)
|
||||
|
46
internal/cloud/backend_run_warning.go
Normal file
46
internal/cloud/backend_run_warning.go
Normal file
@ -0,0 +1,46 @@
|
||||
package cloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
tfe "github.com/hashicorp/go-tfe"
|
||||
)
|
||||
|
||||
const (
|
||||
changedPolicyEnforcementAction = "changed_policy_enforcements"
|
||||
changedTaskEnforcementAction = "changed_task_enforcements"
|
||||
ignoredPolicySetAction = "ignored_policy_sets"
|
||||
)
|
||||
|
||||
func (b *Cloud) renderRunWarnings(ctx context.Context, client *tfe.Client, runId string) error {
|
||||
if b.CLI == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err := client.RunEvents.List(ctx, runId, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We don't have to worry about paging as the API doesn't support it yet
|
||||
for _, re := range result.Items {
|
||||
switch re.Action {
|
||||
case changedPolicyEnforcementAction, changedTaskEnforcementAction, ignoredPolicySetAction:
|
||||
if re.Description != "" {
|
||||
b.CLI.Warn(b.Colorize().Color(strings.TrimSpace(fmt.Sprintf(
|
||||
runWarningHeader, re.Description)) + "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const runWarningHeader = `
|
||||
[reset][yellow]Warning:[reset] %s
|
||||
`
|
153
internal/cloud/backend_run_warning_test.go
Normal file
153
internal/cloud/backend_run_warning_test.go
Normal file
@ -0,0 +1,153 @@
|
||||
package cloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/hashicorp/go-tfe"
|
||||
tfemocks "github.com/hashicorp/go-tfe/mocks"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func MockAllRunEvents(t *testing.T, client *tfe.Client) (fullRunID string, emptyRunID string) {
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
fullRunID = "run-full"
|
||||
emptyRunID = "run-empty"
|
||||
|
||||
mockRunEventsAPI := tfemocks.NewMockRunEvents(ctrl)
|
||||
|
||||
emptyList := tfe.RunEventList{
|
||||
Items: []*tfe.RunEvent{},
|
||||
}
|
||||
fullList := tfe.RunEventList{
|
||||
Items: []*tfe.RunEvent{
|
||||
{
|
||||
Action: "created",
|
||||
CreatedAt: time.Now(),
|
||||
Description: "",
|
||||
},
|
||||
{
|
||||
Action: "changed_task_enforcements",
|
||||
CreatedAt: time.Now(),
|
||||
Description: "The enforcement level for task 'MockTask' was changed to 'advisory' because the run task limit was exceeded.",
|
||||
},
|
||||
{
|
||||
Action: "changed_policy_enforcements",
|
||||
CreatedAt: time.Now(),
|
||||
Description: "The enforcement level for policy 'MockPolicy' was changed to 'advisory' because the policy limit was exceeded.",
|
||||
},
|
||||
{
|
||||
Action: "ignored_policy_sets",
|
||||
CreatedAt: time.Now(),
|
||||
Description: "The policy set 'MockPolicySet' was ignored because the versioned policy set limit was exceeded.",
|
||||
},
|
||||
{
|
||||
Action: "queued",
|
||||
CreatedAt: time.Now(),
|
||||
Description: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
// Mock Full Request
|
||||
mockRunEventsAPI.
|
||||
EXPECT().
|
||||
List(gomock.Any(), fullRunID, gomock.Any()).
|
||||
Return(&fullList, nil).
|
||||
AnyTimes()
|
||||
|
||||
// Mock Full Request
|
||||
mockRunEventsAPI.
|
||||
EXPECT().
|
||||
List(gomock.Any(), emptyRunID, gomock.Any()).
|
||||
Return(&emptyList, nil).
|
||||
AnyTimes()
|
||||
|
||||
// Mock a bad Read response
|
||||
mockRunEventsAPI.
|
||||
EXPECT().
|
||||
List(gomock.Any(), gomock.Any(), gomock.Any()).
|
||||
Return(nil, tfe.ErrInvalidRunID).
|
||||
AnyTimes()
|
||||
|
||||
// Wire up the mock interfaces
|
||||
client.RunEvents = mockRunEventsAPI
|
||||
return
|
||||
}
|
||||
|
||||
func TestRunEventWarningsAll(t *testing.T) {
|
||||
b, bCleanup := testBackendWithName(t)
|
||||
defer bCleanup()
|
||||
|
||||
config := &tfe.Config{
|
||||
Token: "not-a-token",
|
||||
}
|
||||
client, _ := tfe.NewClient(config)
|
||||
fullRunID, _ := MockAllRunEvents(t, client)
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
err := b.renderRunWarnings(ctx, client, fullRunID)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected to not error but received %s", err)
|
||||
}
|
||||
|
||||
output := b.CLI.(*cli.MockUi).ErrorWriter.String()
|
||||
testString := "The enforcement level for task 'MockTask'"
|
||||
if !strings.Contains(output, testString) {
|
||||
t.Fatalf("Expected %q to contain %q but it did not", output, testString)
|
||||
}
|
||||
testString = "The enforcement level for policy 'MockPolicy'"
|
||||
if !strings.Contains(output, testString) {
|
||||
t.Fatalf("Expected %q to contain %q but it did not", output, testString)
|
||||
}
|
||||
testString = "The policy set 'MockPolicySet'"
|
||||
if !strings.Contains(output, testString) {
|
||||
t.Fatalf("Expected %q to contain %q but it did not", output, testString)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunEventWarningsEmpty(t *testing.T) {
|
||||
b, bCleanup := testBackendWithName(t)
|
||||
defer bCleanup()
|
||||
|
||||
config := &tfe.Config{
|
||||
Token: "not-a-token",
|
||||
}
|
||||
client, _ := tfe.NewClient(config)
|
||||
_, emptyRunID := MockAllRunEvents(t, client)
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
err := b.renderRunWarnings(ctx, client, emptyRunID)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected to not error but received %s", err)
|
||||
}
|
||||
|
||||
output := b.CLI.(*cli.MockUi).ErrorWriter.String()
|
||||
if output != "" {
|
||||
t.Fatalf("Expected %q to be empty but it was not", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunEventWarningsWithError(t *testing.T) {
|
||||
b, bCleanup := testBackendWithName(t)
|
||||
defer bCleanup()
|
||||
|
||||
config := &tfe.Config{
|
||||
Token: "not-a-token",
|
||||
}
|
||||
client, _ := tfe.NewClient(config)
|
||||
MockAllRunEvents(t, client)
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
err := b.renderRunWarnings(ctx, client, "bad run id")
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected to error but did not")
|
||||
}
|
||||
}
|
@ -241,6 +241,7 @@ func testBackend(t *testing.T, obj cty.Value, handlers map[string]func(http.Resp
|
||||
b.client.PolicySetOutcomes = mc.PolicySetOutcomes
|
||||
b.client.PolicyChecks = mc.PolicyChecks
|
||||
b.client.Runs = mc.Runs
|
||||
b.client.RunEvents = mc.RunEvents
|
||||
b.client.StateVersions = mc.StateVersions
|
||||
b.client.StateVersionOutputs = mc.StateVersionOutputs
|
||||
b.client.Variables = mc.Variables
|
||||
@ -312,6 +313,7 @@ func testUnconfiguredBackend(t *testing.T) (*Cloud, func()) {
|
||||
b.client.PolicySetOutcomes = mc.PolicySetOutcomes
|
||||
b.client.PolicyChecks = mc.PolicyChecks
|
||||
b.client.Runs = mc.Runs
|
||||
b.client.RunEvents = mc.RunEvents
|
||||
b.client.StateVersions = mc.StateVersions
|
||||
b.client.Variables = mc.Variables
|
||||
b.client.Workspaces = mc.Workspaces
|
||||
|
@ -34,6 +34,7 @@ type MockClient struct {
|
||||
RedactedPlans *MockRedactedPlans
|
||||
PolicyChecks *MockPolicyChecks
|
||||
Runs *MockRuns
|
||||
RunEvents *MockRunEvents
|
||||
StateVersions *MockStateVersions
|
||||
StateVersionOutputs *MockStateVersionOutputs
|
||||
Variables *MockVariables
|
||||
@ -51,6 +52,7 @@ func NewMockClient() *MockClient {
|
||||
c.PolicySetOutcomes = newMockPolicySetOutcomes(c)
|
||||
c.PolicyChecks = newMockPolicyChecks(c)
|
||||
c.Runs = newMockRuns(c)
|
||||
c.RunEvents = newMockRunEvents(c)
|
||||
c.StateVersions = newMockStateVersions(c)
|
||||
c.StateVersionOutputs = newMockStateVersionOutputs(c)
|
||||
c.Variables = newMockVariables(c)
|
||||
@ -1168,6 +1170,31 @@ func (m *MockRuns) Discard(ctx context.Context, runID string, options tfe.RunDis
|
||||
return nil
|
||||
}
|
||||
|
||||
type MockRunEvents struct{}
|
||||
|
||||
func newMockRunEvents(_ *MockClient) *MockRunEvents {
|
||||
return &MockRunEvents{}
|
||||
}
|
||||
|
||||
// List all the runs events of the given run.
|
||||
func (m *MockRunEvents) List(ctx context.Context, runID string, options *tfe.RunEventListOptions) (*tfe.RunEventList, error) {
|
||||
return &tfe.RunEventList{
|
||||
Items: []*tfe.RunEvent{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockRunEvents) Read(ctx context.Context, runEventID string) (*tfe.RunEvent, error) {
|
||||
return m.ReadWithOptions(ctx, runEventID, nil)
|
||||
}
|
||||
|
||||
func (m *MockRunEvents) ReadWithOptions(ctx context.Context, runEventID string, options *tfe.RunEventReadOptions) (*tfe.RunEvent, error) {
|
||||
return &tfe.RunEvent{
|
||||
ID: GenerateID("re-"),
|
||||
Action: "created",
|
||||
CreatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type MockStateVersions struct {
|
||||
client *MockClient
|
||||
states map[string][]byte
|
||||
|
Loading…
Reference in New Issue
Block a user