mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
We previously added the -config mode for showing the entire assembled configuration tree, including the content of any descendent modules, but that mode requires first running "tofu init" to install all of the provider and module dependencies of the configuration. This new -module=DIR mode returns a subset of the same JSON representation for only a single module that can be generated without first installing any dependencies, making this mode more appropriate for situations like generating documentation for a single module when importing it into the OpenTofu Registry. The registry generation process does not want to endure the overhead of installing other providers and modules when all it actually needs is metadata about the top-level declarations in the module. To minimize the risk to the already-working full-config JSON representation while still reusing most of its code, the implementation details of package jsonconfig are a little awkward here. Since this code changes relatively infrequently and is implementing an external interface subject to compatibility constraints, and since this new behavior is relatively marginal and intended primarily for our own OpenTofu Registry purposes, this is a pragmatic tradeoff that is hopefully compensated for well enough by the code comments that aim to explain what's going on for the benefit of future maintainers. If we _do_ find ourselves making substantial changes to this code at a later date then we can consider a more significant restructure of the code at that point; the weird stuff is intentionally encapsulated inside package jsonconfig so it can change later without changing any callers. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
320 lines
7.5 KiB
Go
320 lines
7.5 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package arguments
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
func TestParseShow_valid(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
args []string
|
|
want *Show
|
|
}{
|
|
"no options at all": {
|
|
nil,
|
|
&Show{
|
|
TargetType: ShowState,
|
|
TargetArg: "",
|
|
ViewType: ViewHuman,
|
|
},
|
|
},
|
|
"json with no other options": {
|
|
[]string{"-json"},
|
|
&Show{
|
|
TargetType: ShowState,
|
|
TargetArg: "",
|
|
ViewType: ViewJSON,
|
|
},
|
|
},
|
|
"latest state snapshot": {
|
|
[]string{"-state"},
|
|
&Show{
|
|
TargetType: ShowState,
|
|
TargetArg: "",
|
|
ViewType: ViewHuman,
|
|
},
|
|
},
|
|
"latest state snapshot, JSON": {
|
|
[]string{"-state", "-json"},
|
|
&Show{
|
|
TargetType: ShowState,
|
|
TargetArg: "",
|
|
ViewType: ViewJSON,
|
|
},
|
|
},
|
|
"saved plan file": {
|
|
[]string{"-plan=tfplan"},
|
|
&Show{
|
|
TargetType: ShowPlan,
|
|
TargetArg: "tfplan",
|
|
ViewType: ViewHuman,
|
|
},
|
|
},
|
|
"saved plan file, JSON": {
|
|
[]string{"-plan=tfplan", "-json"},
|
|
&Show{
|
|
TargetType: ShowPlan,
|
|
TargetArg: "tfplan",
|
|
ViewType: ViewJSON,
|
|
},
|
|
},
|
|
"legacy positional argument": {
|
|
[]string{"foo"},
|
|
&Show{
|
|
TargetType: ShowUnknownType, // caller must inspect "foo" to decide the type
|
|
TargetArg: "foo",
|
|
ViewType: ViewHuman,
|
|
},
|
|
},
|
|
"json with legacy positional argument": {
|
|
[]string{"-json", "foo"},
|
|
&Show{
|
|
TargetType: ShowUnknownType, // caller must inspect "foo" to decide the type
|
|
TargetArg: "foo",
|
|
ViewType: ViewJSON,
|
|
},
|
|
},
|
|
"configuration with json": {
|
|
[]string{"-config", "-json"},
|
|
&Show{
|
|
TargetType: ShowConfig,
|
|
TargetArg: "",
|
|
ViewType: ViewJSON,
|
|
},
|
|
},
|
|
"module with json": {
|
|
[]string{"-module=foo", "-json"},
|
|
&Show{
|
|
TargetType: ShowModule,
|
|
TargetArg: "foo",
|
|
ViewType: ViewJSON,
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, tc := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
got, diags := ParseShow(tc.args)
|
|
got.Vars = nil
|
|
if len(diags) > 0 {
|
|
t.Fatalf("unexpected diags: %v", diags)
|
|
}
|
|
if *got != *tc.want {
|
|
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseShow_invalid(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
args []string
|
|
want *Show
|
|
wantDiags tfdiags.Diagnostics
|
|
}{
|
|
"unknown option": {
|
|
[]string{"-boop"},
|
|
&Show{
|
|
TargetType: ShowState,
|
|
ViewType: ViewHuman,
|
|
},
|
|
tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Failed to parse command-line options",
|
|
"flag provided but not defined: -boop",
|
|
),
|
|
},
|
|
},
|
|
"positional arguments with state target selection": {
|
|
[]string{"-state", "bar"},
|
|
&Show{
|
|
ViewType: ViewHuman,
|
|
},
|
|
tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Unexpected command line arguments",
|
|
"This command does not expect any positional arguments when using a target-selection option.",
|
|
),
|
|
},
|
|
},
|
|
"positional arguments with planfile target selection": {
|
|
[]string{"-plan=foo", "bar"},
|
|
&Show{
|
|
ViewType: ViewHuman,
|
|
},
|
|
tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Unexpected command line arguments",
|
|
"This command does not expect any positional arguments when using a target-selection option.",
|
|
),
|
|
},
|
|
},
|
|
"conflicting target selection options": {
|
|
[]string{"-state", "-plan=foo"},
|
|
&Show{
|
|
TargetType: ShowPlan,
|
|
TargetArg: "foo",
|
|
ViewType: ViewHuman,
|
|
},
|
|
tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Conflicting object types to show",
|
|
"The -state, -plan=FILENAME, -config, and -module=DIR options are mutually-exclusive, to specify which kind of object to show.",
|
|
),
|
|
},
|
|
},
|
|
"too many arguments in legacy mode": {
|
|
[]string{"bar", "baz"},
|
|
&Show{
|
|
ViewType: ViewHuman,
|
|
},
|
|
tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Too many command line arguments",
|
|
"Expected at most one positional argument for the legacy positional argument mode.",
|
|
),
|
|
},
|
|
},
|
|
"too many arguments in legacy mode, json": {
|
|
[]string{"-json", "bar", "baz"},
|
|
&Show{
|
|
ViewType: ViewJSON,
|
|
},
|
|
tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Too many command line arguments",
|
|
"Expected at most one positional argument for the legacy positional argument mode.",
|
|
),
|
|
},
|
|
},
|
|
"configuration without json": {
|
|
[]string{"-config"},
|
|
&Show{
|
|
ViewType: ViewNone,
|
|
},
|
|
tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"JSON output required for configuration",
|
|
"The -config option requires -json to be specified.",
|
|
),
|
|
},
|
|
},
|
|
"configuration with state": {
|
|
[]string{"-config", "-state", "-json"},
|
|
&Show{
|
|
TargetType: ShowConfig,
|
|
TargetArg: "",
|
|
ViewType: ViewJSON,
|
|
},
|
|
tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Conflicting object types to show",
|
|
"The -state, -plan=FILENAME, -config, and -module=DIR options are mutually-exclusive, to specify which kind of object to show.",
|
|
),
|
|
},
|
|
},
|
|
"configuration with plan": {
|
|
[]string{"-config", "-plan=tfplan", "-json"},
|
|
&Show{
|
|
TargetType: ShowConfig,
|
|
TargetArg: "",
|
|
ViewType: ViewJSON,
|
|
},
|
|
tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Conflicting object types to show",
|
|
"The -state, -plan=FILENAME, -config, and -module=DIR options are mutually-exclusive, to specify which kind of object to show.",
|
|
),
|
|
},
|
|
},
|
|
"module without json": {
|
|
[]string{"-module=foo"},
|
|
&Show{
|
|
ViewType: ViewNone,
|
|
},
|
|
tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"JSON output required for module",
|
|
"The -module=DIR option requires -json to be specified.",
|
|
),
|
|
},
|
|
},
|
|
"module with state": {
|
|
[]string{"-module=foo", "-state", "-json"},
|
|
&Show{
|
|
TargetType: ShowModule,
|
|
TargetArg: "foo",
|
|
ViewType: ViewJSON,
|
|
},
|
|
tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Conflicting object types to show",
|
|
"The -state, -plan=FILENAME, -config, and -module=DIR options are mutually-exclusive, to specify which kind of object to show.",
|
|
),
|
|
},
|
|
},
|
|
"module with plan": {
|
|
[]string{"-module=foo", "-plan=tfplan", "-json"},
|
|
&Show{
|
|
TargetType: ShowModule,
|
|
TargetArg: "foo",
|
|
ViewType: ViewJSON,
|
|
},
|
|
tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Conflicting object types to show",
|
|
"The -state, -plan=FILENAME, -config, and -module=DIR options are mutually-exclusive, to specify which kind of object to show.",
|
|
),
|
|
},
|
|
},
|
|
"module with config": {
|
|
[]string{"-module=foo", "-config", "-json"},
|
|
&Show{
|
|
TargetType: ShowModule,
|
|
TargetArg: "foo",
|
|
ViewType: ViewJSON,
|
|
},
|
|
tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Conflicting object types to show",
|
|
"The -state, -plan=FILENAME, -config, and -module=DIR options are mutually-exclusive, to specify which kind of object to show.",
|
|
),
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, tc := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
got, gotDiags := ParseShow(tc.args)
|
|
got.Vars = nil
|
|
if *got != *tc.want {
|
|
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
|
|
}
|
|
if !reflect.DeepEqual(gotDiags, tc.wantDiags) {
|
|
t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(gotDiags), spew.Sdump(tc.wantDiags))
|
|
}
|
|
})
|
|
}
|
|
}
|