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

Add Expand Headers Feature #117

Merged
merged 3 commits into from
Sep 26, 2019
Merged

Add Expand Headers Feature #117

merged 3 commits into from
Sep 26, 2019

Conversation

jammerful
Copy link
Contributor

See discussion of the feature here:
#116

See discussion of the feature here:
fullstorydev#116
@jammerful
Copy link
Contributor Author

@jhump As discussed here is the PR. Note I've made a few decisions regarding the functionality:

  1. If no corresponding environmental variable is found, the header is sent as is. This is instead of an error being thrown causing grpcurl to terminate.
  2. I've allowed header keys also to be set using environmental variables.

Open to changing the above.

grpcurl.go Outdated
@@ -161,6 +163,37 @@ func MetadataFromHeaders(headers []string) metadata.MD {
return md
}

/* Expands environmental variables contained in the header string
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note I went with this comment format, as golint was complaining.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please stick with //, for consistency in this file.

To appease go lint, you need the comment to start with the named of the doc'ed thing, in this case "Expand Headers":

// ExpandHeaders expands environment variable references contained in the string.
// etc...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -313,6 +319,13 @@ func main() {
return cc
}

var headers []string
if *expandHeaders {
headers = grpcurl.ExpandHeaders(addlHeaders)
Copy link
Contributor

Choose a reason for hiding this comment

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

Also need to expand rpcHeaders and reflHeaders.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

grpcurl.go Outdated
@@ -161,6 +163,37 @@ func MetadataFromHeaders(headers []string) metadata.MD {
return md
}

/* Expands environmental variables contained in the header string
Copy link
Contributor

Choose a reason for hiding this comment

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

Please stick with //, for consistency in this file.

To appease go lint, you need the comment to start with the named of the doc'ed thing, in this case "Expand Headers":

// ExpandHeaders expands environment variable references contained in the string.
// etc...

grpcurl.go Outdated
expandedHeaders := make([]string, len(headers))
for idx, header := range headers {
if header != "" {
regex := regexp.MustCompile(`\${\w+}`)
Copy link
Contributor

Choose a reason for hiding this comment

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

Definitely don't compile regex's in the loop. I know it's a simple and fast tool, so performance at this step may not usually be a concern. But we can at least hoist this out and only compile it once (make it an unexported package var) in case anyone ends up using this API in an unexpected way in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Sorry for oversight.

grpcurl.go Outdated
envVarValue := os.Getenv(result[2 : len(result)-1])
replacementValue := envVarValue
// If no corresponding env var is found, leave the header as is.
if len(envVarValue) == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this will be confusing. Based on how other tools generally work with env vars, I would expect either an error that my input is bad or missing env vars to interpolate the empty string.

I'm a bit partial to the former since it is more likely catch bugs.

Although this does bring up an interesting point: if you want to use a literal ${ in the value, this scheme is too simple and would result in an error. I think we need a way to escape the $. For example, $$ or \$ meaning literal dollar sign, not the start of an env var reference.

Copy link
Contributor

Choose a reason for hiding this comment

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

Escaping can probably be left as a TODO since, by default, grpcurl does not try to do any variable expansion. So you can either do variable expansion or use literal dollar signs, which is likely sufficient for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, on the confusing behavior. Fixed.

@jammerful
Copy link
Contributor Author

Thanks for the great feedback, I will update shortly.

Expand Headers should throw an error for unset environmental
variables. Also address additional PR feedback.
@@ -313,6 +320,22 @@ func main() {
return cc
}

if *expandHeaders {
var err error
addlHeaders, err = grpcurl.ExpandHeaders(addlHeaders)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me know if you wanted this DRY'd, was on the fence.

Copy link
Contributor

Choose a reason for hiding this comment

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

This seems fine.

Copy link
Contributor

@jhump jhump left a comment

Choose a reason for hiding this comment

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

This is looking pretty good. I left several style nits that should hopefully be easy to address. After they are taken care of, I'll merge.

rpcHeaders multiString
reflHeaders multiString
expandHeaders = flags.Bool("expand-headers", false, prettify(`
If set any environmental variables contained contained in any additional, rpc,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think some of the wording in here is a little awkward. How about something like this instead:

If set, headers may use '${NAME}' syntax to reference environment variables. These
will be expanded to the actual environment variable value before sending to the server.
For example, if there is an environment variable defined like FOO=bar, then a header of
'key: ${FOO}' would expand to 'key: bar'. This applies to -H, -rpc-header, and
-reflect-header options. No other expansion/escaping is performed. This can be used
to supply credentials/secrets without having to put them in command-line arguments.

var err error
addlHeaders, err = grpcurl.ExpandHeaders(addlHeaders)
if err != nil {
fail(err, "Failed to expand additional headers, missing environmental variable")
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: "environment variable" instead of "environmental variable"

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, the error returned from grpcurl.ExpandHeaders should suffice so that you don't need the "missing environment variable" bit -- it just makes the error that gets printed unnecessarily longer.

grpcurl.go Outdated
var envVarRegex = regexp.MustCompile(`\${\w+}`)

// ExpandHeaders expands environmental variables contained in the header string.
// If no corresponding environmental variable is found an error is thrown.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: "environment" instead of "environmental". Also "an error is returned" (not thrown).

grpcurl.go Outdated
envVarName := result[2 : len(result)-1]
envVarValue := os.Getenv(envVarName)
replacementValue := envVarValue
if len(envVarValue) == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is best tested using os.LookupEnv instead of os.GetEnv. That way, an explicitly blank environment variable is allowed -- but an absent variable will result in an error:

envVarValue, ok := os.LookupEnv(envVarName)
if !ok {
  return nil, fmt.Errorf("no variable %q is present in environment", envVarName)
}

grpcurl_test.go Outdated
}
for _, expandedHeader := range outHeaders {
if _, ok := expectedHeaders[expandedHeader]; !ok {
t.Errorf("The ExpandHeaders function has generated an unexpected header. Recieved unexpected header %s", expandedHeader)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: "Received" not "Recieved"

grpcurl.go Outdated
for idx, header := range headers {
if header != "" {
results := envVarRegex.FindAllString(header, -1)
if results != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

It is almost always best to check len(results) > 0 instead of testing against nil (nil and empty slices should be treated the same).

grpcurl.go Outdated
func ExpandHeaders(headers []string) ([]string, error) {
expandedHeaders := make([]string, len(headers))
for idx, header := range headers {
if header != "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of deepening the logic with indentation under an if block, how about invert the condition:

if header == "" {
    continue
}

Similar for below:

if len(results) == 0 {
    expandedHeaders[idx] = headers[idx]
    continue
}

grpcurl.go Outdated
if results != nil {
expandedHeader := header
for _, result := range results {
envVarName := result[2 : len(result)-1]
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe a comment to clarify that these indices are to cut out the '${' in the beginning and the '}' at the end.

@@ -313,6 +320,22 @@ func main() {
return cc
}

if *expandHeaders {
var err error
addlHeaders, err = grpcurl.ExpandHeaders(addlHeaders)
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems fine.

grpcurl.go Outdated
envVarValue := os.Getenv(envVarName)
replacementValue := envVarValue
if len(envVarValue) == 0 {
return nil, fmt.Errorf("environmental variable '%s' is not set", envVarName)
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be nice to reference the actual bad input. Maybe instead this:

return nil, fmt.Errorf("header %q refers to missing environment variable %q", header, envVarName)

Also fix all other PR feedback.
@jammerful jammerful requested a review from jhump September 26, 2019 19:25
@jhump jhump merged commit 9248ea0 into fullstorydev:master Sep 26, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants