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

feat: Job String Processor #234

Conversation

merlin-northern
Copy link
Contributor

Changelog: Title
Ticket: MEN-5819
Signed-off-by: Peter Grzybowski peter@northern.tech

@mender-test-bot
Copy link

@merlin-northern, Let me know if you want to start the integration pipeline by mentioning me and the command "start pipeline".


my commands and options
You can trigger a pipeline on multiple prs with:
  • mentioning me and start pipeline --pr mender/127 --pr mender-connect/255

You can start a fast pipeline, disabling full integration tests with:

  • mentioning me and start pipeline --fast

You can cherry pick to a given branch or branches with:

  • mentioning me and:
 cherry-pick to:
 * 1.0.x
 * 2.0.x

@merlin-northern merlin-northern force-pushed the men_5819_escape_query_params_processor branch from ebca634 to fb79f7e Compare September 4, 2022 10:45
@mendersoftware mendersoftware deleted a comment from mender-test-bot Sep 4, 2022
@merlin-northern
Copy link
Contributor Author

@mender-test-bot start pipeline please

@mender-test-bot
Copy link

Hello 😸 I created a pipeline for you here: Pipeline-630739572

Build Configuration Matrix

Key Value
AUDITLOGS_REV master
BUILD_CLIENT false
CREATE_ARTIFACT_WORKER_REV master
DEPLOYMENTS_ENTERPRISE_REV master
DEPLOYMENTS_REV master
DEVICEAUTH_ENTERPRISE_REV master
DEVICEAUTH_REV master
DEVICECONFIG_REV master
DEVICECONNECT_REV master
DEVICEMONITOR_REV master
GUI_REV master
INTEGRATION_REV master
INVENTORY_ENTERPRISE_REV master
INVENTORY_REV master
IOT_MANAGER_REV master
MENDER_ARTIFACT_REV master
MENDER_BINARY_DELTA_REV master
MENDER_CLI_REV master
MENDER_CONFIGURE_MODULE_REV master
MENDER_CONNECT_REV master
MENDER_CONVERT_REV master
MENDER_GATEWAY_REV master
MENDER_REV master
MONITOR_CLIENT_REV master
MTLS_AMBASSADOR_REV master
REPORTING_REV master
RUN_BACKEND_INTEGRATION_TESTS true
RUN_INTEGRATION_TESTS true
TENANTADM_REV master
USERADM_ENTERPRISE_REV master
USERADM_REV master
WORKFLOWS_ENTERPRISE_REV master
WORKFLOWS_REV pull/234/head

@merlin-northern merlin-northern force-pushed the men_5819_escape_query_params_processor branch 3 times, most recently from 571aeb4 to 6eb3278 Compare September 4, 2022 12:03
@merlin-northern
Copy link
Contributor Author

@mender-test-bot start pipeline please

@mender-test-bot
Copy link

Hello 😸 I created a pipeline for you here: Pipeline-630863595

Build Configuration Matrix

Key Value
AUDITLOGS_REV master
BUILD_CLIENT false
CREATE_ARTIFACT_WORKER_REV master
DEPLOYMENTS_ENTERPRISE_REV master
DEPLOYMENTS_REV master
DEVICEAUTH_ENTERPRISE_REV master
DEVICEAUTH_REV master
DEVICECONFIG_REV master
DEVICECONNECT_REV master
DEVICEMONITOR_REV master
GUI_REV master
INTEGRATION_REV master
INVENTORY_ENTERPRISE_REV master
INVENTORY_REV master
IOT_MANAGER_REV master
MENDER_ARTIFACT_REV master
MENDER_BINARY_DELTA_REV master
MENDER_CLI_REV master
MENDER_CONFIGURE_MODULE_REV master
MENDER_CONNECT_REV master
MENDER_CONVERT_REV master
MENDER_GATEWAY_REV master
MENDER_REV master
MONITOR_CLIENT_REV master
MTLS_AMBASSADOR_REV master
REPORTING_REV master
RUN_BACKEND_INTEGRATION_TESTS true
RUN_INTEGRATION_TESTS true
TENANTADM_REV master
USERADM_ENTERPRISE_REV master
USERADM_REV master
WORKFLOWS_ENTERPRISE_REV master
WORKFLOWS_REV pull/234/head

@merlin-northern
Copy link
Contributor Author

@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)

Comment on lines -145 to -146
l.Infof("processTask: calling http task: %s %s", httpTask.Method,
processJobString(httpTask.URI, workflow, job))
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. I moved it below
  2. it was doubling the very expensive operation of processing the job string, just for printing.

Comment on lines +50 to +55
l.Infof("processHTTPTask: starting with: method=%s uri=%s (options=%+v)",
httpTask.Method,
uri,
options,
)
Copy link
Contributor

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.

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 can remove it, it was moved from one level up (see the previous comment). do you want me to remove it?

value = processJobString(value, workflow, job)
value = maybeExecuteGoTemplate(value, job.InputParameters.Map())
key = ps.ProcessJobString(key, nil)
key = maybeExecuteGoTemplate(key, ps.GetJobInputParameters().Map())
Copy link
Contributor

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.

Copy link
Contributor Author

@merlin-northern merlin-northern Sep 5, 2022

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?

Copy link
Contributor Author

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 :>)

Copy link
Contributor

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?

Copy link
Contributor Author

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.

Comment on lines -145 to -146
l.Infof("processTask: calling http task: %s %s", httpTask.Method,
processJobString(httpTask.URI, workflow, job))
Copy link
Contributor

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?

Copy link
Contributor Author

@merlin-northern merlin-northern Sep 5, 2022

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.

}
}

func (j *JobStringProcessor) ProcessJobString(data string, options *Options) string {
Copy link
Contributor

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:

Suggested change
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/

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 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?

Copy link
Contributor Author

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.

@merlin-northern merlin-northern force-pushed the men_5819_escape_query_params_processor branch from 6eb3278 to 775bc4f Compare September 5, 2022 06:33
@merlin-northern merlin-northern force-pushed the men_5819_escape_query_params_processor branch 4 times, most recently from bf6a9df to 1cfe463 Compare September 5, 2022 19:48
@merlin-northern
Copy link
Contributor Author

@mender-test-bot start pipeline please sugar

@mender-test-bot
Copy link

Hello 😸 I created a pipeline for you here: Pipeline-631733398

Build Configuration Matrix

Key Value
AUDITLOGS_REV master
BUILD_CLIENT false
CREATE_ARTIFACT_WORKER_REV master
DEPLOYMENTS_ENTERPRISE_REV master
DEPLOYMENTS_REV master
DEVICEAUTH_ENTERPRISE_REV master
DEVICEAUTH_REV master
DEVICECONFIG_REV master
DEVICECONNECT_REV master
DEVICEMONITOR_REV master
GUI_REV master
INTEGRATION_REV master
INVENTORY_ENTERPRISE_REV master
INVENTORY_REV master
IOT_MANAGER_REV master
MENDER_ARTIFACT_REV master
MENDER_BINARY_DELTA_REV master
MENDER_CLI_REV master
MENDER_CONFIGURE_MODULE_REV master
MENDER_CONNECT_REV master
MENDER_CONVERT_REV master
MENDER_GATEWAY_REV master
MENDER_REV master
MONITOR_CLIENT_REV master
MTLS_AMBASSADOR_REV master
REPORTING_REV master
RUN_BACKEND_INTEGRATION_TESTS true
RUN_INTEGRATION_TESTS true
TENANTADM_REV master
USERADM_ENTERPRISE_REV master
USERADM_REV master
WORKFLOWS_ENTERPRISE_REV master
WORKFLOWS_REV pull/234/head

@alfrunes
Copy link
Contributor

alfrunes commented Sep 6, 2022

Is this a feature? I would argue that this is a refactor of existing functionality.

Comment on lines +29 to +30
type JsonOptions struct {
}
Copy link
Contributor

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.

Copy link
Contributor Author

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,
Copy link
Contributor

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.

Copy link
Contributor Author

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?

@merlin-northern
Copy link
Contributor Author

merlin-northern commented Sep 6, 2022

Is this a feature? I would argue that this is a refactor of existing functionality.

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)
Copy link
Contributor

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.

Copy link
Contributor Author

@merlin-northern merlin-northern Sep 6, 2022

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:

app/processor/string.go:97

 value = url.QueryEscape(value)

Copy link
Contributor

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

Copy link
Contributor Author

@merlin-northern merlin-northern Sep 6, 2022

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

Copy link
Contributor Author

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.

Copy link
Contributor Author

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>
@merlin-northern merlin-northern force-pushed the men_5819_escape_query_params_processor branch from 1cfe463 to 07f7953 Compare September 7, 2022 11:18
@merlin-northern
Copy link
Contributor Author

@mender-test-bot start pipeline please sugar pretty please

@mender-test-bot
Copy link

Hello 😸 I created a pipeline for you here: Pipeline-633418905

Build Configuration Matrix

Key Value
AUDITLOGS_REV master
BUILD_CLIENT false
CREATE_ARTIFACT_WORKER_REV master
DEPLOYMENTS_ENTERPRISE_REV master
DEPLOYMENTS_REV master
DEVICEAUTH_ENTERPRISE_REV master
DEVICEAUTH_REV master
DEVICECONFIG_REV master
DEVICECONNECT_REV master
DEVICEMONITOR_REV master
GUI_REV master
INTEGRATION_REV master
INVENTORY_ENTERPRISE_REV master
INVENTORY_REV master
IOT_MANAGER_REV master
MENDER_ARTIFACT_REV master
MENDER_BINARY_DELTA_REV master
MENDER_CLI_REV master
MENDER_CONFIGURE_MODULE_REV master
MENDER_CONNECT_REV master
MENDER_CONVERT_REV master
MENDER_GATEWAY_REV master
MENDER_REV master
MONITOR_CLIENT_REV master
MTLS_AMBASSADOR_REV master
REPORTING_REV master
RUN_BACKEND_INTEGRATION_TESTS true
RUN_INTEGRATION_TESTS true
TENANTADM_REV master
USERADM_ENTERPRISE_REV master
USERADM_REV master
WORKFLOWS_ENTERPRISE_REV master
WORKFLOWS_REV pull/234/head

Copy link
Contributor

@alfrunes alfrunes left a 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]+);)([^\}\|]+)(?:\|([^\}]+))?`
Copy link
Contributor

@alfrunes alfrunes Sep 7, 2022

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 ;).

Suggested change
regexVariableFlags = `(?:([a-zA-Z]+)+=([a-zA-Z0-9]+);)([^\}\|]+)(?:\|([^\}]+))?`
regexVariableFlags = `(?P<options>(?:(?:[a-zA-Z]+)=(?:[a-zA-Z0-9]+);)+)(?P<name>[^;\}\|]+)(?:\|(?P<default>[^\}]+))?`

Copy link
Contributor

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 = `\$\{([^\}\|]+)(?:\|([^\}]+))?}`
Copy link
Contributor

@alfrunes alfrunes Sep 7, 2022

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.

Suggested change
regexVariable = `\$\{([^\}\|]+)(?:\|([^\}]+))?}`
regexVariable = `\$\{(?P<options>(?:(?:[a-zA-Z]+)=(?:[a-zA-Z0-9]+);)*)(?P<name>[^;\}\|]+)(?:\|(?P<default>[^\}]+))?}`

This require manually splitting the option key/value pairs.

Comment on lines +86 to +89
expectedMatches = 4
flagMatchIndex = 1
flagValueMatchIndex = 2
identifierMatchIndex = 3
Copy link
Contributor

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)
Copy link
Contributor

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]
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
defaultValue := submatch[2]
value := submatch[2]

Comment on lines +137 to +139
if !found {
data = strings.ReplaceAll(data, submatch[0], defaultValue)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if !found {
data = strings.ReplaceAll(data, submatch[0], defaultValue)
}

Comment on lines +144 to +148
envValue := os.Getenv(envName)
if envValue == "" {
envValue = defaultValue
}
data = strings.ReplaceAll(data, submatch[0], envValue)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
envValue := os.Getenv(envName)
if envValue == "" {
envValue = defaultValue
}
data = strings.ReplaceAll(data, submatch[0], envValue)
if envValue := os.Getenv(envName); envValue != "" {
value = envValue
}

Comment on lines +169 to +174
if err == nil {
if varValueString == "" {
varValueString = defaultValue
}
data = strings.ReplaceAll(data, submatch[0], varValueString)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
for _, submatch := range matches {
SubmatchLoop:
for _, submatch := range matches {

break
}
}
}
Copy link
Contributor

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.

Suggested change
}
}
if option.Encoding == URL {
value = url.QueryEscape(value)
}
data = strings.ReplaceAll(data, submatch[0], value)

@merlin-northern
Copy link
Contributor Author

merlin-northern commented Sep 7, 2022

@alfrunes I moved to this PR where I merged your commit: #235 I fixed the units, and added tests for a new processOptionString, which I had to rewrite as it was not working.

@merlin-northern
Copy link
Contributor Author

this one went with #235

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.

None yet

4 participants