-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Wildcard parameters and arguments #1490
Conversation
workflow/common/util.go
Outdated
for _, stepList := range tmpl.Steps { | ||
for _, step := range stepList { | ||
for _, parameter := range step.Arguments.Parameters { | ||
if parameter.NameRegex != nil { |
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.
Too much nesting here!
workflow/common/util.go
Outdated
|
||
if tmpl.DAG != nil { | ||
for _, task := range tmpl.DAG.Tasks { | ||
for _, parameter := range task.Arguments.Parameters { |
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.
Code needs to be deduplicated with above.
8a72c3b
to
c4134cc
Compare
Maybe 'wildcard' is a better name than 'passthrough' that I've used in the code ... |
Thank you for the PR. I'll review it soon! |
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.
@mark9white Thank you for sending a PR! I actually wanted to use this feature. Although the overall change is good, I made several comments. Would you check them?
workflow/validate/validate.go
Outdated
@@ -147,7 +147,7 @@ func (ctx *wfValidationCtx) validateTemplate(tmpl *wfv1.Template, args wfv1.Argu | |||
} | |||
} | |||
|
|||
_, err = common.ProcessArgs(tmpl, args, ctx.globalParams, localParams, true) | |||
tmpl, err = common.ProcessArgs(tmpl, args, ctx.globalParams, localParams, true) |
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 think this change is not related to this PR.
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.
Fixing.
pkg/apis/workflow/v1alpha1/types.go
Outdated
@@ -293,6 +294,9 @@ type Parameter struct { | |||
// Name is the parameter name | |||
Name string `json:"name"` | |||
|
|||
// PassthroughRegex matches multiple parameter names using a regex and passes through their values | |||
PassthroughRegex *string `json:"passthroughRegex,omitempty"` |
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.
Maybe, this is just a preference issue, but Go lang uses Regexp
instead of Regex
for regular expressions. How about naming it Regexp
?
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.
Hmm... "passthrough" sounds good in arguments, but a little bit weird in inputs because it doesn't "passthrough" but rather just receiving multiple parameters by regexp. However, I don't come up with a good name...
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.
Am renaming it.
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.
Thank you for renaming the variable. Since we renamed this variable, I think renaming related functions and comments in this change helps maintainability.
Hmm... "passthrough" sounds good in arguments, but a little bit weird in inputs because it doesn't "passthrough" but rather just receiving multiple parameters by regexp.
What do you think about 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.
Good point, I've renamed it everywhere else.
I agree is sounds weird in inputs, but I can't think of anything else other than 'wildcard'. I'm open to suggestions ...
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.
Do you think defining 2 fields like NameRegexp
for inputs and PassthroughNameRegexp
for arguments is easier for users although the implementation will be more complicated?
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.
And maybe defining ArgumentPatameter
and InputParameter
separately which contains inline Parameter
?
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.
Two fields would be easier for users, I'll do that.
I'm not convinced that it's worth the extra implementation complexity of ArgumentParameter and InputParameter, but happy to go ahead if you think it is.
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 think having two fields is enough for now. :)
workflow/validate/validate.go
Outdated
names := make(map[string]bool) | ||
|
||
for i, parameter := range parameters { | ||
if parameter.Name == "" && (parameter.PassthroughRegex == nil || *parameter.PassthroughRegex == "") { |
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.
Strictly speaking, we shouldn't allow PassthroughRegex
in global arguments.
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.
Are you looking for a boolean flag passed through to the validateParameterField function indicating whether it's global arguments that are being validated?
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.
That’s one option. Or, how about defining another function for global arguments?
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've done that in the extra commit I just pushed, but still ended up with isValidatingForWorkflow for the intermediate functions which feels a little messy isValidatingForWorkflow
c4134cc
to
fcdf0c7
Compare
I've applied the feedback, and also this commit is based on 'template-crd' as that adds some extra complications. You'll want to review just the last commit: fcdf0c7 |
workflow/validate/validate.go
Outdated
// * Name is provided | ||
// * Name is valid | ||
// * Names are unique | ||
func validateWorkflowInputParameterField(parameters []wfv1.Parameter) error { |
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 think this one is now the same as validateWorkflowFieldNames
if it doesn't check PassthroughRegexp
.
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 think this one is now the same as
validateWorkflowFieldNames
if it doesn't checkPassthroughRegexp
.
Did you check this comment?
I think the WorkflowTemplate CRD PR won't get merged so soon while this PR will get merged much sooner for the complexity difference, so I suggest making your change based on the master. |
634335b
to
4cb9796
Compare
I've modified this PR to be based on master. At this point it's ready to merge on my end, pending any further review notes or build issues. |
#1490 (comment) |
The template overwriting caused other problems, and once I fixed other
issues it seemed no longer necessary.
On 26 July 2019 at 16:18:16, Daisuke Taniwaki (notifications@github.com) wrote:
#1490 (comment)
<#1490 (comment)>
I'm trying your code in my local env and realized this tmpl overwriting is
necessary to validate templates with passthroughRegexp. Sorry for confusing.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1490>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AABO7LSPN62EFLAW3OG7JS3QBMITPANCNFSM4IFAKIZQ>
.
|
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.
Thank you for your hard-working on this PR. I think the implementation looks great except very minor requests. Would you check them?
examples/passthrough-parameters.yaml
Outdated
- name: passthrough-parameters-steps | ||
inputs: | ||
parameters: | ||
- passthroughRegex: ".*" |
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.
Please update the manifest for separated fields of passthroughRegex
and regexp
.
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 tried the following manifest to test your code in my local.
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: passthrough-parameters-
spec:
entrypoint: passthrough-parameters-steps
arguments:
parameters:
- name: message
value: test1
templates:
- name: passthrough-parameters-steps
inputs:
parameters:
- regexp: ".*"
steps:
- - name: display-parameters
template: display-parameters
arguments:
parameters:
- passthroughRegexp: ".*"
- name: extra-parameter
value: extra-value
- name: display-parameters
inputs:
parameters:
- name: message
- name: extra-parameter
script:
image: alpine:latest
command: [sh, -x]
source: |
#!/bin/sh
echo {{inputs.parameters.message}}
echo {{inputs.parameters.extra-parameter}}
workflow/validate/validate.go
Outdated
@@ -32,6 +32,7 @@ type ValidateOpts struct { | |||
} | |||
|
|||
// wfValidationCtx is the context for validating a workflow spec | |||
// templateValidationCtx is the context for validating a workflow spec |
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 think this comment is not from this PR.
workflow/validate/validate.go
Outdated
// * Name is provided | ||
// * Name is valid | ||
// * Names are unique | ||
func validateWorkflowInputParameterField(parameters []wfv1.Parameter) error { |
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 think this one is now the same as
validateWorkflowFieldNames
if it doesn't checkPassthroughRegexp
.
Did you check this comment?
Really? It didn't work with my manifest. Could you check your code with the manifest? Please also don't forget to check the CI failure. |
I've fixed up the issue of it operating with your manifest and the build problem. I also swapped your suggested manifest into the validate unit test. |
workflow/common/util.go
Outdated
@@ -320,10 +393,12 @@ func substituteParams(tmpl *wfv1.Template, globalParams, localParams map[string] | |||
// Now replace the rest of substitutions (the ones that can be made) in the template | |||
replaceMap = make(map[string]string) | |||
for _, inParam := range globalReplacedTmpl.Inputs.Parameters { | |||
if inParam.Value == nil { | |||
return nil, errors.InternalErrorf("inputs.parameters.%s had no value", inParam.Name) | |||
if inParam.PassthroughRegexp == "" { //this can still be set if we're handling a WorkflowTemplate |
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.
Should it be Regexp
?
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.
Good catch. I've gone through all the others and think they're all correct.
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.
LGTM except for the one I commented inline. Please make sure Regexp
and PassthroughRegexp
are used in proper places again just in case.
workflow/common/util.go
Outdated
@@ -257,8 +257,8 @@ func ProcessArgs(tmpl *wfv1.Template, args wfv1.Arguments, globalParams, localPa | |||
for _, argParam := range argParams { | |||
newName := argParam.Name | |||
newValue := argParam.Value | |||
newPassthroughRegexp := argParam.PassthroughRegexp //used for workflow templates where all arguments may not have been resolved already | |||
newParameter := wfv1.Parameter{Name: newName, Value: newValue, PassthroughRegexp: newPassthroughRegexp} | |||
newRegexp := argParam.PassthroughRegexp //used for workflow templates where all arguments may not have been resolved already |
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 fully understand why Regexp
is copied from PassthroughRegexp
here. Could you tell me an example manifests which describe the necessity of this copy?
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.
TestPassthroughParams in operator_test.go fails without it - where we have arguments using a passthroughRegexp being passed into a step with regex inputs.
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.
#1490 (comment)
Please try overwriting tmpl
.
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.
Hmm, changing it causes another error. I'm trying to fix all of them.
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 confirmed unit tests succeeded without copying PassthroughRegexp
to Regexp
. If there's a case we need it, please add a unit test.
workflow/common/util.go
Outdated
} | ||
|
||
for _, inputParam := range inputParams { | ||
newParameter := wfv1.Parameter{Name: inputParam.Name, Value: inputParam.Value, Regexp: inputParam.Regexp} |
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.
We're copying parameters from arguments to inputs, so I think this Regexp: inputParam.Regexp
is not necessary.
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 the top-level arguments aren't available (eg we're validating a WorkflowTemplate) then the regex needs to flow through ...
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 think we don’t have to care WorkflowTemplate so much in this PR. That is the next step.
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, I'll fix this up once #1508 is merged.
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.
#1508 has been merged.
workflow/common/util.go
Outdated
@@ -320,10 +393,12 @@ func substituteParams(tmpl *wfv1.Template, globalParams, localParams map[string] | |||
// Now replace the rest of substitutions (the ones that can be made) in the template | |||
replaceMap = make(map[string]string) | |||
for _, inParam := range globalReplacedTmpl.Inputs.Parameters { | |||
if inParam.Value == nil { | |||
return nil, errors.InternalErrorf("inputs.parameters.%s had no value", inParam.Name) | |||
if inParam.Regexp == "" { //this can still be set if we're handling a WorkflowTemplate |
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 we don't copy Regexp
as https://github.com/argoproj/argo/pull/1490/files#r308056723, inParam here won't have Regexp
.
workflow/common/util.go
Outdated
|
||
// if the input still contains passthrough parameters (eg from FakeArguments.GetParametersByRegexp) | ||
// then we can't resolve any passthrough parameters here | ||
inputsContainsRegexp := inputs.DoesContainRegexpParameters() |
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.
The comment should be updated. However, I think we don't need this check as https://github.com/argoproj/argo/pull/1490/files#r308056868.
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 made more comments after I sent a PR to fix processArgs function overwriting in #1508.
Let me make sure what we're doing in this PR.
- Have
Regexp
to receive matched argument parameters. - Have
PassthroughRegexp
to copy matched input parameters to arguments. - The global arguments can't have
PassthroughRegexp
.
Is it correct?
This is correct, and I've rebased on master and applied those comments. |
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.
@mark9white Thank you for updating the PR. I made several comments to remove unnecessary code as much as possible. I also found we need passthroughRegexp
handling in MergeReferredTemplate
in the template resolution. Please check it too.
workflow/common/util.go
Outdated
@@ -257,8 +257,8 @@ func ProcessArgs(tmpl *wfv1.Template, args wfv1.Arguments, globalParams, localPa | |||
for _, argParam := range argParams { | |||
newName := argParam.Name | |||
newValue := argParam.Value | |||
newPassthroughRegexp := argParam.PassthroughRegexp //used for workflow templates where all arguments may not have been resolved already | |||
newParameter := wfv1.Parameter{Name: newName, Value: newValue, PassthroughRegexp: newPassthroughRegexp} | |||
newRegexp := argParam.PassthroughRegexp //used for workflow templates where all arguments may not have been resolved already |
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 confirmed unit tests succeeded without copying PassthroughRegexp
to Regexp
. If there's a case we need it, please add a unit test.
workflow/common/util.go
Outdated
inParam.Value = &newValue | ||
} | ||
//if one of the args is a regexp (this can be true during validation) then we add a placeholder | ||
if args.HasPassthroughRegexpParameter() { |
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 think setting a placeholder just to pass the validation is not good. It might cause other problems in other places.
I modified your code and confirmed the unit tests passed.
diff --git a/workflow/common/util.go b/workflow/common/util.go
index d0ba361..599d574 100644
--- a/workflow/common/util.go
+++ b/workflow/common/util.go
@@ -258,12 +258,11 @@ func ProcessArgs(tmpl *wfv1.Template, args wfv1.ArgumentsProvider, globalParams,
for _, argParam := range argParams {
newName := argParam.Name
newValue := argParam.Value
- newRegexp := argParam.PassthroughRegexp //used for workflow templates where all arguments may not have been resolved already
- newParameter := wfv1.Parameter{Name: newName, Value: newValue, Regexp: newRegexp}
+ newParameter := wfv1.Parameter{Name: newName, Value: newValue}
newInputParameters = append(newInputParameters, newParameter)
}
- } else {
+ } else if inParam.Name != "" {
if inParam.Default != nil {
// first set to default value
inParam.Value = inParam.Default
@@ -274,13 +273,10 @@ func ProcessArgs(tmpl *wfv1.Template, args wfv1.ArgumentsProvider, globalParams,
newValue := *argParam.Value
inParam.Value = &newValue
}
- //if one of the args is a regexp (this can be true during validation) then we add a placeholder
- if args.HasPassthroughRegexpParameter() {
- newValue := "placeholder"
- inParam.Value = &newValue
- }
- if inParam.Value == nil {
- return nil, errors.Errorf(errors.CodeBadRequest, "inputs.parameters.%s was not supplied", inParam.Name)
+ if !args.HasPassthroughRegexpParameter() {
+ if inParam.Value == nil {
+ return nil, errors.Errorf(errors.CodeBadRequest, "inputs.parameters.%s was not supplied", inParam.Name)
+ }
}
newInputParameters = append(newInputParameters, inParam)
}
@@ -390,7 +386,8 @@ func substituteParams(tmpl *wfv1.Template, globalParams, localParams map[string]
// Now replace the rest of substitutions (the ones that can be made) in the template
replaceMap = make(map[string]string)
for _, inParam := range globalReplacedTmpl.Inputs.Parameters {
- if inParam.Regexp == "" { //this can still be set if we're handling a WorkflowTemplate
+ if inParam.Regexp == "" && inParam.Name != "" { //this can still be set if we're handling a WorkflowTemplate
if inParam.Value == nil {
return nil, errors.InternalErrorf("inputs.parameters.%s had no value", inParam.Name)
}
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.
Yes this works, will commit.
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 think the current test failures are caused by not having a placeholder to pass the validation. I don't see a straightforward way around this, so I'm re-applying it, but am open to ideas ...
template: whalesay-with-passthrough-arguments-inner | ||
inputs: | ||
parameters: | ||
- passthroughRegexp: ".*" |
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.
Should it be regexp
instead of passthroughRegexp
?
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 think we should add validation to avoid Inputs
having PassthroughRegexp
and Arguments
having Regexp
.
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.
Agreed, will commit.
@@ -339,6 +355,22 @@ func TestResolveTemplate(t *testing.T) { | |||
assert.Equal(t, "whalesay-with-arguments", tmpl.Name) | |||
assert.Equal(t, []string{"{{inputs.parameters.message}}-foo"}, tmpl.Container.Args) | |||
|
|||
// Get the template of template reference with passthrough arguments. |
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 think this validation does nothing for passthroughRegexp
handling but just does the same template resolving as the others. I realized we need passthroughRegexp
handling in MergeReferredTemplate
and we can write tests for that function 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.
Agreed, am resolving.
workflow/common/util.go
Outdated
@@ -291,9 +315,54 @@ func ProcessArgs(tmpl *wfv1.Template, args wfv1.ArgumentsProvider, globalParams, | |||
} | |||
newTmpl.Inputs.Artifacts = newInputArtifacts | |||
|
|||
//copy passthrough parameters from Inputs to Arguments | |||
if newTmpl.Steps != nil { |
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.
To implement #1525, I'm thinking we should not modify templates directly. Instead, we can modify Arguments
to pass to executeTemplate
and validateTemplate
in validation. What do you think?
e.g.
diff --git a/pkg/apis/workflow/v1alpha1/workflow_types.go b/pkg/apis/workflow/v1alpha1/workflow_types.go
index c435d5a..bc7a436 100644
--- a/pkg/apis/workflow/v1alpha1/workflow_types.go
+++ b/pkg/apis/workflow/v1alpha1/workflow_types.go
@@ -7,6 +7,7 @@ import (
"regexp"
"strings"
+ "github.com/argoproj/argo/errors"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -1116,6 +1117,29 @@ func (in *Inputs) GetParameterByName(name string) *Parameter {
return nil
}
+// GetPassthroughParameters returns parameter argument list where items matching PassthroughRegexp are added from input parameters
+func (in *Inputs) GetPassthroughParameters(argumentParameters []Parameter) ([]Parameter, error) {
+ updatedArgumentParameters := make([]Parameter, 0)
+
+ for _, parameter := range argumentParameters {
+ if parameter.PassthroughRegexp != "" {
+
+ inputParams, err := in.GetParametersByRegexp(parameter.PassthroughRegexp)
+ if err != nil {
+ return nil, errors.Errorf(errors.CodeBadRequest, "regexp %s could not be processed", parameter.PassthroughRegexp)
+ }
+
+ for _, inputParam := range inputParams {
+ newParameter := Parameter{Name: inputParam.Name, Value: inputParam.Value}
+ updatedArgumentParameters = append(updatedArgumentParameters, newParameter)
+ }
+ } else {
+ updatedArgumentParameters = append(updatedArgumentParameters, parameter)
+ }
+ }
+ return updatedArgumentParameters, nil
+}
+
// HasInputs returns whether or not there are any inputs
func (in *Inputs) HasInputs() bool {
if len(in.Artifacts) > 0 {
diff --git a/workflow/common/util.go b/workflow/common/util.go
index 40dd54f..28785ca 100644
--- a/workflow/common/util.go
+++ b/workflow/common/util.go
@@ -311,54 +311,9 @@ func ProcessArgs(tmpl *wfv1.Template, args wfv1.ArgumentsProvider, globalParams,
}
newTmpl.Inputs.Artifacts = newInputArtifacts
- //copy passthrough parameters from Inputs to Arguments
- if newTmpl.Steps != nil {
- for i, stepList := range newTmpl.Steps {
- for j, step := range stepList {
- updatedParameters, err := handlePassthroughParameters(newTmpl.Inputs, step.Arguments.Parameters)
- if err != nil {
- return nil, err
- }
- newTmpl.Steps[i][j].Arguments.Parameters = updatedParameters
- }
- }
- }
- if newTmpl.DAG != nil {
- for i, task := range newTmpl.DAG.Tasks {
- updatedParameters, err := handlePassthroughParameters(tmpl.Inputs, task.Arguments.Parameters)
- if err != nil {
- return nil, err
- }
- newTmpl.DAG.Tasks[i].Arguments.Parameters = updatedParameters
- }
- }
-
return substituteParams(newTmpl, globalParams, localParams)
}
-// handlePassthroughParameters returns parameter argument list where items matching PassthroughRegexp are added from input parameters
-func handlePassthroughParameters(inputs wfv1.Inputs, argumentParameters []wfv1.Parameter) ([]wfv1.Parameter, error) {
- updatedArgumentParameters := make([]wfv1.Parameter, 0)
-
- for _, parameter := range argumentParameters {
- if parameter.PassthroughRegexp != "" {
-
- inputParams, err := inputs.GetParametersByRegexp(parameter.PassthroughRegexp)
- if err != nil {
- return nil, errors.Errorf(errors.CodeBadRequest, "regexp %s could not be processed", parameter.PassthroughRegexp)
- }
-
- for _, inputParam := range inputParams {
- newParameter := wfv1.Parameter{Name: inputParam.Name, Value: inputParam.Value}
- updatedArgumentParameters = append(updatedArgumentParameters, newParameter)
- }
- } else {
- updatedArgumentParameters = append(updatedArgumentParameters, parameter)
- }
- }
- return updatedArgumentParameters, nil
-}
-
// substituteParams returns a new copy of the template with global, pod, and input parameters substituted
func substituteParams(tmpl *wfv1.Template, globalParams, localParams map[string]string) (*wfv1.Template, error) {
tmplBytes, err := json.Marshal(tmpl)
diff --git a/workflow/controller/dag.go b/workflow/controller/dag.go
index 744238a..b684fc4 100644
--- a/workflow/controller/dag.go
+++ b/workflow/controller/dag.go
@@ -404,8 +404,16 @@ func (woc *wfOperationCtx) executeDAGTask(dagCtx *dagContext, taskName string) {
}
}
+ args := t.Arguments.DeepCopy()
+ updatedParameters, err := dagCtx.tmpl.Inputs.GetPassthroughParameters(args.Parameters)
+ if err != nil {
+ woc.initializeNode(nodeName, wfv1.NodeTypeSkipped, task, dagCtx.boundaryID, wfv1.NodeError, err.Error())
+ return
+ }
+ args.Parameters = updatedParameters
+
// Finally execute the template
- _, _ = woc.executeTemplate(taskNodeName, &t, dagCtx.tmplCtx, t.Arguments, dagCtx.boundaryID)
+ _, _ = woc.executeTemplate(taskNodeName, &t, dagCtx.tmplCtx, args, dagCtx.boundaryID)
}
if taskGroupNode != nil {
diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go
index 2e51d52..177f56d 100644
--- a/workflow/controller/operator.go
+++ b/workflow/controller/operator.go
@@ -205,7 +205,7 @@ func (woc *wfOperationCtx) operate() {
var workflowStatus wfv1.NodePhase
var workflowMessage string
- node, err := woc.executeTemplate(woc.wf.ObjectMeta.Name, &wfv1.Template{Template: woc.wf.Spec.Entrypoint}, woc.tmplCtx, woc.wf.Spec.Arguments, "")
+ node, err := woc.executeTemplate(woc.wf.ObjectMeta.Name, &wfv1.Template{Template: woc.wf.Spec.Entrypoint}, woc.tmplCtx, &woc.wf.Spec.Arguments, "")
if err != nil {
// the error are handled in the callee so just log it.
woc.log.Errorf("%s error in entry template execution: %+v", woc.wf.Name, err)
@@ -233,7 +233,7 @@ func (woc *wfOperationCtx) operate() {
}
woc.log.Infof("Running OnExit handler: %s", woc.wf.Spec.OnExit)
onExitNodeName := woc.wf.ObjectMeta.Name + ".onExit"
- onExitNode, err = woc.executeTemplate(onExitNodeName, &wfv1.Template{Template: woc.wf.Spec.OnExit}, woc.tmplCtx, woc.wf.Spec.Arguments, "")
+ onExitNode, err = woc.executeTemplate(onExitNodeName, &wfv1.Template{Template: woc.wf.Spec.OnExit}, woc.tmplCtx, &woc.wf.Spec.Arguments, "")
if err != nil {
// the error are handled in the callee so just log it.
woc.log.Errorf("%s error in exit template execution: %+v", woc.wf.Name, err)
@@ -1067,7 +1067,7 @@ func (woc *wfOperationCtx) getLastChildNode(node *wfv1.NodeStatus) (*wfv1.NodeSt
// for the created node (if created). Nodes may not be created if parallelism or deadline exceeded.
// nodeName is the name to be used as the name of the node, and boundaryID indicates which template
// boundary this node belongs to.
-func (woc *wfOperationCtx) executeTemplate(nodeName string, orgTmpl wfv1.TemplateHolder, tmplCtx *templateresolution.Context, args wfv1.Arguments, boundaryID string) (*wfv1.NodeStatus, error) {
+func (woc *wfOperationCtx) executeTemplate(nodeName string, orgTmpl wfv1.TemplateHolder, tmplCtx *templateresolution.Context, args *wfv1.Arguments, boundaryID string) (*wfv1.NodeStatus, error) {
woc.log.Debugf("Evaluating node %s: template: %s, boundaryID: %s", nodeName, common.GetTemplateHolderString(orgTmpl), boundaryID)
node := woc.getNodeByName(nodeName)
@@ -1093,7 +1093,7 @@ func (woc *wfOperationCtx) executeTemplate(nodeName string, orgTmpl wfv1.Templat
localParams[common.LocalVarPodName] = woc.wf.NodeID(nodeName)
}
// Do not overwrite tmpl here because the old value is used in the error case.
- newTmpl, err := common.ProcessArgs(tmpl, &args, woc.globalParams, localParams, false)
+ newTmpl, err := common.ProcessArgs(tmpl, args, woc.globalParams, localParams, false)
if err != nil {
return woc.initializeNode(nodeName, wfv1.NodeTypeSkipped, orgTmpl, boundaryID, wfv1.NodeError, err.Error()), err
}
diff --git a/workflow/controller/steps.go b/workflow/controller/steps.go
index 1c16ff2..2de2e1e 100644
--- a/workflow/controller/steps.go
+++ b/workflow/controller/steps.go
@@ -203,7 +203,16 @@ func (woc *wfOperationCtx) executeStepGroup(stepGroup []wfv1.WorkflowStep, sgNod
}
continue
}
- childNode, err := woc.executeTemplate(childNodeName, &step, stepsCtx.tmplCtx, step.Arguments, stepsCtx.boundaryID)
+
+ args := step.Arguments.DeepCopy()
+ updatedParameters, err := stepsCtx.scope.tmpl.Inputs.GetPassthroughParameters(args.Parameters)
+ if err != nil {
+ return woc.markNodeError(sgNodeName, err)
+ }
+ args.Parameters = updatedParameters
+
+ childNode, err := woc.executeTemplate(childNodeName, &step, stepsCtx.tmplCtx, args, stepsCtx.boundaryID)
if err != nil {
switch err {
case ErrDeadlineExceeded:
diff --git a/workflow/templateresolution/context_test.go b/workflow/templateresolution/context_test.go
index a311561..e78d9be 100644
--- a/workflow/templateresolution/context_test.go
+++ b/workflow/templateresolution/context_test.go
@@ -76,14 +76,14 @@ spec:
template: whalesay-with-passthrough-arguments-inner
inputs:
parameters:
- - passthroughRegexp: ".*"
+ - regexp: ".*"
arguments:
parameters:
- passthroughRegexp: ".*"
- name: whalesay-with-passthrough-arguments-inner
inputs:
parameters:
- - passthroughRegexp: ".*"
+ - regexp: ".*"
container:
image: docker/whalesay
command: [cowsay]
diff --git a/workflow/validate/validate.go b/workflow/validate/validate.go
index 8f1da74..ce29e71 100644
--- a/workflow/validate/validate.go
+++ b/workflow/validate/validate.go
@@ -338,9 +338,7 @@ func validateInputs(tmpl *wfv1.Template, extraScope map[string]interface{}) (map
scope[name] = value
}
for _, param := range tmpl.Inputs.Parameters {
- if param.Regexp != "" {
- scope[fmt.Sprintf("inputs.parameters.regexp.%s", param.Regexp)] = true
- } else {
+ if param.Name != "" {
scope[fmt.Sprintf("inputs.parameters.%s", param.Name)] = true
}
}
@@ -394,14 +392,6 @@ func resolveAllVariables(scope map[string]interface{}, tmplStr string) error {
var unresolvedErr error
_, allowAllItemRefs := scope[anyItemMagicValue] // 'item.*' is a magic placeholder value set by addItemsToScope
- //inputs.parameters.regexp. stores regexps for input parameters
- inputRegexps := make([]string, 0)
- for k := range scope {
- if strings.HasPrefix(k, "inputs.parameters.regexp.") {
- inputRegexps = append(inputRegexps, strings.Replace(k, ".regexp", "", -1))
- }
- }
-
fstTmpl := fasttemplate.New(tmplStr, "{{", "}}")
fstTmpl.ExecuteFuncString(func(w io.Writer, tag string) (int, error) {
@@ -417,20 +407,7 @@ func resolveAllVariables(scope map[string]interface{}, tmplStr string) error {
// NOTE: this is far from foolproof.
} else if strings.HasPrefix(tag, common.GlobalVarWorkflowCreationTimestamp) {
} else {
- //check for regexp matches
- foundRegexpMatch := false
- for _, regexpToMatch := range inputRegexps {
- match, err := regexp.MatchString(regexpToMatch, tag)
- if err != nil {
- return 0, err
- }
- if match {
- foundRegexpMatch = true
- }
- }
- if foundRegexpMatch == false {
- unresolvedErr = fmt.Errorf("failed to resolve {{%s}}", tag)
- }
+ unresolvedErr = fmt.Errorf("failed to resolve {{%s}}", tag)
}
}
return 0, nil
@@ -585,7 +562,15 @@ func (ctx *templateValidationCtx) validateSteps(scope map[string]interface{}, tm
if err != nil {
return err
}
- resolvedTmpl, err := ctx.validateTemplateHolder(&step, tmplCtx, &step.Arguments, scope)
+
+ args := step.Arguments.DeepCopy()
+ updatedParameters, err := tmpl.Inputs.GetPassthroughParameters(args.Parameters)
+ if err != nil {
+ return err
+ }
+ args.Parameters = updatedParameters
+
+ resolvedTmpl, err := ctx.validateTemplateHolder(&step, tmplCtx, args, scope)
if err != nil {
return errors.Errorf(errors.CodeBadRequest, "templates.%s.steps[%d].%s %s", tmpl.Name, i, step.Name, err.Error())
}
@@ -1049,8 +1034,16 @@ func (ctx *templateValidationCtx) validateDAG(scope map[string]interface{}, tmpl
if err != nil {
return err
}
+
+ args := task.Arguments.DeepCopy()
+ updatedParameters, err := tmpl.Inputs.GetPassthroughParameters(args.Parameters)
+ if err != nil {
+ return err
+ }
+ args.Parameters = updatedParameters
+
// Validate the template again with actual arguments.
- _, err = ctx.validateTemplateHolder(&task, tmplCtx, &task.Arguments, scope)
+ _, err = ctx.validateTemplateHolder(&task, tmplCtx, args, scope)
if err != nil {
return errors.Errorf(errors.CodeBadRequest, "templates.%s.tasks.%s %s", tmpl.Name, task.Name, err.Error())
}
diff --git a/workflow/validate/validate_test.go b/workflow/validate/validate_test.go
index d7e4010..13722d7 100644
--- a/workflow/validate/validate_test.go
+++ b/workflow/validate/validate_test.go
@@ -294,7 +294,7 @@ func TestPassthrough(t *testing.T) {
err = validate(badArgsRegex)
if assert.NotNil(t, err) {
- assert.Contains(t, err.Error(), "regexp ( could not be processed")
+ assert.Contains(t, err.Error(), "error parsing regexp: missing closing ): `(`")
}
err = validate(badInputRegex)
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.
Maybe, we have to make like above for ProcessArgs
instead of executeTemplate
and validateTemplate
, or make the same in MergeReferredTemplate
.
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.
Good idea, I've just included this, with the same logic in MergeReferredTemplate.
8c4ea40
to
b75b6fa
Compare
b75b6fa
to
10673db
Compare
@dtaniwaki Any next steps here? |
@mark9white Sorry for the late reply. I still need to finish #1552 and it will make huge change around argument passing. |
10673db
to
847d2ac
Compare
@dtaniwaki |
@mark9white Sorry for the late reply. I will review the change soon. |
I have mixed feelings about this change. This will make it harder in future to have evaluated placeholders or just moving to a placeholder system that does not require knowing all placeholder names beforehand ( I also feel that the bigger challenge right now is properly declaring dependencies. When you just get all output references that exist at the given model, you get something random unless you properly express dependencies on the tasks producing those outputs. And dependencies are very limited at this moment. I cannot even depend on "A.succeeded or else B.succeeded" and then pass "A.result if A.succeeded else B.result". |
An easy way to solve the original issue would have been to just pass a single JSON-serialized dictionary to the SparkTemplate. template: spark-template
arguments:
- name: arguments
value: '{"param1": "{{tasks.producer1.outputs.param1}}", "param2": "{{tasks.producer2.outputs.param2}}" }' Maybe this can be done even nicer using Is there any problem with this approach? |
@Ark-kun Something like that could be feasible, but by building strings without building mechanisms to do it natively is subject to escaping problems and could easily start making the yaml look really ugly ... |
AFAIK, WithItems has native support for maps template: spark-template
arguments:
- name: arguments
value: "{{item}}"
withItems:
- param1: "{{tasks.producer1.outputs.param1}}"
param2: "{{tasks.producer2.outputs.param2}}" Having structural templates support can make this even better: #1684 #1293 (comment) |
@Ark-kun Description of WithItems is that it "expands a task into multiple parallel tasks from the items in the list" - here we're just looking to run the task once, passing in a map. I agree structural templates would be another option. |
Yes. The intent of WithItems is to launch multiple tasks. But it can also launch a single task. And it supports maps (syntactically correct). |
@Ark-kun Can you give an example? If I do this then I get an error:
|
847d2ac
to
e291b65
Compare
Oh. Strange. The 3rd and 4th examples here seemd to be implying that it's supported. I need to check. https://github.com/argoproj/argo/tree/master/examples#loops |
@Ark-kun @dtaniwaki Just checking your current thinking on this PR ... |
I've created a PR to fix this (#1906), and am closing this PR. |
This enables input parameters and arguments to be passed through using regexes, from suggestion by @dtaniwaki. It needs some further tidying and tests. Background scenario:
Using the WorkflowTemplate I’m creating a generic template that invokes a job (for example ‘SparkWorkflowTemplate’ that invokes a Spark job. This would take in a set of parameters that get passed onto Spark.
I don’t want to need to hardcode all the possible parameters that any Spark job may need into this ‘SparkWorkflowTemplate’, but at the moment it’s only possible to pass on explicitly declared parameters.