Skip to content

Commit 3bdd007

Browse files
authored
command: tofu show -config (opentofu#2820)
Signed-off-by: Babur Ayanlar <babur.ayanlar@ableton.com>
1 parent 34d9878 commit 3bdd007

11 files changed

Lines changed: 435 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## 1.11.0 (Unreleased)
22

3+
ENHANCEMENTS:
4+
5+
* `tofu show` now supports a `-config` option, to be used in conjunction with `-json` to produce a machine-readable summary of the configuration without first creating a plan. ([#2820](https://github.com/opentofu/opentofu/pull/2820))
36

47
## Previous Releases
58

internal/command/arguments/show.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ const (
4949
//
5050
// For this target type, [Show.TargetArg] is the plan file to load.
5151
ShowPlan
52+
53+
// ShowConfig represents a request to show the current configuration.
54+
//
55+
// This target type does not use [Show.TargetArg].
56+
ShowConfig
5257
)
5358

5459
// ParseShow processes CLI arguments, returning a Show value and errors.
@@ -63,11 +68,13 @@ func ParseShow(args []string) (*Show, tfdiags.Diagnostics) {
6368
var jsonOutput bool
6469
var stateTarget bool
6570
var planTarget string
71+
var configTarget bool
6672
cmdFlags := extendedFlagSet("show", nil, nil, show.Vars)
6773
cmdFlags.BoolVar(&jsonOutput, "json", false, "json")
6874
cmdFlags.BoolVar(&show.ShowSensitive, "show-sensitive", false, "displays sensitive values")
6975
cmdFlags.BoolVar(&stateTarget, "state", false, "show the latest state snapshot")
7076
cmdFlags.StringVar(&planTarget, "plan", "", "show the plan from a saved plan file")
77+
cmdFlags.BoolVar(&configTarget, "config", false, "show the current configuration")
7178

7279
if err := cmdFlags.Parse(args); err != nil {
7380
diags = diags.Append(tfdiags.Sourceless(
@@ -77,15 +84,25 @@ func ParseShow(args []string) (*Show, tfdiags.Diagnostics) {
7784
))
7885
}
7986

87+
// If -config is specified, -json is required
88+
if configTarget && !jsonOutput {
89+
diags = diags.Append(tfdiags.Sourceless(
90+
tfdiags.Error,
91+
"JSON output required for configuration",
92+
"The -config option requires -json to be specified.",
93+
))
94+
return show, diags
95+
}
96+
8097
switch {
8198
case jsonOutput:
8299
show.ViewType = ViewJSON
83100
default:
84101
show.ViewType = ViewHuman
85102
}
86103

87-
if planTarget == "" && !stateTarget {
88-
// If neither of the target type options was provided then we're
104+
if planTarget == "" && !stateTarget && !configTarget {
105+
// If none of the target type options was provided then we're
89106
// in the legacy mode where the target type is implied by
90107
// the number of arguments.
91108
args = cmdFlags.Args()
@@ -131,11 +148,16 @@ func ParseShow(args []string) (*Show, tfdiags.Diagnostics) {
131148
show.TargetType = ShowPlan
132149
show.TargetArg = planTarget
133150
}
151+
if configTarget {
152+
targetTypes++
153+
show.TargetType = ShowConfig
154+
show.TargetArg = ""
155+
}
134156
if targetTypes != 1 {
135157
diags = diags.Append(tfdiags.Sourceless(
136158
tfdiags.Error,
137159
"Conflicting object types to show",
138-
"The -state and -plan=FILENAME options are mutually-exclusive, to specify which kind of object to show.",
160+
"The -state, -plan=FILENAME, and -config options are mutually-exclusive, to specify which kind of object to show.",
139161
))
140162
}
141163
return show, diags

internal/command/arguments/show_test.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ func TestParseShow_valid(t *testing.T) {
8282
ViewType: ViewJSON,
8383
},
8484
},
85+
"configuration with json": {
86+
[]string{"-config", "-json"},
87+
&Show{
88+
TargetType: ShowConfig,
89+
TargetArg: "",
90+
ViewType: ViewJSON,
91+
},
92+
},
8593
}
8694

8795
for name, tc := range testCases {
@@ -155,7 +163,7 @@ func TestParseShow_invalid(t *testing.T) {
155163
tfdiags.Sourceless(
156164
tfdiags.Error,
157165
"Conflicting object types to show",
158-
"The -state and -plan=FILENAME options are mutually-exclusive, to specify which kind of object to show.",
166+
"The -state, -plan=FILENAME, and -config options are mutually-exclusive, to specify which kind of object to show.",
159167
),
160168
},
161169
},
@@ -185,6 +193,49 @@ func TestParseShow_invalid(t *testing.T) {
185193
),
186194
},
187195
},
196+
"configuration without json": {
197+
[]string{"-config"},
198+
&Show{
199+
ViewType: ViewNone,
200+
},
201+
tfdiags.Diagnostics{
202+
tfdiags.Sourceless(
203+
tfdiags.Error,
204+
"JSON output required for configuration",
205+
"The -config option requires -json to be specified.",
206+
),
207+
},
208+
},
209+
"configuration with state": {
210+
[]string{"-config", "-state", "-json"},
211+
&Show{
212+
TargetType: ShowConfig,
213+
TargetArg: "",
214+
ViewType: ViewJSON,
215+
},
216+
tfdiags.Diagnostics{
217+
tfdiags.Sourceless(
218+
tfdiags.Error,
219+
"Conflicting object types to show",
220+
"The -state, -plan=FILENAME, and -config options are mutually-exclusive, to specify which kind of object to show.",
221+
),
222+
},
223+
},
224+
"configuration with plan": {
225+
[]string{"-config", "-plan=tfplan", "-json"},
226+
&Show{
227+
TargetType: ShowConfig,
228+
TargetArg: "",
229+
ViewType: ViewJSON,
230+
},
231+
tfdiags.Diagnostics{
232+
tfdiags.Sourceless(
233+
tfdiags.Error,
234+
"Conflicting object types to show",
235+
"The -state, -plan=FILENAME, and -config options are mutually-exclusive, to specify which kind of object to show.",
236+
),
237+
},
238+
},
188239
}
189240

190241
for name, tc := range testCases {

internal/command/arguments/showtargettype_string.go

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/command/jsonconfig/expression.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ func marshalExpression(ex hcl.Expression) expression {
4343
return ret
4444
}
4545

46-
val, _ := ex.Value(nil)
47-
if val != cty.NilVal {
46+
val, valueDiags := ex.Value(nil)
47+
if val != cty.NilVal && !valueDiags.HasErrors() {
4848
valJSON, _ := ctyjson.Marshal(val, val.Type())
4949
ret.ConstantValue = valJSON
5050
}

internal/command/show.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func (e *errUnusableDataMisc) Unwrap() error {
5353

5454
// ShowCommand is a Command implementation that reads and outputs the
5555
// contents of a OpenTofu plan or state file.
56+
// write about config here
5657
type ShowCommand struct {
5758
Meta
5859
viewType arguments.ViewType
@@ -135,6 +136,7 @@ Target selection options:
135136
136137
-state The latest state snapshot, if any.
137138
-plan=FILENAME The plan from a saved plan file.
139+
-config Show the current configuration (requires -json).
138140
139141
If no target selection options are provided, -state is the default.
140142
@@ -188,6 +190,8 @@ func (c *ShowCommand) show(ctx context.Context, targetType arguments.ShowTargetT
188190
return c.showFromLatestStateSnapshot(ctx, enc)
189191
case arguments.ShowPlan:
190192
return c.showFromSavedPlanFile(ctx, targetArg, enc)
193+
case arguments.ShowConfig:
194+
return c.showConfiguration(ctx)
191195
case arguments.ShowUnknownType:
192196
// This is a legacy case where we just have a filename and need to
193197
// try treating it as either a saved plan file or a local state
@@ -526,3 +530,56 @@ func getStateFromBackend(ctx context.Context, b backend.Backend, workspace strin
526530
stateFile := statemgr.Export(stateStore)
527531
return stateFile, nil
528532
}
533+
534+
// showConfiguration returns a function that will display the current configuration
535+
// in JSON format. This is a new feature that requires -json to be specified.
536+
func (c *ShowCommand) showConfiguration(ctx context.Context) (showRenderFunc, tfdiags.Diagnostics) {
537+
var diags tfdiags.Diagnostics
538+
539+
// Check if the directory is empty
540+
empty, err := configs.IsEmptyDir(".")
541+
if err != nil {
542+
diags = diags.Append(tfdiags.Sourceless(
543+
tfdiags.Error,
544+
"Error validating configuration directory",
545+
fmt.Sprintf("OpenTofu encountered an unexpected error while verifying that the given configuration directory is valid: %s.", err),
546+
))
547+
return nil, diags
548+
}
549+
if empty {
550+
diags = diags.Append(tfdiags.Sourceless(
551+
tfdiags.Error,
552+
"No configuration files",
553+
"This directory contains no OpenTofu configuration files.",
554+
))
555+
return nil, diags
556+
}
557+
558+
// Load the configuration
559+
config, configDiags := c.loadConfig(ctx, ".")
560+
diags = diags.Append(configDiags)
561+
if configDiags.HasErrors() {
562+
return nil, diags
563+
}
564+
565+
// Load provider schemas (without state)
566+
schemas, schemaDiags := c.MaybeGetSchemas(ctx, nil, config)
567+
diags = diags.Append(schemaDiags)
568+
if schemaDiags.HasErrors() {
569+
return nil, diags
570+
}
571+
if schemas == nil {
572+
diags = diags.Append(tfdiags.Sourceless(
573+
tfdiags.Error,
574+
"Failed to load provider schemas",
575+
"The configuration cannot be shown without provider schema information.",
576+
))
577+
return nil, diags
578+
}
579+
580+
// Return a function that will render the configuration as JSON
581+
return func(view views.Show) int {
582+
// Display the configuration using the view
583+
return view.DisplayConfig(config, schemas)
584+
}, diags
585+
}

0 commit comments

Comments
 (0)