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

TOOLS-2779: Add --config option for password values #62

Merged
merged 4 commits into from Dec 14, 2020

Conversation

edobranov
Copy link
Collaborator

Jira: https://jira.mongodb.org/browse/TOOLS-2779

This adds support for the --config option along with some unit tests under TestParseConfigFile. Also vendors in the gopkg.in/yaml.v2 package.

break
} else if strings.HasPrefix(arg, "--config") {
// No next argument or the next argument is an option.
if i+1 == len(args) || strings.HasPrefix(args[i+1], "-") {
Copy link
Collaborator Author

@edobranov edobranov Nov 24, 2020

Choose a reason for hiding this comment

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

One ambiguity I ran into was if the user does this:

$ mongodump --config -d myDB -c ...

Do we want to treat this as a missing config file, or try to open a file named -d instead? I see validity in both, though I'm maybe leaning a bit towards the latter option of just trying to open it and letting the user deduce what happened from the os.Open() error message.

Reason being that they could theoretically have a config file that starts with a hyphen:

$ mongodump --config -tools-config.yaml -d myDB -c ...

It's unlikely, but we'd either have to ensure the arg after --config isn't an option (lot of manual checking) or force the user to rename -tools-config.yaml to something else.

I can remove the || strings.HasPrefix(args[i+1], "-") check if others agree.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah good question, go-flags has this behavior:

$ mongodump --sslPEMKeyFile -d
2020-11-25T18:58:47.875+0100    error parsing command line options: expected argument for flag `--sslPEMKeyFile', but got option `-d'

So I think disallowing files beginning with - is consistent. If people do have files beginning with a -, they can always use the --config="foo" format. But I also think trying to open -d is fine too. It would very quickly obvious what the problem is. If you prefer that approach, I think go for it.

Copy link
Collaborator Author

@edobranov edobranov Nov 25, 2020

Choose a reason for hiding this comment

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

Cool, I'm still leaning towards trying to open -d just because otherwise the error message would be kinda hard to get right. Like if a user says --config -tools-config.yaml the error would be --config option specified but no file given, which would be confusing since they obviously did provide a file and might not think to use --config=. We could separate out the conditions, but that's already more complexity than it's worth I feel.

Instead I'll add a bit of context to the ioutil.ReadFile() error, so now it'll look like:

$ mongodump --config -d myDB
2020-11-25T14:26:20.168-0500	error parsing command line options: error opening file with --config: open -d: no such file or directory

If --config is at the end, it'll stay the same:

$ mongodump -d myDB --config
2020-11-25T14:35:30.196-0500	error parsing command line options: --config option specified but no file given

Copy link
Member

@tfogo tfogo left a comment

Choose a reason for hiding this comment

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

Good tests. I have a couple of concerns about parsing the config option.

break
} else if strings.HasPrefix(arg, "--config") {
// No next argument or the next argument is an option.
if i+1 == len(args) || strings.HasPrefix(args[i+1], "-") {
Copy link
Member

Choose a reason for hiding this comment

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

Yeah good question, go-flags has this behavior:

$ mongodump --sslPEMKeyFile -d
2020-11-25T18:58:47.875+0100    error parsing command line options: expected argument for flag `--sslPEMKeyFile', but got option `-d'

So I think disallowing files beginning with - is consistent. If people do have files beginning with a -, they can always use the --config="foo" format. But I also think trying to open -d is fine too. It would very quickly obvious what the problem is. If you prefer that approach, I think go for it.

}
configPath = arg[9:]
break
} else if strings.HasPrefix(arg, "--config") {
Copy link
Member

Choose a reason for hiding this comment

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

This would match --configFOO too. We need to do an exact match here.

// Get config file path from the arguments, if specified.
configPath := ""
for i, arg := range args {
if strings.HasPrefix(arg, "--config=") {
Copy link
Member

Choose a reason for hiding this comment

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

Is there going to be a difference between --config=foo and --config="foo"? Will the latter make the config path be \"foo\"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm, interesting point. It seems the underlying call to os.Open() is smart enough to recognize that the surrounding quotation marks aren't intended unless they're explicitly escaped. So there should be no difference.

From trying it on the command line as-is:

$ echo "uri: mongodb://abc:@localhost:33333" > config.yaml

$ mongodump --config=config.yaml
Enter password:

$ mongodump --config="config.yaml"
Enter password:

$ mongodump --config=\"config.yaml\"
2020-11-25T14:19:02.953-0500	error parsing command line options: open "config.yaml": no such file or directory

$ mongodump --config="\"config.yaml\""
2020-11-25T14:19:12.025-0500	error parsing command line options: open "config.yaml": no such file or directory

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think it's the call to os.Open -- my understanding is that the shell consumes the quotes, so they are never passed as args in this specific example.

I think there are other issues here though. For example, does go-flags allow /-style flags on windows? If so, that's a whole separate class of thing that we have to manually check for here.

As I have noted in another PR, I'm generally skeptical of trying to match the library's flag-parsing behavior by manually scanning arguments

}
}

Convey(testCase.description, func() {
Copy link
Member

Choose a reason for hiding this comment

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

This can just be Convey(testCase.description, assertion)

So(opts.ParseConfigFile(args), ShouldNotBeNil)
})

Convey("should error with non-existent config file specified", t, func() {
Copy link
Member

Choose a reason for hiding this comment

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

I think the different positions here are overkill to just test a non-existent file.

Copy link
Collaborator

@huan-Mongo huan-Mongo left a comment

Choose a reason for hiding this comment

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

All seems good! Just a comment on the description of this option.

Version bool `long:"version" description:"print the tool version and exit"`
Help bool `long:"help" description:"print usage"`
Version bool `long:"version" description:"print the tool version and exit"`
ConfigPath string `long:"config" description:"path to a configuration file with password secrets"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

I am wondering do we need with password secrets since later we could introduce more fields inside?

Copy link
Collaborator Author

@edobranov edobranov Dec 3, 2020

Choose a reason for hiding this comment

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

Yea, I don't think we really need it either. We could also drop that phrase later on when we do support more/all fields, but I'm not too attached to it. I can go ahead and remove it if @rychipman agrees.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think removing it makes sense!

@edobranov edobranov removed the request for review from tfogo December 3, 2020 18:18
// Get config file path from the arguments, if specified.
configPath := ""
for i, arg := range args {
if strings.HasPrefix(arg, "--config=") {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think it's the call to os.Open -- my understanding is that the shell consumes the quotes, so they are never passed as args in this specific example.

I think there are other issues here though. For example, does go-flags allow /-style flags on windows? If so, that's a whole separate class of thing that we have to manually check for here.

As I have noted in another PR, I'm generally skeptical of trying to match the library's flag-parsing behavior by manually scanning arguments

Copy link
Collaborator Author

@edobranov edobranov left a comment

Choose a reason for hiding this comment

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

Adjusted the --config help menu phrase and changed out the manual command line parsing for the opts.parser.ParseArgs() functionality. Should take care of a lot of what-ifs.

// Assign each parsed value to its respective ToolOptions field.
opts.Auth.Password = config.Password
opts.URI.ConnectionString = config.ConnectionString
opts.SSL.SSLPEMKeyPassword = config.SSLPEMKeyPassword
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To bring up a comment I made in the tech design a while ago -- before storing each field here, we could actually check to see if the fields were already set by the call to opts.parser.ParseArgs() at the beginning of this func. This would allow us to log a message that a config file's value will be overridden by the respective command line option.

Might offer a bit of transparency but I don't feel strongly either way.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't recommend doing that because the fields aren't necessarily directly set inside cmd parameters, for example, the password could be set inside connectionString. In that case, you won't be able to catch the overridden.
If you really wanna log the warning message, this function should return a separate ToolOptions object and later used as the comparison with the ParseArgs() result.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Right, it's true we wouldn't know if a password comes from --uri vs. a positional connection string, but would that matter a lot?

It might be enough to just say something like warning: connection string in config file will be overridden by connection string on command line.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Since we already decided this wasn't a requirement for this project, I would also recommend that we not do this

Copy link
Collaborator

@huan-Mongo huan-Mongo left a comment

Choose a reason for hiding this comment

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

LGTM!

// Assign each parsed value to its respective ToolOptions field.
opts.Auth.Password = config.Password
opts.URI.ConnectionString = config.ConnectionString
opts.SSL.SSLPEMKeyPassword = config.SSLPEMKeyPassword
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't recommend doing that because the fields aren't necessarily directly set inside cmd parameters, for example, the password could be set inside connectionString. In that case, you won't be able to catch the overridden.
If you really wanna log the warning message, this function should return a separate ToolOptions object and later used as the comparison with the ParseArgs() result.

Copy link
Collaborator

@rychipman rychipman left a comment

Choose a reason for hiding this comment

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

LGTM

// Assign each parsed value to its respective ToolOptions field.
opts.Auth.Password = config.Password
opts.URI.ConnectionString = config.ConnectionString
opts.SSL.SSLPEMKeyPassword = config.SSLPEMKeyPassword
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since we already decided this wasn't a requirement for this project, I would also recommend that we not do this

@edobranov edobranov merged commit 56f379e into mongodb:master Dec 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants