Merge pull request #11922 from hashicorp/f-tf-cli
command: add TF_CLI_ARGS to specify additional CLI args
This commit is contained in:
commit
d2f9df37f5
87
main.go
87
main.go
|
@ -7,17 +7,24 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
"github.com/hashicorp/terraform/helper/logging"
|
"github.com/hashicorp/terraform/helper/logging"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mattn/go-colorable"
|
"github.com/mattn/go-colorable"
|
||||||
|
"github.com/mattn/go-shellwords"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/mitchellh/panicwrap"
|
"github.com/mitchellh/panicwrap"
|
||||||
"github.com/mitchellh/prefixedio"
|
"github.com/mitchellh/prefixedio"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EnvCLI is the environment variable name to set additional CLI args.
|
||||||
|
EnvCLI = "TF_CLI_ARGS"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Override global prefix set by go-dynect during init()
|
// Override global prefix set by go-dynect during init()
|
||||||
log.SetPrefix("")
|
log.SetPrefix("")
|
||||||
|
@ -129,9 +136,35 @@ func wrappedMain() int {
|
||||||
// Make sure we clean up any managed plugins at the end of this
|
// Make sure we clean up any managed plugins at the end of this
|
||||||
defer plugin.CleanupClients()
|
defer plugin.CleanupClients()
|
||||||
|
|
||||||
// Get the command line args. We shortcut "--version" and "-v" to
|
// Get the command line args.
|
||||||
// just show the version.
|
|
||||||
args := os.Args[1:]
|
args := os.Args[1:]
|
||||||
|
|
||||||
|
// Build the CLI so far, we do this so we can query the subcommand.
|
||||||
|
cliRunner := &cli.CLI{
|
||||||
|
Args: args,
|
||||||
|
Commands: Commands,
|
||||||
|
HelpFunc: helpFunc,
|
||||||
|
HelpWriter: os.Stdout,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefix the args with any args from the EnvCLI
|
||||||
|
args, err = mergeEnvArgs(EnvCLI, cliRunner.Subcommand(), args)
|
||||||
|
if err != nil {
|
||||||
|
Ui.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefix the args with any args from the EnvCLI targeting this command
|
||||||
|
suffix := strings.Replace(strings.Replace(
|
||||||
|
cliRunner.Subcommand(), "-", "_", -1), " ", "_", -1)
|
||||||
|
args, err = mergeEnvArgs(
|
||||||
|
fmt.Sprintf("%s_%s", EnvCLI, suffix), cliRunner.Subcommand(), args)
|
||||||
|
if err != nil {
|
||||||
|
Ui.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// We shortcut "--version" and "-v" to just show the version
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
if arg == "-v" || arg == "-version" || arg == "--version" {
|
if arg == "-v" || arg == "-version" || arg == "--version" {
|
||||||
newArgs := make([]string, len(args)+1)
|
newArgs := make([]string, len(args)+1)
|
||||||
|
@ -142,7 +175,9 @@ func wrappedMain() int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := &cli.CLI{
|
// Rebuild the CLI with any modified args.
|
||||||
|
log.Printf("[INFO] CLI command args: %#v", args)
|
||||||
|
cliRunner = &cli.CLI{
|
||||||
Args: args,
|
Args: args,
|
||||||
Commands: Commands,
|
Commands: Commands,
|
||||||
HelpFunc: helpFunc,
|
HelpFunc: helpFunc,
|
||||||
|
@ -153,7 +188,7 @@ func wrappedMain() int {
|
||||||
ContextOpts.Providers = config.ProviderFactories()
|
ContextOpts.Providers = config.ProviderFactories()
|
||||||
ContextOpts.Provisioners = config.ProvisionerFactories()
|
ContextOpts.Provisioners = config.ProvisionerFactories()
|
||||||
|
|
||||||
exitCode, err := cli.Run()
|
exitCode, err := cliRunner.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Ui.Error(fmt.Sprintf("Error executing CLI: %s", err.Error()))
|
Ui.Error(fmt.Sprintf("Error executing CLI: %s", err.Error()))
|
||||||
return 1
|
return 1
|
||||||
|
@ -241,3 +276,47 @@ func copyOutput(r io.Reader, doneCh chan<- struct{}) {
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mergeEnvArgs(envName string, cmd string, args []string) ([]string, error) {
|
||||||
|
v := os.Getenv(envName)
|
||||||
|
if v == "" {
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[INFO] %s value: %q", envName, v)
|
||||||
|
extra, err := shellwords.Parse(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Error parsing extra CLI args from %s: %s",
|
||||||
|
envName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the command to look for in the args. If there is a space,
|
||||||
|
// we need to find the last part.
|
||||||
|
search := cmd
|
||||||
|
if idx := strings.LastIndex(search, " "); idx >= 0 {
|
||||||
|
search = cmd[idx+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the index to place the flags. We put them exactly
|
||||||
|
// after the first non-flag arg.
|
||||||
|
idx := -1
|
||||||
|
for i, v := range args {
|
||||||
|
if v == search {
|
||||||
|
idx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// idx points to the exact arg that isn't a flag. We increment
|
||||||
|
// by one so that all the copying below expects idx to be the
|
||||||
|
// insertion point.
|
||||||
|
idx++
|
||||||
|
|
||||||
|
// Copy the args
|
||||||
|
newArgs := make([]string, len(args)+len(extra))
|
||||||
|
copy(newArgs, args[:idx])
|
||||||
|
copy(newArgs[idx:], extra)
|
||||||
|
copy(newArgs[len(extra)+idx:], args[idx:])
|
||||||
|
return newArgs, nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,257 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain_cliArgsFromEnv(t *testing.T) {
|
||||||
|
// Setup the state. This test really messes with the environment and
|
||||||
|
// global state so we set things up to be restored.
|
||||||
|
|
||||||
|
// Restore original CLI args
|
||||||
|
oldArgs := os.Args
|
||||||
|
defer func() { os.Args = oldArgs }()
|
||||||
|
|
||||||
|
// Setup test command and restore that
|
||||||
|
testCommandName := "unit-test-cli-args"
|
||||||
|
testCommand := &testCommandCLI{}
|
||||||
|
defer func() { delete(Commands, testCommandName) }()
|
||||||
|
Commands[testCommandName] = func() (cli.Command, error) {
|
||||||
|
return testCommand, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
Name string
|
||||||
|
Args []string
|
||||||
|
Value string
|
||||||
|
Expected []string
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"no env",
|
||||||
|
[]string{testCommandName, "foo", "bar"},
|
||||||
|
"",
|
||||||
|
[]string{"foo", "bar"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"both env var and CLI",
|
||||||
|
[]string{testCommandName, "foo", "bar"},
|
||||||
|
"-foo bar",
|
||||||
|
[]string{"-foo", "bar", "foo", "bar"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"only env var",
|
||||||
|
[]string{testCommandName},
|
||||||
|
"-foo bar",
|
||||||
|
[]string{"-foo", "bar"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"cli string has blank values",
|
||||||
|
[]string{testCommandName, "bar", "", "baz"},
|
||||||
|
"-foo bar",
|
||||||
|
[]string{"-foo", "bar", "bar", "", "baz"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"cli string has blank values before the command",
|
||||||
|
[]string{"", testCommandName, "bar"},
|
||||||
|
"-foo bar",
|
||||||
|
[]string{"-foo", "bar", "bar"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// this should fail gracefully, this is just testing
|
||||||
|
// that we don't panic with our slice arithmetic
|
||||||
|
"no command",
|
||||||
|
[]string{},
|
||||||
|
"-foo bar",
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"single quoted strings",
|
||||||
|
[]string{testCommandName, "foo"},
|
||||||
|
"-foo 'bar baz'",
|
||||||
|
[]string{"-foo", "bar baz", "foo"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"double quoted strings",
|
||||||
|
[]string{testCommandName, "foo"},
|
||||||
|
`-foo "bar baz"`,
|
||||||
|
[]string{"-foo", "bar baz", "foo"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"double quoted single quoted strings",
|
||||||
|
[]string{testCommandName, "foo"},
|
||||||
|
`-foo "'bar baz'"`,
|
||||||
|
[]string{"-foo", "'bar baz'", "foo"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||||
|
os.Unsetenv(EnvCLI)
|
||||||
|
defer os.Unsetenv(EnvCLI)
|
||||||
|
|
||||||
|
// Set the env var value
|
||||||
|
if tc.Value != "" {
|
||||||
|
if err := os.Setenv(EnvCLI, tc.Value); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the args
|
||||||
|
args := make([]string, len(tc.Args)+1)
|
||||||
|
args[0] = oldArgs[0] // process name
|
||||||
|
copy(args[1:], tc.Args)
|
||||||
|
|
||||||
|
// Run it!
|
||||||
|
os.Args = args
|
||||||
|
testCommand.Args = nil
|
||||||
|
exit := wrappedMain()
|
||||||
|
if (exit != 0) != tc.Err {
|
||||||
|
t.Fatalf("bad: %d", exit)
|
||||||
|
}
|
||||||
|
if tc.Err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
if !reflect.DeepEqual(testCommand.Args, tc.Expected) {
|
||||||
|
t.Fatalf("bad: %#v", testCommand.Args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test just has more options than the test above. Use this for
|
||||||
|
// more control over behavior at the expense of more complex test structures.
|
||||||
|
func TestMain_cliArgsFromEnvAdvanced(t *testing.T) {
|
||||||
|
// Restore original CLI args
|
||||||
|
oldArgs := os.Args
|
||||||
|
defer func() { os.Args = oldArgs }()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
Name string
|
||||||
|
Command string
|
||||||
|
EnvVar string
|
||||||
|
Args []string
|
||||||
|
Value string
|
||||||
|
Expected []string
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"targeted to another command",
|
||||||
|
"command",
|
||||||
|
EnvCLI + "_foo",
|
||||||
|
[]string{"command", "foo", "bar"},
|
||||||
|
"-flag",
|
||||||
|
[]string{"foo", "bar"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"targeted to this command",
|
||||||
|
"command",
|
||||||
|
EnvCLI + "_command",
|
||||||
|
[]string{"command", "foo", "bar"},
|
||||||
|
"-flag",
|
||||||
|
[]string{"-flag", "foo", "bar"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"targeted to a command with a hyphen",
|
||||||
|
"command-name",
|
||||||
|
EnvCLI + "_command_name",
|
||||||
|
[]string{"command-name", "foo", "bar"},
|
||||||
|
"-flag",
|
||||||
|
[]string{"-flag", "foo", "bar"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"targeted to a command with a space",
|
||||||
|
"command name",
|
||||||
|
EnvCLI + "_command_name",
|
||||||
|
[]string{"command", "name", "foo", "bar"},
|
||||||
|
"-flag",
|
||||||
|
[]string{"-flag", "foo", "bar"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||||
|
// Setup test command and restore that
|
||||||
|
testCommandName := tc.Command
|
||||||
|
testCommand := &testCommandCLI{}
|
||||||
|
defer func() { delete(Commands, testCommandName) }()
|
||||||
|
Commands[testCommandName] = func() (cli.Command, error) {
|
||||||
|
return testCommand, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Unsetenv(tc.EnvVar)
|
||||||
|
defer os.Unsetenv(tc.EnvVar)
|
||||||
|
|
||||||
|
// Set the env var value
|
||||||
|
if tc.Value != "" {
|
||||||
|
if err := os.Setenv(tc.EnvVar, tc.Value); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the args
|
||||||
|
args := make([]string, len(tc.Args)+1)
|
||||||
|
args[0] = oldArgs[0] // process name
|
||||||
|
copy(args[1:], tc.Args)
|
||||||
|
|
||||||
|
// Run it!
|
||||||
|
os.Args = args
|
||||||
|
testCommand.Args = nil
|
||||||
|
exit := wrappedMain()
|
||||||
|
if (exit != 0) != tc.Err {
|
||||||
|
t.Fatalf("bad: %d", exit)
|
||||||
|
}
|
||||||
|
if tc.Err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
if !reflect.DeepEqual(testCommand.Args, tc.Expected) {
|
||||||
|
t.Fatalf("bad: %#v", testCommand.Args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCommandCLI struct {
|
||||||
|
Args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testCommandCLI) Run(args []string) int {
|
||||||
|
c.Args = args
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testCommandCLI) Synopsis() string { return "" }
|
||||||
|
func (c *testCommandCLI) Help() string { return "" }
|
|
@ -0,0 +1,47 @@
|
||||||
|
# go-shellwords
|
||||||
|
|
||||||
|
[![Coverage Status](https://coveralls.io/repos/mattn/go-shellwords/badge.png?branch=master)](https://coveralls.io/r/mattn/go-shellwords?branch=master)
|
||||||
|
[![Build Status](https://travis-ci.org/mattn/go-shellwords.svg?branch=master)](https://travis-ci.org/mattn/go-shellwords)
|
||||||
|
|
||||||
|
Parse line as shell words.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
args, err := shellwords.Parse("./foo --bar=baz")
|
||||||
|
// args should be ["./foo", "--bar=baz"]
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
os.Setenv("FOO", "bar")
|
||||||
|
p := shellwords.NewParser()
|
||||||
|
p.ParseEnv = true
|
||||||
|
args, err := p.Parse("./foo $FOO")
|
||||||
|
// args should be ["./foo", "bar"]
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
p := shellwords.NewParser()
|
||||||
|
p.ParseBacktick = true
|
||||||
|
args, err := p.Parse("./foo `echo $SHELL`")
|
||||||
|
// args should be ["./foo", "/bin/bash"]
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
shellwords.ParseBacktick = true
|
||||||
|
p := shellwords.NewParser()
|
||||||
|
args, err := p.Parse("./foo `echo $SHELL`")
|
||||||
|
// args should be ["./foo", "/bin/bash"]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Thanks
|
||||||
|
|
||||||
|
This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine).
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
under the MIT License: http://mattn.mit-license.org/2014
|
||||||
|
|
||||||
|
# Author
|
||||||
|
|
||||||
|
Yasuhiro Matsumoto (a.k.a mattn)
|
|
@ -0,0 +1,142 @@
|
||||||
|
package shellwords
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ParseEnv bool = false
|
||||||
|
ParseBacktick bool = false
|
||||||
|
)
|
||||||
|
|
||||||
|
var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
|
||||||
|
|
||||||
|
func isSpace(r rune) bool {
|
||||||
|
switch r {
|
||||||
|
case ' ', '\t', '\r', '\n':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceEnv(s string) string {
|
||||||
|
return envRe.ReplaceAllStringFunc(s, func(s string) string {
|
||||||
|
s = s[1:]
|
||||||
|
if s[0] == '{' {
|
||||||
|
s = s[1 : len(s)-1]
|
||||||
|
}
|
||||||
|
return os.Getenv(s)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Parser struct {
|
||||||
|
ParseEnv bool
|
||||||
|
ParseBacktick bool
|
||||||
|
Position int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewParser() *Parser {
|
||||||
|
return &Parser{ParseEnv, ParseBacktick, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) Parse(line string) ([]string, error) {
|
||||||
|
args := []string{}
|
||||||
|
buf := ""
|
||||||
|
var escaped, doubleQuoted, singleQuoted, backQuote bool
|
||||||
|
backtick := ""
|
||||||
|
|
||||||
|
pos := -1
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for i, r := range line {
|
||||||
|
if escaped {
|
||||||
|
buf += string(r)
|
||||||
|
escaped = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if r == '\\' {
|
||||||
|
if singleQuoted {
|
||||||
|
buf += string(r)
|
||||||
|
} else {
|
||||||
|
escaped = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if isSpace(r) {
|
||||||
|
if singleQuoted || doubleQuoted || backQuote {
|
||||||
|
buf += string(r)
|
||||||
|
backtick += string(r)
|
||||||
|
} else if buf != "" {
|
||||||
|
if p.ParseEnv {
|
||||||
|
buf = replaceEnv(buf)
|
||||||
|
}
|
||||||
|
args = append(args, buf)
|
||||||
|
buf = ""
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r {
|
||||||
|
case '`':
|
||||||
|
if !singleQuoted && !doubleQuoted {
|
||||||
|
if p.ParseBacktick {
|
||||||
|
if backQuote {
|
||||||
|
out, err := shellRun(backtick)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf = out
|
||||||
|
}
|
||||||
|
backtick = ""
|
||||||
|
backQuote = !backQuote
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
backtick = ""
|
||||||
|
backQuote = !backQuote
|
||||||
|
}
|
||||||
|
case '"':
|
||||||
|
if !singleQuoted {
|
||||||
|
doubleQuoted = !doubleQuoted
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case '\'':
|
||||||
|
if !doubleQuoted {
|
||||||
|
singleQuoted = !singleQuoted
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case ';', '&', '|', '<', '>':
|
||||||
|
if !(escaped || singleQuoted || doubleQuoted || backQuote) {
|
||||||
|
pos = i
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf += string(r)
|
||||||
|
if backQuote {
|
||||||
|
backtick += string(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf != "" {
|
||||||
|
if p.ParseEnv {
|
||||||
|
buf = replaceEnv(buf)
|
||||||
|
}
|
||||||
|
args = append(args, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
if escaped || singleQuoted || doubleQuoted || backQuote {
|
||||||
|
return nil, errors.New("invalid command line string")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Position = pos
|
||||||
|
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(line string) ([]string, error) {
|
||||||
|
return NewParser().Parse(line)
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package shellwords
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func shellRun(line string) (string, error) {
|
||||||
|
shell := os.Getenv("SHELL")
|
||||||
|
b, err := exec.Command(shell, "-c", line).Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New(err.Error() + ":" + string(b))
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(b)), nil
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package shellwords
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func shellRun(line string) (string, error) {
|
||||||
|
shell := os.Getenv("COMSPEC")
|
||||||
|
b, err := exec.Command(shell, "/c", line).Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New(err.Error() + ":" + string(b))
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(b)), nil
|
||||||
|
}
|
|
@ -2107,6 +2107,12 @@
|
||||||
"revision": "30a891c33c7cde7b02a981314b4228ec99380cca",
|
"revision": "30a891c33c7cde7b02a981314b4228ec99380cca",
|
||||||
"revisionTime": "2016-11-23T14:36:37Z"
|
"revisionTime": "2016-11-23T14:36:37Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "7OHq2KeND82oDITMEa+Mx4RmOaU=",
|
||||||
|
"path": "github.com/mattn/go-shellwords",
|
||||||
|
"revision": "753a2322a99f87c0eff284980e77f53041555bc6",
|
||||||
|
"revisionTime": "2017-01-23T01:43:24Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "A0PtnlAbuZA6kKfuVkM8GSx2SSI=",
|
"checksumSHA1": "A0PtnlAbuZA6kKfuVkM8GSx2SSI=",
|
||||||
"comment": "v0.6.0",
|
"comment": "v0.6.0",
|
||||||
|
|
|
@ -65,6 +65,29 @@ export TF_VAR_amap='{ foo = "bar", baz = "qux" }'
|
||||||
|
|
||||||
For more on how to use `TF_VAR_name` in context, check out the section on [Variable Configuration](/docs/configuration/variables.html).
|
For more on how to use `TF_VAR_name` in context, check out the section on [Variable Configuration](/docs/configuration/variables.html).
|
||||||
|
|
||||||
|
## TF_CLI_ARGS and TF_CLI_ARGS_name
|
||||||
|
|
||||||
|
The value of `TF_CLI_ARGS` will specify additional arguments to the
|
||||||
|
command-line. This allows easier automation in CI environments as well as
|
||||||
|
modifying default behavior of Terraform on your own system.
|
||||||
|
|
||||||
|
These arguments are inserted directly _after_ the subcommand
|
||||||
|
(such as `plan`) and _before_ any flags specified directly on the command-line.
|
||||||
|
This behavior ensures that flags on the command-line take precedence over
|
||||||
|
environment variables.
|
||||||
|
|
||||||
|
For example, the following command: `TF_CLI_ARGS="-input=false" terraform apply -force`
|
||||||
|
is the equivalent to manually typing: `terraform apply -input=false -force`.
|
||||||
|
|
||||||
|
The flag `TF_CLI_ARGS` affects all Terraform commands. If you specify a
|
||||||
|
named command in the form of `TF_CLI_ARGS_name` then it will only affect
|
||||||
|
that command. As an example, to specify that only plans never refresh,
|
||||||
|
you can set `TF_CLI_ARGS_plan="-refresh=false"`.
|
||||||
|
|
||||||
|
The value of the flag is parsed as if you typed it directly to the shell.
|
||||||
|
Double and single quotes are allowed to capture strings and arguments will
|
||||||
|
be separated by spaces otherwise.
|
||||||
|
|
||||||
## TF_SKIP_REMOTE_TESTS
|
## TF_SKIP_REMOTE_TESTS
|
||||||
|
|
||||||
This can be set prior to running the unit tests to opt-out of any tests
|
This can be set prior to running the unit tests to opt-out of any tests
|
||||||
|
|
Loading…
Reference in New Issue