-
Notifications
You must be signed in to change notification settings - Fork 14
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
feat: Job String Processor #234
feat: Job String Processor #234
Conversation
@merlin-northern, Let me know if you want to start the integration pipeline by mentioning me and the command "start pipeline". my commands and optionsYou can trigger a pipeline on multiple prs with:
You can start a fast pipeline, disabling full integration tests with:
You can cherry pick to a given branch or branches with:
|
ebca634
to
fb79f7e
Compare
@mender-test-bot start pipeline please |
Hello 😸 I created a pipeline for you here: Pipeline-630739572 Build Configuration Matrix
|
571aeb4
to
6eb3278
Compare
@mender-test-bot start pipeline please |
Hello 😸 I created a pipeline for you here: Pipeline-630863595 Build Configuration Matrix
|
@tranchitella could I kindly ask you to take a look, since you already reviewed the first try (the quck fix from the past weekend in ent) |
l.Infof("processTask: calling http task: %s %s", httpTask.Method, | ||
processJobString(httpTask.URI, workflow, job)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you remove this log?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I moved it below
- it was doubling the very expensive operation of processing the job string, just for printing.
l.Infof("processHTTPTask: starting with: method=%s uri=%s (options=%+v)", | ||
httpTask.Method, | ||
uri, | ||
options, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think logging option is valuable, as it will always be Encoding: URL.
By the way, I don't get why we need this log here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can remove it, it was moved from one level up (see the previous comment). do you want me to remove it?
app/worker/http.go
Outdated
value = processJobString(value, workflow, job) | ||
value = maybeExecuteGoTemplate(value, job.InputParameters.Map()) | ||
key = ps.ProcessJobString(key, nil) | ||
key = maybeExecuteGoTemplate(key, ps.GetJobInputParameters().Map()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks weird obtaining the job's input parameters from a GetJobInputParameters
method on the JobStringProcessor instance. It looks like you did it by convenience because processHTTPTask doesn't receive anymore the job, but honestly I don't like it too much.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know, but there is no good way of going it without a total re-desing. would you prefer ps.GetJob()
method instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alfrunes there is a bit of refactoring going on here (I could not stop) in order not to redesign the whole thing, I added GetJobInputParameters
in the string processor class. we (Fabio and me) both think it is not very elegant, and a bit out of place. what do you think? (Keep in mind that we do not want to go over board here :>)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since maybeExecuteGoTemplate
is called wherever ps.ProcessJobString
is called; wouldn't it be easier to just move it inside the processor
package?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
brilliant. so I created a method MaybeExecuteGoTemplate
but there were complications, in the end, this is the final version. makes sense? beware: I can refactor deeper.
l.Infof("processTask: calling http task: %s %s", httpTask.Method, | ||
processJobString(httpTask.URI, workflow, job)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why did you remove this log and moved it in HTTPTask?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it was super expensive to process the job string twice, here for only logging and then again right after. so moving it was a saver and did not change anything, as we enter the function anyway.
app/processor/string.go
Outdated
} | ||
} | ||
|
||
func (j *JobStringProcessor) ProcessJobString(data string, options *Options) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you use a variadic function parameter, you can save the , nil
where you don't need otpions:
func (j *JobStringProcessor) ProcessJobString(data string, options *Options) string { | |
func (j *JobStringProcessor) ProcessJobString(data string, opts ...*Options) string { |
At this point, you can loop over options (not tested):
options := Options{Encoding: Plain}
for _, o := range opts {
options.Encoding = o.Encoding
}
If you want to spend a bit more time, you can use the functional options pattern:
https://www.sohamkamani.com/golang/options-pattern/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was following your previous suggestions :) I like the ...
. the functional options: nice! I have never used it. I think ...
is nice here. is it ok now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will you believe it? app/processor/string.go:67:1: cyclomatic complexity 21 of func
(*JobStringProcessor).ProcessJobString is high (> 20) (gocyclo)
there is always something.
6eb3278
to
775bc4f
Compare
bf6a9df
to
1cfe463
Compare
@mender-test-bot start pipeline please sugar |
Hello 😸 I created a pipeline for you here: Pipeline-631733398 Build Configuration Matrix
|
Is this a |
type JsonOptions struct { | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the purpose of this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is on purpose: to keep the interface consistent. in case there is a new round of refactoring (i.e.: a general processor interface) can we keep it?
func (j JobProcessor) ProcessJSON( | ||
data interface{}, | ||
ps *JobStringProcessor, | ||
options ...*JsonOptions, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This variadic argument is unused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is on purpose: to keep the interface consistent. in case there is a new round of refactoring (i.e.: a general processor interface) can we keep it?
something in between feat, refact, fix, I will change to fix, since the ticket is about a bug. makes sense? |
l *log.Logger, | ||
) (*model.TaskResult, error) { | ||
options := &processor.Options{Encoding: processor.URL} | ||
uri := ps.ProcessJobString(httpTask.URI, options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to break certain URLs. For example if the url contains a host:port
address. To be honest, I think the query escape should be done at the templating level and thus done as part of the workflow definition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait how can it be? it only URL-encodes the value:
value = url.QueryEscape(value)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This transforms all of the following characters: '$', '&', '+', ',', '/', ':', ';', '=', '?', '@'
many of them are valid in different parts of the schema. For example
url.QueryEscape("https://hosted.mender.io") // -> https%3A%2F%2Fhosted.mender.io
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but the URL is never a workflow input variable, we take it only from env and with default values ("uri": "http://${env.USERADM_ADDR|mender-useradm:8080}/api/internal/v1/useradm/tokens?tenant_id=${workflow.input.tenant_id}"
). this assumes that we should encode the workflow input variables present in the URL (${workflow.input.tenant_id}
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, how about now? we support cool encoding=plain|url;identifier
syntax that overrides everything you may pass. if you do not pass it, by default we URL encode all input variables that exist in the URL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tranchitella I hope you are happy that you used Alf as a mole to have this feature :>
Changelog: Title Ticket: MEN-5819 Signed-off-by: Peter Grzybowski <peter@northern.tech>
1cfe463
to
07f7953
Compare
@mender-test-bot start pipeline please sugar pretty please |
Hello 😸 I created a pipeline for you here: Pipeline-633418905 Build Configuration Matrix
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Take the following suggestions with a grain of 🧂.
As a minimum, I would refine the regular expression for the flags/options. The rest are more suggestions for refactoring.
workflowEnvVariable = "env." | ||
workflowInputVariable = "workflow.input." | ||
regexVariable = `\$\{([^\}\|]+)(?:\|([^\}]+))?}` | ||
regexVariableFlags = `(?:([a-zA-Z]+)+=([a-zA-Z0-9]+);)([^\}\|]+)(?:\|([^\}]+))?` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It took me some time to digest the above regex if I'm being honest. I found one extra +
quantifier and a missing semicolon in the "ignore" character set for the variable name. However, for the sake of readability and allowing multiple options I would name the capturing groups (and also allow matching multiple options ;).
regexVariableFlags = `(?:([a-zA-Z]+)+=([a-zA-Z0-9]+);)([^\}\|]+)(?:\|([^\}]+))?` | |
regexVariableFlags = `(?P<options>(?:(?:[a-zA-Z]+)=(?:[a-zA-Z0-9]+);)+)(?P<name>[^;\}\|]+)(?:\|(?P<default>[^\}]+))?` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, now we almost have an expression for the entire variable, we only need the brackets and change the options quantifier from +
to *
.
const ( | ||
workflowEnvVariable = "env." | ||
workflowInputVariable = "workflow.input." | ||
regexVariable = `\$\{([^\}\|]+)(?:\|([^\}]+))?}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following from my regexVariableFlags
suggestion.
regexVariable = `\$\{([^\}\|]+)(?:\|([^\}]+))?}` | |
regexVariable = `\$\{(?P<options>(?:(?:[a-zA-Z]+)=(?:[a-zA-Z0-9]+);)*)(?P<name>[^;\}\|]+)(?:\|(?P<default>[^\}]+))?}` |
This require manually splitting the option key/value pairs.
expectedMatches = 4 | ||
flagMatchIndex = 1 | ||
flagValueMatchIndex = 2 | ||
identifierMatchIndex = 3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it took me some time before I understood what these were...
// now it is possible to override the encoding with flags: ${encoding=plain;identifier} | ||
// if encoding is supplied via flags, it takes precedence, we return the match | ||
// without the flags, otherwise fail back to original match and encoding | ||
match, option.Encoding = getEncodingFromData(match, option.Encoding) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we apply the encoding options to all replacements? (See following suggestions)
// if encoding is supplied via flags, it takes precedence, we return the match | ||
// without the flags, otherwise fail back to original match and encoding | ||
match, option.Encoding = getEncodingFromData(match, option.Encoding) | ||
defaultValue := submatch[2] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defaultValue := submatch[2] | |
value := submatch[2] |
if !found { | ||
data = strings.ReplaceAll(data, submatch[0], defaultValue) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if !found { | |
data = strings.ReplaceAll(data, submatch[0], defaultValue) | |
} |
envValue := os.Getenv(envName) | ||
if envValue == "" { | ||
envValue = defaultValue | ||
} | ||
data = strings.ReplaceAll(data, submatch[0], envValue) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
envValue := os.Getenv(envName) | |
if envValue == "" { | |
envValue = defaultValue | |
} | |
data = strings.ReplaceAll(data, submatch[0], envValue) | |
if envValue := os.Getenv(envName); envValue != "" { | |
value = envValue | |
} |
if err == nil { | ||
if varValueString == "" { | ||
varValueString = defaultValue | ||
} | ||
data = strings.ReplaceAll(data, submatch[0], varValueString) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if err == nil { | |
if varValueString == "" { | |
varValueString = defaultValue | |
} | |
data = strings.ReplaceAll(data, submatch[0], varValueString) | |
} | |
if err == nil { | |
if varValueString != "" { | |
value = varValueString | |
} | |
} else { | |
continue SubmatchLoop | |
} |
option := getOption(options...) | ||
|
||
// search for ${...} expressions in the data string | ||
for _, submatch := range matches { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for _, submatch := range matches { | |
SubmatchLoop: | |
for _, submatch := range matches { |
break | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally, this applies the option to all the replacements above.
} | |
} | |
if option.Encoding == URL { | |
value = url.QueryEscape(value) | |
} | |
data = strings.ReplaceAll(data, submatch[0], value) |
this one went with #235 |
Changelog: Title
Ticket: MEN-5819
Signed-off-by: Peter Grzybowski peter@northern.tech