mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
Prototype of Drift Check
Signed-off-by: Diogenes Fernandes <diofeher@gmail.com>
This commit is contained in:
@@ -150,6 +150,12 @@ func initCommands(
|
|||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"drift-check": func() (cli.Command, error) {
|
||||||
|
return &command.DriftCheckCommand{
|
||||||
|
Meta: meta,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
|
||||||
"env": func() (cli.Command, error) {
|
"env": func() (cli.Command, error) {
|
||||||
return &command.WorkspaceCommand{
|
return &command.WorkspaceCommand{
|
||||||
Meta: meta,
|
Meta: meta,
|
||||||
|
|||||||
395
internal/command/drift_check.go
Normal file
395
internal/command/drift_check.go
Normal file
@@ -0,0 +1,395 @@
|
|||||||
|
// Copyright (c) The OpenTofu Authors
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
// Copyright (c) 2023 HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/opentofu/opentofu/internal/backend"
|
||||||
|
"github.com/opentofu/opentofu/internal/command/arguments"
|
||||||
|
"github.com/opentofu/opentofu/internal/command/views"
|
||||||
|
"github.com/opentofu/opentofu/internal/encryption"
|
||||||
|
"github.com/opentofu/opentofu/internal/plans/planfile"
|
||||||
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DriftCheckCommand is a Command implementation that compares a OpenTofu
|
||||||
|
// configuration to an actual infrastructure and shows the differences.
|
||||||
|
type DriftCheckCommand struct {
|
||||||
|
Meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DriftCheckCommand) Check(ctx context.Context, rawArgs []string) int {
|
||||||
|
fmt.Println("Running Check Again", rawArgs)
|
||||||
|
// Parse and validate flags
|
||||||
|
args, diags := arguments.ParsePlan(rawArgs)
|
||||||
|
|
||||||
|
c.View.SetShowSensitive(args.ShowSensitive)
|
||||||
|
|
||||||
|
// FIXME: the -input flag value is needed to initialize the backend and the
|
||||||
|
// operation, but there is no clear path to pass this value down, so we
|
||||||
|
// continue to mutate the Meta object state for now.
|
||||||
|
c.Meta.input = args.InputEnabled
|
||||||
|
// Instantiate the view, even if there are flag errors, so that we render
|
||||||
|
// diagnostics according to the desired view
|
||||||
|
view := views.NewDriftCheck(c.View)
|
||||||
|
|
||||||
|
if diags.HasErrors() {
|
||||||
|
view.Diagnostics(diags)
|
||||||
|
view.HelpPrompt()
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for user-supplied plugin path
|
||||||
|
var err error
|
||||||
|
if c.pluginPath, err = c.loadPluginPath(); err != nil {
|
||||||
|
diags = diags.Append(err)
|
||||||
|
view.Diagnostics(diags)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
// FIXME: the -parallelism flag is used to control the concurrency of
|
||||||
|
// OpenTofu operations. At the moment, this value is used both to
|
||||||
|
// initialize the backend via the ContextOpts field inside CLIOpts, and to
|
||||||
|
// set a largely unused field on the Operation request. Again, there is no
|
||||||
|
// clear path to pass this value down, so we continue to mutate the Meta
|
||||||
|
// object state for now.
|
||||||
|
c.Meta.parallelism = args.Operation.Parallelism
|
||||||
|
|
||||||
|
diags = diags.Append(c.providerDevOverrideRuntimeWarnings())
|
||||||
|
|
||||||
|
// Inject variables from args into meta for static evaluation
|
||||||
|
c.GatherVariables(args.Vars)
|
||||||
|
|
||||||
|
// Load the encryption configuration
|
||||||
|
enc, encDiags := c.Encryption(ctx)
|
||||||
|
diags = diags.Append(encDiags)
|
||||||
|
if encDiags.HasErrors() {
|
||||||
|
view.Diagnostics(diags)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the backend, passing the plan file if present, and the
|
||||||
|
// backend-specific arguments
|
||||||
|
be, beDiags := c.PrepareBackend(ctx, args.State, args.ViewType, enc.State())
|
||||||
|
diags = diags.Append(beDiags)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
view.Diagnostics(diags)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the operation request
|
||||||
|
opReq, opDiags := c.OperationRequest(ctx, be, view, args.ViewType, args.Operation, enc)
|
||||||
|
diags = diags.Append(opDiags)
|
||||||
|
|
||||||
|
// Before we delegate to the backend, we'll print any warning diagnostics
|
||||||
|
// we've accumulated here, since the backend will start fresh with its own
|
||||||
|
// diagnostics.
|
||||||
|
view.Diagnostics(diags)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
diags = nil
|
||||||
|
|
||||||
|
// Run the operation
|
||||||
|
op, diags := c.RunOperation(ctx, be, opReq)
|
||||||
|
view.Diagnostics(diags)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if op.Result != backend.OperationSuccess {
|
||||||
|
return op.Result.ExitStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
view.Diagnostics(diags)
|
||||||
|
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DriftCheckCommand) Run(rawArgs []string) int {
|
||||||
|
ctx := c.CommandContext()
|
||||||
|
|
||||||
|
// Parse and apply global view arguments
|
||||||
|
common, rawArgs := arguments.ParseView(rawArgs)
|
||||||
|
c.View.Configure(common)
|
||||||
|
|
||||||
|
// Propagate -no-color for legacy use of Ui. The remote backend and
|
||||||
|
// cloud package use this; it should be removed when/if they are
|
||||||
|
// migrated to views.
|
||||||
|
c.Meta.color = !common.NoColor
|
||||||
|
c.Meta.Color = c.Meta.color
|
||||||
|
|
||||||
|
for {
|
||||||
|
fmt.Println("Calculating Drift")
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
c.Check(ctx, rawArgs)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
// If we don't set the configLoader to nil, it's going to
|
||||||
|
// reuse the same config from the previous run,
|
||||||
|
// so Tofu won't see difference between Check calls
|
||||||
|
c.configLoader = nil
|
||||||
|
}
|
||||||
|
// return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DriftCheckCommand) PrepareBackend(ctx context.Context, args *arguments.State, viewType arguments.ViewType, enc encryption.StateEncryption) (backend.Enhanced, tfdiags.Diagnostics) {
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
c.Meta.applyStateArguments(args)
|
||||||
|
|
||||||
|
// Load the backend
|
||||||
|
var be backend.Enhanced
|
||||||
|
var beDiags tfdiags.Diagnostics
|
||||||
|
backendConfig, configDiags := c.loadBackendConfig(ctx, ".")
|
||||||
|
diags = diags.Append(configDiags)
|
||||||
|
if configDiags.HasErrors() {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
be, beDiags = c.Backend(ctx, &BackendOpts{
|
||||||
|
Config: backendConfig,
|
||||||
|
ViewType: viewType,
|
||||||
|
}, enc)
|
||||||
|
|
||||||
|
diags = diags.Append(beDiags)
|
||||||
|
if beDiags.HasErrors() {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
return be, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DriftCheckCommand) OperationRequest(
|
||||||
|
ctx context.Context,
|
||||||
|
be backend.Enhanced,
|
||||||
|
view views.DriftCheck,
|
||||||
|
viewType arguments.ViewType,
|
||||||
|
args *arguments.Operation,
|
||||||
|
enc encryption.Encryption,
|
||||||
|
) (*backend.Operation, tfdiags.Diagnostics) {
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
|
// Build the operation
|
||||||
|
opReq := c.Operation(ctx, be, viewType, enc)
|
||||||
|
opReq.ConfigDir = "."
|
||||||
|
opReq.PlanMode = args.PlanMode
|
||||||
|
opReq.Hooks = view.Hooks()
|
||||||
|
opReq.PlanRefresh = args.Refresh
|
||||||
|
opReq.Targets = args.Targets
|
||||||
|
opReq.Excludes = args.Excludes
|
||||||
|
opReq.ForceReplace = args.ForceReplace
|
||||||
|
opReq.Type = backend.OperationTypePlan
|
||||||
|
opReq.View = view.Operation()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
opReq.ConfigLoader, err = c.initConfigLoader()
|
||||||
|
if err != nil {
|
||||||
|
diags = diags.Append(fmt.Errorf("Failed to initialize config loader: %w", err))
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
return opReq, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DriftCheckCommand) GatherVariables(args *arguments.Vars) {
|
||||||
|
// FIXME the arguments package currently trivially gathers variable related
|
||||||
|
// arguments in a heterogeneous slice, in order to minimize the number of
|
||||||
|
// code paths gathering variables during the transition to this structure.
|
||||||
|
// Once all commands that gather variables have been converted to this
|
||||||
|
// structure, we could move the variable gathering code to the arguments
|
||||||
|
// package directly, removing this shim layer.
|
||||||
|
|
||||||
|
varArgs := args.All()
|
||||||
|
items := make([]rawFlag, len(varArgs))
|
||||||
|
for i := range varArgs {
|
||||||
|
items[i].Name = varArgs[i].Name
|
||||||
|
items[i].Value = varArgs[i].Value
|
||||||
|
}
|
||||||
|
c.Meta.variableArgs = rawFlags{items: &items}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DriftCheckCommand) Help() string {
|
||||||
|
helpText := `
|
||||||
|
Usage: tofu [global options] plan [options]
|
||||||
|
|
||||||
|
Generates a speculative execution plan, showing what actions OpenTofu would
|
||||||
|
take to apply the current configuration. This command will not actually
|
||||||
|
perform the planned actions.
|
||||||
|
|
||||||
|
You can optionally save the plan to a file, which you can then pass to the
|
||||||
|
"apply" command to perform exactly the actions described in the plan.
|
||||||
|
|
||||||
|
Plan Customization Options:
|
||||||
|
|
||||||
|
The following options customize how OpenTofu will produce its plan. You can
|
||||||
|
also use these options when you run "tofu apply" without passing it a saved
|
||||||
|
plan, in order to plan and apply in a single command.
|
||||||
|
|
||||||
|
-destroy Select the "destroy" planning mode, which creates a
|
||||||
|
plan to destroy all objects currently managed by this
|
||||||
|
OpenTofu configuration instead of the usual behavior.
|
||||||
|
|
||||||
|
-refresh-only Select the "refresh only" planning mode, which checks
|
||||||
|
whether remote objects still match the outcome of the
|
||||||
|
most recent OpenTofu apply but does not propose any
|
||||||
|
actions to undo any changes made outside of OpenTofu.
|
||||||
|
|
||||||
|
-refresh=false Skip checking for external changes to remote objects
|
||||||
|
while creating the plan. This can potentially make
|
||||||
|
planning faster, but at the expense of possibly
|
||||||
|
planning against a stale record of the remote system
|
||||||
|
state.
|
||||||
|
|
||||||
|
-replace=resource Force replacement of a particular resource instance
|
||||||
|
using its resource address. If the plan would've
|
||||||
|
otherwise produced an update or no-op action for this
|
||||||
|
instance, OpenTofu will plan to replace it instead.
|
||||||
|
You can use this option multiple times to replace
|
||||||
|
more than one object.
|
||||||
|
|
||||||
|
-target=resource Limit the planning operation to only the given
|
||||||
|
module, resource, or resource instance and all of its
|
||||||
|
dependencies. You can use this option multiple times
|
||||||
|
to include more than one object. This is for
|
||||||
|
exceptional use only. Cannot be used alongside the
|
||||||
|
-exclude option.
|
||||||
|
|
||||||
|
-target-file=filename Similar to -target, but specifies zero or more
|
||||||
|
resource addresses from a file.
|
||||||
|
|
||||||
|
-exclude=resource Limit the planning operation to not operate on the
|
||||||
|
given module, resource, or resource instance and all
|
||||||
|
of the resources and modules that depend on it. You
|
||||||
|
can use this option multiple times to exclude more
|
||||||
|
than one object. This is for exceptional use only.
|
||||||
|
Cannot be used together with the -target option.
|
||||||
|
|
||||||
|
-exclude-file=filename Similar to -exclude, but specifies zero or more
|
||||||
|
resource addresses from a file.
|
||||||
|
|
||||||
|
-var 'foo=bar' Set a value for one of the input variables in the
|
||||||
|
root module of the configuration. Use this option
|
||||||
|
more than once to set more than one variable.
|
||||||
|
|
||||||
|
-var-file=filename Load variable values from the given file, in addition
|
||||||
|
to the default files terraform.tfvars and
|
||||||
|
*.auto.tfvars. Use this option more than once to
|
||||||
|
include more than one variables file.
|
||||||
|
|
||||||
|
Other Options:
|
||||||
|
|
||||||
|
-compact-warnings If OpenTofu produces any warnings that are not
|
||||||
|
accompanied by errors, shows them in a more
|
||||||
|
compact form that includes only the summary
|
||||||
|
messages.
|
||||||
|
|
||||||
|
-consolidate-warnings=false If OpenTofu produces any warnings, do not
|
||||||
|
attempt to consolidate similar messages. All
|
||||||
|
locations for all warnings will be listed.
|
||||||
|
|
||||||
|
-consolidate-errors If OpenTofu produces any errors, attempt to
|
||||||
|
consolidate similar messages into a single item.
|
||||||
|
|
||||||
|
-detailed-exitcode Return detailed exit codes when the command
|
||||||
|
exits. The detailed exit codes are:
|
||||||
|
0 - Succeeded but no changes proposed
|
||||||
|
1 - Planning failed with an error
|
||||||
|
2 - Succeeded and changes are proposed
|
||||||
|
|
||||||
|
-generate-config-out=path (Experimental) If import blocks are present in
|
||||||
|
configuration, instructs OpenTofu to generate
|
||||||
|
HCL for any imported resources not already
|
||||||
|
present. The configuration is written to a new
|
||||||
|
file at PATH, which must not already exist.
|
||||||
|
OpenTofu may still attempt to write
|
||||||
|
configuration if planning fails with an error.
|
||||||
|
|
||||||
|
-input=false Disable prompting for required input variables
|
||||||
|
that are not set some other way.
|
||||||
|
|
||||||
|
-lock=false Don't hold a state lock during the operation.
|
||||||
|
This is dangerous if others might concurrently
|
||||||
|
run commands against the same workspace.
|
||||||
|
|
||||||
|
-lock-timeout=duration Duration to retry a state lock, such as "5s"
|
||||||
|
to represent five seconds.
|
||||||
|
|
||||||
|
-no-color Disable virtual terminal escape sequences.
|
||||||
|
|
||||||
|
-concise Disable progress-related messages.
|
||||||
|
|
||||||
|
-out=path Write a plan file to the given path. This can be
|
||||||
|
used as input to the "apply" command.
|
||||||
|
|
||||||
|
-parallelism=n Limit the number of concurrent operations.
|
||||||
|
Defaults to 10.
|
||||||
|
|
||||||
|
-state=statefile A legacy option used for the local backend only.
|
||||||
|
Refer to the local backend's documentation for
|
||||||
|
more information.
|
||||||
|
|
||||||
|
-show-sensitive If specified, sensitive values will not be
|
||||||
|
redacted in te UI output.
|
||||||
|
|
||||||
|
-json Produce output in a machine-readable JSON
|
||||||
|
format, suitable for use in text editor
|
||||||
|
integrations and other automated systems.
|
||||||
|
|
||||||
|
-deprecation=module:m Specify what type of warnings are shown.
|
||||||
|
Accepted values for "m": all, local, none.
|
||||||
|
Default: all. When "all" is selected, OpenTofu
|
||||||
|
will show the deprecation warnings for all
|
||||||
|
modules. When "local" is selected, the warns
|
||||||
|
will be shown only for the modules that are
|
||||||
|
imported with a relative path. When "none" is
|
||||||
|
selected, all the deprecation warnings will be
|
||||||
|
dropped.
|
||||||
|
`
|
||||||
|
return strings.TrimSpace(helpText)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DriftCheckCommand) Synopsis() string {
|
||||||
|
return "Show changes required by the current configuration"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DriftCheckCommand) LoadPlanFile(path string, enc encryption.Encryption) (*planfile.WrappedPlanFile, tfdiags.Diagnostics) {
|
||||||
|
var planFile *planfile.WrappedPlanFile
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
|
// Try to load plan if path is specified
|
||||||
|
if path != "" {
|
||||||
|
var err error
|
||||||
|
planFile, err = c.PlanFile(path, enc.Plan())
|
||||||
|
fmt.Println("LoadPlanFile.PlanFile", planFile)
|
||||||
|
if err != nil {
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
fmt.Sprintf("Failed to load %q as a plan file", path),
|
||||||
|
fmt.Sprintf("Error: %s", err),
|
||||||
|
))
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the path doesn't look like a plan, both planFile and err will be
|
||||||
|
// nil. In that case, the user is probably trying to use the positional
|
||||||
|
// argument to specify a configuration path. Point them at -chdir.
|
||||||
|
if planFile == nil {
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
fmt.Sprintf("Failed to load %q as a plan file", path),
|
||||||
|
"The specified path is a directory, not a plan file. You can use the global -chdir flag to use this directory as the configuration root.",
|
||||||
|
))
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return planFile, diags
|
||||||
|
}
|
||||||
100
internal/command/views/drift_check.go
Normal file
100
internal/command/views/drift_check.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
// Copyright (c) The OpenTofu Authors
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
// Copyright (c) 2023 HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/opentofu/opentofu/internal/command/arguments"
|
||||||
|
"github.com/opentofu/opentofu/internal/command/format"
|
||||||
|
"github.com/opentofu/opentofu/internal/states"
|
||||||
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
|
"github.com/opentofu/opentofu/internal/tofu"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The DriftCheck view is used for the drift-check command.
|
||||||
|
type DriftCheck interface {
|
||||||
|
ResourceCount(stateOutPath string)
|
||||||
|
Outputs(outputValues map[string]*states.OutputValue)
|
||||||
|
|
||||||
|
Operation() Operation
|
||||||
|
Hooks() []tofu.Hook
|
||||||
|
|
||||||
|
Diagnostics(diags tfdiags.Diagnostics)
|
||||||
|
HelpPrompt()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDriftCheck returns an initialized DriftCheck implementation for the given ViewType.
|
||||||
|
func NewDriftCheck(view *View) DriftCheck {
|
||||||
|
return &DriftCheckHuman{
|
||||||
|
view: view,
|
||||||
|
inAutomation: view.RunningInAutomation(),
|
||||||
|
countHook: &countHook{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The DriftCheckHuman implementation renders human-readable text logs, suitable for
|
||||||
|
// a scrolling terminal.
|
||||||
|
type DriftCheckHuman struct {
|
||||||
|
view *View
|
||||||
|
|
||||||
|
destroy bool
|
||||||
|
inAutomation bool
|
||||||
|
|
||||||
|
countHook *countHook
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ DriftCheck = (*DriftCheckHuman)(nil)
|
||||||
|
|
||||||
|
func (v *DriftCheckHuman) ResourceCount(stateOutPath string) {
|
||||||
|
// TODO: Use this refactoring on the apply function
|
||||||
|
msg := "[reset][bold][green]\nDrift Check complete! Resources: "
|
||||||
|
|
||||||
|
var actions []string
|
||||||
|
if v.countHook.Imported > 0 {
|
||||||
|
actions = append(actions, fmt.Sprintf("%d imported", v.countHook.Imported))
|
||||||
|
}
|
||||||
|
|
||||||
|
actions = append(actions, fmt.Sprintf("%d added", v.countHook.Added))
|
||||||
|
actions = append(actions, fmt.Sprintf("%d changed", v.countHook.Changed))
|
||||||
|
actions = append(actions, fmt.Sprintf("%d destroyed", v.countHook.Removed))
|
||||||
|
|
||||||
|
if v.countHook.Forgotten > 0 {
|
||||||
|
actions = append(actions, fmt.Sprintf("%d forgotten", v.countHook.Forgotten))
|
||||||
|
}
|
||||||
|
msg = fmt.Sprintf("%s %s.", msg, strings.Join(actions, ", "))
|
||||||
|
v.view.streams.Printf(v.view.colorize.Color(msg))
|
||||||
|
|
||||||
|
if (v.countHook.Added > 0 || v.countHook.Changed > 0) && stateOutPath != "" {
|
||||||
|
v.view.streams.Printf("\n%s\n\n", format.WordWrap(stateOutPathPostApply, v.view.outputColumns()))
|
||||||
|
v.view.streams.Printf("State path: %s\n", stateOutPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *DriftCheckHuman) Outputs(outputValues map[string]*states.OutputValue) {
|
||||||
|
if len(outputValues) > 0 {
|
||||||
|
v.view.streams.Print(v.view.colorize.Color("[reset][bold][green]\nOutputs:\n\n"))
|
||||||
|
NewOutput(arguments.ViewHuman, v.view).Output("", outputValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *DriftCheckHuman) Operation() Operation {
|
||||||
|
return NewOperation(arguments.ViewHuman, v.inAutomation, v.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *DriftCheckHuman) Hooks() []tofu.Hook {
|
||||||
|
return []tofu.Hook{v.countHook, NewUIOptionalHook(v.view)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *DriftCheckHuman) Diagnostics(diags tfdiags.Diagnostics) {
|
||||||
|
v.view.Diagnostics(diags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *DriftCheckHuman) HelpPrompt() {
|
||||||
|
command := "drift-check"
|
||||||
|
v.view.HelpPrompt(command)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user