Repository: argoproj/argo
Pull request #3284 (2020-06-23 21:29:11)

docs: ArchiveLabelSelector document
Checklist:

* [ ] 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.
* [ ] 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"`.  
* [ ] I've signed the CLA.
* [ ] I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
* [ ] My builds are green. Try syncing with master if they are not. 
* [ ] My organization is added to [USERS.md](https://github.com/argoproj/argo/blob/master/USERS.md).



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/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/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/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/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/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/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)
+}