Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require xterm-style virtual terminal on all platforms, including Windows #27487

Merged
merged 6 commits into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion backend/cli.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package backend

import (
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
"github.com/mitchellh/colorstring"

"github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/terraform"
)

// CLI is an optional interface that can be implemented to be initialized
Expand Down Expand Up @@ -48,6 +50,12 @@ type CLIOpts struct {
CLI cli.Ui
CLIColor *colorstring.Colorize

// Streams describes the low-level streams for Stdout, Stderr and Stdin,
// including some metadata about whether they are terminals. Most output
// should go via the object in field CLI above, but Streams can be useful
// for tailoring the output to fit the attached terminal, for example.
Streams *terminal.Streams

// ShowDiagnostics is a function that will format and print diagnostic
// messages to the UI.
ShowDiagnostics func(vals ...interface{})
Expand Down
5 changes: 5 additions & 0 deletions backend/local/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
Expand All @@ -38,6 +39,10 @@ type Local struct {
CLI cli.Ui
CLIColor *colorstring.Colorize

// If CLI is set then Streams might also be set, to describe the physical
// input/output handles that CLI is connected to.
Streams *terminal.Streams

// ShowDiagnostics prints diagnostic messages to the UI.
ShowDiagnostics func(vals ...interface{})

Expand Down
30 changes: 15 additions & 15 deletions backend/local/backend_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ func (b *Local) opPlan(

var diags tfdiags.Diagnostics

outputColumns := b.outputColumns()

if op.PlanFile != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
Expand Down Expand Up @@ -150,6 +152,7 @@ func (b *Local) opPlan(

if runningOp.PlanEmpty {
b.CLI.Output("\n" + b.Colorize().Color(strings.TrimSpace(planNoChanges)))
b.CLI.Output("\n" + strings.TrimSpace(format.WordWrap(planNoChangesDetail, outputColumns)))
// Even if there are no changes, there still could be some warnings
b.ShowDiagnostics(diags)
return
Expand All @@ -166,15 +169,15 @@ func (b *Local) opPlan(
// tool which is presumed to provide its own UI for further actions.
if !b.RunningInAutomation {

b.CLI.Output("\n------------------------------------------------------------------------")
b.outputHorizRule()

if path := op.PlanOutPath; path == "" {
b.CLI.Output(fmt.Sprintf(
"\n" + strings.TrimSpace(planHeaderNoOutput) + "\n",
"\n" + strings.TrimSpace(format.WordWrap(planHeaderNoOutput, outputColumns)) + "\n",
))
} else {
b.CLI.Output(fmt.Sprintf(
"\n"+strings.TrimSpace(planHeaderYesOutput)+"\n",
"\n"+strings.TrimSpace(format.WordWrap(planHeaderYesOutput, outputColumns))+"\n",
path, path,
))
}
Expand All @@ -183,7 +186,7 @@ func (b *Local) opPlan(
}

func (b *Local) renderPlan(plan *plans.Plan, baseState *states.State, schemas *terraform.Schemas) {
RenderPlan(plan, baseState, schemas, b.CLI, b.Colorize())
RenderPlan(plan, baseState, schemas, b.CLI, b.Colorize(), b.outputColumns())
}

// RenderPlan renders the given plan to the given UI.
Expand All @@ -206,7 +209,7 @@ func (b *Local) renderPlan(plan *plans.Plan, baseState *states.State, schemas *t
// output values will not currently be rendered because their prior values
// are currently stored only in the prior state. (see the docstring for
// func planHasSideEffects for why this is and when that might change)
func RenderPlan(plan *plans.Plan, baseState *states.State, schemas *terraform.Schemas, ui cli.Ui, colorize *colorstring.Colorize) {
func RenderPlan(plan *plans.Plan, baseState *states.State, schemas *terraform.Schemas, ui cli.Ui, colorize *colorstring.Colorize, width int) {
counts := map[plans.Action]int{}
var rChanges []*plans.ResourceInstanceChangeSrc
for _, change := range plan.Changes.Resources {
Expand All @@ -220,7 +223,7 @@ func RenderPlan(plan *plans.Plan, baseState *states.State, schemas *terraform.Sc
}

headerBuf := &bytes.Buffer{}
fmt.Fprintf(headerBuf, "\n%s\n", strings.TrimSpace(planHeaderIntro))
fmt.Fprintf(headerBuf, "\n%s\n", strings.TrimSpace(format.WordWrap(planHeaderIntro, width)))
if counts[plans.Create] > 0 {
fmt.Fprintf(headerBuf, "%s create\n", format.DiffActionSymbol(plans.Create))
}
Expand Down Expand Up @@ -330,27 +333,24 @@ func RenderPlan(plan *plans.Plan, baseState *states.State, schemas *terraform.Sc
}

const planHeaderIntro = `
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
`

const planHeaderNoOutput = `
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
`

const planHeaderYesOutput = `
This plan was saved to: %s
Saved the plan to: %s

To perform exactly these actions, run the following command to apply:
terraform apply %q
`

const planNoChanges = `
[reset][bold][green]No changes. Infrastructure is up-to-date.[reset][green]
`

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.
const planNoChangesDetail = `
That Terraform did not detect any differences between your configuration and the remote system(s). As a result, there are no actions to take.
`
16 changes: 8 additions & 8 deletions backend/local/backend_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestLocal_planInAutomation(t *testing.T) {
defer cleanup()
TestLocalProvider(t, b, "test", planFixtureSchema())

const msg = `You didn't specify an "-out" parameter`
const msg = `You didn't use the -out option`

// When we're "in automation" we omit certain text from the
// plan output. However, testing for the absense of text is
Expand All @@ -77,7 +77,7 @@ func TestLocal_planInAutomation(t *testing.T) {

output := b.CLI.(*cli.MockUi).OutputWriter.String()
if !strings.Contains(output, msg) {
t.Fatalf("missing next-steps message when not in automation")
t.Fatalf("missing next-steps message when not in automation\nwant: %s\noutput:\n%s", msg, output)
}
}

Expand Down Expand Up @@ -331,8 +331,8 @@ func TestLocal_planTainted(t *testing.T) {
t.Fatal("plan should not be empty")
}

expectedOutput := `An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
expectedOutput := `Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:
Expand Down Expand Up @@ -433,8 +433,8 @@ func TestLocal_planDeposedOnly(t *testing.T) {
// it's also possible for there to be _multiple_ deposed objects, in the
// unlikely event that create_before_destroy _keeps_ crashing across
// subsequent runs.
expectedOutput := `An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
expectedOutput := `Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
- destroy

Expand Down Expand Up @@ -507,8 +507,8 @@ func TestLocal_planTainted_createBeforeDestroy(t *testing.T) {
t.Fatal("plan should not be empty")
}

expectedOutput := `An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
expectedOutput := `Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+/- create replacement and then destroy

Terraform will perform the following actions:
Expand Down
44 changes: 44 additions & 0 deletions backend/local/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"log"

"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/format"
)

// backend.CLI impl.
func (b *Local) CLIInit(opts *backend.CLIOpts) error {
b.CLI = opts.CLI
b.CLIColor = opts.CLIColor
b.Streams = opts.Streams
b.ShowDiagnostics = opts.ShowDiagnostics
b.ContextOpts = opts.ContextOpts
b.OpInput = opts.Input
Expand All @@ -34,3 +36,45 @@ func (b *Local) CLIInit(opts *backend.CLIOpts) error {

return nil
}

// outputColumns returns the number of text character cells any non-error
// output should be wrapped to.
//
// This is the number of columns to use if you are calling b.CLI.Output or
// b.CLI.Info.
func (b *Local) outputColumns() int {
if b.Streams == nil {
// We can potentially get here in tests, if they don't populate the
// CLIOpts fully.
return 78 // placeholder just so we don't panic
}
return b.Streams.Stdout.Columns()
}

// errorColumns returns the number of text character cells any error
// output should be wrapped to.
//
// This is the number of columns to use if you are calling b.CLI.Error or
// b.CLI.Warn.
func (b *Local) errorColumns() int {
if b.Streams == nil {
// We can potentially get here in tests, if they don't populate the
// CLIOpts fully.
return 78 // placeholder just so we don't panic
}
return b.Streams.Stderr.Columns()
}

// outputHorizRule will call b.CLI.Output with enough horizontal line
// characters to fill an entire row of output.
//
// This function does nothing if the backend doesn't have a CLI attached.
//
// If UI color is enabled, the rule will get a dark grey coloring to try to
// visually de-emphasize it.
func (b *Local) outputHorizRule() {
if b.CLI == nil {
return
}
b.CLI.Output(format.HorizontalRule(b.CLIColor, b.outputColumns()))
}
8 changes: 4 additions & 4 deletions command/e2etest/primary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ func TestPrimarySeparatePlan(t *testing.T) {
t.Errorf("incorrect plan tally; want 1 to add:\n%s", stdout)
}

if !strings.Contains(stdout, "This plan was saved to: tfplan") {
t.Errorf("missing \"This plan was saved to...\" message in plan output\n%s", stdout)
if !strings.Contains(stdout, "Saved the plan to: tfplan") {
t.Errorf("missing \"Saved the plan to...\" message in plan output\n%s", stdout)
}
if !strings.Contains(stdout, "terraform apply \"tfplan\"") {
t.Errorf("missing next-step instruction in plan output\n%s", stdout)
Expand Down Expand Up @@ -169,8 +169,8 @@ func TestPrimaryChdirOption(t *testing.T) {
t.Errorf("incorrect plan tally; want 0 to add:\n%s", stdout)
}

if !strings.Contains(stdout, "This plan was saved to: tfplan") {
t.Errorf("missing \"This plan was saved to...\" message in plan output\n%s", stdout)
if !strings.Contains(stdout, "Saved the plan to: tfplan") {
t.Errorf("missing \"Saved the plan to...\" message in plan output\n%s", stdout)
}
if !strings.Contains(stdout, "terraform apply \"tfplan\"") {
t.Errorf("missing next-step instruction in plan output\n%s", stdout)
Expand Down
8 changes: 4 additions & 4 deletions command/format/diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,22 +172,22 @@ func Diagnostic(diag tfdiags.Diagnostic, sources map[string][]byte, color *color
sort.Strings(stmts) // FIXME: Should maybe use a traversal-aware sort that can sort numeric indexes properly?

if len(stmts) > 0 {
fmt.Fprint(&buf, color.Color(" [dark_gray]|----------------[reset]\n"))
fmt.Fprint(&buf, color.Color(" [dark_gray]├────────────────[reset]\n"))
}
for _, stmt := range stmts {
fmt.Fprintf(&buf, color.Color(" [dark_gray]|[reset] %s\n"), stmt)
fmt.Fprintf(&buf, color.Color(" [dark_gray][reset] %s\n"), stmt)
}
}

buf.WriteByte('\n')
}

if desc.Detail != "" {
if width != 0 {
if width > 1 {
lines := strings.Split(desc.Detail, "\n")
for _, line := range lines {
if !strings.HasPrefix(line, " ") {
line = wordwrap.WrapString(line, uint(width))
line = wordwrap.WrapString(line, uint(width-1))
}
fmt.Fprintf(&buf, "%s\n", line)
}
Expand Down
20 changes: 10 additions & 10 deletions command/format/diagnostic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ Whatever shall we do?

on test.tf line 1:
1: test [underline]source[reset] code
[dark_gray]|----------------[reset]
[dark_gray]|[reset] [bold]boop.beep[reset] is "blah"
[dark_gray]├────────────────[reset]
[dark_gray][reset] [bold]boop.beep[reset] is "blah"

Whatever shall we do?
`,
Expand Down Expand Up @@ -127,8 +127,8 @@ Whatever shall we do?

on test.tf line 1:
1: test [underline]source[reset] code
[dark_gray]|----------------[reset]
[dark_gray]|[reset] [bold]boop.beep[reset] has a sensitive value
[dark_gray]├────────────────[reset]
[dark_gray][reset] [bold]boop.beep[reset] has a sensitive value

Whatever shall we do?
`,
Expand Down Expand Up @@ -160,8 +160,8 @@ Whatever shall we do?

on test.tf line 1:
1: test [underline]source[reset] code
[dark_gray]|----------------[reset]
[dark_gray]|[reset] [bold]boop.beep[reset] is a string, known only after apply
[dark_gray]├────────────────[reset]
[dark_gray][reset] [bold]boop.beep[reset] is a string, known only after apply

Whatever shall we do?
`,
Expand Down Expand Up @@ -193,8 +193,8 @@ Whatever shall we do?

on test.tf line 1:
1: test [underline]source[reset] code
[dark_gray]|----------------[reset]
[dark_gray]|[reset] [bold]boop.beep[reset] will be known only after apply
[dark_gray]├────────────────[reset]
[dark_gray][reset] [bold]boop.beep[reset] will be known only after apply

Whatever shall we do?
`,
Expand Down Expand Up @@ -403,8 +403,8 @@ wrap onto multiple lines. Thank-you very much for listening.
To fix this, run this very long command:
terraform read-my-mind -please -thanks -but-do-not-wrap-this-line-because-it-is-prefixed-with-spaces

Here is a coda which is also long enough to wrap and so it should eventually
make it onto multiple lines. THE END
Here is a coda which is also long enough to wrap and so it should
eventually make it onto multiple lines. THE END
`
output := Diagnostic(diags[0], nil, color, 76)

Expand Down
Loading