Experiment with -json-into command output option

Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Christian Mesh
2025-12-17 11:41:05 -05:00
parent 184830c031
commit 853b6f25bb
16 changed files with 212 additions and 60 deletions

View File

@@ -35,6 +35,8 @@ type Plan struct {
// ViewType specifies which output format to use // ViewType specifies which output format to use
ViewType ViewType ViewType ViewType
JsonInto string
// ShowSensitive is used to display the value of variables marked as sensitive. // ShowSensitive is used to display the value of variables marked as sensitive.
ShowSensitive bool ShowSensitive bool
} }
@@ -59,6 +61,7 @@ func ParsePlan(args []string) (*Plan, tfdiags.Diagnostics) {
var json bool var json bool
cmdFlags.BoolVar(&json, "json", false, "json") cmdFlags.BoolVar(&json, "json", false, "json")
cmdFlags.StringVar(&plan.JsonInto, "json-into", "", "json-into")
if err := cmdFlags.Parse(args); err != nil { if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless( diags = diags.Append(tfdiags.Sourceless(
@@ -86,7 +89,7 @@ func ParsePlan(args []string) (*Plan, tfdiags.Diagnostics) {
} }
switch { switch {
case json: case json || plan.JsonInto != "":
plan.ViewType = ViewJSON plan.ViewType = ViewJSON
default: default:
plan.ViewType = ViewHuman plan.ViewType = ViewHuman

View File

@@ -8,6 +8,7 @@ package command
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/opentofu/opentofu/internal/command/views" "github.com/opentofu/opentofu/internal/command/views"
@@ -30,6 +31,7 @@ func (c *GetCommand) Run(args []string) int {
cmdFlags.BoolVar(&update, "update", false, "update") cmdFlags.BoolVar(&update, "update", false, "update")
cmdFlags.StringVar(&testsDirectory, "test-directory", "tests", "test-directory") cmdFlags.StringVar(&testsDirectory, "test-directory", "tests", "test-directory")
cmdFlags.BoolVar(&c.outputInJSON, "json", false, "json") cmdFlags.BoolVar(&c.outputInJSON, "json", false, "json")
cmdFlags.StringVar(&c.outputJSONInto, "json-into", "", "json-into")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil { if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error())) c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
@@ -41,8 +43,21 @@ func (c *GetCommand) Run(args []string) int {
c.oldUi = c.Ui c.oldUi = c.Ui
c.Ui = &WrappedUi{ c.Ui = &WrappedUi{
cliUi: c.oldUi, cliUi: c.oldUi,
jsonView: views.NewJSONView(c.View), jsonView: views.NewJSONView(c.View, nil),
outputInJSON: true, onlyOutputInJSON: true,
}
}
if c.outputJSONInto != "" {
out, err := os.OpenFile(c.outputJSONInto, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
panic(err)
}
c.oldUi = c.Ui
c.Ui = &WrappedUi{
cliUi: c.oldUi,
jsonView: views.NewJSONView(c.View, out),
onlyOutputInJSON: false,
} }
} }

View File

@@ -9,6 +9,7 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"os"
"reflect" "reflect"
"sort" "sort"
"strings" "strings"
@@ -73,6 +74,7 @@ func (c *InitCommand) Run(args []string) int {
cmdFlags.BoolVar(&c.Meta.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local OpenTofu versions are incompatible") cmdFlags.BoolVar(&c.Meta.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local OpenTofu versions are incompatible")
cmdFlags.StringVar(&testsDirectory, "test-directory", "tests", "test-directory") cmdFlags.StringVar(&testsDirectory, "test-directory", "tests", "test-directory")
cmdFlags.BoolVar(&c.outputInJSON, "json", false, "json") cmdFlags.BoolVar(&c.outputInJSON, "json", false, "json")
cmdFlags.StringVar(&c.outputJSONInto, "json-into", "", "json-into")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil { if err := cmdFlags.Parse(args); err != nil {
return 1 return 1
@@ -84,8 +86,21 @@ func (c *InitCommand) Run(args []string) int {
c.oldUi = c.Ui c.oldUi = c.Ui
c.Ui = &WrappedUi{ c.Ui = &WrappedUi{
cliUi: c.oldUi, cliUi: c.oldUi,
jsonView: views.NewJSONView(c.View), jsonView: views.NewJSONView(c.View, nil),
outputInJSON: true, onlyOutputInJSON: true,
}
}
if c.outputJSONInto != "" {
out, err := os.OpenFile(c.outputJSONInto, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
panic(err)
}
c.oldUi = c.Ui
c.Ui = &WrappedUi{
cliUi: c.oldUi,
jsonView: views.NewJSONView(c.View, out),
onlyOutputInJSON: false,
} }
} }

View File

@@ -300,6 +300,7 @@ type Meta struct {
ignoreRemoteVersion bool ignoreRemoteVersion bool
outputInJSON bool outputInJSON bool
outputJSONInto string
// Used to cache the root module rootModuleCallCache and known variables. // Used to cache the root module rootModuleCallCache and known variables.
// This helps prevent duplicate errors/warnings. // This helps prevent duplicate errors/warnings.
@@ -760,8 +761,17 @@ func (m *Meta) showDiagnostics(vals ...interface{}) {
return return
} }
if m.outputJSONInto != "" {
out, err := os.OpenFile(m.outputJSONInto, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
panic(err)
}
jsonView := views.NewJSONView(m.View, out)
jsonView.Diagnostics(diags)
return
}
if m.outputInJSON { if m.outputInJSON {
jsonView := views.NewJSONView(m.View) jsonView := views.NewJSONView(m.View, nil)
jsonView.Diagnostics(diags) jsonView.Diagnostics(diags)
return return
} }

View File

@@ -21,7 +21,7 @@ import (
type WrappedUi struct { type WrappedUi struct {
cliUi cli.Ui cliUi cli.Ui
jsonView *views.JSONView jsonView *views.JSONView
outputInJSON bool onlyOutputInJSON bool
} }
func (m *WrappedUi) Ask(s string) (string, error) { func (m *WrappedUi) Ask(s string) (string, error) {
@@ -33,32 +33,32 @@ func (m *WrappedUi) AskSecret(s string) (string, error) {
} }
func (m *WrappedUi) Output(s string) { func (m *WrappedUi) Output(s string) {
if m.outputInJSON {
m.jsonView.Output(s) m.jsonView.Output(s)
if m.onlyOutputInJSON {
return return
} }
m.cliUi.Output(s) m.cliUi.Output(s)
} }
func (m *WrappedUi) Info(s string) { func (m *WrappedUi) Info(s string) {
if m.outputInJSON {
m.jsonView.Info(s) m.jsonView.Info(s)
if m.onlyOutputInJSON {
return return
} }
m.cliUi.Info(s) m.cliUi.Info(s)
} }
func (m *WrappedUi) Error(s string) { func (m *WrappedUi) Error(s string) {
if m.outputInJSON {
m.jsonView.Error(s) m.jsonView.Error(s)
if m.onlyOutputInJSON {
return return
} }
m.cliUi.Error(s) m.cliUi.Error(s)
} }
func (m *WrappedUi) Warn(s string) { func (m *WrappedUi) Warn(s string) {
if m.outputInJSON {
m.jsonView.Warn(s) m.jsonView.Warn(s)
if m.onlyOutputInJSON {
return return
} }
m.cliUi.Warn(s) m.cliUi.Warn(s)

View File

@@ -43,7 +43,7 @@ func (c *PlanCommand) Run(rawArgs []string) int {
// Instantiate the view, even if there are flag errors, so that we render // Instantiate the view, even if there are flag errors, so that we render
// diagnostics according to the desired view // diagnostics according to the desired view
view := views.NewPlan(args.ViewType, c.View) view := views.NewPlan(args, c.View)
if diags.HasErrors() { if diags.HasErrors() {
view.Diagnostics(diags) view.Diagnostics(diags)

View File

@@ -33,7 +33,7 @@ func NewApply(vt arguments.ViewType, destroy bool, view *View) Apply {
switch vt { switch vt {
case arguments.ViewJSON: case arguments.ViewJSON:
return &ApplyJSON{ return &ApplyJSON{
view: NewJSONView(view), view: NewJSONView(view, nil),
destroy: destroy, destroy: destroy,
countHook: &countHook{}, countHook: &countHook{},
} }

View File

@@ -24,7 +24,7 @@ import (
// Test a sequence of hooks associated with creating a resource // Test a sequence of hooks associated with creating a resource
func TestJSONHook_create(t *testing.T) { func TestJSONHook_create(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
hook := newJSONHook(NewJSONView(NewView(streams))) hook := newJSONHook(NewJSONView(NewView(streams), nil))
var nowMu sync.Mutex var nowMu sync.Mutex
now := time.Now() now := time.Now()
@@ -195,7 +195,7 @@ func TestJSONHook_create(t *testing.T) {
func TestJSONHook_errors(t *testing.T) { func TestJSONHook_errors(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
hook := newJSONHook(NewJSONView(NewView(streams))) hook := newJSONHook(NewJSONView(NewView(streams), nil))
addr := addrs.Resource{ addr := addrs.Resource{
Mode: addrs.ManagedResourceMode, Mode: addrs.ManagedResourceMode,
@@ -282,7 +282,7 @@ func TestJSONHook_errors(t *testing.T) {
func TestJSONHook_refresh(t *testing.T) { func TestJSONHook_refresh(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
hook := newJSONHook(NewJSONView(NewView(streams))) hook := newJSONHook(NewJSONView(NewView(streams), nil))
addr := addrs.Resource{ addr := addrs.Resource{
Mode: addrs.DataResourceMode, Mode: addrs.DataResourceMode,
@@ -497,7 +497,7 @@ func TestJSONHook_ephemeral(t *testing.T) {
for _, tt := range cases { for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
h := newJSONHook(NewJSONView(NewView(streams))) h := newJSONHook(NewJSONView(NewView(streams), nil))
action, err := tt.preF(h) action, err := tt.preF(h)
if err != nil { if err != nil {

View File

@@ -8,6 +8,7 @@ package views
import ( import (
encJson "encoding/json" encJson "encoding/json"
"fmt" "fmt"
"os"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
@@ -22,10 +23,13 @@ import (
// command/views/json package. // command/views/json package.
const JSON_UI_VERSION = "1.2" const JSON_UI_VERSION = "1.2"
func NewJSONView(view *View) *JSONView { func NewJSONView(view *View, out *os.File) *JSONView {
if out == nil {
out = view.streams.Stdout.File
}
log := hclog.New(&hclog.LoggerOptions{ log := hclog.New(&hclog.LoggerOptions{
Name: "tofu.ui", Name: "tofu.ui",
Output: view.streams.Stdout.File, Output: out,
JSONFormat: true, JSONFormat: true,
JSONEscapeDisabled: true, JSONEscapeDisabled: true,
}) })

View File

@@ -27,7 +27,7 @@ import (
// convenient way to test that NewJSONView works. // convenient way to test that NewJSONView works.
func TestNewJSONView(t *testing.T) { func TestNewJSONView(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
NewJSONView(NewView(streams)) NewJSONView(NewView(streams), nil)
version := tfversion.String() version := tfversion.String()
want := []map[string]interface{}{ want := []map[string]interface{}{
@@ -78,7 +78,7 @@ func TestJSONView_Log(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.caseName, func(t *testing.T) { t.Run(tc.caseName, func(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
jv := NewJSONView(NewView(streams)) jv := NewJSONView(NewView(streams), nil)
jv.Log(tc.input) jv.Log(tc.input)
testJSONViewOutputEquals(t, done(t).Stdout(), tc.want) testJSONViewOutputEquals(t, done(t).Stdout(), tc.want)
}) })
@@ -89,7 +89,7 @@ func TestJSONView_Log(t *testing.T) {
// complex diagnostics are tested elsewhere. // complex diagnostics are tested elsewhere.
func TestJSONView_Diagnostics(t *testing.T) { func TestJSONView_Diagnostics(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
jv := NewJSONView(NewView(streams)) jv := NewJSONView(NewView(streams), nil)
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.Sourceless( diags = diags.Append(tfdiags.Sourceless(
@@ -134,7 +134,7 @@ func TestJSONView_Diagnostics(t *testing.T) {
func TestJSONView_DiagnosticsWithMetadata(t *testing.T) { func TestJSONView_DiagnosticsWithMetadata(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
jv := NewJSONView(NewView(streams)) jv := NewJSONView(NewView(streams), nil)
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.Sourceless( diags = diags.Append(tfdiags.Sourceless(
@@ -181,7 +181,7 @@ func TestJSONView_DiagnosticsWithMetadata(t *testing.T) {
func TestJSONView_PlannedChange(t *testing.T) { func TestJSONView_PlannedChange(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
jv := NewJSONView(NewView(streams)) jv := NewJSONView(NewView(streams), nil)
foo, diags := addrs.ParseModuleInstanceStr("module.foo") foo, diags := addrs.ParseModuleInstanceStr("module.foo")
if len(diags) > 0 { if len(diags) > 0 {
@@ -222,7 +222,7 @@ func TestJSONView_PlannedChange(t *testing.T) {
func TestJSONView_ResourceDrift(t *testing.T) { func TestJSONView_ResourceDrift(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
jv := NewJSONView(NewView(streams)) jv := NewJSONView(NewView(streams), nil)
foo, diags := addrs.ParseModuleInstanceStr("module.foo") foo, diags := addrs.ParseModuleInstanceStr("module.foo")
if len(diags) > 0 { if len(diags) > 0 {
@@ -263,7 +263,7 @@ func TestJSONView_ResourceDrift(t *testing.T) {
func TestJSONView_ChangeSummary(t *testing.T) { func TestJSONView_ChangeSummary(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
jv := NewJSONView(NewView(streams)) jv := NewJSONView(NewView(streams), nil)
jv.ChangeSummary(&viewsjson.ChangeSummary{ jv.ChangeSummary(&viewsjson.ChangeSummary{
Add: 1, Add: 1,
@@ -293,7 +293,7 @@ func TestJSONView_ChangeSummary(t *testing.T) {
func TestJSONView_ChangeSummaryWithImport(t *testing.T) { func TestJSONView_ChangeSummaryWithImport(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
jv := NewJSONView(NewView(streams)) jv := NewJSONView(NewView(streams), nil)
jv.ChangeSummary(&viewsjson.ChangeSummary{ jv.ChangeSummary(&viewsjson.ChangeSummary{
Add: 1, Add: 1,
@@ -324,7 +324,7 @@ func TestJSONView_ChangeSummaryWithImport(t *testing.T) {
func TestJSONView_ChangeSummaryWithForget(t *testing.T) { func TestJSONView_ChangeSummaryWithForget(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
jv := NewJSONView(NewView(streams)) jv := NewJSONView(NewView(streams), nil)
jv.ChangeSummary(&viewsjson.ChangeSummary{ jv.ChangeSummary(&viewsjson.ChangeSummary{
Add: 1, Add: 1,
@@ -355,7 +355,7 @@ func TestJSONView_ChangeSummaryWithForget(t *testing.T) {
func TestJSONView_Hook(t *testing.T) { func TestJSONView_Hook(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
jv := NewJSONView(NewView(streams)) jv := NewJSONView(NewView(streams), nil)
foo, diags := addrs.ParseModuleInstanceStr("module.foo") foo, diags := addrs.ParseModuleInstanceStr("module.foo")
if len(diags) > 0 { if len(diags) > 0 {
@@ -395,7 +395,7 @@ func TestJSONView_Hook(t *testing.T) {
func TestJSONView_Outputs(t *testing.T) { func TestJSONView_Outputs(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
jv := NewJSONView(NewView(streams)) jv := NewJSONView(NewView(streams), nil)
jv.Outputs(jsonentities.Outputs{ jv.Outputs(jsonentities.Outputs{
"boop_count": { "boop_count": {

View File

@@ -7,6 +7,7 @@ package views
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"strings" "strings"
@@ -49,6 +50,66 @@ func NewOperation(vt arguments.ViewType, inAutomation bool, view *View) Operatio
} }
} }
type OperationMulti []Operation
var _ Operation = (OperationMulti)(nil)
func (o OperationMulti) Interrupted() {
for _, operation := range o {
operation.Interrupted()
}
}
func (o OperationMulti) FatalInterrupt() {
for _, operation := range o {
operation.FatalInterrupt()
}
}
func (o OperationMulti) Stopping() {
for _, operation := range o {
operation.Stopping()
}
}
func (o OperationMulti) Cancelled(planMode plans.Mode) {
for _, operation := range o {
operation.Cancelled(planMode)
}
}
func (o OperationMulti) EmergencyDumpState(stateFile *statefile.File, enc encryption.StateEncryption) error {
var errs []error
for _, operation := range o {
errs = append(errs, operation.EmergencyDumpState(stateFile, enc))
}
return errors.Join(errs...)
}
func (o OperationMulti) PlannedChange(change *plans.ResourceInstanceChangeSrc) {
for _, operation := range o {
operation.PlannedChange(change)
}
}
func (o OperationMulti) Plan(plan *plans.Plan, schemas *tofu.Schemas) {
for _, operation := range o {
operation.Plan(plan, schemas)
}
}
func (o OperationMulti) PlanNextStep(planPath string, genConfigPath string) {
for _, operation := range o {
operation.PlanNextStep(planPath, genConfigPath)
}
}
func (o OperationMulti) Diagnostics(diags tfdiags.Diagnostics) {
for _, operation := range o {
operation.Diagnostics(diags)
}
}
type OperationHuman struct { type OperationHuman struct {
view *View view *View

View File

@@ -508,7 +508,7 @@ func TestOperation_planNextStepInAutomation(t *testing.T) {
// This test is not a realistic stream of messages. // This test is not a realistic stream of messages.
func TestOperationJSON_logs(t *testing.T) { func TestOperationJSON_logs(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
v := &OperationJSON{view: NewJSONView(NewView(streams))} v := &OperationJSON{view: NewJSONView(NewView(streams), nil)}
// Added an ephemeral resource change to double-check that it's not // Added an ephemeral resource change to double-check that it's not
// shown. // shown.
@@ -567,7 +567,7 @@ func TestOperationJSON_logs(t *testing.T) {
// we upgrade state format in the future. // we upgrade state format in the future.
func TestOperationJSON_emergencyDumpState(t *testing.T) { func TestOperationJSON_emergencyDumpState(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
v := &OperationJSON{view: NewJSONView(NewView(streams))} v := &OperationJSON{view: NewJSONView(NewView(streams), nil)}
stateFile := statefile.New(nil, "foo", 1) stateFile := statefile.New(nil, "foo", 1)
stateBuf := new(bytes.Buffer) stateBuf := new(bytes.Buffer)
@@ -601,7 +601,7 @@ func TestOperationJSON_emergencyDumpState(t *testing.T) {
func TestOperationJSON_planNoChanges(t *testing.T) { func TestOperationJSON_planNoChanges(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
v := &OperationJSON{view: NewJSONView(NewView(streams))} v := &OperationJSON{view: NewJSONView(NewView(streams), nil)}
plan := &plans.Plan{ plan := &plans.Plan{
Changes: plans.NewChanges(), Changes: plans.NewChanges(),
@@ -630,7 +630,7 @@ func TestOperationJSON_planNoChanges(t *testing.T) {
func TestOperationJSON_plan(t *testing.T) { func TestOperationJSON_plan(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
v := &OperationJSON{view: NewJSONView(NewView(streams))} v := &OperationJSON{view: NewJSONView(NewView(streams), nil)}
root := addrs.RootModuleInstance root := addrs.RootModuleInstance
vpc, diags := addrs.ParseModuleInstanceStr("module.vpc") vpc, diags := addrs.ParseModuleInstanceStr("module.vpc")
@@ -799,7 +799,7 @@ func TestOperationJSON_plan(t *testing.T) {
func TestOperationJSON_planWithImport(t *testing.T) { func TestOperationJSON_planWithImport(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
v := &OperationJSON{view: NewJSONView(NewView(streams))} v := &OperationJSON{view: NewJSONView(NewView(streams), nil)}
root := addrs.RootModuleInstance root := addrs.RootModuleInstance
vpc, diags := addrs.ParseModuleInstanceStr("module.vpc") vpc, diags := addrs.ParseModuleInstanceStr("module.vpc")
@@ -947,7 +947,7 @@ func TestOperationJSON_planWithImport(t *testing.T) {
func TestOperationJSON_planDriftWithMove(t *testing.T) { func TestOperationJSON_planDriftWithMove(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
v := &OperationJSON{view: NewJSONView(NewView(streams))} v := &OperationJSON{view: NewJSONView(NewView(streams), nil)}
root := addrs.RootModuleInstance root := addrs.RootModuleInstance
boop := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "boop"} boop := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "boop"}
@@ -1085,7 +1085,7 @@ func TestOperationJSON_planDriftWithMove(t *testing.T) {
func TestOperationJSON_planDriftWithMoveRefreshOnly(t *testing.T) { func TestOperationJSON_planDriftWithMoveRefreshOnly(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
v := &OperationJSON{view: NewJSONView(NewView(streams))} v := &OperationJSON{view: NewJSONView(NewView(streams), nil)}
root := addrs.RootModuleInstance root := addrs.RootModuleInstance
boop := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "boop"} boop := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "boop"}
@@ -1217,7 +1217,7 @@ func TestOperationJSON_planDriftWithMoveRefreshOnly(t *testing.T) {
func TestOperationJSON_planOutputChanges(t *testing.T) { func TestOperationJSON_planOutputChanges(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
v := &OperationJSON{view: NewJSONView(NewView(streams))} v := &OperationJSON{view: NewJSONView(NewView(streams), nil)}
root := addrs.RootModuleInstance root := addrs.RootModuleInstance
@@ -1303,7 +1303,7 @@ func TestOperationJSON_planOutputChanges(t *testing.T) {
func TestOperationJSON_plannedChange(t *testing.T) { func TestOperationJSON_plannedChange(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
v := &OperationJSON{view: NewJSONView(NewView(streams))} v := &OperationJSON{view: NewJSONView(NewView(streams), nil)}
root := addrs.RootModuleInstance root := addrs.RootModuleInstance
boop := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "boop"} boop := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "boop"}

View File

@@ -7,6 +7,7 @@ package views
import ( import (
"fmt" "fmt"
"os"
"github.com/opentofu/opentofu/internal/command/arguments" "github.com/opentofu/opentofu/internal/command/arguments"
"github.com/opentofu/opentofu/internal/tfdiags" "github.com/opentofu/opentofu/internal/tfdiags"
@@ -23,19 +24,62 @@ type Plan interface {
} }
// NewPlan returns an initialized Plan implementation for the given ViewType. // NewPlan returns an initialized Plan implementation for the given ViewType.
func NewPlan(vt arguments.ViewType, view *View) Plan { func NewPlan(args *arguments.Plan, view *View) Plan {
switch vt { human := &PlanHuman{
case arguments.ViewJSON:
return &PlanJSON{
view: NewJSONView(view),
}
case arguments.ViewHuman:
return &PlanHuman{
view: view, view: view,
inAutomation: view.RunningInAutomation(), inAutomation: view.RunningInAutomation(),
} }
switch args.ViewType {
case arguments.ViewJSON:
if args.JsonInto != "" {
out, err := os.OpenFile(args.JsonInto, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
panic(err)
}
return PlanMulti{human, &PlanJSON{
view: NewJSONView(view, out),
}}
}
return &PlanJSON{
view: NewJSONView(view, nil),
}
case arguments.ViewHuman:
return human
default: default:
panic(fmt.Sprintf("unknown view type %v", vt)) panic(fmt.Sprintf("unknown view type %v", args.ViewType))
}
}
type PlanMulti []Plan
var _ Plan = (PlanMulti)(nil)
func (p PlanMulti) Operation() Operation {
var operation OperationMulti
for _, plan := range p {
operation = append(operation, plan.Operation())
}
return operation
}
func (p PlanMulti) Hooks() []tofu.Hook {
var hooks []tofu.Hook
for _, plan := range p {
hooks = append(hooks, plan.Hooks()...)
}
return hooks
}
func (p PlanMulti) Diagnostics(diags tfdiags.Diagnostics) {
for _, plan := range p {
plan.Diagnostics(diags)
}
}
func (p PlanMulti) HelpPrompt() {
for _, plan := range p {
plan.HelpPrompt()
} }
} }

View File

@@ -24,7 +24,7 @@ import (
func TestPlanHuman_operation(t *testing.T) { func TestPlanHuman_operation(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
defer done(t) defer done(t)
v := NewPlan(arguments.ViewHuman, NewView(streams).SetRunningInAutomation(true)).Operation() v := NewPlan(&arguments.Plan{ViewType: arguments.ViewHuman}, NewView(streams).SetRunningInAutomation(true)).Operation()
if hv, ok := v.(*OperationHuman); !ok { if hv, ok := v.(*OperationHuman); !ok {
t.Fatalf("unexpected return type %t", v) t.Fatalf("unexpected return type %t", v)
} else if hv.inAutomation != true { } else if hv.inAutomation != true {
@@ -36,7 +36,7 @@ func TestPlanHuman_operation(t *testing.T) {
func TestPlanHuman_hooks(t *testing.T) { func TestPlanHuman_hooks(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
defer done(t) defer done(t)
v := NewPlan(arguments.ViewHuman, NewView(streams).SetRunningInAutomation((true))) v := NewPlan(&arguments.Plan{ViewType: arguments.ViewHuman}, NewView(streams).SetRunningInAutomation((true)))
hooks := v.Hooks() hooks := v.Hooks()
var uiHook *UiHook var uiHook *UiHook

View File

@@ -31,7 +31,7 @@ func NewRefresh(vt arguments.ViewType, view *View) Refresh {
switch vt { switch vt {
case arguments.ViewJSON: case arguments.ViewJSON:
return &RefreshJSON{ return &RefreshJSON{
view: NewJSONView(view), view: NewJSONView(view, nil),
} }
case arguments.ViewHuman: case arguments.ViewHuman:
return &RefreshHuman{ return &RefreshHuman{

View File

@@ -78,7 +78,7 @@ func NewTest(vt arguments.ViewType, view *View) Test {
switch vt { switch vt {
case arguments.ViewJSON: case arguments.ViewJSON:
return &TestJSON{ return &TestJSON{
view: NewJSONView(view), view: NewJSONView(view, nil),
} }
case arguments.ViewHuman: case arguments.ViewHuman:
return &TestHuman{ return &TestHuman{