Repository: argoproj/argo (go)

Issue:
Integers are no longer supported in parameters
## Summary 

What happened/what you expected to happen?

When we have a workflow template with a parameter that can be an integer we get the following error `cannot unmarshall spec: cannot convert int64 to v1alpha1.Int64OrString`.

Integers were supported in 2.10 and it no longer works with 2.11.6 (only 2.11 version I've tested)

## Diagnostics

What Kubernetes provider are you using? 
1.19

What version of Argo Workflows are you running? 
2.11.6

The workflow I ran
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: hello-world-parameters-
spec:
  entrypoint: whalesay
  arguments:
    parameters:
    - name: message
      value: Your age is
    - name: age
      value: 30
    - name: image
      value: docker/whalesay

  templates:
  - name: whalesay
    inputs:
      parameters:
      - name: message
      - name: age
      - name: image       
    container:
      image: "{{inputs.parameters.image}}"
      command: [cowsay]
      args: ["{{inputs.parameters.message}} {{inputs.parameters.age}}"]
```

The workflow object created
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  creationTimestamp: "2020-10-23T15:24:47Z"
  generateName: hello-world-parameters-
  generation: 2
  labels:
    workflows.argoproj.io/completed: "true"
    workflows.argoproj.io/phase: Failed
  managedFields:
  - apiVersion: argoproj.io/v1alpha1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:generateName: {}
      f:spec:
        .: {}
        f:arguments: {}
        f:entrypoint: {}
        f:templates: {}
      f:status: {}
    manager: argo
    operation: Update
    time: "2020-10-23T15:24:47Z"
  - apiVersion: argoproj.io/v1alpha1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:labels:
          .: {}
          f:workflows.argoproj.io/completed: {}
          f:workflows.argoproj.io/phase: {}
      f:spec:
        f:arguments:
          f:parameters: {}
      f:status:
        f:conditions: {}
        f:finishedAt: {}
        f:message: {}
        f:phase: {}
        f:startedAt: {}
    manager: workflow-controller
    operation: Update
    time: "2020-10-23T15:24:47Z"
  name: hello-world-parameters-sjqh9
  namespace: argo
  resourceVersion: "735"
  selfLink: /apis/argoproj.io/v1alpha1/namespaces/argo/workflows/hello-world-parameters-sjqh9
  uid: 252f88a7-f24d-4270-9a96-b9e6e71ecc32
spec:
  arguments:
    parameters:
    - name: message
      value: Your age is
    - name: age
      value: ""
    - name: ""
  entrypoint: whalesay
  templates:
  - arguments: {}
    container:
      args:
      - '{{inputs.parameters.message}} {{inputs.parameters.age}}'
      command:
      - cowsay
      image: '{{inputs.parameters.image}}'
      name: ""
      resources: {}
    inputs:
      parameters:
      - name: message
      - name: age
      - name: image
    metadata: {}
    name: whalesay
    outputs: {}
status:
  conditions:
  - status: "True"
    type: Completed
  finishedAt: "2020-10-23T15:24:47Z"
  message: 'cannot unmarshall spec: cannot convert int64 to v1alpha1.Int64OrString'
  phase: Failed
  startedAt: "2020-10-23T15:24:47Z"
```
Paste the logs from the workflow controller:
```
time="2020-10-23T15:24:47Z" level=warning msg="Failed to unmarshal key to workflow object" error="cannot convert int64 to v1alpha1.Int64OrString" key=argo/hello-world-parameters-sjqh9
time="2020-10-23T15:24:47Z" level=info msg="Updated phase  -> Failed" namespace=argo workflow=hello-world-parameters-sjqh9
time="2020-10-23T15:24:47Z" level=info msg="Updated message  -> cannot unmarshall spec: cannot convert int64 to v1alpha1.Int64OrString" namespace=argo workflow=hello-world-parameters-sjqh9
time="2020-10-23T15:24:47Z" level=info msg="Marking workflow completed" namespace=argo workflow=hello-world-parameters-sjqh9
time="2020-10-23T15:24:47Z" level=warning msg="Invalid workflow object" key="&{map[apiVersion:argoproj.io/v1alpha1 kind:Workflow metadata:map[creationTimestamp:2020-10-23T15:24:47Z generateName:hello-world-parameters- generation:1 managedFields:[map[apiVersion:argoproj.io/v1alpha1 fieldsType:FieldsV1 fieldsV1:map[f:metadata:map[f:generateName:map[]] f:spec:map[.:map[] f:arguments:map[.:map[] f:parameters:map[]] f:entrypoint:map[] f:templates:map[]] f:status:map[.:map[] f:finishedAt:map[] f:startedAt:map[]]] manager:argo operation:Update time:2020-10-23T15:24:47Z]] name:hello-world-parameters-sjqh9 namespace:argo resourceVersion:734 selfLink:/apis/argoproj.io/v1alpha1/namespaces/argo/workflows/hello-world-parameters-sjqh9 uid:252f88a7-f24d-4270-9a96-b9e6e71ecc32] spec:map[arguments:map[parameters:[map[name:message value:Your age is] map[name:age value:30] map[name:image value:docker/whalesay]]] entrypoint:whalesay templates:[map[arguments:map[] container:map[args:[{{inputs.parameters.message}} {{inputs.parameters.age}}] command:[cowsay] image:{{inputs.parameters.image}} name: resources:map[]] inputs:map[parameters:[map[name:message] map[name:age] map[name:image]]] metadata:map[] name:whalesay outputs:map[]]]] status:map[finishedAt:<nil> startedAt:<nil>]]}"
time="2020-10-23T15:24:47Z" level=info msg="Workflow update successful" namespace=argo phase=Failed resourceVersion=735 workflow=hello-world-parameters-sjqh9
```

I get the same issue if the parameter **age** has a default integer value (say 30) and I don't pass it to the workflow template
---
<!-- Issue Author: Don't delete this message to encourage other users to support your issue! -->
**Message from the maintainers**:

Impacted by this bug? Give it a 👍. We prioritise the issues with the most 👍.


Pull request description:
fix(controller): Tolerate int64 parameters. Fixes #4359
Signed-off-by: Alex Collins <alex_collins@intuit.com>
Fixes #4359

Checklist:

* [x] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this is a chore.
* [x] The title of the PR is (a) [conventional](https://www.conventionalcommits.org/en/v1.0.0/), (b) states what changed, and (c) suffixes the related issues number. E.g. `"fix(controller): Updates such and such. Fixes #1234"`.  
* [x] My organization is added to [USERS.md](https://github.com/argoproj/argo/blob/master/USERS.md).
* [x] I've signed the CLA.
* [x] I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
* [x] My builds are green. Try syncing with master if they are not. 


Patch:
diff --git a/workflow/controller/estimation/estimator_factory.go b/workflow/controller/estimation/estimator_factory.go
index 319bc6a11..899640138 100644
--- a/workflow/controller/estimation/estimator_factory.go
+++ b/workflow/controller/estimation/estimator_factory.go
@@ -6,7 +6,6 @@ import (
 
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/labels"
-	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/tools/cache"
 
 	"github.com/argoproj/argo/persist/sqldb"
@@ -14,6 +13,7 @@ import (
 	"github.com/argoproj/argo/workflow/common"
 	"github.com/argoproj/argo/workflow/controller/indexes"
 	"github.com/argoproj/argo/workflow/hydrator"
+	"github.com/argoproj/argo/workflow/util"
 )
 
 type EstimatorFactory interface {
@@ -55,8 +55,7 @@ func (f *estimatorFactory) NewEstimator(wf *wfv1.Workflow) (Estimator, error) {
 				if un.GetLabels()[common.LabelKeyPhase] != string(wfv1.NodeSucceeded) {
 					continue
 				}
-				candidateWf := &wfv1.Workflow{}
-				err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, candidateWf)
+				candidateWf, err := util.FromUnstructured(un)
 				if err != nil {
 					return defaultEstimator, fmt.Errorf("failed convert unstructured to workflow: %w", err)
 				}
diff --git a/workflow/controller/informer/cluster_workflow_template_convert.go b/workflow/controller/informer/cluster_workflow_template_convert.go
index c74bfd54a..1fac41a08 100644
--- a/workflow/controller/informer/cluster_workflow_template_convert.go
+++ b/workflow/controller/informer/cluster_workflow_template_convert.go
@@ -8,6 +8,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 
 	wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
+	"github.com/argoproj/argo/workflow/util"
 )
 
 // this function always tries to return a value, even if it is badly formed
@@ -17,7 +18,7 @@ func objectToClusterWorkflowTemplate(object runtime.Object) (*wfv1.ClusterWorkfl
 	if !ok {
 		return v, fmt.Errorf("malformed cluster workflow template: expected \"*unstructured.Unstructured\", got \"%s\"", reflect.TypeOf(object).String())
 	}
-	err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, v)
+	err := util.FromUnstructuredObj(un, v)
 	if err != nil {
 		return v, fmt.Errorf("malformed cluster workflow template \"%s\": %w", un.GetName(), err)
 	}
diff --git a/workflow/controller/informer/workflow_template_convert.go b/workflow/controller/informer/workflow_template_convert.go
index 89410bd79..a68c69320 100644
--- a/workflow/controller/informer/workflow_template_convert.go
+++ b/workflow/controller/informer/workflow_template_convert.go
@@ -8,6 +8,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 
 	wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
+	"github.com/argoproj/argo/workflow/util"
 )
 
 func objectToWorkflowTemplate(object runtime.Object) (*wfv1.WorkflowTemplate, error) {
@@ -16,7 +17,7 @@ func objectToWorkflowTemplate(object runtime.Object) (*wfv1.WorkflowTemplate, er
 	if !ok {
 		return v, fmt.Errorf("malformed workflow template: expected \"*unstructured.Unstructured\", got \"%s\"", reflect.TypeOf(object).String())
 	}
-	err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, v)
+	err := util.FromUnstructuredObj(un, v)
 	if err != nil {
 		return v, fmt.Errorf("malformed workflow template \"%s/%s\": %w", un.GetNamespace(), un.GetName(), err)
 	}
diff --git a/workflow/cron/controller.go b/workflow/cron/controller.go
index d41016cd1..15fee2737 100644
--- a/workflow/cron/controller.go
+++ b/workflow/cron/controller.go
@@ -12,7 +12,6 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/fields"
 	"k8s.io/apimachinery/pkg/labels"
-	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/selection"
 	"k8s.io/apimachinery/pkg/util/wait"
@@ -140,7 +139,7 @@ func (cc *Controller) processNextCronItem() bool {
 		return true
 	}
 	cronWf := &v1alpha1.CronWorkflow{}
-	err = runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, cronWf)
+	err = util.FromUnstructuredObj(un, cronWf)
 	if err != nil {
 		cc.eventRecorderManager.Get(un.GetNamespace()).Event(un, apiv1.EventTypeWarning, "Malformed", err.Error())
 		logCtx.WithError(err).Error("malformed cron workflow: could not convert from unstructured")
diff --git a/workflow/util/util.go b/workflow/util/util.go
index 95a1fd74c..f120b49d2 100644
--- a/workflow/util/util.go
+++ b/workflow/util/util.go
@@ -119,10 +119,25 @@ func NewWorkflowLister(informer cache.SharedIndexInformer) WorkflowLister {
 // FromUnstructured converts an unstructured object to a workflow
 func FromUnstructured(un *unstructured.Unstructured) (*wfv1.Workflow, error) {
 	var wf wfv1.Workflow
-	err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &wf)
+	err := FromUnstructuredObj(un, &wf)
 	return &wf, err
 }
 
+func FromUnstructuredObj(un *unstructured.Unstructured, v interface{}) error {
+	err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, v)
+	if err != nil {
+		if err.Error() == "cannot convert int64 to v1alpha1.Int64OrString" {
+			data, err := json.Marshal(un)
+			if err != nil {
+				return err
+			}
+			return json.Unmarshal(data, v)
+		}
+		return err
+	}
+	return nil
+}
+
 // ToUnstructured converts an workflow to an Unstructured object
 func ToUnstructured(wf *wfv1.Workflow) (*unstructured.Unstructured, error) {
 	obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(wf)


Test patch:
diff --git a/workflow/util/util_test.go b/workflow/util/util_test.go
index d472c84c4..671d58eae 100644
--- a/workflow/util/util_test.go
+++ b/workflow/util/util_test.go
@@ -10,6 +10,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	kubefake "k8s.io/client-go/kubernetes/fake"
 	"sigs.k8s.io/yaml"
 
@@ -749,6 +750,30 @@ func TestRetryWorkflow(t *testing.T) {
 	}
 }
 
+func TestFromUnstructuredObj(t *testing.T) {
+	un := &unstructured.Unstructured{}
+	err := yaml.Unmarshal([]byte(`apiVersion: argoproj.io/v1alpha1
+kind: CronWorkflow
+metadata:
+  name: example-integers
+spec:
+  schedule: "* * * * *"
+  workflowSpec:
+    entrypoint: whalesay
+    templates:
+      - name: whalesay
+        inputs:
+          parameters:
+            - name: age
+              value: 20
+        container:
+          image: my-image`), un)
+	assert.NoError(t, err)
+	x := &wfv1.CronWorkflow{}
+	err = FromUnstructuredObj(un, x)
+	assert.NoError(t, err)
+}
+
 func TestToUnstructured(t *testing.T) {
 	un, err := ToUnstructured(&wfv1.Workflow{})
 	if assert.NoError(t, err) {