Skip to content

Allow flag or file based configuration - #620

Merged
RJPercival merged 10 commits into
google:masterfrom
rolandshoemaker:conf-files
May 26, 2017
Merged

RJPercival merged 10 commits into
google:masterfrom
rolandshoemaker:conf-files

Conversation

@rolandshoemaker

Copy link
Copy Markdown
Contributor

Initial implementation, slightly less hacky than I originally anticipated.

Fixes #578.

@codingllama
codingllama self-requested a review May 17, 2017 10:42
@pphaneuf

Copy link
Copy Markdown
Contributor

Could you please rebase this on top of master, rather than merge it in? Thanks!

@codingllama codingllama left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good to me, I have mostly nits / suggestions.

Would you mind applying similar changes to server/vmap/trillian_map_server/main.go too? It's fairly small and the only missing entry point.

Comment thread examples/ct/ct_server/main.go Outdated
etcdServers = flag.String("etcd_servers", "", "A comma-separated list of etcd servers")
)
type config struct {
Host string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: As the struct is private, the fields should possibly be private too. (It seems the large majority of the existing code follows this convention, apart from 1 outlier that I found.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Right, I missed that. Would you mind adding a comment? I.e.:

// config represents all configurable settings of {CT,Log,Map} server.
// Fields may be set either via flag or a configuration file in JSON format.
// Fields must be public due to a requirement of json.Unmarshal.

Not everyone is going to use config files, so a "stylistic" change might break you and leave us none the wiser.

Ditto for others.

Comment thread examples/ct/ct_server/main.go Outdated
if *configFlag != "" {
// Only allow either --configFlag OR any other flags
if flag.CommandLine.NFlag() > 2 {
glog.Exit("Cannot use --configFlag with any other flags")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: --config instead of --configFlag

Comment thread server/trillian_log_server/main.go Outdated
etcdService = flag.String("etcd_service", "trillian-log", "Service name to announce ourselves under")
)
type config struct {
MySQLURI string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ditto for private struct / public fields.

Same for other files.

Comment thread server/trillian_log_server/main.go Outdated
if *configFlag != "" {
// Only allow either --configFlag OR any other flags
if flag.CommandLine.NFlag() > 2 {
glog.Exit("Cannot use --configFlag with any other flags")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ditto for --config.

Same for other files.

Comment thread util/config.go Outdated
// See the License for the specific language governing permissions and
// limitations under the License.

package util

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WDYT about moving this to a configs package? I kind of dislike util on principle.

Comment thread util/config.go Outdated
}

// UnmarshalJSON parses a time.Duration field from a string
func (cd ConfigDuration) UnmarshalJSON(data []byte) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would you mind including a test for this?

Comment thread examples/ct/ct_server/main.go Outdated

if *maxGetEntriesFlag > 0 {
ct.MaxGetEntriesAllowed = *maxGetEntriesFlag
if *configFlag != "" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Have you considered moving this block to a shared function, possibly under the configs package I suggested below?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment thread util/config.go Outdated

// ConfigDuration is an abstraction used to simplifying unmarshalling
// json into structs that contain time.Duration fields
type ConfigDuration struct {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe rename to JsonDuration?

Please document the expected marshaled format as well (a mention to time.ParseDuration would be good enough, IMO).

@rolandshoemaker

Copy link
Copy Markdown
Contributor Author

I've run into a bit of a problem while testing these changes, github.com/golang/glog adds a set of flags related to logging which break the test that checks if you are providing --config and other flags because they aren't part of the per binary configs. It's entirely plausible that you'd want to provide --config and --log_dir or something but with the current flag.CommandLine.NFlags() > 1 test you are prevented from doing this.

Not entirely sure what the best way around this is at the moment, going to drink some coffee and see if I can come up with a good solution.

@rolandshoemaker

rolandshoemaker commented May 17, 2017 •

Copy link
Copy Markdown
Contributor Author

(this idea doesn't work)

Post caffeine: easiest solution is probably to just make the per-binary flags use their own flag.FlagSet and let glog use the default flat.CommandLine set so we can still test for --config and other binary specific flags.

Will require some rewording of the --config usage text to specify you can use it and the glog flags but that should be fine. It's a little annoying that you won't be able to specify everything via the config file but meh ¯_(ツ)_/¯.

@gdbelvin

Copy link
Copy Markdown
Contributor

I know I'm a bit late to this conversation, but while we're discussing flags and configurations, have we thought about configuring through environment variables? Many cluster management products use a .env file to supply environment variables to server binaries ala the 12 factor app model

@rolandshoemaker

Copy link
Copy Markdown
Contributor Author

Blerp, current solution doesn't work: I forgot that flag doesn't like parsing flags it doesn't know and the only other option is basically 'just allow anything, even badly formatted flag arguments'. There are some really hacky ways around this by for instance changing the error handling for flag.CommandLine and then adding the glog flags to the per-binary flag.FlagSet but, guh, it's not very clean.

One super simple option I can think of that would make life significantly easier (and allow all configuration to happen via flags or files) would be to fork github.com/golang/glog and remove the flags it registers and move the configuration to a Setup or Init method that has to be called before logging happens. This would preserve the same properties the package already has (i.e. you currently have to call flag.Parse before you can do any logging) and remove all of the annoying side effects of a package magically registering its own flags that you don't have control over...

I know I'm a bit late to this conversation, but while we're discussing flags and configurations, have we thought about configuring through environment variables? Many cluster management products use a .env file to supply environment variables to server binaries ala the 12 factor app model

@gdbelvin This would be a viable solution but I personally don't super like it as a method of configuration, you get the benefits of having everything in a file but have to worry about when the last time you sourced it was/if there the current ENV is out of sync with what is in the file etc. It would also require writing some reflect code or something to simplify pulling from vars unless each binary just had a big section for populating variables.

@codingllama

Copy link
Copy Markdown
Contributor

The case of flags defined outside the Trillian codebase is an interesting one, because it seems to force us to a mixed configuration. @rolandshoemaker, what's your deployment plan in regards to those flags? Should they be defined in your configuration object as well?

About implementation options, I don't think this is a strong enough reason to fork glog. I would suggest something in those lines instead:

allowedFlags := map[string]bool{
  "logtostderr":      true,
  "alsologtostderr":  true,
  "stderrthreshold":  true,
  "log_dir":          true,
  "log_backtrace_at": true,
  "v":                true,
  "vmodule":          true,
}
for _, arg := range os.Args[1:] {
  var flag string
  switch {
  case strings.HasPrefix(arg, "--"):
    flag = arg[2:]
  case strings.HasPrefix(arg, "-"):
    flag = arg[1:]
  default:
    continue // Ignore non-flags
  }
  if index := strings.Index(flag, "="); index != -1 {
    flag = flag[0:index]
  }
  if _, ok := allowedFlags[flag]; !ok {
    return fmt.Errorf("flag not allowed in conjunction with --config: %v", arg)
  }
}

(Warning: I haven't tested / executed this.)

Another option, if we'd like other flags to be configurable via config files, is to add them to the configuration and call flag.Set for each flag. The caveat is that logging won't work as expected up until that point.

I'm OK with dropping the --config "exclusiveness" requirement if people think those options are not worth it, though.

@codingllama codingllama left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please have a look at the Travis failure as well.

@RJPercival had an interesting suggestion: we could use flag.CommandLine.Parse directly on the config file, which would preclude all the json-parsing logic and config struct intermediates. That would change the format from json to a "flags file", though (one flag per line, possibly).

At that point, IMO, we might just have a .sh file wrapping the entry points and set the flags there, which would avoid the entire config file business. IDK much about your deployment environment, so I'll leave that to you.

Comment thread configs/configs_test.go Outdated
Dur JSONDuration
}

a := []byte(`{"dur":"10s"}`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would mind refactoring the test into a table-driven style?

https://github.com/golang/go/wiki/TableDrivenTests

Comment thread configs/configs.go Outdated
// UnmarshalJSON parses a time.Duration field from a string
func (jd *JSONDuration) UnmarshalJSON(data []byte) error {
s := ""
err := json.Unmarshal(data, &s)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: Scope err in the if? if err := json.Unmarshal(data, &s); err != nil { ... }

Comment thread examples/ct/ct_server/main.go Outdated
ct.MaxGetEntriesAllowed = *maxGetEntriesFlag
if *configFlag != "" {
// Only allow either --config OR any other flags
if flags.NFlag() > 1 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we move the --config "exclusiveness" check to the shared function as well? (Whatever form it might take, it is.)

Comment thread examples/ct/ct_server/main.go Outdated
etcdServers = flag.String("etcd_servers", "", "A comma-separated list of etcd servers")
)
type config struct {
Host string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Right, I missed that. Would you mind adding a comment? I.e.:

// config represents all configurable settings of {CT,Log,Map} server.
// Fields may be set either via flag or a configuration file in JSON format.
// Fields must be public due to a requirement of json.Unmarshal.

Not everyone is going to use config files, so a "stylistic" change might break you and leave us none the wiser.

Ditto for others.

@rolandshoemaker

Copy link
Copy Markdown
Contributor Author

@RJPercival had an interesting suggestion: we could use flag.CommandLine.Parse directly on the config file, which would preclude all the json-parsing logic and config struct intermediates. That would change the format from json to a "flags file", though (one flag per line, possibly).

At that point, IMO, we might just have a .sh file wrapping the entry points and set the flags there, which would avoid the entire config file business. IDK much about your deployment environment, so I'll leave that to you.

I wouldn't be explicitly opposed to this, generating a file is generating a file, the only real difference in terms of management is that it's a little simpler to verify that the file has been properly formatted when it's JSON since we can use basically any parser on the config management node to verify the file.

I think this approach does have a few downsides though, mainly it would make the config take precedence over flags when both are provided (which I think is basically the opposite of expected behavior) and it would require either a bunch of extra parsing code (to properly split up the flags/flag arguments in the way that flag.Parse expects them to be provided or strict rules about how flags should be provided (i.e. each flag must be on a new line and use the -flag=value format etc etc) in the files.

@codingllama codingllama left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Many thanks for addressing all comments, Roland. Could you rebase on master again after you address the next round?

Comment thread configs/configs.go Outdated
// be set from a file.
func SetGLogFlags(gf GLogFlags) error {
if gf.LogToStderr {
if err := flag.Set("logtostderr", fmt.Sprintf("%t", gf.LogToStderr)); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: Just fmt.Sprint(gf.LogToStderr) should work.

Ditto for alsologtostderr and v.

Comment thread configs/configs_test.go Outdated
wantErr string
wantDuration time.Duration
}{
{[]byte(`{"dur":"10s"}`), "", time.Second * 10},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: Please use named literals on the struct fields. This way you don't have to specify default/unrelated values as well.

Comment thread configs/configs_test.go Outdated
var s struct {
Dur JSONDuration
testCases := []struct {
json []byte

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: You could make this a string and move the cast to the test body.

Comment thread configs/configs_test.go Outdated
"time"
)

func TestJSONDuration(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe TestJSONDuration_ UnmarshalJSON, so it's clear which method is being tested?

Comment thread configs/configs_test.go Outdated
Dur JSONDuration
testCases := []struct {
json []byte
wantErr string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is tying the test to error messages we don't own. I'd be ok with an wantErr bool and a err != nil check. We have a bunch of tests like that, eg, noop_test.go.

Comment thread configs/configs_test.go Outdated
var s struct {
Dur JSONDuration
}
err := json.Unmarshal(tc.json, &s)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion:

err := json.Unmarshal(tc.json, &s)
switch hasErr, wantErr := err != nil, tc.wantErr != ""; {
case hasErr != wantErr:
  t.Errorf(...)
case hasErr && err.Error() != tc.wantErr:
  t.Errorf(...)
}

Although, if you take the comment above about the error string, the case comparing the messages goes away, so we could simplify it further to an if.

Comment thread configs/configs_test.go Outdated
} else if err == nil && tc.wantErr != "" {
t.Errorf("json.Unmarshal didn't fail: want %q", tc.wantErr)
}
if tc.wantDuration != 0 && tc.wantDuration != s.Dur.Duration {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think you can drop the tc.wantDuration != 0 condition and it'll still work, as zero should be the default on s.Dur.Duration.

Comment thread examples/ct/ct_server/main.go Outdated
"google.golang.org/grpc/naming"
)

// config represents all configurable settings of {CT,Log,Map} server and Log signer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: Replace "{CT,Log,Map} server and Log signer" with just "CT server". This particular config is exclusive to CT server (and so are the others to their respective binaries).

Ditto for others.

Comment thread examples/ct/ct_server/main.go Outdated
if err := configs.LoadConfig(*configFlag, &c); err != nil {
glog.Exit(err)
}
if err := configs.SetGLogFlags(c.GLogFlags); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: Could we move this to LoadConfig?

If we make changes similar to the ones below I reckon it should work:

// Change SetGLogFlags signature.
func (gf *GLogFlags) SetGLogFlags() error { ... }

type glogFlagsSetter interface {
    SetGLogFlags() error
}

func LoadConfig(path string, obj interface{}) error {
  // ...
  if setter, ok := obj.(glogFlagsSetter); ok {
    if err := setter.SetGlogFlags(); err != nil {
      return err
    }
  }
}

Comment thread configs/configs.go Outdated

// GLogFlags contains the values of the flags that github.com/golang/glog
// registers.
type GLogFlags struct {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: I'd be tempted to remove call this "CommonFlags" (or something in that spirit), so if we add non-glog flags to it later we won't have to rename it. (Ditto for SetGlogFlags).

@RJPercival

Copy link
Copy Markdown
Contributor

To illustrate my proposal of reading flags from a file, see RJPercival@c6d3fc8. This supports things like glog without any special code being required. Parsing is off-loaded to a shellwords package (admittedly, this means an extra dependency). It supports either letting command-line arguments take precedence or raising an error if any command-line arguments other than "--config" are supplied.

Having said all that, it doesn't give you much over just having a wrapper script around the binary with whatever flags you'd like. Can you explain to me what this PR gives us? You mentioned it allows you to "use basically any parser on the config management node to verify the [config]". What sort of verification were you planning to do?

@rolandshoemaker

Copy link
Copy Markdown
Contributor Author

To illustrate my proposal of reading flags from a file, see RJPercival/trillian@c6d3fc8. This supports things like glog without any special code being required. Parsing is off-loaded to a shellwords package (admittedly, this means an extra dependency). It supports either letting command-line arguments take precedence or raising an error if any command-line arguments other than "--config" are supplied.

Yup, this is a MUCH cleaner implementation. Looking at the code I think this is probably the best way forward if we do want to support some kind of file based configuration.

Can you explain to me what this PR gives us? You mentioned it allows you to "use basically any parser on the config management node to verify the [config]". What sort of verification were you planning to do?

By verification I really just meant a super basic sanity check, for instance in our existing deployment of other software we are able to simply parse the JSON config after it has been generated on the config management node from a template in order to verify that it is at least properly formatted.

Having said all that, it doesn't give you much over just having a wrapper script around the binary with whatever flags you'd like.

After having deployed trillian in its current form a bunch recently I've pretty much come around to this being a basically fine solution. I think the only thing I'd really like file based configuration for at this point is basic secret isolation so that we don't have PEM key passwords or MySQL credentials in the process list but ¯_(ツ)_/¯.

Conclusion: I think I'm convinced at this point that this JSON-based solution ends up being very heavyweight for relatively little benefit (we could have native lists or maps! but... nowhere uses that now so eh). I still think having the separation of config from service is nice to have and provides for basic secret isolation but is not necessary.

At this point I think there are two real choices: (a.) close this and #578 out and go on our merry way and keep everything as-is or (b.) implement the solution from RJPercival/trillian@c6d3fc8. I think I'd probably go with (b.) but could really go either way to be honest.

@RJPercival

Copy link
Copy Markdown
Contributor

I can appreciate not wanting certain flags visible in the process list; that's a good reason to have a config file. Would you like to pick up RJPercival@c6d3fc8, finish it off and see how it works for your use case?

@rolandshoemaker

Copy link
Copy Markdown
Contributor Author

I can appreciate not wanting certain flags visible in the process list; that's a good reason to have a config file. Would you like to pick up RJPercival/trillian@c6d3fc8, finish it off and see how it works for your use case?

Sounds good to me.

Comment thread cmd/flags.go Outdated
"flag"
"io/ioutil"

"github.com/mattn/go-shellwords"

@RJPercival RJPercival May 23, 2017 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Having looked at what is available within Google, could we swap this for https://bitbucket.org/creachadair/shell instead please? We'll lose the ability to use environment variables in the flag file, which is a bit of a shame, but it saves having to get https://github.com/mattn/go-shellwords reviewed by our security team. I can go down that road if supporting environment variables is important to you though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Works for me.

Comment thread server/vmap/trillian_map_server/main.go Outdated
if *configFile != "" {
if err := cmd.ParseFlagFile(*configFile); err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse %v: %v\n", *configFile, err)
os.Exit(1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

glog.Exitf() rather than fmt.Fprintf(), os.Exit(1) would be more consistent with the rest of the file.

Comment thread server/trillian_log_signer/main.go Outdated
if *configFile != "" {
if err := cmd.ParseFlagFile(*configFile); err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse %v: %v\n", *configFile, err)
os.Exit(1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

glog.Exitf() would be better here.

Comment thread server/trillian_log_server/main.go Outdated
if *configFile != "" {
if err := cmd.ParseFlagFile(*configFile); err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse %v: %v\n", *configFile, err)
os.Exit(1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

glog.Exitf() would be better here.

Comment thread cmd/flags.go Outdated
// path. Re-calls flag.Parse() after parsing the flags in the file
// so that flags provided on the command line take precedence over
// flags provided in the file.
func ParseFlagFile(path string) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This func could do with some tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment thread cmd/flags.go Outdated
}

// Expand any environment variables in the file
flagsString := os.ExpandEnv(string(file))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Expanding envvars before splitting might lead to unintended splitting if the envvar value contains spaces, quotation marks, etc. Unfortunately, you might have to instead call os.ExpandEnv() on each of args. A test case could confirm this.

Comment thread cmd/flags.go Outdated
}

err = flag.CommandLine.Parse(args)
if err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These two lines could be combined, e.g. if err := flag.CommandLine.Parse(args); err != nil {

@RJPercival RJPercival left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Once these comments have been addressed, it should be good to merge.

Comment thread cmd/flags_test.go Outdated
},
}

initalArgs := os.Args[:]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

typo: s/initalArgs/initialArgs/

Comment thread cmd/flags_test.go Outdated
}
}
err := parseFlags(tc.contents)
if err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could combine the above two lines.

Comment thread cmd/flags_test.go Outdated
for _, tc := range tests {
a, b = "", ""
os.Args = initalArgs[:]
if len(tc.cliArgs) > 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No need for this check - os.Args = append(os.Args, tc.cliArgs...) will simply be a noop if len(tc.cliArgs) == 0.

Comment thread cmd/flags_test.go Outdated
initalArgs := os.Args[:]
for _, tc := range tests {
a, b = "", ""
os.Args = initalArgs[:]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Lines 80-83 could be condensed into os.Args = append(initialArgs, tc.cliArgs...).

Comment thread cmd/flags_test.go

flag.CommandLine.Init(os.Args[0], flag.ContinueOnError)

tests := []struct {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add a name field and include it in all of the t.Errorf() calls so that it's easy to identify which test case failed.

@RJPercival RJPercival left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

To save time, and because my comments were minor, I've taken the liberty of addressing them myself and pushing a commit to your branch. Feel free to amend that commit as you like, then you can squash and merge this PR.

@RJPercival
RJPercival dismissed codingllama’s stale review May 25, 2017 08:54

Superseded by a more recent review.

@rolandshoemaker

Copy link
Copy Markdown
Contributor Author

Changes look fine to me, thanks! Side-note: looks like TestUpdateRoot may be flaky.

@rolandshoemaker

Copy link
Copy Markdown
Contributor Author

(Oh also, I don't have perms on this repo so someone else should merge/kick the travis build)

@RJPercival

RJPercival commented May 25, 2017 •

Copy link
Copy Markdown
Contributor

I've given it a kick. Yes I've noticed client_test.go being pretty flaky, likely because of the sleep call it's relying on. @gdbelvin, any chance you could take a look at that?

@RJPercival

Copy link
Copy Markdown
Contributor

The second build hit issue #617 (I'll look into that). Third time is hopefully the charm...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants