Lokasi ngalangkungan proxy:   [ UP ]  
[Ngawartoskeun bug]   [Panyetelan cookie]                
Skip to content

Commit a867457

Browse files
committed
command/console
1 parent a633cdf commit a867457

5 files changed

Lines changed: 242 additions & 0 deletions

File tree

command/console.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package command
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"io"
7+
"os"
8+
"strings"
9+
10+
"github.com/hashicorp/terraform/helper/wrappedreadline"
11+
"github.com/hashicorp/terraform/repl"
12+
13+
"github.com/chzyer/readline"
14+
"github.com/mitchellh/cli"
15+
)
16+
17+
// ConsoleCommand is a Command implementation that applies a Terraform
18+
// configuration and actually builds or changes infrastructure.
19+
type ConsoleCommand struct {
20+
Meta
21+
22+
// When this channel is closed, the apply will be cancelled.
23+
ShutdownCh <-chan struct{}
24+
}
25+
26+
func (c *ConsoleCommand) Run(args []string) int {
27+
args = c.Meta.process(args, true)
28+
cmdFlags := c.Meta.flagSet("console")
29+
cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
30+
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
31+
if err := cmdFlags.Parse(args); err != nil {
32+
return 1
33+
}
34+
35+
pwd, err := os.Getwd()
36+
if err != nil {
37+
c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
38+
return 1
39+
}
40+
41+
var configPath string
42+
args = cmdFlags.Args()
43+
if len(args) > 1 {
44+
c.Ui.Error("The console command expects at most one argument.")
45+
cmdFlags.Usage()
46+
return 1
47+
} else if len(args) == 1 {
48+
configPath = args[0]
49+
} else {
50+
configPath = pwd
51+
}
52+
53+
// Build the context based on the arguments given
54+
ctx, _, err := c.Context(contextOpts{
55+
Path: configPath,
56+
PathEmptyOk: true,
57+
StatePath: c.Meta.statePath,
58+
})
59+
if err != nil {
60+
c.Ui.Error(err.Error())
61+
return 1
62+
}
63+
64+
// Setup the UI so we can output directly to stdout
65+
ui := &cli.BasicUi{
66+
Writer: c.Stdout(),
67+
ErrorWriter: c.Stderr(),
68+
}
69+
70+
// IO Loop
71+
session := &repl.Session{
72+
Interpolater: ctx.Interpolater(),
73+
}
74+
75+
// Determine if stdin is a pipe. If so, we evaluate directly.
76+
if c.StdinPiped() {
77+
return c.modePiped(session, ui)
78+
}
79+
80+
return c.modeInteractive(session, ui)
81+
}
82+
83+
func (c *ConsoleCommand) modePiped(session *repl.Session, ui cli.Ui) int {
84+
var lastResult string
85+
scanner := bufio.NewScanner(c.Stdin())
86+
for scanner.Scan() {
87+
// Handle it. If there is an error exit immediately
88+
result, err := session.Handle(strings.TrimSpace(scanner.Text()))
89+
if err != nil {
90+
ui.Error(err.Error())
91+
return 1
92+
}
93+
94+
// Store the last result
95+
lastResult = result
96+
}
97+
98+
// Output the final result
99+
ui.Output(lastResult)
100+
101+
return 0
102+
}
103+
104+
func (c *ConsoleCommand) modeInteractive(session *repl.Session, ui cli.Ui) int {
105+
// Configure input
106+
l, err := readline.NewEx(wrappedreadline.Override(&readline.Config{
107+
Prompt: "> ",
108+
InterruptPrompt: "^C",
109+
EOFPrompt: "exit",
110+
HistorySearchFold: true,
111+
}))
112+
if err != nil {
113+
c.Ui.Error(fmt.Sprintf(
114+
"Error initializing console: %s",
115+
err))
116+
return 1
117+
}
118+
defer l.Close()
119+
120+
for {
121+
// Read a line
122+
line, err := l.Readline()
123+
if err == readline.ErrInterrupt {
124+
if len(line) == 0 {
125+
break
126+
} else {
127+
continue
128+
}
129+
} else if err == io.EOF {
130+
break
131+
}
132+
133+
out, err := session.Handle(line)
134+
if err == repl.ErrSessionExit {
135+
break
136+
}
137+
if err != nil {
138+
ui.Error(err.Error())
139+
continue
140+
}
141+
142+
ui.Output(out)
143+
}
144+
145+
return 0
146+
}
147+
148+
func (c *ConsoleCommand) Help() string {
149+
helpText := `
150+
Usage: terraform console [options] [DIR]
151+
152+
Starts an interactive console for experimenting with Terraform
153+
interpolations.
154+
155+
This will open an interactive console that you can use to type
156+
interpolations into and inspect their values. This command loads the
157+
current state. This lets you explore and test interpolations before
158+
using them in future configurations.
159+
160+
This command will never modify your state.
161+
162+
DIR can be set to a directory with a Terraform state to load. By
163+
default, this will default to the current working directory.
164+
165+
Options:
166+
167+
-state=path Path to read state. Defaults to "terraform.tfstate"
168+
169+
-var 'foo=bar' Set a variable in the Terraform configuration. This
170+
flag can be set multiple times.
171+
172+
-var-file=foo Set variables in the Terraform configuration from
173+
a file. If "terraform.tfvars" is present, it will be
174+
automatically loaded if this flag is not specified.
175+
176+
177+
`
178+
return strings.TrimSpace(helpText)
179+
}
180+
181+
func (c *ConsoleCommand) Synopsis() string {
182+
return "Interactive console for Terraform interpolations"
183+
}

command/console_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package command
2+
3+
// ConsoleCommand is tested primarily with tests in the "repl" package.
4+
// It is not tested here because the Console uses a readline-like library
5+
// that takes over stdin/stdout. It is difficult to test directly. The
6+
// core logic is tested in "repl"

command/meta.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ import (
1515
"github.com/hashicorp/terraform/config"
1616
"github.com/hashicorp/terraform/config/module"
1717
"github.com/hashicorp/terraform/helper/experiment"
18+
"github.com/hashicorp/terraform/helper/wrappedreadline"
1819
"github.com/hashicorp/terraform/state"
1920
"github.com/hashicorp/terraform/terraform"
2021
"github.com/mitchellh/cli"
2122
"github.com/mitchellh/colorstring"
23+
"github.com/mitchellh/panicwrap"
2224
)
2325

2426
// Meta are the meta-options that are available on all or most commands.
@@ -310,6 +312,47 @@ func (m *Meta) Input() bool {
310312
return !test && m.input && len(m.variables) == 0
311313
}
312314

315+
// StdinPiped returns true if the input is piped.
316+
func (m *Meta) StdinPiped() bool {
317+
fi, err := m.Stdin().Stat()
318+
if err != nil {
319+
// If there is an error, let's just say its not piped
320+
return false
321+
}
322+
323+
return fi.Mode()&os.ModeNamedPipe != 0
324+
}
325+
326+
// Stdin returns the stdin for this command.
327+
func (m *Meta) Stdin() *os.File {
328+
stdin := os.Stdin
329+
if panicwrap.Wrapped(nil) {
330+
stdin = wrappedreadline.Stdin
331+
}
332+
333+
return stdin
334+
}
335+
336+
// Stdout returns the stdout for this command.
337+
func (m *Meta) Stdout() *os.File {
338+
stdout := os.Stdout
339+
if panicwrap.Wrapped(nil) {
340+
stdout = wrappedreadline.Stdout
341+
}
342+
343+
return stdout
344+
}
345+
346+
// Stderr returns the stderr for this command.
347+
func (m *Meta) Stderr() *os.File {
348+
stderr := os.Stderr
349+
if panicwrap.Wrapped(nil) {
350+
stderr = wrappedreadline.Stderr
351+
}
352+
353+
return stderr
354+
}
355+
313356
// contextOpts returns the options to use to initialize a Terraform
314357
// context with the settings from this Meta.
315358
func (m *Meta) contextOpts() *terraform.ContextOpts {

commands.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ func init() {
4747
}, nil
4848
},
4949

50+
"console": func() (cli.Command, error) {
51+
return &command.ConsoleCommand{
52+
Meta: meta,
53+
ShutdownCh: makeShutdownCh(),
54+
}, nil
55+
},
56+
5057
"destroy": func() (cli.Command, error) {
5158
return &command.ApplyCommand{
5259
Meta: meta,

helper/wrappedreadline/wrappedreadline.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
// panicwrap overrides the standard file descriptors so that the child process
55
// no longer looks like a TTY. The helpers here access the extra file descriptors
66
// passed by panicwrap to fix that.
7+
//
8+
// panicwrap should be checked for with panicwrap.Wrapped before using this
9+
// librar, since this library won't adapt if the binary is not wrapped.
710
package wrappedreadline
811

912
import (

0 commit comments

Comments
 (0)