Repository: argoproj/argo
Pull request #3272 (2020-06-22 21:36:35)

fix: Reapply Update if CronWorkflow resource changed


Patch:
diff --git a/.gitignore b/.gitignore
index 1dc33ec82..fde72085d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ hack/**/debug
 /argo
 /argoexec
 debug.test
+git-ask-pass.sh
 *.iml
 /coverage.out
 .envrc
diff --git a/Makefile b/Makefile
index 2019e9d8b..4c711f5c6 100644
--- a/Makefile
+++ b/Makefile
@@ -20,6 +20,8 @@ DOCKERFILE            := Dockerfile
 
 # docker image publishing options
 IMAGE_NAMESPACE       ?= argoproj
+# The name of the namespace where Kubernetes resources/RBAC will be installed
+KUBE_NAMESPACE        ?= argo
 
 # The rules for what version are, in order of precedence
 # 1. If anything passed at the command line (e.g. make release VERSION=...)
@@ -335,8 +337,9 @@ install: dist/$(PROFILE).yaml
 ifeq ($(K3D),true)
 	k3d start
 endif
-	kubectl apply -f test/e2e/manifests/argo-ns.yaml
-	kubectl -n argo apply -l app.kubernetes.io/part-of=argo --prune --force -f dist/$(PROFILE).yaml
+	cat test/e2e/manifests/argo-ns.yaml | sed 's/argo/$(KUBE_NAMESPACE)/' > dist/argo-ns.yaml
+	kubectl apply -f dist/argo-ns.yaml
+	kubectl -n $(KUBE_NAMESPACE) apply -l app.kubernetes.io/part-of=argo --prune --force -f dist/$(PROFILE).yaml
 
 .PHONY: pull-build-images
 pull-build-images:
@@ -368,8 +371,8 @@ $(GOPATH)/bin/goreman:
 
 .PHONY: start
 start: status stop install controller cli executor-image $(GOPATH)/bin/goreman
-	kubectl config set-context --current --namespace=argo
-	kubectl -n argo wait --for=condition=Ready pod --all -l app --timeout 2m
+	kubectl config set-context --current --namespace=$(KUBE_NAMESPACE)
+	kubectl -n $(KUBE_NAMESPACE) wait --for=condition=Ready pod --all -l app --timeout 2m
 	./hack/port-forward.sh
 	# Check dex, minio, postgres and mysql are in hosts file
 ifeq ($(AUTH_MODE),sso)
@@ -405,7 +408,7 @@ env:
 .PHONY: logs
 logs:
 	# Tail logs
-	kubectl -n argo logs -f -l app --max-log-requests 10 --tail 100
+	kubectl -n $(KUBE_NAMESPACE) logs -f -l app --max-log-requests 10 --tail 100
 
 .PHONY: postgres-cli
 postgres-cli:
@@ -511,13 +514,13 @@ pre-commit: test lint codegen manifests start smoke test-api test-cli
 ifneq ($(findstring release,$(GIT_BRANCH)),)
 
 .PHONY: prepare-release
-prepare-release: clean codegen manifests
+prepare-release: check-version-warning clean codegen manifests
 	# Commit if any changes
 	git diff --quiet || git commit -am "Update manifests to $(VERSION)"
 	git tag $(VERSION)
 
 .PHONY: publish-release
-publish-release: build
+publish-release: check-version-warning build
 	# Push images to Docker Hub
 	docker push $(IMAGE_NAMESPACE)/argocli:$(VERSION)
 	docker push $(IMAGE_NAMESPACE)/argoexec:$(VERSION)
@@ -525,3 +528,7 @@ publish-release: build
 	git push
 	git push $(GIT_REMOTE) $(VERSION)
 endif
+
+.PHONY: check-version-warning
+check-version-warning:
+	@if [[ "$(VERSION)" =~ ^[0-9]+\.[0-9]+\.[0-9]+.*$  ]]; then echo -n "It looks like you're trying to use a SemVer version, but have not prepended it with a "v" (such as "v$(VERSION)"). The "v" is required for our releases. Do you wish to continue anyway? [y/N]" && read ans && [ $${ans:-N} = y ]; fi
diff --git a/README.md b/README.md
index 1905ce675..066e35ed9 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,5 @@
 [![slack](https://img.shields.io/badge/slack-argoproj-brightgreen.svg?logo=slack)](https://argoproj.github.io/community/join-slack)
-[![CircleCI](https://circleci.com/gh/argoproj/argo.svg?style=svg)](https://circleci.com/gh/argoproj/argo)
-[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=argoproj_argo&metric=alert_status)](https://sonarcloud.io/dashboard?id=argoproj_argo)
+![CI](https://github.com/argoproj/argo/workflows/CI/badge.svg)
 
 ![Argo Image](docs/assets/argo.png)
 
diff --git a/cmd/argo/commands/delete.go b/cmd/argo/commands/delete.go
index 103b2f319..43ad7e8be 100644
--- a/cmd/argo/commands/delete.go
+++ b/cmd/argo/commands/delete.go
@@ -30,6 +30,7 @@ func NewDeleteCommand() *cobra.Command {
   argo delete my-wf
 
 # Delete the latest workflow:
+
   argo delete @latest
 `,
 		Run: func(cmd *cobra.Command, args []string) {
diff --git a/cmd/argo/commands/list.go b/cmd/argo/commands/list.go
index 2eb5ac94a..90f28dddc 100644
--- a/cmd/argo/commands/list.go
+++ b/cmd/argo/commands/list.go
@@ -79,7 +79,8 @@ func listWorkflows(ctx context.Context, serviceClient workflowpkg.WorkflowServic
 	listOpts := &metav1.ListOptions{
 		Limit: flags.chunkSize,
 	}
-	labelSelector := labels.NewSelector()
+	labelSelector, err := labels.Parse(flags.labels)
+	errors.CheckError(err)
 	if len(flags.status) != 0 {
 		req, _ := labels.NewRequirement(common.LabelKeyPhase, selection.In, flags.status)
 		if req != nil {
@@ -94,10 +95,7 @@ func listWorkflows(ctx context.Context, serviceClient workflowpkg.WorkflowServic
 		req, _ := labels.NewRequirement(common.LabelKeyCompleted, selection.NotEquals, []string{"true"})
 		labelSelector = labelSelector.Add(*req)
 	}
-	if listOpts.LabelSelector = labelSelector.String(); listOpts.LabelSelector != "" {
-		listOpts.LabelSelector = listOpts.LabelSelector + ","
-	}
-	listOpts.LabelSelector = listOpts.LabelSelector + flags.labels
+	listOpts.LabelSelector = labelSelector.String()
 	listOpts.FieldSelector = flags.fields
 	var workflows wfv1.Workflows
 	for {
diff --git a/docs/cluster-workflow-templates.md b/docs/cluster-workflow-templates.md
index fb036cc21..70e196418 100644
--- a/docs/cluster-workflow-templates.md
+++ b/docs/cluster-workflow-templates.md
@@ -55,6 +55,64 @@ spec:
             - name: message
               value: "hello world"
 ```
+> 2.9 and after
+#### Create `Workflow` from `ClusterWorkflowTemplate` Spec
+You can create `Workflow` from `ClusterWorkflowTemplate` spec using `workflowTemplateRef` with `clusterScope: true`. If you pass the arguments to created `Workflow`, it will be merged with ClusterWorkflowTemplate arguments 
+
+Here is an example for `ClusterWorkflowTemplate` with `entrypoint` and `arguments`
+```yaml
+apiVersion: argoproj.io/v1alpha1
+kind: ClusterWorkflowTemplate
+metadata:
+  name: cluster-workflow-template-submittable
+spec:
+  entryPoint: whalesay-template
+  arguments:
+    parameters:
+      - name: message
+        value: hello world
+  templates:
+    - name: whalesay-template
+      inputs:
+        parameters:
+          - name: message
+      container:
+        image: docker/whalesay
+        command: [cowsay]
+        args: ["{{inputs.parameters.message}}"]
+
+```
+Here is an example for creating `ClusterWorkflowTemplate` as Workflow with passing `entrypoint` and `arguments` to `ClusterWorkflowTemplate`
+```yaml
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  generateName: cluster-workflow-template-hello-world-
+spec:
+  entrypoint: whalesay-template
+  arguments:
+    parameters:
+      - name: message
+        value: "from workflow"
+  workflowTemplateRef:
+    name: cluster-workflow-template-submittable
+    clusterScope: true
+```  
+
+Here is an example of a creating `WorkflowTemplate` as Workflow and using `WorkflowTemplates`'s `entrypoint` and `Workflow Arguments`
+```yaml
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  generateName: cluster-workflow-template-hello-world-
+spec:
+  workflowTemplateRef:
+    name: cluster-workflow-template-submittable
+    clusterScope: true
+
+```
+
+
 
 ## Managing `ClusterWorkflowTemplates`
 
@@ -72,6 +130,13 @@ The submit a workflow using one of those templates:
 argo submit https://raw.githubusercontent.com/argoproj/argo/master/examples/cluster-workflow-template/cluster-wftmpl-dag.yaml
 ```
 
+> 2.7 and after
+>
+The submit a `ClusterWorkflowTemplate` as a `Workflow`:
+```shell script
+argo submit --from clusterworkflowtemplate/workflow-template-submittable
+```
+
 ### `kubectl`
 
 Using `kubectl apply -f` and `kubectl get cwft`
diff --git a/docs/fields.md b/docs/fields.md
index 9dc48663f..8e283589b 100644
--- a/docs/fields.md
+++ b/docs/fields.md
@@ -90,6 +90,8 @@ Workflow is the definition of a workflow resource
 
 - [`global-parameters.yaml`](../examples/global-parameters.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`hello-hybrid.yaml`](../examples/hello-hybrid.yaml)
@@ -389,6 +391,8 @@ WorkflowSpec is the specification of a Workflow.
 
 - [`global-parameters.yaml`](../examples/global-parameters.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`hello-hybrid.yaml`](../examples/hello-hybrid.yaml)
@@ -699,6 +703,8 @@ CronWorkflowSpec is the specification of a CronWorkflow
 
 - [`global-parameters.yaml`](../examples/global-parameters.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`hello-hybrid.yaml`](../examples/hello-hybrid.yaml)
@@ -974,6 +980,8 @@ WorkflowTemplateSpec is a spec of WorkflowTemplate.
 
 - [`global-parameters.yaml`](../examples/global-parameters.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`hello-hybrid.yaml`](../examples/hello-hybrid.yaml)
@@ -1225,6 +1233,8 @@ Arguments to a template
 
 - [`global-parameters.yaml`](../examples/global-parameters.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`influxdb-ci.yaml`](../examples/influxdb-ci.yaml)
@@ -1456,6 +1466,8 @@ Template is a reusable and composable unit of execution in a workflow
 
 - [`global-parameters.yaml`](../examples/global-parameters.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`hello-hybrid.yaml`](../examples/hello-hybrid.yaml)
@@ -1752,6 +1764,8 @@ Outputs hold parameters, artifacts, and results from a step
 
 - [`global-outputs.yaml`](../examples/global-outputs.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`influxdb-ci.yaml`](../examples/influxdb-ci.yaml)
@@ -1815,6 +1829,8 @@ Artifact indicates an artifact to place at a specified path
 
 - [`global-outputs.yaml`](../examples/global-outputs.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`influxdb-ci.yaml`](../examples/influxdb-ci.yaml)
@@ -1910,6 +1926,8 @@ Parameter indicate a passed string parameter to a service template with an optio
 
 - [`global-parameters.yaml`](../examples/global-parameters.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`influxdb-ci.yaml`](../examples/influxdb-ci.yaml)
 
 - [`k8s-jobs.yaml`](../examples/k8s-jobs.yaml)
@@ -2180,6 +2198,8 @@ Inputs are the mechanism for passing parameters, artifacts, volumes from one tem
 
 - [`global-outputs.yaml`](../examples/global-outputs.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`influxdb-ci.yaml`](../examples/influxdb-ci.yaml)
@@ -2351,6 +2371,8 @@ Pod metdata
 
 - [`global-parameters.yaml`](../examples/global-parameters.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`hello-hybrid.yaml`](../examples/hello-hybrid.yaml)
@@ -2710,6 +2732,8 @@ WorkflowStep is a reference to a template to execute in a series of step
 
 - [`global-outputs.yaml`](../examples/global-outputs.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`hello-hybrid.yaml`](../examples/hello-hybrid.yaml)
@@ -3072,6 +3096,8 @@ ValueFrom describes a location in which to obtain the value to a parameter
 
 - [`global-outputs.yaml`](../examples/global-outputs.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`k8s-jobs.yaml`](../examples/k8s-jobs.yaml)
 
 - [`k8s-orchestration.yaml`](../examples/k8s-orchestration.yaml)
@@ -3326,6 +3352,8 @@ Sequence expands a workflow step into numeric range
 
 - [`cron-backfill.yaml`](../examples/cron-backfill.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`loops-sequence.yaml`](../examples/loops-sequence.yaml)
 
 - [`work-avoidance.yaml`](../examples/work-avoidance.yaml)
@@ -3465,6 +3493,8 @@ ObjectMeta is metadata that all persisted resources must have, which includes al
 
 - [`global-parameters.yaml`](../examples/global-parameters.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`hello-hybrid.yaml`](../examples/hello-hybrid.yaml)
@@ -3935,6 +3965,8 @@ A single application container that you want to run within a pod.
 
 - [`global-parameters.yaml`](../examples/global-parameters.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`hello-hybrid.yaml`](../examples/hello-hybrid.yaml)
@@ -4547,6 +4579,8 @@ PersistentVolumeClaimSpec describes the common attributes of storage devices and
 
 - [`global-parameters.yaml`](../examples/global-parameters.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`hello-hybrid.yaml`](../examples/hello-hybrid.yaml)
@@ -5108,6 +5142,8 @@ EnvVarSource represents a source for the value of an EnvVar.
 
 - [`global-outputs.yaml`](../examples/global-outputs.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`k8s-jobs.yaml`](../examples/k8s-jobs.yaml)
 
 - [`k8s-orchestration.yaml`](../examples/k8s-orchestration.yaml)
@@ -5515,6 +5551,8 @@ ListMeta describes metadata that synthetic resources must have, including lists
 
 - [`global-parameters.yaml`](../examples/global-parameters.yaml)
 
+- [`handle-large-output-results.yaml`](../examples/handle-large-output-results.yaml)
+
 - [`hdfs-artifact.yaml`](../examples/hdfs-artifact.yaml)
 
 - [`hello-hybrid.yaml`](../examples/hello-hybrid.yaml)
diff --git a/docs/releasing.md b/docs/releasing.md
index 70622b34a..39c56dba0 100644
--- a/docs/releasing.md
+++ b/docs/releasing.md
@@ -6,7 +6,7 @@ Allow 1h to do a release.
 
 Cherry-pick your changes from master onto the release branch.
 
-The release branch should be green [in CircleCI](https://app.circleci.com/github/argoproj/argo/pipelines) before you start.
+The release branch should be green in CI before you start.
 
 ## Release
 
@@ -17,7 +17,7 @@ To generate new manifests and perform basic checks:
 Publish the images and local Git changes (disabling K3D as this is faster and more reliable for releases):
 
     make publish-release K3D=false VERSION=v2.7.2
-
+    
 * [ ] Check the images were pushed successfully.
 
 ```
@@ -36,6 +36,8 @@ docker run argoproj/argocli:v2.7.2 version
 
 * [ ] Check the manifests contain the correct tags: https://raw.githubusercontent.com/argoproj/argo/v2.7.2/manifests/install.yaml
 
+* [ ] Check the manifests apply: `kubectl -n argo apply -f https://raw.githubusercontent.com/argoproj/argo/v2.7.2/manifests/install.yaml`
+
 ### Release Notes
 
 Create [the release](https://github.com/argoproj/argo/releases) in Github. You can get some text for this using [Github Toolkit](https://github.com/alexec/github-toolkit):
diff --git a/docs/workflow-controller-configmap.yaml b/docs/workflow-controller-configmap.yaml
index 5e272bd93..0353426b7 100644
--- a/docs/workflow-controller-configmap.yaml
+++ b/docs/workflow-controller-configmap.yaml
@@ -161,6 +161,13 @@ data:
       archive: false
       # the number of days to keep archived workflows (the default is forever)
       archiveTTL: 180d
+
+      # LabelSelector determines the workflow that matches with the matchlabels or matchrequirements, will be archived.
+      # https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
+      archiveLabelSelector:
+        matchLabels:
+          workflows.argoproj.io/archive-strategy: "always"
+
       # Optional name of the cluster I'm running in. This must be unique for your cluster.
       clusterName: default
       postgresql:
diff --git a/docs/workflow-templates.md b/docs/workflow-templates.md
index 64e1ae414..805696879 100644
--- a/docs/workflow-templates.md
+++ b/docs/workflow-templates.md
@@ -187,6 +187,37 @@ to pass in "live" arguments and reference other templates (those other templates
 
 This behavior has been problematic and dangerous. It causes confusion and has design inconsistencies.
 
+> 2.9 and after
+#### Create `Workflow` from `WorkflowTemplate` Spec
+You can create `Workflow` from `WorkflowTemplate` spec using `workflowTemplateRef`. If you pass the arguments to created `Workflow`, it will be merged with WorkflowTemplate arguments 
+Here is an example for referring `WorkflowTemplate` as Workflow with passing `entrypoint` and `Workflow Arguments` to `WorkflowTemplate`
+```yaml
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  generateName: workflow-template-hello-world-
+spec:
+  entrypoint: whalesay-template
+  arguments:
+    parameters:
+      - name: message
+        value: "from workflow"
+  workflowTemplateRef:
+    name: workflow-template-submittable
+```  
+
+Here is an example of a referring `WorkflowTemplate` as Workflow and using `WorkflowTemplates`'s `entrypoint` and `Workflow Arguments`
+```yaml
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  generateName: workflow-template-hello-world-
+spec:
+  workflowTemplateRef:
+    name: workflow-template-submittable
+
+```
+
 ## Managing `WorkflowTemplates`
 
 ### CLI
@@ -201,6 +232,12 @@ The submit a workflow using one of those templates:
 
 ```
 argo submit https://raw.githubusercontent.com/argoproj/argo/master/examples/workflow-template/hello-world.yaml
+```
+> 2.7 and after
+The submit a `WorkflowTemplate` as a `Workflow`:
+```shell script
+argo submit --from workflowtemplate/workflow-template-submittable
+
 ```
 
 ### `kubectl`
diff --git a/examples/handle-large-output-results.yaml b/examples/handle-large-output-results.yaml
new file mode 100644
index 000000000..ee2bcc2eb
--- /dev/null
+++ b/examples/handle-large-output-results.yaml
@@ -0,0 +1,55 @@
+# Output parameters and 'results' are passed between pods via annotations. Annotations have a size
+# limit. If your output is larger than 256 kB, you should use an artifact instead of a parameter.
+#
+# If you're looping over an output parameter using withParam, and your parameter gets too large, you
+# can use an output artifact combined with a 'count' and withSequence loop to work around the size
+# limit. See (https://stackoverflow.com/q/62537126/684776) for further explanation.
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  generateName: handle-large-output-results-
+spec:
+  entrypoint: handle-large-output-results
+  templates:
+    - name: handle-large-output-results
+      steps:
+        - - name: get-items
+            template: get-items
+        - - name: sequence-param
+            template: echo
+            arguments:
+              parameters:
+                - name: index
+                  value: "{{item}}"
+              artifacts:
+                - name: items
+                  from: "{{steps.get-items.outputs.artifacts.items}}"
+            withSequence:
+              count: "{{steps.get-items.outputs.parameters.count}}"
+    - name: get-items
+      container:
+        image: alpine:latest
+        command: ["/bin/sh", "-c"]
+        # This small array is just for an example. Make sure the count matches the actual size of
+        # your JSON array.
+        args: ["echo '[\"a\", \"b\", \"c\"]' > /tmp/items && echo '3' > /tmp/count"]
+      outputs:
+        artifacts:
+          - name: items
+            path: /tmp/items
+        parameters:
+          - name: count
+            valueFrom:
+              path: /tmp/count
+    - name: echo
+      inputs:
+        parameters:
+          - name: index
+        artifacts:
+          - name: items
+            path: /tmp/items
+      container:
+        image: stedolan/jq:latest
+        command: [sh, -c]
+        # jq is just one way to get the appropriate item.
+        args: ["cat /tmp/items | jq '.[{{inputs.parameters.index}}]'"]
\ No newline at end of file
diff --git a/pkg/apis/workflow/v1alpha1/workflow_types.go b/pkg/apis/workflow/v1alpha1/workflow_types.go
index 903041373..f958ef237 100644
--- a/pkg/apis/workflow/v1alpha1/workflow_types.go
+++ b/pkg/apis/workflow/v1alpha1/workflow_types.go
@@ -1440,6 +1440,13 @@ func (g *GitArtifact) HasLocation() bool {
 	return g != nil && g.Repo != ""
 }
 
+func (g *GitArtifact) GetDepth() int {
+	if g == nil || g.Depth == nil {
+		return 0
+	}
+	return int(*g.Depth)
+}
+
 // ArtifactoryAuth describes the secret selectors required for authenticating to artifactory
 type ArtifactoryAuth struct {
 	// UsernameSecret is the secret selector to the repository username
diff --git a/server/workflow/workflow_server.go b/server/workflow/workflow_server.go
index e8121ee9c..a6b019165 100644
--- a/server/workflow/workflow_server.go
+++ b/server/workflow/workflow_server.go
@@ -391,7 +391,7 @@ func (s *workflowServer) getWorkflow(wfClient versioned.Interface, namespace str
 		if err != nil {
 			return nil, err
 		}
-		log.Infof("Resolved alias %s to workflow %s.\n", latestAlias, latest.Name)
+		log.Debugf("Resolved alias %s to workflow %s.\n", latestAlias, latest.Name)
 		return latest, nil
 	}
 	wf, err := wfClient.ArgoprojV1alpha1().Workflows(namespace).Get(name, options)
diff --git a/ui/src/app/cluster-workflow-templates/components/cluster-workflow-template-details/cluster-workflow-template-details.tsx b/ui/src/app/cluster-workflow-templates/components/cluster-workflow-template-details/cluster-workflow-template-details.tsx
index 47b3f6066..bc98da33e 100644
--- a/ui/src/app/cluster-workflow-templates/components/cluster-workflow-template-details/cluster-workflow-template-details.tsx
+++ b/ui/src/app/cluster-workflow-templates/components/cluster-workflow-template-details/cluster-workflow-template-details.tsx
@@ -14,6 +14,7 @@ import {ClusterWorkflowTemplateSummaryPanel} from '../cluster-workflow-template-
 require('../../../workflows/components/workflow-details/workflow-details.scss');
 
 interface State {
+    namespace?: string;
     template?: models.ClusterWorkflowTemplate;
     error?: Error;
 }
@@ -37,11 +38,13 @@ export class ClusterWorkflowTemplateDetails extends BasePage<RouteComponentProps
     }
 
     public componentDidMount(): void {
+        services.info
+            .getInfo()
+            .then(info => this.setState({namespace: info.managedNamespace || 'default'}))
+            .catch(error => this.setState(error));
         services.clusterWorkflowTemplate
             .get(this.name)
-            .then(template => {
-                this.setState({template});
-            })
+            .then(template => this.setState({template}))
             .catch(error => this.setState({error}));
     }
 
@@ -127,10 +130,10 @@ export class ClusterWorkflowTemplateDetails extends BasePage<RouteComponentProps
         return {
             metadata: {
                 generateName: template.metadata.name + '-',
-                namespace: '<enter the namespace>'
+                namespace: this.state.namespace
             },
             spec: {
-                entrypoint: template.spec.templates[0].name,
+                entrypoint: !!template.spec.templates ? template.spec.templates[0].name : '',
                 workflowTemplateRef: {
                     name: template.metadata.name,
                     clusterScope: true
diff --git a/ui/src/app/workflow-templates/components/workflow-template-details/workflow-template-details.tsx b/ui/src/app/workflow-templates/components/workflow-template-details/workflow-template-details.tsx
index 6353881a5..68028459c 100644
--- a/ui/src/app/workflow-templates/components/workflow-template-details/workflow-template-details.tsx
+++ b/ui/src/app/workflow-templates/components/workflow-template-details/workflow-template-details.tsx
@@ -43,9 +43,7 @@ export class WorkflowTemplateDetails extends BasePage<RouteComponentProps<any>,
     public componentDidMount(): void {
         services.workflowTemplate
             .get(this.name, this.namespace)
-            .then(template => {
-                this.setState({template});
-            })
+            .then(template => this.setState({template}))
             .catch(error => this.setState({error}));
     }
 
@@ -134,7 +132,7 @@ export class WorkflowTemplateDetails extends BasePage<RouteComponentProps<any>,
                 namespace: template.metadata.namespace
             },
             spec: {
-                entrypoint: template.spec.templates[0].name,
+                entrypoint: !!template.spec.templates ? template.spec.templates[0].name : '',
                 workflowTemplateRef: {
                     name: template.metadata.name
                 }
diff --git a/workflow/artifacts/git/git.go b/workflow/artifacts/git/git.go
index 98c31e43c..5c2fc7d7e 100644
--- a/workflow/artifacts/git/git.go
+++ b/workflow/artifacts/git/git.go
@@ -1,22 +1,21 @@
 package git
 
 import (
-	"fmt"
+	"errors"
 	"io/ioutil"
 	"os"
 	"os/exec"
-	"os/user"
+	"path/filepath"
 	"strings"
 
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
-	git "gopkg.in/src-d/go-git.v4"
+	"gopkg.in/src-d/go-git.v4"
 	"gopkg.in/src-d/go-git.v4/config"
 	"gopkg.in/src-d/go-git.v4/plumbing/transport"
 	"gopkg.in/src-d/go-git.v4/plumbing/transport/http"
 	ssh2 "gopkg.in/src-d/go-git.v4/plumbing/transport/ssh"
 
-	"github.com/argoproj/argo/errors"
 	wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
 )
 
@@ -28,130 +27,140 @@ type GitArtifactDriver struct {
 	InsecureIgnoreHostKey bool
 }
 
-// Load download artifacts from an git URL
-func (g *GitArtifactDriver) Load(inputArtifact *wfv1.Artifact, path string) error {
+func (g *GitArtifactDriver) auth() (func(), transport.AuthMethod, []string, error) {
 	if g.SSHPrivateKey != "" {
 		signer, err := ssh.ParsePrivateKey([]byte(g.SSHPrivateKey))
 		if err != nil {
-			return errors.InternalWrapError(err)
+			return nil, nil, nil, err
+		}
+		privateKeyFile, err := ioutil.TempFile("", "id_rsa.")
+		if err != nil {
+			return nil, nil, nil, err
+		}
+		err = ioutil.WriteFile(privateKeyFile.Name(), []byte(g.SSHPrivateKey), 0600)
+		if err != nil {
+			return nil, nil, nil, err
 		}
 		auth := &ssh2.PublicKeys{User: "git", Signer: signer}
 		if g.InsecureIgnoreHostKey {
 			auth.HostKeyCallback = ssh.InsecureIgnoreHostKey()
 		}
-		return gitClone(path, inputArtifact, auth, g.SSHPrivateKey)
+		args := []string{"ssh", "-i", privateKeyFile.Name()}
+		if g.InsecureIgnoreHostKey {
+			args = append(args, "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null")
+		} else {
+			args = append(args, "-o", "StrictHostKeyChecking=yes", "-o")
+		}
+		env := []string{"GIT_SSH_COMMAND=" + strings.Join(args, " ")}
+		if g.InsecureIgnoreHostKey {
+			auth.HostKeyCallback = ssh.InsecureIgnoreHostKey()
+			env = append(env, "GIT_SSL_NO_VERIFY=true")
+		}
+		return func() { _ = os.Remove(privateKeyFile.Name()) },
+			auth,
+			env,
+			nil
 	}
 	if g.Username != "" || g.Password != "" {
-		auth := &http.BasicAuth{Username: g.Username, Password: g.Password}
-		return gitClone(path, inputArtifact, auth, "")
+		filename, err := filepath.Abs("git-ask-pass.sh")
+		if err != nil {
+			return nil, nil, nil, err
+		}
+		_, err = os.Stat(filename)
+		if os.IsNotExist(err) {
+			err := ioutil.WriteFile(filename, []byte(`#!/bin/sh
+case "$1" in
+Username*) echo "${GIT_USERNAME}" ;;
+Password*) echo "${GIT_PASSWORD}" ;;
+esac
+`), 0755)
+			if err != nil {
+				return nil, nil, nil, err
+			}
+		}
+		return func() {},
+			&http.BasicAuth{Username: g.Username, Password: g.Password},
+			[]string{
+				"GIT_ASKPASS=" + filename,
+				"GIT_USERNAME=" + g.Username,
+				"GIT_PASSWORD=" + g.Password,
+			},
+			nil
 	}
-	return gitClone(path, inputArtifact, nil, "")
+	return func() {}, nil, nil, nil
 }
 
 // Save is unsupported for git output artifacts
 func (g *GitArtifactDriver) Save(string, *wfv1.Artifact) error {
-	return errors.Errorf(errors.CodeBadRequest, "Git output artifacts unsupported")
+	return errors.New("git output artifacts unsupported")
 }
 
-func writePrivateKey(key string, insecureIgnoreHostKey bool) error {
-	usr, err := user.Current()
-	if err != nil {
-		return errors.InternalWrapError(err)
-	}
-	sshDir := fmt.Sprintf("%s/.ssh", usr.HomeDir)
-	err = os.MkdirAll(sshDir, 0700)
-	if err != nil {
-		return errors.InternalWrapError(err)
-	}
-
-	if insecureIgnoreHostKey {
-		sshConfig := `Host *
-	StrictHostKeyChecking no
-	UserKnownHostsFile /dev/null`
-		err = ioutil.WriteFile(fmt.Sprintf("%s/config", sshDir), []byte(sshConfig), 0644)
-		if err != nil {
-			return errors.InternalWrapError(err)
-		}
-	}
-	err = ioutil.WriteFile(fmt.Sprintf("%s/id_rsa", sshDir), []byte(key), 0600)
+func (g *GitArtifactDriver) Load(inputArtifact *wfv1.Artifact, path string) error {
+	closer, auth, env, err := g.auth()
 	if err != nil {
-		return errors.InternalWrapError(err)
+		return err
 	}
-
-	return nil
-}
-
-func gitClone(path string, inputArtifact *wfv1.Artifact, auth transport.AuthMethod, privateKey string) error {
-	cloneOptions := git.CloneOptions{
+	defer closer()
+	repo, err := git.PlainClone(path, false, &git.CloneOptions{
 		URL:               inputArtifact.Git.Repo,
 		RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
 		Auth:              auth,
-	}
-	if inputArtifact.Git.Depth != nil {
-		cloneOptions.Depth = int(*inputArtifact.Git.Depth)
-	}
-
-	repo, err := git.PlainClone(path, false, &cloneOptions)
+		Depth:             inputArtifact.Git.GetDepth(),
+	})
 	if err != nil {
-		return errors.InternalWrapError(err)
+		return err
 	}
-
 	if inputArtifact.Git.Fetch != nil {
 		refSpecs := make([]config.RefSpec, len(inputArtifact.Git.Fetch))
 		for i, spec := range inputArtifact.Git.Fetch {
 			refSpecs[i] = config.RefSpec(spec)
 		}
-
 		fetchOptions := git.FetchOptions{
+			Auth:     auth,
 			RefSpecs: refSpecs,
+			Depth:    inputArtifact.Git.GetDepth(),
 		}
-		if inputArtifact.Git.Depth != nil {
-			fetchOptions.Depth = int(*inputArtifact.Git.Depth)
-		}
-
 		err = fetchOptions.Validate()
 		if err != nil {
-			return errors.InternalWrapError(err)
+			return err
 		}
-
 		err = repo.Fetch(&fetchOptions)
-		if err != nil {
-			return errors.InternalWrapError(err)
+		if isAlreadyUpToDateErr(err) {
+			return err
 		}
 	}
-
 	if inputArtifact.Git.Revision != "" {
 		// We still rely on forking git for checkout, since go-git does not have a reliable
 		// way of resolving revisions (e.g. mybranch, HEAD^, v1.2.3)
 		log.Infof("Checking out revision %s", inputArtifact.Git.Revision)
 		cmd := exec.Command("git", "checkout", inputArtifact.Git.Revision)
 		cmd.Dir = path
+		cmd.Env = env
 		output, err := cmd.Output()
 		if err != nil {
-			if exErr, ok := err.(*exec.ExitError); ok {
-				log.Errorf("`%s` stderr:\n%s", cmd.Args, string(exErr.Stderr))
-				return errors.InternalError(strings.Split(string(exErr.Stderr), "\n")[0])
-			}
-			return errors.InternalWrapError(err)
-		}
-		log.Errorf("`%s` stdout:\n%s", cmd.Args, string(output))
-		if privateKey != "" {
-			err := writePrivateKey(privateKey, inputArtifact.Git.InsecureIgnoreHostKey)
-			if err != nil {
-				return errors.InternalWrapError(err)
-			}
+			return g.error(err, cmd)
 		}
+		log.Infof("`%s` stdout:\n%s", cmd.Args, string(output))
 		submodulesCmd := exec.Command("git", "submodule", "update", "--init", "--recursive", "--force")
 		submodulesCmd.Dir = path
+		submodulesCmd.Env = env
 		submoduleOutput, err := submodulesCmd.Output()
 		if err != nil {
-			if exErr, ok := err.(*exec.ExitError); ok {
-				log.Errorf("`%s` stderr:\n%s", submodulesCmd.Args, string(exErr.Stderr))
-				return errors.InternalError(strings.Split(string(exErr.Stderr), "\n")[0])
-			}
-			return errors.InternalWrapError(err)
+			return g.error(err, cmd)
 		}
-		log.Errorf("`%s` stdout:\n%s", submodulesCmd.Args, string(submoduleOutput))
+		log.Infof("`%s` stdout:\n%s", cmd.Args, string(submoduleOutput))
 	}
 	return nil
 }
+
+func isAlreadyUpToDateErr(err error) bool {
+	return err != nil && err.Error() != "already up-to-date"
+}
+
+func (g *GitArtifactDriver) error(err error, cmd *exec.Cmd) error {
+	if exErr, ok := err.(*exec.ExitError); ok {
+		log.Errorf("`%s` stderr:\n%s", cmd.Args, string(exErr.Stderr))
+		return errors.New(strings.Split(string(exErr.Stderr), "\n")[0])
+	}
+	return err
+}
diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go
index 37b5babc2..e6cc449b6 100644
--- a/workflow/controller/operator.go
+++ b/workflow/controller/operator.go
@@ -399,6 +399,7 @@ func (woc *wfOperationCtx) operate() {
 		realTimeScope := map[string]func() float64{common.GlobalVarWorkflowDuration: func() float64 {
 			return node.FinishedAt.Sub(node.StartedAt.Time).Seconds()
 		}}
+		woc.globalParams[common.GlobalVarWorkflowStatus] = string(workflowStatus)
 		woc.computeMetrics(woc.wfSpec.Metrics.Prometheus, woc.globalParams, realTimeScope, false)
 	}
 }
diff --git a/workflow/cron/operator.go b/workflow/cron/operator.go
index 6d9770b50..b45ecdae9 100644
--- a/workflow/cron/operator.go
+++ b/workflow/cron/operator.go
@@ -1,10 +1,12 @@
 package cron
 
 import (
+	"encoding/json"
 	"fmt"
 	"sort"
 	"time"
 
+	jsonpatch "github.com/evanphx/json-patch"
 	"github.com/robfig/cron/v3"
 	log "github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
@@ -26,6 +28,7 @@ type cronWfOperationCtx struct {
 	// CronWorkflow is the CronWorkflow to be run
 	name        string
 	cronWf      *v1alpha1.CronWorkflow
+	origCronWf  *v1alpha1.CronWorkflow
 	wfClientset versioned.Interface
 	wfClient    typed.WorkflowInterface
 	wfLister    util.WorkflowLister
@@ -38,6 +41,7 @@ func newCronWfOperationCtx(cronWorkflow *v1alpha1.CronWorkflow, wfClientset vers
 	return &cronWfOperationCtx{
 		name:        cronWorkflow.ObjectMeta.Name,
 		cronWf:      cronWorkflow,
+		origCronWf:  cronWorkflow.DeepCopy(),
 		wfClientset: wfClientset,
 		wfClient:    wfClientset.ArgoprojV1alpha1().Workflows(cronWorkflow.Namespace),
 		wfLister:    wfLister,
@@ -115,7 +119,57 @@ func getWorkflowObjectReference(wf *v1alpha1.Workflow, runWf *v1alpha1.Workflow)
 func (woc *cronWfOperationCtx) persistUpdate() {
 	_, err := woc.cronWfIf.Update(woc.cronWf)
 	if err != nil {
-		woc.log.WithError(err).Error("failed to update CronWorkflow")
+		if errors.IsConflict(err) {
+			reapplyErr := woc.reapplyUpdate()
+			if reapplyErr != nil {
+				woc.log.WithError(reapplyErr).WithField("original error", err).Error("failed to update CronWorkflow after reapply attempt")
+			}
+		} else {
+			woc.log.WithError(err).Error("failed to update CronWorkflow")
+		}
+	}
+}
+
+func (woc *cronWfOperationCtx) reapplyUpdate() error {
+	orig, err := json.Marshal(woc.origCronWf)
+	if err != nil {
+		return err
+	}
+	curr, err := json.Marshal(woc.cronWf)
+	if err != nil {
+		return err
+	}
+	patch, err := jsonpatch.CreateMergePatch(orig, curr)
+	if err != nil {
+		return err
+	}
+	attempts := 0
+	for {
+		currCronWf, err := woc.cronWfIf.Get(woc.name, v1.GetOptions{})
+		if err != nil {
+			return err
+		}
+		currCronWfBytes, err := json.Marshal(currCronWf)
+		if err != nil {
+			return err
+		}
+		newCronWfBytes, err := jsonpatch.MergePatch(currCronWfBytes, patch)
+		if err != nil {
+			return err
+		}
+		var newCronWf v1alpha1.CronWorkflow
+		err = json.Unmarshal(newCronWfBytes, &newCronWf)
+		if err != nil {
+			return err
+		}
+		_, err = woc.cronWfIf.Update(&newCronWf)
+		if err == nil {
+			return nil
+		}
+		attempts++
+		if attempts == 5 {
+			return fmt.Errorf("ran out of retries when trying to reapply update: %s", err)
+		}
 	}
 }
 
diff --git a/workflow/executor/resource.go b/workflow/executor/resource.go
index 515a91ea2..9e64c7765 100644
--- a/workflow/executor/resource.go
+++ b/workflow/executor/resource.go
@@ -330,7 +330,7 @@ func (we *WorkflowExecutor) SaveResourceParameters(resourceNamespace string, res
 			if resourceNamespace != "" {
 				resArgs = append(resArgs, "-n", resourceNamespace)
 			}
-			cmdStr := fmt.Sprintf("kubectl get %s -o json | jq -c '%s'", strings.Join(resArgs, " "), param.ValueFrom.JQFilter)
+			cmdStr := fmt.Sprintf("kubectl get %s -o json | jq -rc '%s'", strings.Join(resArgs, " "), param.ValueFrom.JQFilter)
 			cmd = exec.Command("sh", "-c", cmdStr)
 		} else {
 			continue
diff --git a/workflow/validate/validate.go b/workflow/validate/validate.go
index e59a1a7de..3d28f4b68 100644
--- a/workflow/validate/validate.go
+++ b/workflow/validate/validate.go
@@ -149,7 +149,7 @@ func ValidateWorkflow(wftmplGetter templateresolution.WorkflowTemplateNamespaced
 	wfArgs := wf.Spec.Arguments
 
 	if wf.Spec.WorkflowTemplateRef != nil {
-		wfArgs.Parameters = util.MergeParameters(wfSpecHolder.GetWorkflowSpec().Arguments.Parameters, wfArgs.Parameters)
+		wfArgs.Parameters = util.MergeParameters(wfArgs.Parameters, wfSpecHolder.GetWorkflowSpec().Arguments.Parameters)
 	}
 	if err != nil {
 		return nil, errors.Errorf(errors.CodeBadRequest, "spec.templates%s", err.Error())


Test patch:
diff --git a/cmd/argo/commands/list_test.go b/cmd/argo/commands/list_test.go
index 3ca2c1f94..dfb87bb17 100644
--- a/cmd/argo/commands/list_test.go
+++ b/cmd/argo/commands/list_test.go
@@ -15,20 +15,26 @@ import (
 )
 
 func Test_listWorkflows(t *testing.T) {
+	t.Run("Nothing", func(t *testing.T) {
+		workflows, err := list(&metav1.ListOptions{}, listFlags{})
+		if assert.NoError(t, err) {
+			assert.NotNil(t, workflows)
+		}
+	})
 	t.Run("Status", func(t *testing.T) {
-		workflows, err := list(&metav1.ListOptions{LabelSelector: "workflows.argoproj.io/phase in (Pending,Running),"}, listFlags{status: []string{"Running", "Pending"}})
+		workflows, err := list(&metav1.ListOptions{LabelSelector: "workflows.argoproj.io/phase in (Pending,Running)"}, listFlags{status: []string{"Running", "Pending"}})
 		if assert.NoError(t, err) {
 			assert.NotNil(t, workflows)
 		}
 	})
 	t.Run("Completed", func(t *testing.T) {
-		workflows, err := list(&metav1.ListOptions{LabelSelector: "workflows.argoproj.io/completed=true,"}, listFlags{completed: true})
+		workflows, err := list(&metav1.ListOptions{LabelSelector: "workflows.argoproj.io/completed=true"}, listFlags{completed: true})
 		if assert.NoError(t, err) {
 			assert.NotNil(t, workflows)
 		}
 	})
 	t.Run("Running", func(t *testing.T) {
-		workflows, err := list(&metav1.ListOptions{LabelSelector: "workflows.argoproj.io/completed!=true,"}, listFlags{running: true})
+		workflows, err := list(&metav1.ListOptions{LabelSelector: "workflows.argoproj.io/completed!=true"}, listFlags{running: true})
 		if assert.NoError(t, err) {
 			assert.NotNil(t, workflows)
 		}
diff --git a/test/e2e/functional/output-jqfilter-parameters.yaml b/test/e2e/functional/output-jqfilter-parameters.yaml
new file mode 100644
index 000000000..8dc0226aa
--- /dev/null
+++ b/test/e2e/functional/output-jqfilter-parameters.yaml
@@ -0,0 +1,103 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  generateName: output-jqfilter-parameters-
+spec:
+  entrypoint: main
+  templates:
+
+  # main template that:
+  #   * create a configmap with json payload
+  #   * extract data using jqFilter from it
+  #   * compare the output parameters are what we expect
+  - name: main
+    steps:
+    - - name: create-param
+        template: create-param
+
+    - - name: get-param
+        template: get-param
+
+    - - name: check-param-one-value
+        template: check-param
+        arguments:
+          parameters:
+          - name: key
+            value: "{{steps.get-param.outputs.parameters.one_value}}"
+          - name: value
+            value: "foo"
+      - name: check-param-one-dict
+        template: check-param
+        arguments:
+          parameters:
+          - name: key
+            value: "{{steps.get-param.outputs.parameters.one_dict}}"
+          - name: value
+            value: |
+              {"foo":"bar"}
+      - name: check-param-one-list
+        template: check-param
+        arguments:
+          parameters:
+          - name: key
+            value: "{{steps.get-param.outputs.parameters.one_list}}"
+          - name: value
+            value: |
+              [{"foo":"bar"}]
+
+  # check that key and value parameters are aligned
+  - name: check-param
+    inputs:
+      parameters:
+      - name: key
+      - name: value
+    container:
+      image: argoproj/argoexec:latest
+      command: [sh, -c]
+      args: ["
+        echo '<{{inputs.parameters.key}}>' > /tmp/input;
+        echo '<{{inputs.parameters.value}}>' > /tmp/expected;
+        diff /tmp/input /tmp/expected;
+      "]
+
+  # extract parameters from a configmap json payload using jqFilter
+  - name: get-param
+    resource:
+      action: get
+      manifest: |
+        apiVersion: v1
+        kind: ConfigMap
+        metadata:
+          name: cm-output-jqfilter-parameters
+    outputs:
+      parameters:
+      - name: one_value
+        valueFrom:
+          jqFilter: '.data.some | fromjson | .one_value'
+      - name: one_dict
+        valueFrom:
+          jqfilter: '.data.some | fromjson | .one_dict'
+      - name: one_list
+        valueFrom:
+          jqfilter: '.data.some | fromjson | .one_list'
+
+  # create a configmap with json payload
+  - name: create-param
+    resource:
+      action: create
+      manifest: |
+        apiVersion: v1
+        kind: ConfigMap
+        metadata:
+          name: cm-output-jqfilter-parameters
+        data:
+          some: |
+            {
+              "one_value": "foo",
+              "one_dict": {"foo":"bar"},
+              "one_list": [
+                {
+                  "foo":"bar"
+                }
+              ]
+            }
diff --git a/workflow/artifacts/git/git_test.go b/workflow/artifacts/git/git_test.go
index 9b5d04e40..c2d51424a 100644
--- a/workflow/artifacts/git/git_test.go
+++ b/workflow/artifacts/git/git_test.go
@@ -1,6 +1,7 @@
 package git
 
 import (
+	"io/ioutil"
 	"os"
 	"testing"
 
@@ -9,23 +10,28 @@ import (
 	wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
 )
 
+var d = uint64(1)
+
 func TestGitArtifactDriver_Load(t *testing.T) {
+	_ = os.Remove("git-ask-pass.sh")
 	driver := &GitArtifactDriver{}
-	t.Run("Found", func(t *testing.T) {
-		path := "/tmp/git-found"
-		assert.NoError(t, os.RemoveAll(path))
-		assert.NoError(t, os.MkdirAll(path, 0777))
-		d := uint64(1)
-		err := driver.Load(&wfv1.Artifact{
-			ArtifactLocation: wfv1.ArtifactLocation{
-				Git: &wfv1.GitArtifact{Repo: "https://github.com/argoproj/argoproj.git", Revision: "master", Depth: &d},
+	path := "/tmp/git-found"
+	assert.NoError(t, os.RemoveAll(path))
+	assert.NoError(t, os.MkdirAll(path, 0777))
+	err := driver.Load(&wfv1.Artifact{
+		ArtifactLocation: wfv1.ArtifactLocation{
+			Git: &wfv1.GitArtifact{
+				Repo:     "https://github.com/argoproj/argoproj.git",
+				Fetch:    []string{"+refs/heads/*:refs/remotes/origin/*"},
+				Revision: "HEAD",
+				Depth:    &d,
 			},
-		}, path)
-		if assert.NoError(t, err) {
-			_, err := os.Stat(path)
-			assert.NoError(t, err)
-		}
-	})
+		},
+	}, path)
+	if assert.NoError(t, err) {
+		_, err := os.Stat(path)
+		assert.NoError(t, err)
+	}
 }
 
 func TestGitArtifactDriver_Save(t *testing.T) {
@@ -33,3 +39,62 @@ func TestGitArtifactDriver_Save(t *testing.T) {
 	err := driver.Save("", nil)
 	assert.Error(t, err)
 }
+
+func TestGitArtifactDriverLoad_HTTPS(t *testing.T) {
+	if os.Getenv("GITHUB_TOKEN") == "" {
+		t.Skip("not running an GITHUB_TOKEN not set")
+	}
+	_ = os.Remove("git-ask-pass.sh")
+	tmp, err := ioutil.TempDir("", "")
+	assert.NoError(t, err)
+	driver := &GitArtifactDriver{Username: os.Getenv("GITHUB_TOKEN")}
+	assert.NotEmpty(t, driver.Username)
+	err = driver.Load(&wfv1.Artifact{
+		ArtifactLocation: wfv1.ArtifactLocation{
+			Git: &wfv1.GitArtifact{
+				Repo:     "https://github.com/argoproj/argo.git",
+				Fetch:    []string{"+refs/heads/*:refs/remotes/origin/*"},
+				Revision: "HEAD",
+				Depth:    &d,
+			},
+		},
+	}, tmp)
+	assert.NoError(t, err)
+	println(tmp)
+}
+
+func TestGitArtifactDriverLoad_SSL(t *testing.T) {
+	for _, tt := range []struct {
+		name     string
+		insecure bool
+	}{
+		{"Insecure", true},
+		{"Secure", false},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			_ = os.Remove("git-ask-pass.sh")
+			key := os.Getenv("HOME") + "/.ssh/id_rsa"
+			data, err := ioutil.ReadFile(key)
+			if err != nil && os.IsNotExist(err) {
+				t.Skip(key + " does not exist")
+			}
+			assert.NoError(t, err)
+			tmp, err := ioutil.TempDir("", "")
+			assert.NoError(t, err)
+			println(tmp)
+			driver := &GitArtifactDriver{SSHPrivateKey: string(data)}
+			err = driver.Load(&wfv1.Artifact{
+				ArtifactLocation: wfv1.ArtifactLocation{
+					Git: &wfv1.GitArtifact{
+						Repo:                  "git@github.com:argoproj/argo.git",
+						Fetch:                 []string{"+refs/heads/*:refs/remotes/origin/*"},
+						Revision:              "HEAD",
+						InsecureIgnoreHostKey: tt.insecure,
+						Depth:                 &d,
+					},
+				},
+			}, tmp)
+			assert.NoError(t, err)
+		})
+	}
+}
diff --git a/workflow/controller/config_test.go b/workflow/controller/config_test.go
new file mode 100644
index 000000000..3b21b8245
--- /dev/null
+++ b/workflow/controller/config_test.go
@@ -0,0 +1,22 @@
+package controller
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"k8s.io/client-go/util/workqueue"
+
+	"github.com/argoproj/argo/config"
+)
+
+func TestUpdateConfig(t *testing.T) {
+	cancel, controller := newController()
+	defer cancel()
+	controller.throttler = NewThrottler(0, workqueue.NewNamedRateLimitingQueue(nil, ""))
+	err := controller.updateConfig(config.Config{ExecutorImage: "argoexec:latest"})
+	assert.NoError(t, err)
+	assert.NotNil(t, controller.Config)
+	assert.NotNil(t, controller.archiveLabelSelector)
+	assert.NotNil(t, controller.wfArchive)
+	assert.NotNil(t, controller.offloadNodeStatusRepo)
+}
diff --git a/workflow/controller/operator_test.go b/workflow/controller/operator_test.go
index 4981189ff..fec9bc9b0 100644
--- a/workflow/controller/operator_test.go
+++ b/workflow/controller/operator_test.go
@@ -3739,3 +3739,113 @@ func TestValidReferenceMode(t *testing.T) {
 	woc.operate()
 	assert.Equal(t, wfv1.NodeRunning, woc.wf.Status.Phase)
 }
+
+var workflowStatusMetric = `
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  name: retry-to-completion-rngcr
+spec:
+  arguments: {}
+  entrypoint: retry-to-completion
+  metrics:
+    prometheus:
+    - counter:
+        value: "1"
+      gauge: null
+      help: Count of step execution by result status
+      histogram: null
+      labels:
+      - key: name
+        value: retry
+      - key: status
+        value: '{{workflow.status}}'
+      name: result_counter
+      when: ""
+  templates:
+  - arguments: {}
+    container:
+      args:
+      - import random; import sys; exit_code = random.choice(range(0, 5)); sys.exit(exit_code)
+      command:
+      - python
+      - -c
+      image: python
+      name: ""
+      resources: {}
+    inputs: {}
+    metadata: {}
+    name: retry-to-completion
+    outputs: {}
+    retryStrategy: {}
+status:
+  nodes:
+    retry-to-completion-rngcr:
+      children:
+      - retry-to-completion-rngcr-1856960714
+      displayName: retry-to-completion-rngcr
+      finishedAt: "2020-06-22T20:33:10Z"
+      id: retry-to-completion-rngcr
+      name: retry-to-completion-rngcr
+      outputs:
+        artifacts:
+        - archiveLogs: true
+          name: main-logs
+          s3:
+            accessKeySecret:
+              key: accesskey
+              name: my-minio-cred
+            bucket: my-bucket
+            endpoint: minio:9000
+            insecure: true
+            key: retry-to-completion-rngcr/retry-to-completion-rngcr-4003951493/main.log
+            secretKeySecret:
+              key: secretkey
+              name: my-minio-cred
+      phase: Succeeded
+      startedAt: "2020-06-22T20:32:15Z"
+      templateName: retry-to-completion
+      templateScope: local/retry-to-completion-rngcr
+      type: Retry
+    retry-to-completion-rngcr-1856960714:
+      displayName: retry-to-completion-rngcr(0)
+      finishedAt: "2020-06-22T20:32:25Z"
+      hostNodeName: minikube
+      id: retry-to-completion-rngcr-1856960714
+      message: failed with exit code 3
+      name: retry-to-completion-rngcr(0)
+      outputs:
+        artifacts:
+        - archiveLogs: true
+          name: main-logs
+          s3:
+            accessKeySecret:
+              key: accesskey
+              name: my-minio-cred
+            bucket: my-bucket
+            endpoint: minio:9000
+            insecure: true
+            key: retry-to-completion-rngcr/retry-to-completion-rngcr-1856960714/main.log
+            secretKeySecret:
+              key: secretkey
+              name: my-minio-cred
+        exitCode: "3"
+      phase: Failed
+      resourcesDuration:
+        cpu: 10
+        memory: 6
+      startedAt: "2020-06-22T20:32:15Z"
+      templateName: retry-to-completion
+      templateScope: local/retry-to-completion-rngcr
+      type: Pod
+  phase: Running
+  startedAt: "2020-06-22T20:32:15Z"
+`
+
+func TestWorkflowStatusMetric(t *testing.T) {
+	wf := unmarshalWF(workflowStatusMetric)
+	woc := newWoc(*wf)
+	woc.operate()
+	// Must only be one (completed: true)
+	assert.Len(t, woc.wf.Status.Conditions, 1)
+}
diff --git a/workflow/cron/operator_test.go b/workflow/cron/operator_test.go
index 7acea475c..4d76426ba 100644
--- a/workflow/cron/operator_test.go
+++ b/workflow/cron/operator_test.go
@@ -243,3 +243,32 @@ func TestSpecError(t *testing.T) {
 	assert.Equal(t, v1alpha1.ConditionTypeSpecError, submissionErrorCond.Type)
 	assert.Contains(t, submissionErrorCond.Message, "cron schedule is malformed: End of range (12737123) above maximum (12): 12737123")
 }
+
+func TestReapplyUpdate(t *testing.T) {
+	cronWf := v1alpha1.CronWorkflow{
+		ObjectMeta: v1.ObjectMeta{Name: "my-wf"},
+		Spec:       v1alpha1.CronWorkflowSpec{Schedule: "* * * * *"},
+	}
+
+	cs := fake.NewSimpleClientset(&cronWf)
+	testMetrics := metrics.New(metrics.ServerConfig{}, metrics.ServerConfig{})
+	woc := &cronWfOperationCtx{
+		wfClientset: cs,
+		wfClient:    cs.ArgoprojV1alpha1().Workflows(""),
+		cronWfIf:    cs.ArgoprojV1alpha1().CronWorkflows(""),
+		wfLister:    &fakeLister{},
+		cronWf:      &cronWf,
+		origCronWf:  cronWf.DeepCopy(),
+		log:         logrus.WithFields(logrus.Fields{}),
+		metrics:     &testMetrics,
+	}
+
+	cronWf.Spec.Schedule = "1 * * * *"
+	err := woc.reapplyUpdate()
+	if assert.NoError(t, err) {
+		updatedCronWf, err := woc.cronWfIf.Get("my-wf", v1.GetOptions{})
+		if assert.NoError(t, err) {
+			assert.Equal(t, "1 * * * *", updatedCronWf.Spec.Schedule)
+		}
+	}
+}
diff --git a/workflow/validate/validate_test.go b/workflow/validate/validate_test.go
index 5d3cf9ee2..632d97650 100644
--- a/workflow/validate/validate_test.go
+++ b/workflow/validate/validate_test.go
@@ -2530,3 +2530,42 @@ func TestInvalidWfNoImageFieldScript(t *testing.T) {
 	_, err := validate(invalidWfNoImageScript)
 	assert.EqualError(t, err, "templates.whalesay.script.image may not be empty")
 }
+
+var templateRefWithParam = `
+apiVersion: argoproj.io/v1alpha1
+kind: WorkflowTemplate
+metadata:
+  name: template-ref-with-param
+spec:
+  entrypoint: A
+  arguments:
+    parameters:
+    - name: some-param
+  templates:
+  - name: A
+    container:
+      image: alpine:latest
+      command: [echo, hello]
+`
+
+var wfWithWFTRefOverrideParam = `
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  generateName: hello-world-
+  namespace: default
+spec:
+  arguments:
+    parameters:
+    - name: some-param
+      value: a-value
+  workflowTemplateRef:
+    name: template-ref-with-param
+`
+
+func TestWorkflowWithWFTRefWithOverrideParam(t *testing.T) {
+	err := createWorkflowTemplate(templateRefWithParam)
+	assert.NoError(t, err)
+	_, err = validate(wfWithWFTRefOverrideParam)
+	assert.NoError(t, err)
+}