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

command: add TF_CLI_ARGS to specify additional CLI args #11922

Merged
merged 6 commits into from
Feb 13, 2017
Merged
Show file tree
Hide file tree
Changes from 2 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
47 changes: 45 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ import (
"github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/terraform"
"github.com/mattn/go-colorable"
"github.com/mattn/go-shellwords"
"github.com/mitchellh/cli"
"github.com/mitchellh/panicwrap"
"github.com/mitchellh/prefixedio"
)

const (
// EnvCLI is the environment variable name to set additional CLI args.
EnvCLI = "TF_CLI_ARGS"
)

func main() {
// Override global prefix set by go-dynect during init()
log.SetPrefix("")
Expand Down Expand Up @@ -129,9 +135,45 @@ func wrappedMain() int {
// Make sure we clean up any managed plugins at the end of this
defer plugin.CleanupClients()

// Get the command line args. We shortcut "--version" and "-v" to
// just show the version.
// Get the command line args.
args := os.Args[1:]

// Prefix the args with any args from the EnvCLI
if v := os.Getenv(EnvCLI); v != "" {
log.Printf("[INFO] %s value: %q", EnvCLI, v)
extra, err := shellwords.Parse(v)
if err != nil {
Ui.Error(fmt.Sprintf(
"Error parsing extra CLI args from %s: %s",
EnvCLI, err))
return 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[0] != '-' {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An arg can be 0 length -- even more likely in an automated system like TFE.
strings.HasPrefix?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really good catch, really good. Fixing now.

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 link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to worry about non-flag arguments in the TF_CLI_ARGS variable? ( I can't think of a case where it matters, just tossing out the question)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we just make explicit that we insert the args right after the command. If that works, great, if that doesn't for them for normal args, then its documented.

copy(newArgs, args[:idx])
copy(newArgs[idx:], extra)
copy(newArgs[len(extra)+idx:], args[idx:])
args = newArgs

}

// We shortcut "--version" and "-v" to just show the version
for _, arg := range args {
if arg == "-v" || arg == "-version" || arg == "--version" {
newArgs := make([]string, len(args)+1)
Expand All @@ -142,6 +184,7 @@ func wrappedMain() int {
}
}

log.Printf("[INFO] CLI command args: %#v", args)
cli := &cli.CLI{
Args: args,
Commands: Commands,
Expand Down
139 changes: 139 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
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,
},

{
// 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)

// 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)
}
})
}
}

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 "" }
47 changes: 47 additions & 0 deletions vendor/github.com/mattn/go-shellwords/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading