// 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 ( "github.com/opentofu/opentofu/internal/tfdiags" ) // Show represents the command-line arguments for the show command. type Show struct { // TargetType and TargetArg together describe the object that was // requested to be shown. // // The meaning of TargetArg varies depending on TargetType. Refer to // the documentation for each [ShowTargetType] constant for details. TargetType ShowTargetType TargetArg string // ViewType specifies which output format to use: human, JSON, or "raw". ViewType ViewType Vars *Vars // ShowSensitive is used to display the value of variables marked as sensitive. ShowSensitive bool } // ShowTargetType represents the type of object that is requested to be // shown by the "tofu show" command. type ShowTargetType int //go:generate go tool golang.org/x/tools/cmd/stringer -type=ShowTargetType const ( // ShowUnknownType is the zero value of [ShowTargetType], and represents // that the target type is ambiguous and so must be inferred by the // caller based on the [Show.TargetArg] value. ShowUnknownType ShowTargetType = iota // ShowState represents a request to show the latest state snapshot. // // This target type does not use [Show.TargetArg]. ShowState // ShowPlan represents a request to show a plan loaded from a saved // plan file. // // For this target type, [Show.TargetArg] is the plan file to load. ShowPlan // ShowConfig represents a request to show the current configuration. // // This target type does not use [Show.TargetArg]. ShowConfig // ShowModule represents a request to show just one module in isolation, // without requiring any of its dependencies to be installed. // // For this target type, [Show.TargetArg] is a path to the directory // containing the module. ShowModule ) // ParseShow processes CLI arguments, returning a Show value and errors. // If errors are encountered, a Show value is still returned representing // the best effort interpretation of the arguments. func ParseShow(args []string) (*Show, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics show := &Show{ Vars: &Vars{}, } var jsonOutput bool var stateTarget bool var planTarget string var configTarget bool var moduleTarget string cmdFlags := extendedFlagSet("show", nil, nil, show.Vars) cmdFlags.BoolVar(&jsonOutput, "json", false, "json") cmdFlags.BoolVar(&show.ShowSensitive, "show-sensitive", false, "displays sensitive values") cmdFlags.BoolVar(&stateTarget, "state", false, "show the latest state snapshot") cmdFlags.StringVar(&planTarget, "plan", "", "show the plan from a saved plan file") cmdFlags.BoolVar(&configTarget, "config", false, "show the current configuration") cmdFlags.StringVar(&moduleTarget, "module", "", "show metadata about one module") if err := cmdFlags.Parse(args); err != nil { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Failed to parse command-line options", err.Error(), )) } // If -config or -module=... is selected, -json is required if configTarget && !jsonOutput { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "JSON output required for configuration", "The -config option requires -json to be specified.", )) return show, diags } if moduleTarget != "" && !jsonOutput { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "JSON output required for module", "The -module=DIR option requires -json to be specified.", )) return show, diags } switch { case jsonOutput: show.ViewType = ViewJSON default: show.ViewType = ViewHuman } if planTarget == "" && moduleTarget == "" && !stateTarget && !configTarget { // If none of the target type options was provided then we're // in the legacy mode where the target type is implied by // the number of arguments. args = cmdFlags.Args() switch len(args) { case 0: show.TargetType = ShowState show.TargetArg = "" case 1: // This case is ambiguous: the argument could be either // a saved plan file or a local state snapshot such as // the output from "tofu state pull". The caller will need // to probe TargetArg to decide which it is. show.TargetType = ShowUnknownType show.TargetArg = args[0] default: diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Too many command line arguments", "Expected at most one positional argument for the legacy positional argument mode.", )) } return show, diags } // The following handles the modern mode where the target type is // chosen based on which target type option was used. if len(cmdFlags.Args()) != 0 { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Unexpected command line arguments", "This command does not expect any positional arguments when using a target-selection option.", )) return show, diags } targetTypes := 0 if stateTarget { targetTypes++ show.TargetType = ShowState show.TargetArg = "" } if planTarget != "" { targetTypes++ show.TargetType = ShowPlan show.TargetArg = planTarget } if configTarget { targetTypes++ show.TargetType = ShowConfig show.TargetArg = "" } if moduleTarget != "" { targetTypes++ show.TargetType = ShowModule show.TargetArg = moduleTarget } if targetTypes != 1 { diags = diags.Append(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.", )) } return show, diags }