Repository: argoproj/argo
Pull request #2327 (2020-02-28 01:13:21)

fix(auth): Fixed returning  expired  Auth token for GKE
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"`.  #2310 
* [ ] I have written unit and/or e2e tests for my change. PRs without these are unlike to be merged.
* [ ] Optional. I've added My organization is added to the README.
* [ ] I've signed the CLA and required builds are green. 


codecov[bot]: # [Codecov](https://codecov.io/gh/argoproj/argo/pull/2327?src=pr&el=h1) Report
> Merging [#2327](https://codecov.io/gh/argoproj/argo/pull/2327?src=pr&el=desc) into [master](https://codecov.io/gh/argoproj/argo/commit/3ac482d003c404d79e0d81183dc658b3258f1934?src=pr&el=desc) will **increase** coverage by `0.2%`.
> The diff coverage is `5.35%`.

[![Impacted file tree graph](https://codecov.io/gh/argoproj/argo/pull/2327/graphs/tree.svg?width=650&token=4c3DPSTq8o&height=150&src=pr)](https://codecov.io/gh/argoproj/argo/pull/2327?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##           master    #2327     +/-   ##
=========================================
+ Coverage   13.01%   13.22%   +0.2%     
=========================================
  Files          70       70             
  Lines       24492    24160    -332     
=========================================
+ Hits         3188     3194      +6     
+ Misses      20899    20561    -338     
  Partials      405      405
```


| [Impacted Files](https://codecov.io/gh/argoproj/argo/pull/2327?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [persist/sqldb/offload\_node\_status\_repo.go](https://codecov.io/gh/argoproj/argo/pull/2327/diff?src=pr&el=tree#diff-cGVyc2lzdC9zcWxkYi9vZmZsb2FkX25vZGVfc3RhdHVzX3JlcG8uZ28=) | `3.9% <ø> (ø)` | :arrow_up: |
| [cmd/argo/commands/server.go](https://codecov.io/gh/argoproj/argo/pull/2327/diff?src=pr&el=tree#diff-Y21kL2FyZ28vY29tbWFuZHMvc2VydmVyLmdv) | `0% <0%> (ø)` | :arrow_up: |
| [server/workflow/workflow\_server.go](https://codecov.io/gh/argoproj/argo/pull/2327/diff?src=pr&el=tree#diff-c2VydmVyL3dvcmtmbG93L3dvcmtmbG93X3NlcnZlci5nbw==) | `33.33% <0%> (-1.38%)` | :arrow_down: |
| [cmd/argo/commands/watch.go](https://codecov.io/gh/argoproj/argo/pull/2327/diff?src=pr&el=tree#diff-Y21kL2FyZ28vY29tbWFuZHMvd2F0Y2guZ28=) | `0% <0%> (ø)` | :arrow_up: |
| [cmd/argo/commands/wait.go](https://codecov.io/gh/argoproj/argo/pull/2327/diff?src=pr&el=tree#diff-Y21kL2FyZ28vY29tbWFuZHMvd2FpdC5nbw==) | `0% <0%> (ø)` | :arrow_up: |
| [cmd/argo/commands/logs.go](https://codecov.io/gh/argoproj/argo/pull/2327/diff?src=pr&el=tree#diff-Y21kL2FyZ28vY29tbWFuZHMvbG9ncy5nbw==) | `0% <0%> (ø)` | :arrow_up: |
| [cmd/argo/commands/common.go](https://codecov.io/gh/argoproj/argo/pull/2327/diff?src=pr&el=tree#diff-Y21kL2FyZ28vY29tbWFuZHMvY29tbW9uLmdv) | `30.76% <100%> (+26.92%)` | :arrow_up: |
| [util/kubeconfig/kubeconfig.go](https://codecov.io/gh/argoproj/argo/pull/2327/diff?src=pr&el=tree#diff-dXRpbC9rdWJlY29uZmlnL2t1YmVjb25maWcuZ28=) | `43.07% <7.14%> (-11.83%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/argoproj/argo/pull/2327?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/argoproj/argo/pull/2327?src=pr&el=footer). Last update [3ac482d...6b695ac](https://codecov.io/gh/argoproj/argo/pull/2327?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/Dockerfile b/Dockerfile
index fd0c029a1..5500adb9f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -5,14 +5,23 @@
 ####################################################################################################
 FROM golang:1.13.4 as builder
 
-RUN apt-get update && apt-get install -y \
+RUN apt-get update && apt-get --no-install-recommends install -y \
     git \
     make \
+    apt-utils \
+    apt-transport-https \
+    ca-certificates \
     wget \
     gcc \
     zip && \
-    apt-get clean && \
-    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+    apt-get clean \
+    && rm -rf \
+        /var/lib/apt/lists/* \
+        /tmp/* \
+        /var/tmp/* \
+        /usr/share/man \
+        /usr/share/doc \
+        /usr/share/doc-base
 
 WORKDIR /tmp
 
@@ -37,8 +46,15 @@ FROM debian:9.6-slim as argoexec-base
 ENV KUBECTL_VERSION=1.15.1
 ENV JQ_VERSION=1.6
 RUN apt-get update && \
-    apt-get install -y curl procps git tar mime-support && \
-    rm -rf /var/lib/apt/lists/* && \
+    apt-get --no-install-recommends install -y curl procps git apt-utils apt-transport-https ca-certificates tar mime-support && \
+    apt-get clean \
+    && rm -rf \
+        /var/lib/apt/lists/* \
+        /tmp/* \
+        /var/tmp/* \
+        /usr/share/man \
+        /usr/share/doc \
+        /usr/share/doc-base && \
     curl -L -o /usr/local/bin/kubectl -LO https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl && \
     chmod +x /usr/local/bin/kubectl && \
     curl -L -o /usr/local/bin/jq -LO https://github.com/stedolan/jq/releases/download/jq-${JQ_VERSION}/jq-linux64 && \
diff --git a/Dockerfile.dev b/Dockerfile.dev
index d470642f9..1e90b6870 100644
--- a/Dockerfile.dev
+++ b/Dockerfile.dev
@@ -5,14 +5,23 @@
 ####################################################################################################
 FROM golang:1.13.4 as builder
 
-RUN apt-get update && apt-get install -y \
+RUN apt-get update && apt-get --no-install-recommends install -y \
     git \
     make \
+    apt-utils \
+    apt-transport-https \
+    ca-certificates \
     wget \
     gcc \
     zip && \
-    apt-get clean && \
-    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+    apt-get clean \
+    && rm -rf \
+        /var/lib/apt/lists/* \
+        /tmp/* \
+        /var/tmp/* \
+        /usr/share/man \
+        /usr/share/doc \
+        /usr/share/doc-base
 
 WORKDIR /tmp
 
@@ -37,8 +46,15 @@ FROM debian:9.6-slim as argoexec-base
 ENV KUBECTL_VERSION=1.15.1
 ENV JQ_VERSION=1.6
 RUN apt-get update && \
-    apt-get install -y curl procps git tar mime-support && \
-    rm -rf /var/lib/apt/lists/* && \
+    apt-get --no-install-recommends install -y curl ca-certificates apt-utils apt-transport-https procps git tar mime-support && \
+    apt-get clean \
+    && rm -rf \
+        /var/lib/apt/lists/* \
+        /tmp/* \
+        /var/tmp/* \
+        /usr/share/man \
+        /usr/share/doc \
+        /usr/share/doc-base && \
     curl -L -o /usr/local/bin/kubectl -LO https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl && \
     chmod +x /usr/local/bin/kubectl && \
     curl -L -o /usr/local/bin/jq -LO https://github.com/stedolan/jq/releases/download/jq-${JQ_VERSION}/jq-linux64 && \
diff --git a/Makefile b/Makefile
index 72f29e011..a8865b0b2 100644
--- a/Makefile
+++ b/Makefile
@@ -203,11 +203,18 @@ endif
 .PHONY: codegen
 codegen:
 	# Generate code
+	# We need the vendor folder for compatibility
+	go mod vendor
+
 	./hack/generate-proto.sh
 	./hack/update-codegen.sh
 	make api/openapi-spec/swagger.json
 	find . -path '*/mocks/*' -type f -not -path '*/vendor/*' -exec ./hack/update-mocks.sh {} ';'
 
+	rm -rf ./vendor
+	go mod tidy
+
+
 .PHONY: manifests
 manifests: status manifests/install.yaml manifests/namespace-install.yaml manifests/quick-start-mysql.yaml manifests/quick-start-postgres.yaml manifests/quick-start-no-db.yaml test/e2e/manifests/postgres.yaml test/e2e/manifests/mysql.yaml test/e2e/manifests/no-db.yaml
 
diff --git a/README.md b/README.md
index 0490bd99f..cea6d6593 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Argoproj is a collection of tools for getting work done with Kubernetes.
 * [Argo Workflows](https://github.com/argoproj/argo) - Container-native Workflow Engine
 * [Argo CD](https://github.com/argoproj/argo-cd) - Declarative GitOps Continuous Delivery
 * [Argo Events](https://github.com/argoproj/argo-events) - Event-based Dependency Manager
-* [Argo Rollouts](https://github.com/argoproj/argo-rollouts) - Deployment CR with support for Canary and Blue Green deployment strategies
+* [Argo Rollouts](https://github.com/argoproj/argo-rollouts) - Progressive Delivery with support for Canary and Blue Green deployment strategies
 
 Also argoproj-labs is a separate GitHub org that we setup for community contributions related to the Argoproj ecosystem. Repos in argoproj-labs are administered by the owners of each project. Please reach out to us on the Argo slack channel if you have a project that you would like to add to the org to make it easier to others in the Argo community to find, use, and contribute back.
 * https://github.com/argoproj-labs
diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json
index 7bf0c0a07..fae466187 100644
--- a/api/openapi-spec/swagger.json
+++ b/api/openapi-spec/swagger.json
@@ -848,6 +848,12 @@
             "description": "The continue option should be set when retrieving more results from the server. Since this value is\nserver defined, clients may only use the continue value from a previous query result with identical\nquery parameters (except for the value of continue) and the server may reject a continue value it\ndoes not recognize. If the specified continue value is no longer valid whether due to expiration\n(generally five to fifteen minutes) or a configuration change on the server, the server will\nrespond with a 410 ResourceExpired error together with a continue token. If the client needs a\nconsistent list, it must restart their list without the continue field. Otherwise, the client may\nsend another list request with the token received with the 410 error, the server will respond with\na list starting from the next key, but from the latest snapshot, which is inconsistent from the\nprevious list results - objects that are created, modified, or deleted after the first list request\nwill be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last\nresourceVersion value returned by the server and not miss any modifications.",
             "name": "listOptions.continue",
             "in": "query"
+          },
+          {
+            "type": "string",
+            "description": "Fields to be included or excluded in the response. e.g. \"items.spec,items.status.phase\", \"-items.status.nodes\".",
+            "name": "fields",
+            "in": "query"
           }
         ],
         "responses": {
@@ -946,6 +952,12 @@
             "description": "When specified:\n- if unset, then the result is returned from remote storage based on quorum-read flag;\n- if it's 0, then we simply return what we currently have in cache, no guarantee;\n- if set to non zero, then the result is at least as fresh as given rv.",
             "name": "getOptions.resourceVersion",
             "in": "query"
+          },
+          {
+            "type": "string",
+            "description": "Fields to be included or excluded in the response. e.g. \"spec,status.phase\", \"-status.nodes\".",
+            "name": "fields",
+            "in": "query"
           }
         ],
         "responses": {
diff --git a/cmd/argo/commands/client/conn.go b/cmd/argo/commands/client/conn.go
index 9543d9eb8..a724cca37 100644
--- a/cmd/argo/commands/client/conn.go
+++ b/cmd/argo/commands/client/conn.go
@@ -16,13 +16,16 @@ var argoServer string
 
 var Config clientcmd.ClientConfig
 
+var ExplicitPath string
+
 func AddKubectlFlagsToCmd(cmd *cobra.Command) {
 	// The "usual" clientcmd/kubectl flags
 	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
 	loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
 	overrides := clientcmd.ConfigOverrides{}
 	kflags := clientcmd.RecommendedConfigOverrideFlags("")
-	cmd.PersistentFlags().StringVar(&loadingRules.ExplicitPath, "kubeconfig", "", "Path to a kube config. Only required if out-of-cluster")
+	cmd.PersistentFlags().StringVar(&ExplicitPath, "kubeconfig", "", "Path to a kube config. Only required if out-of-cluster")
+	loadingRules.ExplicitPath = ExplicitPath
 	clientcmd.BindOverrideFlags(&overrides, cmd.PersistentFlags(), kflags)
 	Config = clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, &overrides, os.Stdin)
 }
@@ -54,7 +57,7 @@ func GetAuthString() string {
 	if err != nil {
 		log.Fatal(err)
 	}
-	authString, err := kubeconfig.GetAuthString(restConfig)
+	authString, err := kubeconfig.GetAuthString(restConfig, ExplicitPath)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/go.mod b/go.mod
index f4b609a76..5e890611c 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	cloud.google.com/go v0.51.0 // indirect
 	github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible
 	github.com/ajg/form v1.5.1 // indirect
-	github.com/argoproj/pkg v0.0.0-20200102163130-2dd1f3f6b4de
+	github.com/argoproj/pkg v0.0.0-20200226231057-d69f0b04da65
 	github.com/aws/aws-sdk-go v1.27.1 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/colinmarc/hdfs v1.1.4-0.20180805212432-9746310a4d31
diff --git a/go.sum b/go.sum
index ba4baacd5..6bc1680f9 100644
--- a/go.sum
+++ b/go.sum
@@ -33,8 +33,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
-github.com/argoproj/pkg v0.0.0-20200102163130-2dd1f3f6b4de h1:i19Yd0ERYAHtR84jnB8vmTuizk2q4Qw4NuayfRja5/A=
-github.com/argoproj/pkg v0.0.0-20200102163130-2dd1f3f6b4de/go.mod h1:2EZ44RG/CcgtPTwrRR0apOc7oU6UIw8GjCUJWZ8X3bM=
+github.com/argoproj/pkg v0.0.0-20200226231057-d69f0b04da65 h1:bBC9exkuWvqhyDDhoNr66BpE6B4f+vxxKIXKxg6E6us=
+github.com/argoproj/pkg v0.0.0-20200226231057-d69f0b04da65/go.mod h1:2EZ44RG/CcgtPTwrRR0apOc7oU6UIw8GjCUJWZ8X3bM=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/aws/aws-sdk-go v1.27.1 h1:MXnqY6SlWySaZAqNnXThOvjRFdiiOuKtC6i7baFdNdU=
diff --git a/pkg/apiclient/workflow/forwarder_overwrite.go b/pkg/apiclient/workflow/forwarder_overwrite.go
index 98750c4c7..b9d097522 100644
--- a/pkg/apiclient/workflow/forwarder_overwrite.go
+++ b/pkg/apiclient/workflow/forwarder_overwrite.go
@@ -1,10 +1,12 @@
 package workflow
 
 import (
-	"github.com/argoproj/argo/util/http"
+	"github.com/argoproj/pkg/grpc/http"
 )
 
 func init() {
 	forward_WorkflowService_WatchWorkflows_0 = http.StreamForwarder
 	forward_WorkflowService_PodLogs_0 = http.StreamForwarder
+	forward_WorkflowService_ListWorkflows_0 = http.UnaryForwarder
+	forward_WorkflowService_GetWorkflow_0 = http.UnaryForwarder
 }
diff --git a/pkg/apiclient/workflow/workflow.pb.go b/pkg/apiclient/workflow/workflow.pb.go
index 407e0765a..478efcffd 100644
--- a/pkg/apiclient/workflow/workflow.pb.go
+++ b/pkg/apiclient/workflow/workflow.pb.go
@@ -115,12 +115,14 @@ func (m *WorkflowCreateRequest) GetCreateOptions() *v1.CreateOptions {
 }
 
 type WorkflowGetRequest struct {
-	Name                 string         `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
-	Namespace            string         `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"`
-	GetOptions           *v1.GetOptions `protobuf:"bytes,3,opt,name=getOptions,proto3" json:"getOptions,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
-	XXX_unrecognized     []byte         `json:"-"`
-	XXX_sizecache        int32          `json:"-"`
+	Name       string         `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	Namespace  string         `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"`
+	GetOptions *v1.GetOptions `protobuf:"bytes,3,opt,name=getOptions,proto3" json:"getOptions,omitempty"`
+	// Fields to be included or excluded in the response. e.g. "spec,status.phase", "-status.nodes"
+	Fields               string   `protobuf:"bytes,4,opt,name=fields,proto3" json:"fields,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
 }
 
 func (m *WorkflowGetRequest) Reset()         { *m = WorkflowGetRequest{} }
@@ -177,12 +179,21 @@ func (m *WorkflowGetRequest) GetGetOptions() *v1.GetOptions {
 	return nil
 }
 
+func (m *WorkflowGetRequest) GetFields() string {
+	if m != nil {
+		return m.Fields
+	}
+	return ""
+}
+
 type WorkflowListRequest struct {
-	Namespace            string          `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
-	ListOptions          *v1.ListOptions `protobuf:"bytes,2,opt,name=listOptions,proto3" json:"listOptions,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}        `json:"-"`
-	XXX_unrecognized     []byte          `json:"-"`
-	XXX_sizecache        int32           `json:"-"`
+	Namespace   string          `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
+	ListOptions *v1.ListOptions `protobuf:"bytes,2,opt,name=listOptions,proto3" json:"listOptions,omitempty"`
+	// Fields to be included or excluded in the response. e.g. "items.spec,items.status.phase", "-items.status.nodes"
+	Fields               string   `protobuf:"bytes,3,opt,name=fields,proto3" json:"fields,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
 }
 
 func (m *WorkflowListRequest) Reset()         { *m = WorkflowListRequest{} }
@@ -232,6 +243,13 @@ func (m *WorkflowListRequest) GetListOptions() *v1.ListOptions {
 	return nil
 }
 
+func (m *WorkflowListRequest) GetFields() string {
+	if m != nil {
+		return m.Fields
+	}
+	return ""
+}
+
 type WorkflowResubmitRequest struct {
 	Name                 string   `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
 	Namespace            string   `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"`
@@ -933,74 +951,76 @@ func init() {
 }
 
 var fileDescriptor_1f6bb75f9e833cb6 = []byte{
-	// 1071 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x97, 0xcf, 0x6e, 0xdc, 0x44,
-	0x18, 0xc0, 0x35, 0xdb, 0xd0, 0x6e, 0x27, 0xcd, 0x16, 0x86, 0x7f, 0x2b, 0x2b, 0xdd, 0xa6, 0x03,
-	0x81, 0x28, 0x6d, 0xec, 0x6c, 0x52, 0xa0, 0x54, 0x2a, 0x52, 0x49, 0xaa, 0x50, 0xb4, 0x82, 0xca,
-	0x05, 0xa1, 0x72, 0xf3, 0x7a, 0xa7, 0x8e, 0x1b, 0x7b, 0xc6, 0xd8, 0xb3, 0x5b, 0x2d, 0x55, 0x40,
-	0x70, 0x42, 0x1c, 0xb8, 0x70, 0xa9, 0xe0, 0x08, 0x48, 0x48, 0x48, 0x20, 0xde, 0x82, 0x23, 0x12,
-	0x2f, 0x80, 0x22, 0x24, 0x5e, 0x03, 0xcd, 0xd8, 0x33, 0xb6, 0xb3, 0xcb, 0x62, 0xd6, 0x95, 0x7a,
-	0x9b, 0xf1, 0x78, 0xbe, 0xef, 0xe7, 0xef, 0xbf, 0xe1, 0x6a, 0x74, 0xe0, 0x59, 0x4e, 0xe4, 0xbb,
-	0x81, 0x4f, 0x28, 0xb7, 0xee, 0xb3, 0xf8, 0xe0, 0x6e, 0xc0, 0xee, 0xeb, 0x85, 0x19, 0xc5, 0x8c,
-	0x33, 0xd4, 0x54, 0x7b, 0x63, 0xc3, 0xf3, 0xf9, 0xfe, 0xb0, 0x6f, 0xba, 0x2c, 0xb4, 0x3c, 0xe6,
-	0x31, 0x4b, 0xbe, 0xd0, 0x1f, 0xde, 0x95, 0x3b, 0xb9, 0x91, 0xab, 0xf4, 0xa2, 0xb1, 0xec, 0x31,
-	0xe6, 0x05, 0x44, 0xa8, 0xb0, 0x1c, 0x4a, 0x19, 0x77, 0xb8, 0xcf, 0x68, 0x92, 0x9d, 0x5e, 0x3e,
-	0xb8, 0x92, 0x98, 0x3e, 0x13, 0xa7, 0xa1, 0xe3, 0xee, 0xfb, 0x94, 0xc4, 0x63, 0x2b, 0x23, 0x4a,
-	0xac, 0x90, 0x70, 0xc7, 0x1a, 0x75, 0x2d, 0x8f, 0x50, 0x12, 0x3b, 0x9c, 0x0c, 0xb2, 0x5b, 0x3b,
-	0x05, 0x04, 0x27, 0x96, 0x4a, 0xef, 0xc9, 0x45, 0x7e, 0x55, 0x7f, 0xc7, 0xa8, 0xeb, 0x04, 0xd1,
-	0xbe, 0x33, 0x29, 0x04, 0xe7, 0xaa, 0x2d, 0x97, 0xc5, 0x64, 0x8a, 0x22, 0xfc, 0x4b, 0x03, 0x3e,
-	0xfb, 0x41, 0x26, 0x69, 0x27, 0x26, 0x0e, 0x27, 0x36, 0xf9, 0x68, 0x48, 0x12, 0x8e, 0x96, 0xe1,
-	0x69, 0xea, 0x84, 0x24, 0x89, 0x1c, 0x97, 0xb4, 0xc1, 0x0a, 0x58, 0x3b, 0x6d, 0xe7, 0x0f, 0xd0,
-	0x1d, 0xa8, 0xed, 0xd5, 0x6e, 0xac, 0x80, 0xb5, 0xc5, 0xad, 0x6b, 0x66, 0xce, 0x6c, 0x2a, 0x66,
-	0xb9, 0x30, 0xa3, 0x03, 0xcf, 0x14, 0xcc, 0xa6, 0x36, 0xb9, 0x62, 0x36, 0x95, 0x6e, 0x5b, 0x8b,
-	0x43, 0x1d, 0x08, 0x7d, 0x9a, 0x70, 0x87, 0xba, 0xe4, 0xe6, 0x6e, 0xfb, 0x84, 0xd4, 0x5c, 0x78,
-	0x82, 0x30, 0x3c, 0x93, 0x90, 0x78, 0x44, 0xe2, 0xdd, 0x78, 0x6c, 0x0f, 0x69, 0x7b, 0x61, 0x05,
-	0xac, 0x35, 0xed, 0xd2, 0x33, 0x74, 0x07, 0x2e, 0xb9, 0xf2, 0x6b, 0xde, 0x8d, 0xa4, 0x33, 0xda,
-	0x4f, 0x48, 0xc6, 0x6d, 0x33, 0x35, 0x89, 0x59, 0xf4, 0x46, 0x8e, 0x27, 0xbc, 0x61, 0x8e, 0xba,
-	0xe6, 0x4e, 0xf1, 0xaa, 0x5d, 0x96, 0x84, 0x1f, 0x02, 0x88, 0x14, 0xf5, 0x1e, 0xe1, 0xca, 0x5c,
-	0x08, 0x2e, 0x08, 0xeb, 0x64, 0x96, 0x92, 0xeb, 0xb2, 0x09, 0x1b, 0xc7, 0x4d, 0x78, 0x0b, 0x42,
-	0x8f, 0x70, 0x05, 0x78, 0x42, 0x02, 0x6e, 0x56, 0x03, 0xdc, 0xd3, 0xf7, 0xec, 0x82, 0x0c, 0xfc,
-	0x05, 0x80, 0x4f, 0x2b, 0xb4, 0x9e, 0x9f, 0xf0, 0x6a, 0xae, 0xbc, 0x0d, 0x17, 0x03, 0x3f, 0xd1,
-	0x20, 0xa9, 0x37, 0xbb, 0xd5, 0x40, 0x7a, 0xf9, 0x45, 0xbb, 0x28, 0x05, 0x7b, 0xf0, 0x79, 0xed,
-	0x5a, 0x92, 0x0c, 0xfb, 0xa1, 0x5f, 0xc3, 0x52, 0x06, 0x6c, 0x86, 0x24, 0x64, 0xfe, 0xc7, 0x64,
-	0x20, 0xed, 0xd4, 0xb4, 0xf5, 0x1e, 0xbf, 0x05, 0x9f, 0xc9, 0x15, 0xf1, 0x78, 0x3c, 0xb7, 0x16,
-	0x7c, 0x33, 0xcf, 0x04, 0x81, 0x1c, 0x92, 0xf9, 0x45, 0xf5, 0x60, 0x5b, 0x89, 0x7a, 0x8f, 0xc4,
-	0xa1, 0x4f, 0x0b, 0x79, 0xf5, 0xff, 0xa5, 0xbd, 0x0d, 0x9f, 0x53, 0xd2, 0x6e, 0x0f, 0x93, 0x88,
-	0xd0, 0xc1, 0xfc, 0xb2, 0xbe, 0x2b, 0x44, 0x6f, 0x8f, 0x79, 0xf3, 0xfb, 0xa4, 0x0d, 0x4f, 0x45,
-	0x6c, 0xf0, 0x8e, 0xb8, 0x94, 0xa6, 0xa8, 0xda, 0xa2, 0xeb, 0x10, 0x06, 0xcc, 0x53, 0xe1, 0xb4,
-	0x20, 0xc3, 0xe9, 0x42, 0x21, 0x9c, 0x4c, 0x51, 0x8b, 0x44, 0xf0, 0xdc, 0x62, 0x83, 0x9e, 0x7e,
-	0xd1, 0x2e, 0x5c, 0xc2, 0xdf, 0x83, 0xdc, 0x17, 0xbb, 0x24, 0x20, 0x35, 0xac, 0x27, 0x4a, 0xc1,
-	0x40, 0x8a, 0x28, 0x67, 0x5a, 0xc5, 0x52, 0xb0, 0x5b, 0xbc, 0x6a, 0x97, 0x25, 0xe1, 0x76, 0xee,
-	0x18, 0x45, 0x99, 0x44, 0x8c, 0x26, 0x04, 0x7f, 0x29, 0x3e, 0xc0, 0xe1, 0xee, 0xbe, 0x3a, 0x4f,
-	0x1e, 0x63, 0x2e, 0x7e, 0x9a, 0xbb, 0x5c, 0x32, 0xdd, 0x18, 0x11, 0x2a, 0x2d, 0xc9, 0xc7, 0x91,
-	0xb6, 0xa4, 0x58, 0xa3, 0xf7, 0xe1, 0x49, 0xd6, 0xbf, 0x47, 0x5c, 0xfe, 0x68, 0x6a, 0x7a, 0x26,
-	0x0c, 0xbf, 0x01, 0x9b, 0x3d, 0xe6, 0xdd, 0xa0, 0x3c, 0x1e, 0x8b, 0xb8, 0x71, 0x19, 0xe5, 0x84,
-	0xf2, 0x4c, 0xb3, 0xda, 0x16, 0x23, 0xaa, 0x51, 0x8a, 0x28, 0xfc, 0x55, 0xa9, 0xae, 0x51, 0xfe,
-	0xb8, 0x5b, 0xd4, 0xd6, 0xdf, 0x2d, 0x78, 0x56, 0xa7, 0x24, 0x89, 0x47, 0xbe, 0x4b, 0xd0, 0x37,
-	0x00, 0xb6, 0xd2, 0xc6, 0xa1, 0x4e, 0xd0, 0xf9, 0x5c, 0xda, 0xd4, 0x1e, 0x6b, 0xd4, 0x03, 0xc2,
-	0x6b, 0x9f, 0xff, 0xf1, 0xd7, 0xd7, 0x0d, 0x8c, 0xcf, 0xc9, 0x16, 0x3f, 0xea, 0xea, 0x99, 0x20,
-	0xb1, 0x1e, 0x68, 0x3b, 0x1c, 0x5e, 0x05, 0xeb, 0xe8, 0x21, 0x80, 0x8b, 0x7b, 0x84, 0x6b, 0xb2,
-	0xe5, 0x49, 0xb2, 0xbc, 0x97, 0xd5, 0xc5, 0xba, 0x24, 0xb1, 0x5e, 0x42, 0x2f, 0xce, 0xc4, 0x4a,
-	0xd7, 0x87, 0x02, 0x6d, 0x49, 0x84, 0xae, 0xce, 0x14, 0x74, 0x6e, 0x12, 0xae, 0xd0, 0xcd, 0x8c,
-	0xeb, 0xb5, 0xe8, 0x84, 0x24, 0xbc, 0x2a, 0x09, 0xcf, 0xa3, 0xd9, 0x86, 0x43, 0x9f, 0xc0, 0x56,
-	0x39, 0x89, 0x4b, 0x1e, 0x9d, 0x96, 0xde, 0xc6, 0x14, 0xc3, 0xe6, 0x39, 0x87, 0x2f, 0x4a, 0xbd,
-	0xab, 0xe8, 0x85, 0xe3, 0x7a, 0x37, 0x88, 0x38, 0x2f, 0x69, 0xdf, 0x04, 0xe8, 0x33, 0x00, 0x5b,
-	0x69, 0x61, 0x99, 0x15, 0x52, 0xa5, 0x02, 0x69, 0xac, 0xfc, 0xfb, 0x0b, 0x59, 0x6d, 0xca, 0xdc,
-	0xb3, 0x5e, 0xcd, 0x3d, 0x3f, 0x00, 0xb8, 0x24, 0x1b, 0xab, 0x46, 0xe8, 0x4c, 0x6a, 0x28, 0x76,
-	0xde, 0xba, 0xd1, 0xf3, 0x8a, 0xc4, 0xb3, 0x8c, 0xf5, 0x2a, 0x78, 0x56, 0x2c, 0x34, 0x8b, 0x08,
-	0xff, 0x19, 0xc0, 0x27, 0xd5, 0xa4, 0xa1, 0x51, 0x2f, 0x4c, 0x43, 0x2d, 0x4d, 0x23, 0x75, 0x69,
-	0xaf, 0x48, 0xda, 0x2d, 0x63, 0xa3, 0x22, 0x6d, 0xaa, 0x5c, 0x00, 0xff, 0x08, 0x60, 0x2b, 0x9d,
-	0x33, 0x66, 0x39, 0xb7, 0x34, 0x89, 0xd4, 0x85, 0x7d, 0x55, 0xc2, 0x6e, 0x1a, 0x17, 0x2b, 0xc3,
-	0x86, 0x44, 0xa0, 0xfe, 0x04, 0xe0, 0xd9, 0x6c, 0xf2, 0xd0, 0xac, 0x53, 0xe2, 0xac, 0x3c, 0x9c,
-	0xd4, 0x85, 0x7d, 0x4d, 0xc2, 0x76, 0x8d, 0x4b, 0x95, 0x60, 0x93, 0x54, 0xb7, 0xa0, 0xfd, 0x15,
-	0xc0, 0xa7, 0xf4, 0xd4, 0xa5, 0x79, 0xf1, 0x24, 0xef, 0xf1, 0xd1, 0xac, 0x2e, 0xf1, 0xeb, 0x92,
-	0x78, 0xdb, 0x30, 0x2b, 0x11, 0x73, 0xa5, 0x5d, 0x30, 0x7f, 0x0b, 0xe0, 0x19, 0xd1, 0xd9, 0x34,
-	0xee, 0xd4, 0x1a, 0x48, 0x1f, 0x55, 0xd4, 0x6e, 0x48, 0xd2, 0x97, 0x31, 0x9e, 0x4d, 0x1a, 0xf8,
-	0x54, 0x86, 0xea, 0x18, 0x9e, 0x4a, 0x67, 0xb5, 0x64, 0x5a, 0xe3, 0xc8, 0xc7, 0x48, 0x03, 0xe5,
-	0xa7, 0xaa, 0xe1, 0xe3, 0xab, 0x52, 0xd7, 0x65, 0xb4, 0x55, 0xc9, 0x2a, 0x0f, 0xb2, 0x9e, 0x7f,
-	0x68, 0x05, 0xcc, 0xdb, 0x04, 0x6f, 0x5e, 0xfb, 0xed, 0xa8, 0x03, 0x7e, 0x3f, 0xea, 0x80, 0x3f,
-	0x8f, 0x3a, 0xe0, 0x43, 0xeb, 0xbf, 0x7e, 0x8b, 0x8f, 0xfd, 0xe3, 0xf7, 0x4f, 0xca, 0xbf, 0xdc,
-	0xed, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xcf, 0x7b, 0x3c, 0x26, 0x04, 0x10, 0x00, 0x00,
+	// 1095 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x98, 0xdf, 0x6e, 0xdc, 0xc4,
+	0x17, 0xc7, 0x35, 0x49, 0x7e, 0xe9, 0x66, 0xd2, 0x6c, 0x7f, 0x0c, 0x50, 0x56, 0x56, 0xba, 0x4d,
+	0x07, 0x02, 0x51, 0xda, 0xd8, 0xd9, 0xa4, 0x40, 0xa9, 0x54, 0xa4, 0x92, 0x54, 0xa1, 0x68, 0x05,
+	0x95, 0x0b, 0x42, 0xe5, 0xce, 0xeb, 0x9d, 0x38, 0x6e, 0xec, 0x19, 0x63, 0xcf, 0x6e, 0xb5, 0x54,
+	0x01, 0xc1, 0x25, 0x17, 0xdc, 0x70, 0x83, 0xe0, 0x06, 0x09, 0x90, 0x90, 0x90, 0xf8, 0xf3, 0x16,
+	0x5c, 0x22, 0xf1, 0x02, 0x28, 0x42, 0xe2, 0x35, 0xd0, 0x8c, 0x3d, 0x63, 0x3b, 0xbb, 0x2c, 0x26,
+	0xae, 0xd4, 0xbb, 0x19, 0x8f, 0xe7, 0x7c, 0x3f, 0x3e, 0xe7, 0xcc, 0x9c, 0xb3, 0x0b, 0x57, 0xa3,
+	0x43, 0xcf, 0x72, 0x22, 0xdf, 0x0d, 0x7c, 0x42, 0xb9, 0xf5, 0x80, 0xc5, 0x87, 0xfb, 0x01, 0x7b,
+	0xa0, 0x07, 0x66, 0x14, 0x33, 0xce, 0x50, 0x43, 0xcd, 0x8d, 0x0d, 0xcf, 0xe7, 0x07, 0x83, 0x9e,
+	0xe9, 0xb2, 0xd0, 0xf2, 0x98, 0xc7, 0x2c, 0xf9, 0x42, 0x6f, 0xb0, 0x2f, 0x67, 0x72, 0x22, 0x47,
+	0xe9, 0x46, 0x63, 0xd9, 0x63, 0xcc, 0x0b, 0x88, 0x90, 0xb0, 0x1c, 0x4a, 0x19, 0x77, 0xb8, 0xcf,
+	0x68, 0x92, 0xad, 0x5e, 0x3d, 0xbc, 0x96, 0x98, 0x3e, 0x13, 0xab, 0xa1, 0xe3, 0x1e, 0xf8, 0x94,
+	0xc4, 0x23, 0x2b, 0x23, 0x4a, 0xac, 0x90, 0x70, 0xc7, 0x1a, 0x76, 0x2c, 0x8f, 0x50, 0x12, 0x3b,
+	0x9c, 0xf4, 0xb3, 0x5d, 0x3b, 0x05, 0x04, 0x27, 0x96, 0xa2, 0xf7, 0xe5, 0x20, 0xdf, 0xaa, 0xbf,
+	0x63, 0xd8, 0x71, 0x82, 0xe8, 0xc0, 0x19, 0x37, 0x82, 0x73, 0x69, 0xcb, 0x65, 0x31, 0x99, 0x20,
+	0x84, 0x7f, 0x9a, 0x81, 0x4f, 0xbf, 0x9b, 0x59, 0xda, 0x89, 0x89, 0xc3, 0x89, 0x4d, 0xde, 0x1f,
+	0x90, 0x84, 0xa3, 0x65, 0xb8, 0x40, 0x9d, 0x90, 0x24, 0x91, 0xe3, 0x92, 0x16, 0x58, 0x01, 0x6b,
+	0x0b, 0x76, 0xfe, 0x00, 0xdd, 0x83, 0xda, 0x5f, 0xad, 0x99, 0x15, 0xb0, 0xb6, 0xb8, 0x75, 0xc3,
+	0xcc, 0x99, 0x4d, 0xc5, 0x2c, 0x07, 0x66, 0x74, 0xe8, 0x99, 0x82, 0xd9, 0xd4, 0x2e, 0x57, 0xcc,
+	0xa6, 0xd2, 0xb6, 0xb5, 0x39, 0xd4, 0x86, 0xd0, 0xa7, 0x09, 0x77, 0xa8, 0x4b, 0x6e, 0xef, 0xb6,
+	0x66, 0xa5, 0x72, 0xe1, 0x09, 0xc2, 0xf0, 0x6c, 0x42, 0xe2, 0x21, 0x89, 0x77, 0xe3, 0x91, 0x3d,
+	0xa0, 0xad, 0xb9, 0x15, 0xb0, 0xd6, 0xb0, 0x4b, 0xcf, 0xd0, 0x3d, 0xb8, 0xe4, 0xca, 0xaf, 0x79,
+	0x2b, 0x92, 0xc1, 0x68, 0xfd, 0x4f, 0x32, 0x6e, 0x9b, 0xa9, 0x4b, 0xcc, 0x62, 0x34, 0x72, 0x3c,
+	0x11, 0x0d, 0x73, 0xd8, 0x31, 0x77, 0x8a, 0x5b, 0xed, 0xb2, 0x25, 0xfc, 0x33, 0x80, 0x48, 0x51,
+	0xef, 0x11, 0xae, 0xdc, 0x85, 0xe0, 0x9c, 0xf0, 0x4e, 0xe6, 0x29, 0x39, 0x2e, 0xbb, 0x70, 0xe6,
+	0xa4, 0x0b, 0xef, 0x40, 0xe8, 0x11, 0xae, 0x00, 0x67, 0x25, 0xe0, 0x66, 0x35, 0xc0, 0x3d, 0xbd,
+	0xcf, 0x2e, 0xd8, 0x40, 0xe7, 0xe1, 0xfc, 0xbe, 0x4f, 0x82, 0x7e, 0x22, 0x7d, 0xb2, 0x60, 0x67,
+	0x33, 0xfc, 0x35, 0x80, 0x4f, 0x2a, 0xe4, 0xae, 0x9f, 0xf0, 0x6a, 0x21, 0xbe, 0x0b, 0x17, 0x03,
+	0x3f, 0xd1, 0x80, 0x69, 0x94, 0x3b, 0xd5, 0x00, 0xbb, 0xf9, 0x46, 0xbb, 0x68, 0xa5, 0x80, 0x38,
+	0x5b, 0x42, 0xf4, 0xe0, 0x33, 0x3a, 0x15, 0x48, 0x32, 0xe8, 0x85, 0x7e, 0x0d, 0xcf, 0x1a, 0xb0,
+	0x11, 0x92, 0x90, 0xf9, 0x1f, 0x90, 0xbe, 0x94, 0x69, 0xd8, 0x7a, 0x8e, 0x5f, 0x87, 0x4f, 0xe5,
+	0x42, 0x3c, 0x1e, 0x9d, 0x5a, 0x05, 0xdf, 0xce, 0x4f, 0x8e, 0x40, 0x0e, 0xc9, 0xe9, 0x4d, 0x75,
+	0x61, 0x4b, 0x99, 0x7a, 0x9b, 0xc4, 0xa1, 0x4f, 0x0b, 0xe7, 0xf0, 0xbf, 0x5b, 0x7b, 0x03, 0x9e,
+	0x57, 0xd6, 0xee, 0x0e, 0x92, 0x88, 0xd0, 0xfe, 0xe9, 0x6d, 0x7d, 0x53, 0xc8, 0xf6, 0x2e, 0xf3,
+	0x4e, 0x1f, 0x93, 0x16, 0x3c, 0x13, 0xb1, 0xfe, 0x9b, 0x62, 0x53, 0x1a, 0x79, 0x35, 0x45, 0x37,
+	0x21, 0x0c, 0x98, 0xa7, 0xd2, 0x6c, 0x4e, 0xa6, 0xd9, 0xa5, 0x42, 0x9a, 0x99, 0xe2, 0xee, 0x12,
+	0x49, 0x75, 0x87, 0xf5, 0xbb, 0xfa, 0x45, 0xbb, 0xb0, 0x09, 0x7f, 0x0b, 0xf2, 0x58, 0xec, 0x92,
+	0x80, 0xd4, 0xf0, 0x9e, 0xb8, 0x3a, 0xfa, 0xd2, 0x44, 0xf9, 0x64, 0x56, 0xbc, 0x3a, 0x76, 0x8b,
+	0x5b, 0xed, 0xb2, 0x25, 0xdc, 0xca, 0x03, 0xa3, 0x28, 0x93, 0x88, 0xd1, 0x84, 0xe0, 0x4f, 0xc5,
+	0x07, 0x38, 0xdc, 0x3d, 0x50, 0xeb, 0xc9, 0xe3, 0x3b, 0xa3, 0xf8, 0xa3, 0x3c, 0xe4, 0x92, 0xe9,
+	0xd6, 0x90, 0x50, 0xe9, 0x49, 0x3e, 0x8a, 0xb4, 0x27, 0xc5, 0x18, 0xbd, 0x03, 0xe7, 0x59, 0xef,
+	0x3e, 0x71, 0xf9, 0xa3, 0xa9, 0x01, 0x99, 0x31, 0xfc, 0x2a, 0x6c, 0x74, 0x99, 0x77, 0x8b, 0xf2,
+	0x78, 0x24, 0xf2, 0xc6, 0x65, 0x94, 0x13, 0xca, 0x33, 0x65, 0x35, 0x2d, 0x66, 0xd4, 0x4c, 0x29,
+	0xa3, 0xf0, 0x67, 0xa5, 0xfb, 0x8e, 0xf2, 0xc7, 0x5d, 0xd2, 0xb6, 0xfe, 0x6a, 0xc2, 0x73, 0xfa,
+	0x48, 0x92, 0x78, 0xe8, 0xbb, 0x04, 0x7d, 0x09, 0x60, 0x33, 0x2d, 0x34, 0x6a, 0x05, 0x5d, 0xcc,
+	0xad, 0x4d, 0xac, 0xc9, 0x46, 0x3d, 0x20, 0xbc, 0xf6, 0xc9, 0xef, 0x7f, 0x7e, 0x3e, 0x83, 0xf1,
+	0x05, 0xd9, 0x12, 0x0c, 0x3b, 0xba, 0x87, 0x48, 0xac, 0x87, 0xda, 0x0f, 0x47, 0xd7, 0xc1, 0x3a,
+	0xfa, 0x02, 0xc0, 0xc5, 0x3d, 0xc2, 0x35, 0xd9, 0xf2, 0x38, 0x59, 0x5e, 0xfb, 0xea, 0x62, 0x5d,
+	0x91, 0x58, 0xcf, 0xa3, 0xe7, 0xa6, 0x62, 0xa5, 0xe3, 0x23, 0x81, 0xb6, 0x24, 0x52, 0x57, 0x9f,
+	0x14, 0x74, 0x61, 0x1c, 0xae, 0x50, 0xe5, 0x8c, 0x9b, 0xb5, 0xe8, 0x84, 0x25, 0xbc, 0x2a, 0x09,
+	0x2f, 0xa2, 0xe9, 0x8e, 0x43, 0x1f, 0xc2, 0x66, 0xf9, 0x10, 0x97, 0x22, 0x3a, 0xe9, 0x78, 0x1b,
+	0x13, 0x1c, 0x9b, 0x9f, 0x39, 0x7c, 0x59, 0xea, 0xae, 0xa2, 0x67, 0x4f, 0xea, 0x6e, 0x10, 0xb1,
+	0x5e, 0x52, 0xdf, 0x04, 0xe8, 0x63, 0x00, 0x9b, 0xe9, 0xc5, 0x32, 0x2d, 0xa5, 0x4a, 0x17, 0xa4,
+	0xb1, 0xf2, 0xcf, 0x2f, 0x64, 0x77, 0x53, 0x16, 0x9e, 0xf5, 0x6a, 0xe1, 0xf9, 0x0e, 0xc0, 0x25,
+	0x59, 0x58, 0x35, 0x42, 0x7b, 0x5c, 0xa1, 0x58, 0x79, 0xeb, 0x66, 0xcf, 0x8b, 0x12, 0xcf, 0x32,
+	0xd6, 0xab, 0xe0, 0x59, 0xb1, 0x50, 0x16, 0x19, 0xfe, 0x23, 0x80, 0xff, 0x57, 0x9d, 0x86, 0x46,
+	0xbd, 0x34, 0x09, 0xb5, 0xd4, 0x8d, 0xd4, 0xa5, 0xbd, 0x26, 0x69, 0xb7, 0x8c, 0x8d, 0x8a, 0xb4,
+	0xa9, 0xb8, 0x00, 0xfe, 0x1e, 0xc0, 0x66, 0xda, 0x67, 0x4c, 0x0b, 0x6e, 0xa9, 0x13, 0xa9, 0x0b,
+	0xfb, 0x92, 0x84, 0xdd, 0x34, 0x2e, 0x57, 0x86, 0x0d, 0x89, 0x40, 0xfd, 0x01, 0xc0, 0x73, 0x59,
+	0xe7, 0xa1, 0x59, 0x27, 0xe4, 0x59, 0xb9, 0x39, 0xa9, 0x0b, 0xfb, 0xb2, 0x84, 0xed, 0x18, 0x57,
+	0x2a, 0xc1, 0x26, 0xa9, 0xb6, 0xa0, 0xfd, 0x05, 0xc0, 0x27, 0x74, 0xd7, 0xa5, 0x79, 0xf1, 0x38,
+	0xef, 0xc9, 0xd6, 0xac, 0x2e, 0xf1, 0x2b, 0x92, 0x78, 0xdb, 0x30, 0x2b, 0x11, 0x73, 0xa5, 0x2e,
+	0x98, 0xbf, 0x02, 0xf0, 0xac, 0xa8, 0x6c, 0x1a, 0x77, 0xe2, 0x1d, 0x48, 0x1f, 0x55, 0xd6, 0x6e,
+	0x48, 0xd2, 0x17, 0x30, 0x9e, 0x4e, 0x1a, 0xf8, 0x54, 0xa6, 0xea, 0x08, 0x9e, 0x49, 0x7b, 0xb5,
+	0x64, 0x52, 0xe1, 0xc8, 0xdb, 0x48, 0x03, 0xe5, 0xab, 0xaa, 0xe0, 0xe3, 0xeb, 0x52, 0xeb, 0x2a,
+	0xda, 0xaa, 0xe4, 0x95, 0x87, 0x59, 0xcd, 0x3f, 0xb2, 0x02, 0xe6, 0x6d, 0x82, 0xd7, 0x6e, 0xfc,
+	0x7a, 0xdc, 0x06, 0xbf, 0x1d, 0xb7, 0xc1, 0x1f, 0xc7, 0x6d, 0xf0, 0x9e, 0xf5, 0x6f, 0x3f, 0xa3,
+	0x4f, 0xfc, 0x27, 0xd0, 0x9b, 0x97, 0xbf, 0x8a, 0xb7, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x88,
+	0xf6, 0xfb, 0x76, 0x34, 0x10, 0x00, 0x00,
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -1633,6 +1653,13 @@ func (m *WorkflowGetRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i -= len(m.XXX_unrecognized)
 		copy(dAtA[i:], m.XXX_unrecognized)
 	}
+	if len(m.Fields) > 0 {
+		i -= len(m.Fields)
+		copy(dAtA[i:], m.Fields)
+		i = encodeVarintWorkflow(dAtA, i, uint64(len(m.Fields)))
+		i--
+		dAtA[i] = 0x22
+	}
 	if m.GetOptions != nil {
 		{
 			size, err := m.GetOptions.MarshalToSizedBuffer(dAtA[:i])
@@ -1686,6 +1713,13 @@ func (m *WorkflowListRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i -= len(m.XXX_unrecognized)
 		copy(dAtA[i:], m.XXX_unrecognized)
 	}
+	if len(m.Fields) > 0 {
+		i -= len(m.Fields)
+		copy(dAtA[i:], m.Fields)
+		i = encodeVarintWorkflow(dAtA, i, uint64(len(m.Fields)))
+		i--
+		dAtA[i] = 0x1a
+	}
 	if m.ListOptions != nil {
 		{
 			size, err := m.ListOptions.MarshalToSizedBuffer(dAtA[:i])
@@ -2302,6 +2336,10 @@ func (m *WorkflowGetRequest) Size() (n int) {
 		l = m.GetOptions.Size()
 		n += 1 + l + sovWorkflow(uint64(l))
 	}
+	l = len(m.Fields)
+	if l > 0 {
+		n += 1 + l + sovWorkflow(uint64(l))
+	}
 	if m.XXX_unrecognized != nil {
 		n += len(m.XXX_unrecognized)
 	}
@@ -2322,6 +2360,10 @@ func (m *WorkflowListRequest) Size() (n int) {
 		l = m.ListOptions.Size()
 		n += 1 + l + sovWorkflow(uint64(l))
 	}
+	l = len(m.Fields)
+	if l > 0 {
+		n += 1 + l + sovWorkflow(uint64(l))
+	}
 	if m.XXX_unrecognized != nil {
 		n += len(m.XXX_unrecognized)
 	}
@@ -2920,6 +2962,38 @@ func (m *WorkflowGetRequest) Unmarshal(dAtA []byte) error {
 				return err
 			}
 			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Fields", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowWorkflow
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthWorkflow
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthWorkflow
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Fields = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipWorkflow(dAtA[iNdEx:])
@@ -3042,6 +3116,38 @@ func (m *WorkflowListRequest) Unmarshal(dAtA []byte) error {
 				return err
 			}
 			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Fields", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowWorkflow
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthWorkflow
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthWorkflow
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Fields = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipWorkflow(dAtA[iNdEx:])
diff --git a/pkg/apiclient/workflow/workflow.proto b/pkg/apiclient/workflow/workflow.proto
index 781f795e3..c7c4d0001 100644
--- a/pkg/apiclient/workflow/workflow.proto
+++ b/pkg/apiclient/workflow/workflow.proto
@@ -24,11 +24,15 @@ message WorkflowGetRequest {
     string name = 1;
     string namespace = 2;
     k8s.io.apimachinery.pkg.apis.meta.v1.GetOptions getOptions = 3;
+    // Fields to be included or excluded in the response. e.g. "spec,status.phase", "-status.nodes"
+    string fields = 4;
 }
 
 message WorkflowListRequest {
     string namespace = 1;
     k8s.io.apimachinery.pkg.apis.meta.v1.ListOptions listOptions = 2;
+    // Fields to be included or excluded in the response. e.g. "items.spec,items.status.phase", "-items.status.nodes"
+    string fields = 3;
 }
 
 message WorkflowResubmitRequest {
diff --git a/pkg/apiclient/workflow/workflow.swagger.json b/pkg/apiclient/workflow/workflow.swagger.json
index 4f4861ae5..dc917dd94 100644
--- a/pkg/apiclient/workflow/workflow.swagger.json
+++ b/pkg/apiclient/workflow/workflow.swagger.json
@@ -173,6 +173,13 @@
             "in": "query",
             "required": false,
             "type": "string"
+          },
+          {
+            "name": "fields",
+            "description": "Fields to be included or excluded in the response. e.g. \"items.spec,items.status.phase\", \"-items.status.nodes\".",
+            "in": "query",
+            "required": false,
+            "type": "string"
           }
         ],
         "tags": [
@@ -272,6 +279,13 @@
             "in": "query",
             "required": false,
             "type": "string"
+          },
+          {
+            "name": "fields",
+            "description": "Fields to be included or excluded in the response. e.g. \"spec,status.phase\", \"-status.nodes\".",
+            "in": "query",
+            "required": false,
+            "type": "string"
           }
         ],
         "tags": [
diff --git a/ui/src/app/archived-workflows/components/archived-workflow-details/archived-workflow-details.tsx b/ui/src/app/archived-workflows/components/archived-workflow-details/archived-workflow-details.tsx
index bc9b5bfe5..d2fb941fa 100644
--- a/ui/src/app/archived-workflows/components/archived-workflow-details/archived-workflow-details.tsx
+++ b/ui/src/app/archived-workflows/components/archived-workflow-details/archived-workflow-details.tsx
@@ -9,8 +9,10 @@ import {Loading} from '../../../shared/components/loading';
 import {YamlEditor} from '../../../shared/components/yaml/yaml-editor';
 import {services} from '../../../shared/services';
 import {
+    defaultWorkflowDagRenderOptions,
     WorkflowArtifacts,
     WorkflowDag,
+    WorkflowDagRenderOptions,
     WorkflowLogsViewer,
     WorkflowNodeInfo,
     WorkflowParametersPanel,
@@ -18,10 +20,12 @@ import {
     WorkflowTimeline,
     WorkflowYamlViewer
 } from '../../../workflows/components';
+import {WorkflowDagRenderOptionsPanel} from '../../../workflows/components/workflow-dag/workflow-dag-render-options-panel';
 
 require('../../../workflows/components/workflow-details/workflow-details.scss');
 
 interface State {
+    workflowDagRenderOptions: WorkflowDagRenderOptions;
     workflow?: Workflow;
     error?: Error;
 }
@@ -61,7 +65,7 @@ export class ArchivedWorkflowDetails extends BasePage<RouteComponentProps<any>,
 
     constructor(props: RouteComponentProps<any>, context: any) {
         super(props, context);
-        this.state = {};
+        this.state = {workflowDagRenderOptions: defaultWorkflowDagRenderOptions};
     }
 
     public componentDidMount(): void {
@@ -102,6 +106,12 @@ export class ArchivedWorkflowDetails extends BasePage<RouteComponentProps<any>,
                     ],
                     tools: (
                         <div className='workflow-details__topbar-buttons'>
+                            {this.tab === 'workflow' && (
+                                <WorkflowDagRenderOptionsPanel
+                                    {...this.state.workflowDagRenderOptions}
+                                    onChange={workflowDagRenderOptions => this.setState({workflowDagRenderOptions})}
+                                />
+                            )}
                             <a className={classNames({active: this.tab === 'summary'})} onClick={() => (this.tab = 'summary')}>
                                 <i className='fa fa-columns' />
                             </a>
@@ -143,7 +153,12 @@ export class ArchivedWorkflowDetails extends BasePage<RouteComponentProps<any>,
                     <div>
                         <div className='workflow-details__graph-container'>
                             {this.tab === 'workflow' ? (
-                                <WorkflowDag workflow={this.state.workflow} selectedNodeId={this.nodeId} nodeClicked={node => (this.nodeId = node.id)} />
+                                <WorkflowDag
+                                    renderOptions={this.state.workflowDagRenderOptions}
+                                    workflow={this.state.workflow}
+                                    selectedNodeId={this.nodeId}
+                                    nodeClicked={node => (this.nodeId = node.id)}
+                                />
                             ) : (
                                 <WorkflowTimeline workflow={this.state.workflow} selectedNodeId={this.nodeId} nodeClicked={node => (this.nodeId = node.id)} />
                             )}
diff --git a/ui/src/app/shared/components/resource-submit.tsx b/ui/src/app/shared/components/resource-submit.tsx
index b7a9df274..2baf2b2c0 100644
--- a/ui/src/app/shared/components/resource-submit.tsx
+++ b/ui/src/app/shared/components/resource-submit.tsx
@@ -41,14 +41,45 @@ export class ResourceSubmit<T> extends React.Component<ResourceSubmitProps<T>, R
                                 <button type='submit' className='argo-button argo-button--base' disabled={formikApi.isSubmitting || this.state.invalid}>
                                     Submit
                                 </button>
-                                {this.state.error && (
+                                {this.state.error ? (
                                     <p>
                                         <i className='fa fa-exclamation-triangle status-icon--failed' />
                                         {this.state.error.response && this.state.error.response.body && this.state.error.response.body.message
                                             ? this.state.error.response.body.message
                                             : this.state.error.message}
                                     </p>
+                                ) : (
+                                    <p />
                                 )}
+                                <button type='button' className='argo-button argo-button--sm' id='uploadWf' onClick={() => document.getElementById('file').click()}>
+                                    Upload Workflow{' '}
+                                </button>
+                                <input
+                                    type='file'
+                                    style={{display: 'none'}}
+                                    id='file'
+                                    name='file'
+                                    onChange={e => {
+                                        try {
+                                            this.readFile(e.target.files, (newResource: string) => {
+                                                formikApi.setFieldValue('resource', jsYaml.load(e.currentTarget.value));
+                                                this.setState({
+                                                    error: undefined,
+                                                    invalid: false
+                                                });
+                                                formikApi.setFieldValue('resourceString', newResource);
+                                            });
+                                        } catch (e) {
+                                            this.setState({
+                                                error: {
+                                                    name: this.props.resourceName + ' is invalid',
+                                                    message: this.props.resourceName + ' is invalid' + (e.reason ? ': ' + e.reason : '')
+                                                },
+                                                invalid: true
+                                            });
+                                        }
+                                    }}
+                                />
                                 <textarea
                                     name={'resourceString'}
                                     className='yaml'
@@ -93,6 +124,22 @@ export class ResourceSubmit<T> extends React.Component<ResourceSubmitProps<T>, R
         );
     }
 
+    private readFile(files: FileList, handleRead: (newResource: string) => void) {
+        if (files.length !== 1) {
+            this.setState({
+                error: {
+                    name: 'Must upload exactly one file',
+                    message: 'Must upload exactly one file'
+                }
+            });
+        }
+        const fileReader = new FileReader();
+        fileReader.onload = () => {
+            handleRead(fileReader.result);
+        };
+        fileReader.readAsText(files.item(0));
+    }
+
     private renderParameterFields(sectionTitle: string, path: string, parameters: models.Parameter[], formikApi: any): JSX.Element {
         return (
             <div className='white-box__details' style={{paddingTop: '50px'}}>
diff --git a/ui/src/app/shared/services/requests.ts b/ui/src/app/shared/services/requests.ts
index cdf094305..65cd65cb8 100644
--- a/ui/src/app/shared/services/requests.ts
+++ b/ui/src/app/shared/services/requests.ts
@@ -55,7 +55,9 @@ export default {
             eventSource.onmessage = (msg: any) => observer.next(msg.data);
             eventSource.onerror = (e: any) => {
                 if (e.eventPhase === Event.AT_TARGET) {
-                    observer.complete();
+                    if (!allowAutoRetry) {
+                        observer.complete();
+                    }
                 } else {
                     observer.error(e);
                 }
diff --git a/ui/src/app/workflows/components/workflow-dag/workflow-dag-render-options-panel.tsx b/ui/src/app/workflows/components/workflow-dag/workflow-dag-render-options-panel.tsx
new file mode 100644
index 000000000..215d862a4
--- /dev/null
+++ b/ui/src/app/workflows/components/workflow-dag/workflow-dag-render-options-panel.tsx
@@ -0,0 +1,49 @@
+import * as classNames from 'classnames';
+import * as React from 'react';
+import {WorkflowDagRenderOptions} from './workflow-dag';
+
+export class WorkflowDagRenderOptionsPanel extends React.Component<WorkflowDagRenderOptions & {onChange: (changed: WorkflowDagRenderOptions) => void}> {
+    private get workflowDagRenderOptions() {
+        return this.props as WorkflowDagRenderOptions;
+    }
+
+    public render() {
+        return (
+            <>
+                <a
+                    className={classNames({active: this.props.horizontal})}
+                    onClick={() =>
+                        this.props.onChange({
+                            ...this.workflowDagRenderOptions,
+                            horizontal: !this.props.horizontal
+                        })
+                    }
+                    title='Horizontal layout'>
+                    <i className='fa fa-project-diagram' />
+                </a>
+                <a
+                    className={classNames({active: this.props.zoom > 1})}
+                    onClick={() =>
+                        this.props.onChange({
+                            ...this.workflowDagRenderOptions,
+                            zoom: this.props.zoom === 1 ? 2 : 1
+                        })
+                    }
+                    title='Zoom into the timeline'>
+                    2x
+                </a>
+                <a
+                    className={classNames({active: !this.props.hideSucceeded})}
+                    onClick={() =>
+                        this.props.onChange({
+                            ...this.workflowDagRenderOptions,
+                            hideSucceeded: !this.props.hideSucceeded
+                        })
+                    }
+                    title='Hide succeeded'>
+                    <i className='fa fa-check' />
+                </a>
+            </>
+        );
+    }
+}
diff --git a/ui/src/app/workflows/components/workflow-dag/workflow-dag.scss b/ui/src/app/workflows/components/workflow-dag/workflow-dag.scss
index b68d30399..38b847aa8 100644
--- a/ui/src/app/workflows/components/workflow-dag/workflow-dag.scss
+++ b/ui/src/app/workflows/components/workflow-dag/workflow-dag.scss
@@ -28,7 +28,6 @@
 
     &__node {
         position: absolute;
-        padding-left: 3.5em;
         box-shadow: 1px 1px 1px $argo-color-gray-4;
         background-color: white;
         border-radius: 4px;
@@ -39,7 +38,7 @@
             border-color: $argo-color-teal-6;
         }
 
-        &.virtual {
+        &.small {
             background-color: transparent;
             box-shadow: none;
             border: none;
@@ -49,7 +48,6 @@
                 content: '';
                 position: absolute;
                 display: block;
-                border-radius: 10px;
                 width: 20px;
                 height: 20px;
                 left: -10px;
@@ -57,11 +55,17 @@
                 border: 1px dashed $argo-color-gray-5;
             }
 
-            .workflow-dag__node-status, .workflow-dag__node-title {
+            .workflow-dag__node-title, .workflow-dag__node-status {
                 display: none;
             }
         }
-        &.virtual.active {
+        &.virtual {
+            &:after {
+                /* give virtual nodes a circle */
+                border-radius: 10px;
+            }
+        }
+        &.small.active {
             &:after {
                 border-color: $argo-color-teal-6;
             }
@@ -78,7 +82,6 @@
         left: -1px;
         bottom: -1px;
         top: -1px;
-        width: 3em;
         border-top-left-radius: 4px;
         border-bottom-left-radius: 4px;
         text-align: center;
@@ -121,7 +124,7 @@
             from {transform:rotate(0deg);}
             to {transform:rotate(360deg);}
         }
-    
+
         &--succeeded {
             background-color: $argo-success-color;
             &::after {
diff --git a/ui/src/app/workflows/components/workflow-dag/workflow-dag.tsx b/ui/src/app/workflows/components/workflow-dag/workflow-dag.tsx
index 7d232c96f..f76b81669 100644
--- a/ui/src/app/workflows/components/workflow-dag/workflow-dag.tsx
+++ b/ui/src/app/workflows/components/workflow-dag/workflow-dag.tsx
@@ -3,12 +3,26 @@ import * as dagre from 'dagre';
 import * as React from 'react';
 
 import * as models from '../../../../models';
+import {NODE_PHASE} from '../../../../models';
 import {Utils} from '../../../shared/utils';
 
+export const defaultWorkflowDagRenderOptions: WorkflowDagRenderOptions = {
+    hideSucceeded: false,
+    horizontal: false,
+    zoom: 1
+};
+
+export interface WorkflowDagRenderOptions {
+    horizontal: boolean;
+    zoom: number;
+    hideSucceeded: boolean;
+}
+
 export interface WorkflowDagProps {
     workflow: models.Workflow;
     selectedNodeId?: string;
     nodeClicked?: (node: models.NodeStatus) => any;
+    renderOptions: WorkflowDagRenderOptions;
 }
 
 interface Line {
@@ -21,21 +35,36 @@ interface Line {
 
 require('./workflow-dag.scss');
 
-const NODE_WIDTH = 182;
-const NODE_HEIGHT = 52;
-
 export class WorkflowDag extends React.Component<WorkflowDagProps> {
+    private get zoom() {
+        return this.props.renderOptions.zoom || 1;
+    }
+
+    private get nodeWidth() {
+        return 140 / this.zoom;
+    }
+
+    private get nodeHeight() {
+        return 52 / this.zoom;
+    }
+
     public render() {
         const graph = new dagre.graphlib.Graph();
-        graph.setGraph({});
+        // https://github.com/dagrejs/dagre/wiki
+        graph.setGraph({
+            edgesep: 20 / this.zoom,
+            nodesep: 50 / this.zoom,
+            rankdir: this.props.renderOptions.horizontal ? 'LR' : 'TB',
+            ranksep: 50 / this.zoom
+        });
         graph.setDefaultEdgeLabel(() => ({}));
         const nodes = (this.props.workflow.status && this.props.workflow.status.nodes) || {};
-        Object.keys(nodes).forEach(nodeId => {
-            const node = nodes[nodeId];
-            if (this.isVirtual(node)) {
-                graph.setNode(nodeId, {width: 1, height: 1, ...nodes[nodeId]});
+        Object.values(nodes).forEach(node => {
+            const label = Utils.shortNodeName(node);
+            if (this.isSmall(node)) {
+                graph.setNode(node.id, {label, width: 1, height: 1, ...nodes[node.id]});
             } else {
-                graph.setNode(nodeId, {width: NODE_WIDTH, height: NODE_HEIGHT, ...nodes[nodeId]});
+                graph.setNode(node.id, {label, width: this.nodeWidth, height: this.nodeHeight, ...nodes[node.id]});
             }
         });
         Object.keys(nodes).forEach(nodeId => {
@@ -60,7 +89,13 @@ export class WorkflowDag extends React.Component<WorkflowDagProps> {
             if (edge.points.length > 1) {
                 for (let i = 1; i < edge.points.length; i++) {
                     const toNode = nodes[edgeInfo.w];
-                    lines.push({x1: edge.points[i - 1].x, y1: edge.points[i - 1].y, x2: edge.points[i].x, y2: edge.points[i].y, noArrow: this.isVirtual(toNode)});
+                    lines.push({
+                        x1: edge.points[i - 1].x,
+                        y1: edge.points[i - 1].y,
+                        x2: edge.points[i].x,
+                        y2: edge.points[i].y,
+                        noArrow: this.isSmall(toNode)
+                    });
                 }
             }
             edges.push({from: edgeInfo.v, to: edgeInfo.w, lines});
@@ -70,19 +105,30 @@ export class WorkflowDag extends React.Component<WorkflowDagProps> {
             <div className='workflow-dag' style={{width: size.width, height: size.height}}>
                 {graph.nodes().map(id => {
                     const node = graph.node(id) as models.NodeStatus & dagre.Node;
-                    const shortName = Utils.shortNodeName(node);
+                    const statusWidth = 3 / this.zoom;
                     return (
                         <div
                             key={id}
-                            className={classNames('workflow-dag__node', {active: node.id === this.props.selectedNodeId, virtual: this.isVirtual(node)})}
-                            style={{left: node.x - node.width / 2, top: node.y - node.height / 2, width: node.width, height: node.height}}
+                            title={node.label}
+                            className={classNames('workflow-dag__node', {
+                                active: node.id === this.props.selectedNodeId,
+                                virtual: this.isVirtual(node),
+                                small: this.isSmall(node)
+                            })}
+                            style={{
+                                left: node.x - node.width / 2,
+                                top: node.y - node.height / 2,
+                                width: node.width,
+                                height: node.height,
+                                paddingLeft: 0.5 + statusWidth + 'em'
+                            }}
                             onClick={() => this.props.nodeClicked && this.props.nodeClicked(node)}>
                             <div
                                 className={`fas workflow-dag__node-status workflow-dag__node-status--${Utils.isNodeSuspended(node) ? 'suspended' : node.phase.toLocaleLowerCase()}`}
-                                style={{lineHeight: NODE_HEIGHT + 'px'}}
+                                style={{width: statusWidth + 'em', lineHeight: this.nodeHeight + 'px'}}
                             />
-                            <div className='workflow-dag__node-title' style={{lineHeight: NODE_HEIGHT + 'px'}}>
-                                {shortName}
+                            <div className='workflow-dag__node-title' style={{lineHeight: this.nodeHeight + 'px', fontSize: 1 / this.zoom + 'em'}}>
+                                {node.label}
                             </div>
                         </div>
                     );
@@ -98,7 +144,12 @@ export class WorkflowDag extends React.Component<WorkflowDagProps> {
                                 <div
                                     className={classNames('workflow-dag__line', {'workflow-dag__line--no-arrow': line.noArrow})}
                                     key={i}
-                                    style={{width: distance, left: xMid - distance / 2, top: yMid, transform: ` rotate(${angle}deg)`}}
+                                    style={{
+                                        width: distance,
+                                        left: xMid - distance / 2,
+                                        top: yMid,
+                                        transform: ` rotate(${angle}deg)`
+                                    }}
                                 />
                             );
                         })}
@@ -108,6 +159,10 @@ export class WorkflowDag extends React.Component<WorkflowDagProps> {
         );
     }
 
+    private isSmall(node: models.NodeStatus) {
+        return this.isVirtual(node) || (this.props.renderOptions.hideSucceeded && node.phase === NODE_PHASE.SUCCEEDED);
+    }
+
     private getOutboundNodes(nodeID: string): string[] {
         const node = this.props.workflow.status.nodes[nodeID];
         if (node.type === 'Pod' || node.type === 'Skipped') {
diff --git a/ui/src/app/workflows/components/workflow-details/workflow-details.tsx b/ui/src/app/workflows/components/workflow-details/workflow-details.tsx
index a717b0068..a18fc99bd 100644
--- a/ui/src/app/workflows/components/workflow-details/workflow-details.tsx
+++ b/ui/src/app/workflows/components/workflow-details/workflow-details.tsx
@@ -10,9 +10,10 @@ import {NodePhase} from '../../../../models';
 import {uiUrl} from '../../../shared/base';
 import {services} from '../../../shared/services';
 
-import {WorkflowArtifacts, WorkflowDag, WorkflowLogsViewer, WorkflowNodeInfo, WorkflowSummaryPanel, WorkflowTimeline, WorkflowYamlViewer} from '..';
+import {WorkflowArtifacts, WorkflowDag, WorkflowDagRenderOptions, WorkflowLogsViewer, WorkflowNodeInfo, WorkflowSummaryPanel, WorkflowTimeline, WorkflowYamlViewer} from '..';
 import {Consumer, ContextApis} from '../../../shared/context';
 import {Utils} from '../../../shared/utils';
+import {WorkflowDagRenderOptionsPanel} from '../workflow-dag/workflow-dag-render-options-panel';
 import {WorkflowParametersPanel} from '../workflow-parameters-panel';
 
 require('./workflow-details.scss');
@@ -25,7 +26,7 @@ function parseSidePanelParam(param: string) {
     return null;
 }
 
-export class WorkflowDetails extends React.Component<RouteComponentProps<any>, {workflow: models.Workflow}> {
+export class WorkflowDetails extends React.Component<RouteComponentProps<any>, {workflowDagRenderOptions: WorkflowDagRenderOptions; workflow: models.Workflow}> {
     public static contextTypes = {
         router: PropTypes.object,
         apis: PropTypes.object
@@ -48,7 +49,7 @@ export class WorkflowDetails extends React.Component<RouteComponentProps<any>, {
 
     constructor(props: RouteComponentProps<any>) {
         super(props);
-        this.state = {workflow: null};
+        this.state = {workflowDagRenderOptions: {horizontal: false, zoom: 1, hideSucceeded: false}, workflow: null};
     }
 
     public componentDidMount() {
@@ -131,6 +132,12 @@ export class WorkflowDetails extends React.Component<RouteComponentProps<any>, {
                             },
                             tools: (
                                 <div className='workflow-details__topbar-buttons'>
+                                    {this.selectedTabKey === 'workflow' && (
+                                        <WorkflowDagRenderOptionsPanel
+                                            {...this.state.workflowDagRenderOptions}
+                                            onChange={workflowDagRenderOptions => this.setState({workflowDagRenderOptions})}
+                                        />
+                                    )}
                                     <a className={classNames({active: this.selectedTabKey === 'summary'})} onClick={() => this.selectTab('summary')}>
                                         <i className='fa fa-columns' />
                                     </a>
@@ -149,7 +156,12 @@ export class WorkflowDetails extends React.Component<RouteComponentProps<any>, {
                                     <div>
                                         <div className='workflow-details__graph-container'>
                                             {(this.selectedTabKey === 'workflow' && (
-                                                <WorkflowDag workflow={this.state.workflow} selectedNodeId={this.selectedNodeId} nodeClicked={node => this.selectNode(node.id)} />
+                                                <WorkflowDag
+                                                    renderOptions={this.state.workflowDagRenderOptions}
+                                                    workflow={this.state.workflow}
+                                                    selectedNodeId={this.selectedNodeId}
+                                                    nodeClicked={node => this.selectNode(node.id)}
+                                                />
                                             )) || (
                                                 <WorkflowTimeline
                                                     workflow={this.state.workflow}
@@ -196,7 +208,7 @@ export class WorkflowDetails extends React.Component<RouteComponentProps<any>, {
         }
         services.workflows
             .delete(this.props.match.params.name, this.props.match.params.namespace)
-            .then(wfDeleteRes => ctx.navigation.goto(uiUrl(`workflows/`)))
+            .then(() => ctx.navigation.goto(uiUrl(`workflows/`)))
             .catch(error => {
                 this.appContext.apis.notifications.show({
                     content: 'Unable to delete workflow',
diff --git a/util/http/forwarders.go b/util/http/forwarders.go
deleted file mode 100644
index 0ab0b3e47..000000000
--- a/util/http/forwarders.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package http
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-
-	"github.com/golang/protobuf/proto"
-	"github.com/grpc-ecosystem/grpc-gateway/runtime"
-	"golang.org/x/net/context"
-)
-
-type sseMarshaller struct {
-	runtime.Marshaler
-}
-
-func (m *sseMarshaller) ContentType() string {
-	return "text/event-stream"
-}
-
-func (m *sseMarshaller) Marshal(v interface{}) ([]byte, error) {
-	dataBytes, err := json.Marshal(v)
-	if err != nil {
-		return nil, err
-	}
-	return []byte(fmt.Sprintf("data: %s \n\n", string(dataBytes))), nil
-}
-
-var (
-	StreamForwarder = func(
-		ctx context.Context,
-		mux *runtime.ServeMux,
-		marshaler runtime.Marshaler,
-		w http.ResponseWriter,
-		req *http.Request,
-		recv func() (proto.Message, error),
-		opts ...func(context.Context, http.ResponseWriter, proto.Message) error,
-	) {
-		if req.Header.Get("Accept") == "text/event-stream" {
-			w.Header().Set("Content-Type", "text/event-stream")
-			w.Header().Set("Transfer-Encoding", "chunked")
-			w.Header().Set("X-Content-Type-Options", "nosniff")
-			runtime.ForwardResponseStream(ctx, mux, &sseMarshaller{marshaler}, w, req, recv, opts...)
-		} else {
-			runtime.ForwardResponseStream(ctx, mux, marshaler, w, req, recv, opts...)
-		}
-	}
-)
diff --git a/util/kubeconfig/kubeconfig.go b/util/kubeconfig/kubeconfig.go
index 4833fa959..db40b77e8 100644
--- a/util/kubeconfig/kubeconfig.go
+++ b/util/kubeconfig/kubeconfig.go
@@ -5,6 +5,7 @@ import (
 	"net/http"
 	"os"
 	"strings"
+	"time"
 
 	"github.com/pkg/errors"
 
@@ -83,14 +84,14 @@ func GetBearerRestConfig(token string) (*restclient.Config, error) {
 }
 
 //Return the AuthString include Auth type(Basic or Bearer)
-func GetAuthString(in *restclient.Config) (string, error) {
+func GetAuthString(in *restclient.Config, explicitKubeConfigPath string) (string, error) {
 	//Checking Basic Auth
 	if in.Username != "" {
 		token, err := GetBasicAuthToken(in)
 		return BasicAuthScheme + " " + token, err
 	}
 
-	token, err := GetBearerToken(in)
+	token, err := GetBearerToken(in, explicitKubeConfigPath)
 	return BearerAuthScheme + " " + token, err
 }
 
@@ -104,7 +105,7 @@ func GetBasicAuthToken(in *restclient.Config) (string, error) {
 }
 
 // convert the REST config into a bearer token
-func GetBearerToken(in *restclient.Config) (string, error) {
+func GetBearerToken(in *restclient.Config, explicitKubeConfigPath string) (string, error) {
 
 	if len(in.BearerToken) > 0 {
 		return in.BearerToken, nil
@@ -145,26 +146,11 @@ func GetBearerToken(in *restclient.Config) (string, error) {
 	}
 	if in.AuthProvider != nil {
 		if in.AuthProvider.Name == "gcp" {
-			tc, err := in.TransportConfig()
-			if err != nil {
-				return "", err
-			}
-
-			auth, err := restclient.GetAuthProvider(in.Host, in.AuthProvider, in.AuthConfigPersister)
-			if err != nil {
-				return "", err
-			}
-
-			rt, err := transport.New(tc)
+			token := in.AuthProvider.Config["access-token"]
+			token, err := RefreshTokenIfExpired(in, explicitKubeConfigPath, token)
 			if err != nil {
 				return "", err
 			}
-			rt = auth.WrapTransport(rt)
-			req := http.Request{Header: map[string][]string{}}
-
-			_, _ = rt.RoundTrip(&req)
-
-			token := in.AuthProvider.Config["access-token"]
 			return strings.TrimPrefix(token, "Bearer "), nil
 		}
 	}
@@ -194,3 +180,58 @@ func decodeBasicAuthToken(auth string) (username, password string, ok bool) {
 	}
 	return cs[:s], cs[s+1:], true
 }
+
+func ReloadKubeConfig(explicitPath string) clientcmd.ClientConfig {
+	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
+	loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
+	loadingRules.ExplicitPath = explicitPath
+	overrides := clientcmd.ConfigOverrides{}
+	return clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, &overrides, os.Stdin)
+}
+
+func RefreshTokenIfExpired(restConfig *restclient.Config, explicitPath, curentToken string) (string, error) {
+	if restConfig.AuthProvider != nil {
+		timestr := restConfig.AuthProvider.Config["expiry"]
+		if timestr != "" {
+			t, err := time.Parse(time.RFC3339, timestr)
+			if err != nil {
+				return "", errors.Errorf("Invalid expiry date in Kubeconfig. %v", err)
+			}
+			if time.Now().After(t) {
+				err = RefreshAuthToken(restConfig)
+				if err != nil {
+					return "", err
+				}
+				config := ReloadKubeConfig(explicitPath)
+				restConfig, err = config.ClientConfig()
+				if err != nil {
+					return "", err
+				}
+				return restConfig.AuthProvider.Config["access-token"], nil
+			}
+		}
+	}
+	return curentToken, nil
+}
+
+func RefreshAuthToken(in *restclient.Config) error {
+	tc, err := in.TransportConfig()
+	if err != nil {
+		return err
+	}
+
+	auth, err := restclient.GetAuthProvider(in.Host, in.AuthProvider, in.AuthConfigPersister)
+	if err != nil {
+		return err
+	}
+
+	rt, err := transport.New(tc)
+	if err != nil {
+		return err
+	}
+	rt = auth.WrapTransport(rt)
+	req := http.Request{Header: map[string][]string{}}
+
+	_, _ = rt.RoundTrip(&req)
+	return nil
+}
diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go
index fcba59605..ca7aacd3c 100644
--- a/workflow/controller/operator.go
+++ b/workflow/controller/operator.go
@@ -624,9 +624,8 @@ func (woc *wfOperationCtx) processNodeRetries(node *wfv1.NodeStatus, retryStrate
 		if time.Now().Before(waitingDeadline) {
 			retryMessage := fmt.Sprintf("Retrying in %s", humanize.Duration(time.Until(waitingDeadline)))
 			return woc.markNodePhase(node.Name, node.Phase, retryMessage), false, nil
-		} else {
-			node = woc.markNodePhase(node.Name, node.Phase, "")
 		}
+		node = woc.markNodePhase(node.Name, node.Phase, "")
 	}
 
 	var retryOnFailed bool
@@ -1312,7 +1311,7 @@ func (woc *wfOperationCtx) executeTemplate(nodeName string, orgTmpl wfv1.Templat
 	// the container. The status of this node should be "Success" if any
 	// of the retries succeed. Otherwise, it is "Failed".
 	retryNodeName := ""
-	if processedTmpl.IsLeaf() && processedTmpl.RetryStrategy != nil {
+	if processedTmpl.RetryStrategy != nil {
 		retryNodeName = nodeName
 		retryParentNode := node
 		if retryParentNode == nil {
@@ -1337,19 +1336,20 @@ func (woc *wfOperationCtx) executeTemplate(nodeName string, orgTmpl wfv1.Templat
 		}
 		if lastChildNode != nil && !lastChildNode.Completed() {
 			// Last child node is still running.
-			return retryParentNode, nil
-		}
-
-		// Create a new child node and append it to the retry node.
-		nodeName = fmt.Sprintf("%s(%d)", retryNodeName, len(retryParentNode.Children))
-		woc.addChildNode(retryNodeName, nodeName)
-		node = nil
-
-		// Change the `pod.name` variable to the new retry node name
-		if processedTmpl.IsPodType() {
-			processedTmpl, err = common.SubstituteParams(processedTmpl, map[string]string{}, map[string]string{common.LocalVarPodName: woc.wf.NodeID(nodeName)})
-			if err != nil {
-				return woc.initializeNodeOrMarkError(node, nodeName, wfv1.NodeTypeSkipped, templateScope, orgTmpl, boundaryID, err), err
+			nodeName = lastChildNode.Name
+			node = lastChildNode
+		} else {
+			// Create a new child node and append it to the retry node.
+			nodeName = fmt.Sprintf("%s(%d)", retryNodeName, len(retryParentNode.Children))
+			woc.addChildNode(retryNodeName, nodeName)
+			node = nil
+
+			// Change the `pod.name` variable to the new retry node name
+			if processedTmpl.IsPodType() {
+				processedTmpl, err = common.SubstituteParams(processedTmpl, map[string]string{}, map[string]string{common.LocalVarPodName: woc.wf.NodeID(nodeName)})
+				if err != nil {
+					return woc.initializeNodeOrMarkError(node, nodeName, wfv1.NodeTypeSkipped, templateScope, orgTmpl, boundaryID, err), err
+				}
 			}
 		}
 	}
@@ -1383,9 +1383,16 @@ func (woc *wfOperationCtx) executeTemplate(nodeName string, orgTmpl wfv1.Templat
 	}
 	node = woc.getNodeByName(node.Name)
 
-	// Swap the node back to retry node.
+	// Swap the node back to retry node
 	if retryNodeName != "" {
-		node = woc.getNodeByName(retryNodeName)
+		retryNode := woc.getNodeByName(retryNodeName)
+		if !retryNode.Completed() && node.Completed() { //if the retry child has completed we need to update outself
+			node, err = woc.executeTemplate(retryNodeName, orgTmpl, tmplCtx, args, boundaryID)
+			if err != nil {
+				return woc.markNodeError(node.Name, err), err
+			}
+		}
+		node = retryNode
 	}
 
 	return node, nil
diff --git a/workflow/cron/operator.go b/workflow/cron/operator.go
index e2b8ebb98..44743c3c3 100644
--- a/workflow/cron/operator.go
+++ b/workflow/cron/operator.go
@@ -139,16 +139,43 @@ func (woc *cronWfOperationCtx) terminateOutstandingWorkflows() error {
 }
 
 func (woc *cronWfOperationCtx) runOutstandingWorkflows() error {
+	proceed, err := woc.shouldOutstandingWorkflowsBeRun()
+	if err != nil {
+		return err
+	}
+	if proceed {
+		woc.Run()
+	}
+	return nil
+}
 
+func (woc *cronWfOperationCtx) shouldOutstandingWorkflowsBeRun() (bool, error) {
 	// If this CronWorkflow has been run before, check if we have missed any scheduled executions
 	if woc.cronWf.Status.LastScheduledTime != nil {
-		now := time.Now()
-		var missedExecutionTime time.Time
-		cronSchedule, err := cron.ParseStandard(woc.cronWf.Spec.Schedule)
-		if err != nil {
-			return err
+		var now time.Time
+		var cronSchedule cron.Schedule
+		if woc.cronWf.Spec.Timezone != "" {
+			loc, err := time.LoadLocation(woc.cronWf.Spec.Timezone)
+			if err != nil {
+				return false, fmt.Errorf("invalid timezone '%s': %s", woc.cronWf.Spec.Timezone, err)
+			}
+			now = time.Now().In(loc)
+
+			cronScheduleString := "CRON_TZ=" + woc.cronWf.Spec.Timezone + " " + woc.cronWf.Spec.Schedule
+			cronSchedule, err = cron.ParseStandard(cronScheduleString)
+			if err != nil {
+				return false, fmt.Errorf("unable to form timezone schedule '%s': %s", cronScheduleString, err)
+			}
+		} else {
+			var err error
+			now = time.Now()
+			cronSchedule, err = cron.ParseStandard(woc.cronWf.Spec.Schedule)
+			if err != nil {
+				return false, err
+			}
 		}
 
+		var missedExecutionTime time.Time
 		nextScheduledRunTime := cronSchedule.Next(woc.cronWf.Status.LastScheduledTime.Time)
 		// Workflow should have ran
 		for nextScheduledRunTime.Before(now) {
@@ -161,11 +188,11 @@ func (woc *cronWfOperationCtx) runOutstandingWorkflows() error {
 			// If StartingDeadlineSeconds is not set, or we are still within the deadline window, run the Workflow
 			if woc.cronWf.Spec.StartingDeadlineSeconds == nil || now.Before(missedExecutionTime.Add(time.Duration(*woc.cronWf.Spec.StartingDeadlineSeconds)*time.Second)) {
 				log.Infof("%s missed an execution at %s and is within StartingDeadline", woc.cronWf.Name, missedExecutionTime.Format("Mon Jan _2 15:04:05 2006"))
-				woc.Run()
+				return true, nil
 			}
 		}
 	}
-	return nil
+	return false, nil
 }
 
 func (woc *cronWfOperationCtx) reconcileDeletedWfs() error {
diff --git a/workflow/validate/validate.go b/workflow/validate/validate.go
index 5295fc218..475717111 100644
--- a/workflow/validate/validate.go
+++ b/workflow/validate/validate.go
@@ -457,9 +457,6 @@ func validateNonLeaf(tmpl *wfv1.Template) error {
 	if tmpl.ActiveDeadlineSeconds != nil {
 		return errors.Errorf(errors.CodeBadRequest, "templates.%s.activeDeadlineSeconds is only valid for leaf templates", tmpl.Name)
 	}
-	if tmpl.RetryStrategy != nil {
-		return errors.Errorf(errors.CodeBadRequest, "templates.%s.retryStrategy is only valid for container templates", tmpl.Name)
-	}
 	return nil
 }
 


Test patch:
diff --git a/test/e2e/argo_server_test.go b/test/e2e/argo_server_test.go
index 241bc6e53..a5dd9f6c0 100644
--- a/test/e2e/argo_server_test.go
+++ b/test/e2e/argo_server_test.go
@@ -462,6 +462,21 @@ func (s *ArgoServerSuite) TestWorkflowService() {
 			NotNull()
 	})
 
+	s.Run("ListWithFields", func() {
+		j := s.e(s.T()).GET("/api/v1/workflows/argo").
+			WithQuery("listOptions.labelSelector", "argo-e2e=subject").
+			WithQuery("fields", "-items.status.nodes,items.status.finishedAt,items.status.startedAt").
+			Expect().
+			Status(200).
+			JSON()
+		j.
+			Path("$.items").
+			Array().
+			Length().
+			Equal(1)
+		j.Path("$.items[0].status").Object().ContainsKey("phase").NotContainsKey("nodes")
+	})
+
 	s.Run("Get", func() {
 		j := s.e(s.T()).GET("/api/v1/workflows/argo/test").
 			Expect().
@@ -480,6 +495,15 @@ func (s *ArgoServerSuite) TestWorkflowService() {
 			Status(404)
 	})
 
+	s.Run("GetWithFields", func() {
+		j := s.e(s.T()).GET("/api/v1/workflows/argo/test").
+			WithQuery("fields", "status.phase").
+			Expect().
+			Status(200).
+			JSON()
+		j.Path("$.status").Object().ContainsKey("phase").NotContainsKey("nodes")
+	})
+
 	s.Run("Suspend", func() {
 		s.e(s.T()).PUT("/api/v1/workflows/argo/test/suspend").
 			Expect().
diff --git a/test/e2e/images/cowsay/Dockerfile b/test/e2e/images/cowsay/Dockerfile
index 39a7e3561..870b2676f 100644
--- a/test/e2e/images/cowsay/Dockerfile
+++ b/test/e2e/images/cowsay/Dockerfile
@@ -1,8 +1,17 @@
 FROM ubuntu:14.04
 
-RUN apt-get update && apt-get install -y cowsay --no-install-recommends && rm -rf /var/lib/apt/lists/* 
+RUN apt-get update && \
+    apt-get install -y apt-utils ca-certificates apt-transport-https cowsay --no-install-recommends && \
+    apt-get clean \
+    && rm -rf \
+        /var/lib/apt/lists/* \
+        /tmp/* \
+        /var/tmp/* \
+        /usr/share/man \
+        /usr/share/doc \
+        /usr/share/doc-base
 
 # "cowsay" installs to /usr/games
 ENV PATH $PATH:/usr/games
 
-CMD ["cowsay"]
\ No newline at end of file
+CMD ["cowsay"]
diff --git a/util/kubeconfig/kubeconfig_test.go b/util/kubeconfig/kubeconfig_test.go
index 490df3be9..cca4fa0c4 100644
--- a/util/kubeconfig/kubeconfig_test.go
+++ b/util/kubeconfig/kubeconfig_test.go
@@ -41,7 +41,7 @@ func Test_BasicAuthString(t *testing.T) {
 
 		restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(config))
 		assert.NoError(t, err)
-		authString, err := GetAuthString(restConfig)
+		authString, err := GetAuthString(restConfig, "")
 		assert.NoError(t, err)
 		assert.True(t, IsBasicAuthScheme(authString))
 		token := strings.TrimSpace(strings.TrimPrefix(authString, BasicAuthScheme))
@@ -72,7 +72,7 @@ func Test_BearerAuthString(t *testing.T) {
 
 	t.Run("Bearer Auth", func(t *testing.T) {
 		os.Setenv("ARGO_TOKEN", bearerToken)
-		authString, err := GetAuthString(&restConfig)
+		authString, err := GetAuthString(&restConfig, "")
 		assert.NoError(t, err)
 		assert.True(t, IsBearerAuthScheme(authString))
 		config, err := GetRestConfig(authString)
diff --git a/util/kubeconfig/token_test.go b/util/kubeconfig/token_test.go
index c81e202cf..6ad621e07 100644
--- a/util/kubeconfig/token_test.go
+++ b/util/kubeconfig/token_test.go
@@ -18,7 +18,7 @@ func Test_getDefaultTokenVersion(t *testing.T) {
 		envToken := os.Getenv("ARGO_TOKEN")
 		os.Unsetenv("ARGO_TOKEN")
 		defer os.Setenv("ARGO_TOKEN", envToken)
-		_, err = GetBearerToken(restConfig)
+		_, err = GetBearerToken(restConfig, "")
 		assert.Error(t, err)
 	})
 	t.Run("Existing token", func(t *testing.T) {
@@ -28,7 +28,7 @@ func Test_getDefaultTokenVersion(t *testing.T) {
 		envToken := os.Getenv("ARGO_TOKEN")
 		os.Setenv("ARGO_TOKEN", "test")
 		defer os.Setenv("ARGO_TOKEN", envToken)
-		token, err := GetBearerToken(restConfig)
+		token, err := GetBearerToken(restConfig, "")
 		assert.NoError(t, err)
 		assert.Equal(t, "test12", token)
 	})
@@ -39,7 +39,7 @@ func Test_getDefaultTokenVersion(t *testing.T) {
 		envToken := os.Getenv("ARGO_TOKEN")
 		os.Setenv("ARGO_TOKEN", "test")
 		defer os.Setenv("ARGO_TOKEN", envToken)
-		token, err := GetBearerToken(restConfig)
+		token, err := GetBearerToken(restConfig, "")
 		assert.NoError(t, err)
 		assert.Equal(t, "test", token)
 	})
diff --git a/workflow/controller/controller_test.go b/workflow/controller/controller_test.go
index a26e73fdf..d981f60b4 100644
--- a/workflow/controller/controller_test.go
+++ b/workflow/controller/controller_test.go
@@ -83,13 +83,18 @@ func unmarshalWFTmpl(yamlStr string) *wfv1.WorkflowTemplate {
 	return &wftmpl
 }
 
-// makePodsRunning acts like a pod controller and simulates the transition of pods transitioning into a running state
-func makePodsRunning(t *testing.T, kubeclientset kubernetes.Interface, namespace string) {
+// makePodsPhase acts like a pod controller and simulates the transition of pods transitioning into a specified state
+func makePodsPhase(t *testing.T, phase apiv1.PodPhase, kubeclientset kubernetes.Interface, namespace string) {
 	podcs := kubeclientset.CoreV1().Pods(namespace)
 	pods, err := podcs.List(metav1.ListOptions{})
 	assert.NoError(t, err)
 	for _, pod := range pods.Items {
-		pod.Status.Phase = apiv1.PodRunning
-		_, _ = podcs.Update(&pod)
+		if pod.Status.Phase == "" {
+			pod.Status.Phase = phase
+			if phase == apiv1.PodFailed {
+				pod.Status.Message = "Pod failed"
+			}
+			_, _ = podcs.Update(&pod)
+		}
 	}
 }
diff --git a/workflow/controller/operator_test.go b/workflow/controller/operator_test.go
index 797e261ad..17e5bc45c 100644
--- a/workflow/controller/operator_test.go
+++ b/workflow/controller/operator_test.go
@@ -405,6 +405,80 @@ func TestAssessNodeStatus(t *testing.T) {
 	}
 }
 
+var workflowStepRetry = `
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  name: step-retry
+spec:
+  entrypoint: step-retry
+  templates:
+  - name: step-retry
+    retryStrategy:
+      limit: 1
+    steps:
+      - - name: whalesay-success
+          arguments:
+            parameters:
+            - name: message
+              value: success
+          template: whalesay
+      - - name: whalesay-failure
+          arguments:
+            parameters:
+            - name: message
+              value: failure
+          template: whalesay
+
+  - name: whalesay
+    inputs:
+      parameters:
+        - name: message
+    container:
+      image: docker/whalesay:latest
+      command: [sh, -c]
+      args: ["cowsay {{inputs.parameters.message}}"]
+`
+
+// TestWorkflowParallelismLimit verifies parallelism at a workflow level is honored.
+func TestWorkflowStepRetry(t *testing.T) {
+	controller := newController()
+	wfcset := controller.wfclientset.ArgoprojV1alpha1().Workflows("")
+	wf := unmarshalWF(workflowStepRetry)
+	wf, err := wfcset.Create(wf)
+	assert.Nil(t, err)
+	wf, err = wfcset.Get(wf.ObjectMeta.Name, metav1.GetOptions{})
+	assert.Nil(t, err)
+	woc := newWorkflowOperationCtx(wf, controller)
+	woc.operate()
+	pods, err := controller.kubeclientset.CoreV1().Pods("").List(metav1.ListOptions{})
+	assert.Nil(t, err)
+	assert.Equal(t, 1, len(pods.Items))
+
+	//complete the first pod
+	makePodsPhase(t, apiv1.PodSucceeded, controller.kubeclientset, wf.ObjectMeta.Namespace)
+	wf, err = wfcset.Get(wf.ObjectMeta.Name, metav1.GetOptions{})
+	assert.Nil(t, err)
+	woc = newWorkflowOperationCtx(wf, controller)
+	woc.operate()
+
+	// fail the second pod
+	makePodsPhase(t, apiv1.PodFailed, controller.kubeclientset, wf.ObjectMeta.Namespace)
+	wf, err = wfcset.Get(wf.ObjectMeta.Name, metav1.GetOptions{})
+	assert.Nil(t, err)
+	woc = newWorkflowOperationCtx(wf, controller)
+	woc.operate()
+	pods, err = controller.kubeclientset.CoreV1().Pods("").List(metav1.ListOptions{})
+	assert.Nil(t, err)
+	assert.Equal(t, 3, len(pods.Items))
+	assert.Equal(t, "cowsay success", pods.Items[0].Spec.Containers[1].Args[0])
+	assert.Equal(t, "cowsay failure", pods.Items[1].Spec.Containers[1].Args[0])
+
+	//verify that after the cowsay failure pod failed, we are retrying cowsay success
+	assert.Equal(t, "cowsay success", pods.Items[2].Spec.Containers[1].Args[0])
+
+}
+
 var workflowParallelismLimit = `
 apiVersion: argoproj.io/v1alpha1
 kind: Workflow
@@ -451,7 +525,7 @@ func TestWorkflowParallelismLimit(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, 2, len(pods.Items))
 	// operate again and make sure we don't schedule any more pods
-	makePodsRunning(t, controller.kubeclientset, wf.ObjectMeta.Namespace)
+	makePodsPhase(t, apiv1.PodRunning, controller.kubeclientset, wf.ObjectMeta.Namespace)
 	wf, err = wfcset.Get(wf.ObjectMeta.Name, metav1.GetOptions{})
 	assert.NoError(t, err)
 	// wfBytes, _ := json.MarshalIndent(wf, "", "  ")
@@ -510,7 +584,7 @@ func TestStepsTemplateParallelismLimit(t *testing.T) {
 	assert.Equal(t, 2, len(pods.Items))
 
 	// operate again and make sure we don't schedule any more pods
-	makePodsRunning(t, controller.kubeclientset, wf.ObjectMeta.Namespace)
+	makePodsPhase(t, apiv1.PodRunning, controller.kubeclientset, wf.ObjectMeta.Namespace)
 	wf, err = wfcset.Get(wf.ObjectMeta.Name, metav1.GetOptions{})
 	assert.NoError(t, err)
 	// wfBytes, _ := json.MarshalIndent(wf, "", "  ")
@@ -566,7 +640,7 @@ func TestDAGTemplateParallelismLimit(t *testing.T) {
 	assert.Equal(t, 2, len(pods.Items))
 
 	// operate again and make sure we don't schedule any more pods
-	makePodsRunning(t, controller.kubeclientset, wf.ObjectMeta.Namespace)
+	makePodsPhase(t, apiv1.PodRunning, controller.kubeclientset, wf.ObjectMeta.Namespace)
 	wf, err = wfcset.Get(wf.ObjectMeta.Name, metav1.GetOptions{})
 	assert.NoError(t, err)
 	// wfBytes, _ := json.MarshalIndent(wf, "", "  ")
@@ -2135,7 +2209,7 @@ func TestEventTimeout(t *testing.T) {
 	assert.NoError(t, err)
 	woc := newWorkflowOperationCtx(wf, controller)
 	woc.operate()
-	makePodsRunning(t, controller.kubeclientset, wf.ObjectMeta.Namespace)
+	makePodsPhase(t, apiv1.PodRunning, controller.kubeclientset, wf.ObjectMeta.Namespace)
 	wf, err = wfcset.Get(wf.ObjectMeta.Name, metav1.GetOptions{})
 	assert.NoError(t, err)
 	woc = newWorkflowOperationCtx(wf, controller)
diff --git a/workflow/cron/operator_test.go b/workflow/cron/operator_test.go
new file mode 100644
index 000000000..e58255e24
--- /dev/null
+++ b/workflow/cron/operator_test.go
@@ -0,0 +1,120 @@
+package cron
+
+import (
+	"testing"
+	"time"
+
+	"github.com/argoproj/pkg/humanize"
+	"github.com/ghodss/yaml"
+	"github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
+)
+
+var scheduledWf = `
+  apiVersion: argoproj.io/v1alpha1
+  kind: CronWorkflow
+  metadata:
+    creationTimestamp: "2020-02-28T18:31:32Z"
+    generation: 69
+    name: hello-world
+    namespace: argo
+    resourceVersion: "53389"
+    selfLink: /apis/argoproj.io/v1alpha1/namespaces/argo/cronworkflows/hello-world
+    uid: f230ee83-2ddc-435e-b27c-f0ca63293100
+  spec:
+    schedule: '* * * * *'
+    startingDeadlineSeconds: 30
+    workflowSpec:
+      arguments: {}
+      entrypoint: whalesay
+      templates:
+      - container:
+          args:
+          - "\U0001F553 hello world"
+          command:
+          - cowsay
+          image: docker/whalesay:latest
+          name: ""
+          resources: {}
+        inputs: {}
+        metadata: {}
+        name: whalesay
+        outputs: {}
+  status:
+    lastScheduledTime: "2020-02-28T19:05:00Z"
+`
+
+func TestRunOutstandingWorkflows(t *testing.T) {
+	// To ensure consistency, always start at the next 30 second mark
+	_, _, sec := time.Now().Clock()
+	var toWait time.Duration
+	if sec <= 30 {
+		toWait = time.Duration(30-sec) * time.Second
+	} else {
+		toWait = time.Duration(90-sec) * time.Second
+	}
+	logrus.Infof("Waiting %s to start", humanize.Duration(toWait))
+	time.Sleep(toWait)
+
+	var cronWf v1alpha1.CronWorkflow
+	err := yaml.Unmarshal([]byte(scheduledWf), &cronWf)
+	if err != nil {
+		panic(err)
+	}
+
+	// Second value at runtime should be 30-31
+
+	cronWf.Status.LastScheduledTime = &v1.Time{Time: time.Now().Add(-1 * time.Minute)}
+	// StartingDeadlineSeconds is after the current second, so cron should be run
+	startingDeadlineSeconds := int64(35)
+	cronWf.Spec.StartingDeadlineSeconds = &startingDeadlineSeconds
+	woc := &cronWfOperationCtx{
+		cronWf: &cronWf,
+	}
+	proceed, err := woc.shouldOutstandingWorkflowsBeRun()
+	assert.NoError(t, err)
+	assert.True(t, proceed)
+
+	// StartingDeadlineSeconds is not after the current second, so cron should not be run
+	startingDeadlineSeconds = int64(25)
+	cronWf.Spec.StartingDeadlineSeconds = &startingDeadlineSeconds
+	woc = &cronWfOperationCtx{
+		cronWf: &cronWf,
+	}
+	proceed, err = woc.shouldOutstandingWorkflowsBeRun()
+	assert.NoError(t, err)
+	assert.False(t, proceed)
+
+	// Run the same test in a different timezone
+
+	testTimezone := "Pacific/Niue"
+	testLocation, err := time.LoadLocation(testTimezone)
+	if err != nil {
+		panic(err)
+	}
+	cronWf.Spec.Timezone = testTimezone
+	cronWf.Status.LastScheduledTime = &v1.Time{Time: cronWf.Status.LastScheduledTime.In(testLocation)}
+
+	// StartingDeadlineSeconds is after the current second, so cron should be run
+	startingDeadlineSeconds = int64(35)
+	cronWf.Spec.StartingDeadlineSeconds = &startingDeadlineSeconds
+	woc = &cronWfOperationCtx{
+		cronWf: &cronWf,
+	}
+	proceed, err = woc.shouldOutstandingWorkflowsBeRun()
+	assert.NoError(t, err)
+	assert.True(t, proceed)
+
+	// StartingDeadlineSeconds is not after the current second, so cron should not be run
+	startingDeadlineSeconds = int64(25)
+	cronWf.Spec.StartingDeadlineSeconds = &startingDeadlineSeconds
+	woc = &cronWfOperationCtx{
+		cronWf: &cronWf,
+	}
+	proceed, err = woc.shouldOutstandingWorkflowsBeRun()
+	assert.NoError(t, err)
+	assert.False(t, proceed)
+}
diff --git a/workflow/validate/validate_test.go b/workflow/validate/validate_test.go
index c76fd0bf2..4e2ae0ab6 100644
--- a/workflow/validate/validate_test.go
+++ b/workflow/validate/validate_test.go
@@ -1066,34 +1066,6 @@ func TestLeafWithParallelism(t *testing.T) {
 	}
 }
 
-var nonLeafWithRetryStrategy = `
-apiVersion: argoproj.io/v1alpha1
-kind: Workflow
-metadata:
-  name: non-leaf-with-retry-strategy
-spec:
-  entrypoint: non-leaf-with-retry-strategy
-  templates:
-  - name: non-leaf-with-retry-strategy
-    retryStrategy:
-      limit: 4
-    steps:
-    - - name: try
-        template: try
-  - name: try
-    container:
-      image: debian:9.4
-      command: [sh, -c]
-      args: ["kubectl version"]
-`
-
-func TestNonLeafWithRetryStrategy(t *testing.T) {
-	err := validate(nonLeafWithRetryStrategy)
-	if assert.NotNil(t, err) {
-		assert.Contains(t, err.Error(), "is only valid")
-	}
-}
-
 var invalidStepsArgumentNoFromOrLocation = `
 apiVersion: argoproj.io/v1alpha1
 kind: Workflow