Fix for no json output of state locking actions for --json flag (#32451)

* Add viewType to Meta object and use it at the call sites

* Assign viewType passed from flags to state-locking cli commands

* Remove temp files

* Set correct mode for statelocker depending on json flag passed to commands

* Add StateLocker interface conformation check for StateLockerJSON

* Remove empty line at end of comment

* Pass correct ViewType to StateLocker from Backend call chain

* Pass viewType to backend migration and initialization functions

* Remove json processing info in process comment

* Restore documentation style of backendMigrateOpts
This commit is contained in:
zetHannes 2023-02-07 09:06:12 +01:00 committed by GitHub
parent 5264510c79
commit c70244426a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 113 additions and 29 deletions

View File

@ -94,7 +94,7 @@ func (c *ApplyCommand) Run(rawArgs []string) int {
// Prepare the backend, passing the plan file if present, and the
// backend-specific arguments
be, beDiags := c.PrepareBackend(planFile, args.State)
be, beDiags := c.PrepareBackend(planFile, args.State, args.ViewType)
diags = diags.Append(beDiags)
if diags.HasErrors() {
view.Diagnostics(diags)
@ -102,7 +102,7 @@ func (c *ApplyCommand) Run(rawArgs []string) int {
}
// Build the operation request
opReq, opDiags := c.OperationRequest(be, view, planFile, args.Operation, args.AutoApprove)
opReq, opDiags := c.OperationRequest(be, view, args.ViewType, planFile, args.Operation, args.AutoApprove)
diags = diags.Append(opDiags)
// Collect variable value and add them to the operation request
@ -191,7 +191,7 @@ func (c *ApplyCommand) LoadPlanFile(path string) (*planfile.Reader, tfdiags.Diag
return planFile, diags
}
func (c *ApplyCommand) PrepareBackend(planFile *planfile.Reader, args *arguments.State) (backend.Enhanced, tfdiags.Diagnostics) {
func (c *ApplyCommand) PrepareBackend(planFile *planfile.Reader, args *arguments.State, viewType arguments.ViewType) (backend.Enhanced, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// FIXME: we need to apply the state arguments to the meta object here
@ -211,7 +211,8 @@ func (c *ApplyCommand) PrepareBackend(planFile *planfile.Reader, args *arguments
}
be, beDiags = c.Backend(&BackendOpts{
Config: backendConfig,
Config: backendConfig,
ViewType: viewType,
})
} else {
plan, err := planFile.ReadPlan()
@ -245,6 +246,7 @@ func (c *ApplyCommand) PrepareBackend(planFile *planfile.Reader, args *arguments
func (c *ApplyCommand) OperationRequest(
be backend.Enhanced,
view views.Apply,
viewType arguments.ViewType,
planFile *planfile.Reader,
args *arguments.Operation,
autoApprove bool,
@ -257,7 +259,7 @@ func (c *ApplyCommand) OperationRequest(
diags = diags.Append(c.providerDevOverrideRuntimeWarnings())
// Build the operation
opReq := c.Operation(be)
opReq := c.Operation(be, viewType)
opReq.AutoApprove = autoApprove
opReq.ConfigDir = "."
opReq.PlanMode = args.PlanMode

View File

@ -8,6 +8,7 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/repl"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
@ -75,7 +76,7 @@ func (c *ConsoleCommand) Run(args []string) int {
c.ignoreRemoteVersionConflict(b)
// Build the operation
opReq := c.Operation(b)
opReq := c.Operation(b, arguments.ViewHuman)
opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader()
opReq.AllowUnsetVariables = true // we'll just evaluate them as unknown

View File

@ -5,6 +5,7 @@ import (
"strings"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/dag"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/plans/planfile"
@ -91,7 +92,7 @@ func (c *GraphCommand) Run(args []string) int {
c.ignoreRemoteVersionConflict(b)
// Build the operation
opReq := c.Operation(b)
opReq := c.Operation(b, arguments.ViewHuman)
opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader()
opReq.PlanFile = planFile

View File

@ -182,7 +182,7 @@ func (c *ImportCommand) Run(args []string) int {
}
// Build the operation
opReq := c.Operation(b)
opReq := c.Operation(b, arguments.ViewHuman)
opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader()
if err != nil {

View File

@ -53,6 +53,10 @@ type BackendOpts struct {
// ForceLocal will force a purely local backend, including state.
// You probably don't want to set this.
ForceLocal bool
// ViewType will set console output format for the
// initialization operation (JSON or human-readable).
ViewType arguments.ViewType
}
// BackendWithRemoteTerraformVersion is a shared interface between the 'remote' and 'cloud' backends
@ -391,7 +395,7 @@ func (m *Meta) backendCLIOpts() (*backend.CLIOpts, error) {
// This prepares the operation. After calling this, the caller is expected
// to modify fields of the operation such as Sequence to specify what will
// be called.
func (m *Meta) Operation(b backend.Backend) *backend.Operation {
func (m *Meta) Operation(b backend.Backend, vt arguments.ViewType) *backend.Operation {
schema := b.ConfigSchema()
workspace, err := m.Workspace()
if err != nil {
@ -411,7 +415,7 @@ func (m *Meta) Operation(b backend.Backend) *backend.Operation {
stateLocker := clistate.NewNoopLocker()
if m.stateLock {
view := views.NewStateLocker(arguments.ViewHuman, m.View)
view := views.NewStateLocker(vt, m.View)
stateLocker = clistate.NewLocker(m.stateLockTimeout, view)
}
@ -611,7 +615,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
return nil, diags
}
return m.backend_c_r_S(c, cHash, sMgr, true)
return m.backend_c_r_S(c, cHash, sMgr, true, opts)
// Configuring a backend for the first time or -reconfigure flag was used
case c != nil && s.Backend.Empty():
@ -845,9 +849,18 @@ func (m *Meta) backendFromState() (backend.Backend, tfdiags.Diagnostics) {
//-------------------------------------------------------------------
// Unconfiguring a backend (moving from backend => local).
func (m *Meta) backend_c_r_S(c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool) (backend.Backend, tfdiags.Diagnostics) {
func (m *Meta) backend_c_r_S(
c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
vt := arguments.ViewJSON
// Set default viewtype if none was set as the StateLocker needs to know exactly
// what viewType we want to have.
if opts == nil || opts.ViewType != vt {
vt = arguments.ViewHuman
}
s := sMgr.State()
cloudMode := cloud.DetectConfigChangeType(s.Backend, c, false)
@ -885,6 +898,7 @@ func (m *Meta) backend_c_r_S(c *configs.Backend, cHash int, sMgr *clistate.Local
DestinationType: "local",
Source: b,
Destination: localB,
ViewType: vt,
})
if err != nil {
diags = diags.Append(err)
@ -916,6 +930,13 @@ func (m *Meta) backend_c_r_S(c *configs.Backend, cHash int, sMgr *clistate.Local
func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.LocalState, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
vt := arguments.ViewJSON
// Set default viewtype if none was set as the StateLocker needs to know exactly
// what viewType we want to have.
if opts == nil || opts.ViewType != vt {
vt = arguments.ViewHuman
}
// Grab a purely local backend to get the local state if it exists
localB, localBDiags := m.Backend(&BackendOpts{ForceLocal: true, Init: true})
if localBDiags.HasErrors() {
@ -970,6 +991,7 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local
DestinationType: c.Type,
Source: localB,
Destination: b,
ViewType: vt,
})
if err != nil {
diags = diags.Append(err)
@ -1007,7 +1029,7 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local
}
if m.stateLock {
view := views.NewStateLocker(arguments.ViewHuman, m.View)
view := views.NewStateLocker(vt, m.View)
stateLocker := clistate.NewLocker(m.stateLockTimeout, view)
if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil {
diags = diags.Append(fmt.Errorf("Error locking state: %s", err))
@ -1077,6 +1099,13 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local
func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
vt := arguments.ViewJSON
// Set default viewtype if none was set as the StateLocker needs to know exactly
// what viewType we want to have.
if opts == nil || opts.ViewType != vt {
vt = arguments.ViewHuman
}
// Get the old state
s := sMgr.State()
@ -1136,6 +1165,7 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista
DestinationType: c.Type,
Source: oldB,
Destination: b,
ViewType: vt,
})
if err != nil {
diags = diags.Append(err)
@ -1143,7 +1173,7 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista
}
if m.stateLock {
view := views.NewStateLocker(arguments.ViewHuman, m.View)
view := views.NewStateLocker(vt, m.View)
stateLocker := clistate.NewLocker(m.stateLockTimeout, view)
if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil {
diags = diags.Append(fmt.Errorf("Error locking state: %s", err))

View File

@ -26,6 +26,7 @@ import (
type backendMigrateOpts struct {
SourceType, DestinationType string
Source, Destination backend.Backend
ViewType arguments.ViewType
// Fields below are set internally when migrate is called
@ -339,8 +340,13 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
if m.stateLock {
lockCtx := context.Background()
view := views.NewStateLocker(arguments.ViewHuman, m.View)
vt := arguments.ViewJSON
// Set default viewtype if none was set as the StateLocker needs to know exactly
// what viewType we want to have.
if opts == nil || opts.ViewType != vt {
vt = arguments.ViewHuman
}
view := views.NewStateLocker(vt, m.View)
locker := clistate.NewLocker(m.stateLockTimeout, view)
lockerSource := locker.WithContext(lockCtx)

View File

@ -64,7 +64,7 @@ func (c *PlanCommand) Run(rawArgs []string) int {
diags = diags.Append(c.providerDevOverrideRuntimeWarnings())
// Prepare the backend with the backend-specific arguments
be, beDiags := c.PrepareBackend(args.State)
be, beDiags := c.PrepareBackend(args.State, args.ViewType)
diags = diags.Append(beDiags)
if diags.HasErrors() {
view.Diagnostics(diags)
@ -72,7 +72,7 @@ func (c *PlanCommand) Run(rawArgs []string) int {
}
// Build the operation request
opReq, opDiags := c.OperationRequest(be, view, args.Operation, args.OutPath)
opReq, opDiags := c.OperationRequest(be, view, args.ViewType, args.Operation, args.OutPath)
diags = diags.Append(opDiags)
if diags.HasErrors() {
view.Diagnostics(diags)
@ -110,7 +110,7 @@ func (c *PlanCommand) Run(rawArgs []string) int {
return op.Result.ExitStatus()
}
func (c *PlanCommand) PrepareBackend(args *arguments.State) (backend.Enhanced, tfdiags.Diagnostics) {
func (c *PlanCommand) PrepareBackend(args *arguments.State, viewType arguments.ViewType) (backend.Enhanced, tfdiags.Diagnostics) {
// FIXME: we need to apply the state arguments to the meta object here
// because they are later used when initializing the backend. Carving a
// path to pass these arguments to the functions that need them is
@ -124,7 +124,8 @@ func (c *PlanCommand) PrepareBackend(args *arguments.State) (backend.Enhanced, t
// Load the backend
be, beDiags := c.Backend(&BackendOpts{
Config: backendConfig,
Config: backendConfig,
ViewType: viewType,
})
diags = diags.Append(beDiags)
if beDiags.HasErrors() {
@ -137,13 +138,14 @@ func (c *PlanCommand) PrepareBackend(args *arguments.State) (backend.Enhanced, t
func (c *PlanCommand) OperationRequest(
be backend.Enhanced,
view views.Plan,
viewType arguments.ViewType,
args *arguments.Operation,
planOutPath string,
) (*backend.Operation, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// Build the operation
opReq := c.Operation(be)
opReq := c.Operation(be, viewType)
opReq.ConfigDir = "."
opReq.PlanMode = args.PlanMode
opReq.Hooks = view.Hooks()

View File

@ -5,6 +5,7 @@ import (
"os"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/tfdiags"
)
@ -78,7 +79,7 @@ func (c *ProvidersSchemaCommand) Run(args []string) int {
}
// Build the operation
opReq := c.Operation(b)
opReq := c.Operation(b, arguments.ViewJSON)
opReq.ConfigDir = cwd
opReq.ConfigLoader, err = c.initConfigLoader()
opReq.AllowUnsetVariables = true

View File

@ -64,7 +64,7 @@ func (c *RefreshCommand) Run(rawArgs []string) int {
c.Meta.parallelism = args.Operation.Parallelism
// Prepare the backend with the backend-specific arguments
be, beDiags := c.PrepareBackend(args.State)
be, beDiags := c.PrepareBackend(args.State, args.ViewType)
diags = diags.Append(beDiags)
if diags.HasErrors() {
view.Diagnostics(diags)
@ -72,7 +72,7 @@ func (c *RefreshCommand) Run(rawArgs []string) int {
}
// Build the operation request
opReq, opDiags := c.OperationRequest(be, view, args.Operation)
opReq, opDiags := c.OperationRequest(be, view, args.ViewType, args.Operation)
diags = diags.Append(opDiags)
if diags.HasErrors() {
view.Diagnostics(diags)
@ -107,7 +107,7 @@ func (c *RefreshCommand) Run(rawArgs []string) int {
return op.Result.ExitStatus()
}
func (c *RefreshCommand) PrepareBackend(args *arguments.State) (backend.Enhanced, tfdiags.Diagnostics) {
func (c *RefreshCommand) PrepareBackend(args *arguments.State, viewType arguments.ViewType) (backend.Enhanced, tfdiags.Diagnostics) {
// FIXME: we need to apply the state arguments to the meta object here
// because they are later used when initializing the backend. Carving a
// path to pass these arguments to the functions that need them is
@ -121,7 +121,8 @@ func (c *RefreshCommand) PrepareBackend(args *arguments.State) (backend.Enhanced
// Load the backend
be, beDiags := c.Backend(&BackendOpts{
Config: backendConfig,
Config: backendConfig,
ViewType: viewType,
})
diags = diags.Append(beDiags)
if beDiags.HasErrors() {
@ -131,12 +132,12 @@ func (c *RefreshCommand) PrepareBackend(args *arguments.State) (backend.Enhanced
return be, diags
}
func (c *RefreshCommand) OperationRequest(be backend.Enhanced, view views.Refresh, args *arguments.Operation,
func (c *RefreshCommand) OperationRequest(be backend.Enhanced, view views.Refresh, viewType arguments.ViewType, args *arguments.Operation,
) (*backend.Operation, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// Build the operation
opReq := c.Operation(be)
opReq := c.Operation(be, viewType)
opReq.ConfigDir = "."
opReq.Hooks = view.Hooks()
opReq.Targets = args.Targets

View File

@ -7,6 +7,7 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/format"
"github.com/hashicorp/terraform/internal/states"
"github.com/mitchellh/cli"
@ -71,7 +72,7 @@ func (c *StateShowCommand) Run(args []string) int {
}
// Build the operation (required to get the schemas)
opReq := c.Operation(b)
opReq := c.Operation(b, arguments.ViewHuman)
opReq.AllowUnsetVariables = true
opReq.ConfigDir = cwd

View File

@ -1,7 +1,9 @@
package views
import (
"encoding/json"
"fmt"
"time"
"github.com/hashicorp/terraform/internal/command/arguments"
)
@ -18,6 +20,8 @@ func NewStateLocker(vt arguments.ViewType, view *View) StateLocker {
switch vt {
case arguments.ViewHuman:
return &StateLockerHuman{view: view}
case arguments.ViewJSON:
return &StateLockerJSON{view: view}
default:
panic(fmt.Sprintf("unknown view type %v", vt))
}
@ -30,6 +34,7 @@ type StateLockerHuman struct {
}
var _ StateLocker = (*StateLockerHuman)(nil)
var _ StateLocker = (*StateLockerJSON)(nil)
func (v *StateLockerHuman) Locking() {
v.view.streams.Println("Acquiring state lock. This may take a few moments...")
@ -38,3 +43,37 @@ func (v *StateLockerHuman) Locking() {
func (v *StateLockerHuman) Unlocking() {
v.view.streams.Println("Releasing state lock. This may take a few moments...")
}
// StateLockerJSON is an implementation of StateLocker which prints the state lock status
// to a terminal in machine-readable JSON form.
type StateLockerJSON struct {
view *View
}
func (v *StateLockerJSON) Locking() {
current_timestamp := time.Now().Format(time.RFC3339)
json_data := map[string]string{
"@level": "info",
"@message": "Acquiring state lock. This may take a few moments...",
"@module": "terraform.ui",
"@timestamp": current_timestamp,
"type": "state_lock_acquire"}
lock_info_message, _ := json.Marshal(json_data)
v.view.streams.Println(string(lock_info_message))
}
func (v *StateLockerJSON) Unlocking() {
current_timestamp := time.Now().Format(time.RFC3339)
json_data := map[string]string{
"@level": "info",
"@message": "Releasing state lock. This may take a few moments...",
"@module": "terraform.ui",
"@timestamp": current_timestamp,
"type": "state_lock_release"}
lock_info_message, _ := json.Marshal(json_data)
v.view.streams.Println(string(lock_info_message))
}