diff --git a/internal/command/apply.go b/internal/command/apply.go index 935b5102d22c..072709472afe 100644 --- a/internal/command/apply.go +++ b/internal/command/apply.go @@ -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 diff --git a/internal/command/console.go b/internal/command/console.go index 2859fbc7d5eb..b3d83087620f 100644 --- a/internal/command/console.go +++ b/internal/command/console.go @@ -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 diff --git a/internal/command/graph.go b/internal/command/graph.go index 4fe742804928..c1c225beb85b 100644 --- a/internal/command/graph.go +++ b/internal/command/graph.go @@ -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 diff --git a/internal/command/import.go b/internal/command/import.go index 28984d51f65e..5042cc82e7d6 100644 --- a/internal/command/import.go +++ b/internal/command/import.go @@ -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 { diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index d7fa62e00121..f61563d81544 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -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)) diff --git a/internal/command/meta_backend_migrate.go b/internal/command/meta_backend_migrate.go index 46161cd623a8..9df931c09714 100644 --- a/internal/command/meta_backend_migrate.go +++ b/internal/command/meta_backend_migrate.go @@ -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) diff --git a/internal/command/plan.go b/internal/command/plan.go index d5ffedbff686..674e9a8ff26d 100644 --- a/internal/command/plan.go +++ b/internal/command/plan.go @@ -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() diff --git a/internal/command/providers_schema.go b/internal/command/providers_schema.go index b4d61ec76428..61201192c368 100644 --- a/internal/command/providers_schema.go +++ b/internal/command/providers_schema.go @@ -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 diff --git a/internal/command/refresh.go b/internal/command/refresh.go index 546d7f0fbdd4..825981f142e9 100644 --- a/internal/command/refresh.go +++ b/internal/command/refresh.go @@ -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 diff --git a/internal/command/state_show.go b/internal/command/state_show.go index 7ee86624dfbc..3abd63c48934 100644 --- a/internal/command/state_show.go +++ b/internal/command/state_show.go @@ -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 diff --git a/internal/command/views/state_locker.go b/internal/command/views/state_locker.go index df3d51b0f912..baa465f2933a 100644 --- a/internal/command/views/state_locker.go +++ b/internal/command/views/state_locker.go @@ -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)) +}