Repository: argoproj/argo
Pull request #3239 (2020-06-16 15:55:13)

fix: Support the TTLStrategy for WorkflowTemplateRef
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"`.  #3227 
* [ ] 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).


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/100.png' alt='100.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=argoproj_argo&pullRequest=3239&metric=new_coverage&view=list) [100.0% Coverage](https://sonarcloud.io/component_measures?id=argoproj_argo&pullRequest=3239&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=argoproj_argo&pullRequest=3239&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=argoproj_argo&pullRequest=3239&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=argoproj_argo&pullRequest=3239&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/100.png' alt='100.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=argoproj_argo&pullRequest=3239&metric=new_coverage&view=list) [100.0% Coverage](https://sonarcloud.io/component_measures?id=argoproj_argo&pullRequest=3239&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=argoproj_argo&pullRequest=3239&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=argoproj_argo&pullRequest=3239&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 9398d0249..90fc4e238 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,18 +2,6 @@ version: 2.1
 orbs:
   sonarcloud: sonarsource/sonarcloud@1.0.1
 commands:
-  maybe_skip_job:
-    steps:
-      - run:
-          name: Maybe skip job if not relevant
-          command: ./hack/maybe-skip-job.sh $CIRCLE_JOB
-  install_kustomize:
-    steps:
-      - run:
-          name: Install Kustomize
-          command: |
-            cd /usr/local/bin && curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  | sudo bash
-            kustomize version
   restore_go_mod:
     steps:
       - restore_cache:
@@ -53,117 +41,18 @@ commands:
           keys:
             - go-v7-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}
             - go-v7-master-{{ .Environment.CIRCLE_JOB }}
-  e2e:
-    steps:
-      - checkout
-      - maybe_skip_job
-      - install_kustomize
-      - run:
-          name: Install and start K3S v1.0.1
-          command: curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.0.1 INSTALL_K3S_EXEC=--docker K3S_KUBECONFIG_MODE=644 sh -
-          background: true
-      - restore_go_cache
-      - install_golang
-      - run:
-          name: Preemptively pull images
-          command: make pull-build-images test-images
-          background: true
-      - restore_go_mod
-      - run: go mod download
-      - run:
-          name: Create KUBECONFIG
-          # Copy kubeconfig file, and add a fake user for "argo --token xxx" testing
-          command: |
-            mkdir -p ~/.kube
-            until stat /etc/rancher/k3s/k3s.yaml ; do sleep 10s ; done
-            cat /etc/rancher/k3s/k3s.yaml | sed "s/127.0.0.1/$(hostname)/g" > ~/.kube/config
-            echo "- name: fake_token_user" >> ~/.kube/config
-            echo "  user:" >> ~/.kube/config
-            echo "    token: xxxxxx" >> ~/.kube/config
-      - run:
-          name: Start Argo
-          command: |
-            echo '127.0.0.1 dex'      | sudo tee -a /etc/hosts
-            echo '127.0.0.1 minio'    | sudo tee -a /etc/hosts
-            echo '127.0.0.1 postgres' | sudo tee -a /etc/hosts
-            echo '127.0.0.1 mysql'    | sudo tee -a /etc/hosts
-            mkdir dist
-            KUBECONFIG=~/.kube/config make start PROFILE=$PROFILE E2E_EXECUTOR=docker DEV_IMAGE=true
-            # CircleCI will kill spawned processes, so lets block for 20m
-            sleep 20m
-          background: true
-      - run:
-          name: Wait for Argo Server to be ready
-          command: make wait
-      - run:
-          name: Run tests
-          command: |
-            trap 'make test-results/junit.xml' EXIT
-            make smoke $SUITE
-      - store_test_results:
-          path: test-results
-          when: always
-      - store_artifacts:
-          name: Store test results
-          path: test-results
-          destination: .
-          when: always
-      - store_artifacts:
-          name: Store logs
-          path: /tmp/log/argo-e2e
-          destination: .
-          when: always
-      - save_go_mod
-      - save_go_cache
 jobs:
-  codegen:
-    working_directory: /home/circleci/.go_workspace/src/github.com/argoproj/argo
-    machine:
-      image: ubuntu-1604:201903-01
-    steps:
-      - checkout
-      - maybe_skip_job
-      - install_kustomize
-      - restore_go_cache
-      - install_golang
-      - run:
-          name: Install protoc
-          command: |
-            set -eux -o pipefail
-            PROTOC_ZIP=protoc-3.11.1-linux-x86_64.zip
-            curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.11.1/$PROTOC_ZIP
-            sudo unzip -o $PROTOC_ZIP -d /usr/local bin/protoc
-            sudo unzip -o $PROTOC_ZIP -d /usr/local 'include/*'
-            sudo chmod +x /usr/local/bin/protoc
-            sudo find /usr/local/include -type f | xargs sudo chmod a+r
-            sudo find /usr/local/include -type d | xargs sudo chmod a+rx
-            rm -f $PROTOC_ZIP
-            ls /usr/local/include/google/protobuf/
-      - run: go mod download
-      - run: make codegen
-      - run: make manifests
-      - run: make lint
-      - run: git diff --exit-code
-      - save_go_cache
-      - save_go_mod
-  test:
+  sonar:
     working_directory: /home/circleci/.go_workspace/src/github.com/argoproj/argo
     machine:
       image: ubuntu-1604:201903-01
     steps:
       - checkout
-      - maybe_skip_job
-      - install_kustomize
       - restore_go_cache
       - install_golang
       - restore_go_mod
-      - run: go mod download
-      - run:
-          name: Run tests
-          command: |
-            trap 'make test-results/junit.xml' EXIT
-            make test
       - run: |
+          mkdir dist
           cp sonar-project.properties dist/
           if [ "$(echo "$CIRCLE_BRANCH" | grep 'master\|release-')" = '' ]; then
             echo "sonar.pullrequest.branch=$CIRCLE_BRANCH" >> sonar-project.properties
@@ -180,81 +69,9 @@ jobs:
           when: always
       - save_go_cache
       - save_go_mod
-  e2e-cron:
-    working_directory: /home/circleci/.go_workspace/src/github.com/argoproj/argo
-    machine:
-      image: ubuntu-1604:201903-01
-    environment:
-      PROFILE: mysql
-      SUITE: test-e2e-cron
-    steps:
-      - e2e
-  e2e-minimal:
-    working_directory: /home/circleci/.go_workspace/src/github.com/argoproj/argo
-    machine:
-      image: ubuntu-1604:201903-01
-    environment:
-      PROFILE: minimal
-      SUITE: test-e2e
-    steps:
-      - e2e
-  e2e-postgres:
-    working_directory: /home/circleci/.go_workspace/src/github.com/argoproj/argo
-    machine:
-      image: ubuntu-1604:201903-01
-    environment:
-      PROFILE: postgres
-      SUITE: test-e2e
-    steps:
-      - e2e
-  e2e-mysql:
-    working_directory: /home/circleci/.go_workspace/src/github.com/argoproj/argo
-    machine:
-      image: ubuntu-1604:201903-01
-    environment:
-      PROFILE: mysql
-      SUITE: test-e2e
-    steps:
-      - e2e
-  docker-build:
-    working_directory: /home/circleci/.go_workspace/src/github.com/argoproj/argo
-    machine:
-      image: ubuntu-1604:201903-01
-    steps:
-      - checkout
-      - maybe_skip_job
-      - run: DOCKER_BUILDKIT=1 docker build .
-  ui:
-    docker:
-      - image: circleci/node:14.0.0
-    steps:
-      - checkout
-      - maybe_skip_job
-      - restore_cache:
-          name: Restore Yarn cache
-          keys:
-            - yarn-packages-v3-{{ checksum "ui/yarn.lock" }}
-      - run:
-          name: Download dependencies
-          command: yarn --cwd ui install
-      # - run: yarn test
-      - run: yarn --cwd ui build
-      - run: yarn --cwd ui lint
-      - run: git diff --exit-code
-      - save_cache:
-          name: Save Yarn cache
-          key: yarn-packages-v3-{{ checksum "ui/yarn.lock" }}
-          paths: [~/.cache/yarn, node_modules]
-          when: always
+
 workflows:
   version: 2
-  argo-workflow:
+  CI:
     jobs:
-      - test
-      - ui
-      - codegen
-      - e2e-cron
-      - e2e-mysql
-      - e2e-minimal
-      - e2e-postgres
-      - docker-build
+      - sonar
diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml
new file mode 100644
index 000000000..f5c1e6e8d
--- /dev/null
+++ b/.github/workflows/ci-build.yaml
@@ -0,0 +1,177 @@
+name: CI
+on: 
+  push:
+    branches:
+      - 'master' 
+      - 'release-*'
+      - '!release-2.8'
+  pull_request:
+    branches:
+      - 'master'
+
+jobs:
+  tests:
+    name: Tests
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        test: ["test", "test-e2e", "test-e2e-cron"]
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+        with:
+          ref: ${{ github.event.pull_request.head.sha }}
+      - name: Specify branch
+        run: git checkout -b pull/$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')
+      - name: Restore go build cache
+        uses: actions/cache@v1
+        with:
+          path: ~/.cache/go-build
+          key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
+      - name: Setup Golang
+        uses: actions/setup-go@v1
+        with:
+          go-version: '1.13.12'
+      - name: Add bins to PATH
+        run: |
+          echo "::add-path::/home/runner/go/bin"
+          echo "::add-path::/usr/local/bin"
+      - name: Install Kustomize
+        run: |
+            cd /usr/local/bin && curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  | sudo bash
+            kustomize version
+      - name: Install and start K3S v1.0.1
+        if: ${{ matrix.test != 'test' }}
+        run: curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.0.1 INSTALL_K3S_EXEC=--docker K3S_KUBECONFIG_MODE=644 sh - &
+      - name: Pre-pull images
+        if: ${{ matrix.test != 'test' }}
+        env:
+          GOPATH: /home/runner/go
+        run: make pull-build-images test-images &
+      - name: Download all Go modules
+        run: |
+          go mod download
+      - name: Create Kubeconfig
+        if: ${{ matrix.test != 'test' }}
+        run: |
+          mkdir -p ~/.kube
+          until stat /etc/rancher/k3s/k3s.yaml ; do sleep 10s ; done
+          cat /etc/rancher/k3s/k3s.yaml | sed "s/127.0.0.1/$(hostname)/g" > ~/.kube/config
+          echo "- name: fake_token_user" >> ~/.kube/config
+          echo "  user:" >> ~/.kube/config
+          echo "    token: xxxxxx" >> ~/.kube/config
+      - name: Start Argo
+        if: ${{ matrix.test != 'test' }}
+        env:
+          GOPATH: /home/runner/go
+          PROFILE: mysql
+        run: |
+          echo '127.0.0.1 dex'      | sudo tee -a /etc/hosts
+          echo '127.0.0.1 minio'    | sudo tee -a /etc/hosts
+          echo '127.0.0.1 postgres' | sudo tee -a /etc/hosts
+          echo '127.0.0.1 mysql'    | sudo tee -a /etc/hosts
+          mkdir dist
+          KUBECONFIG=~/.kube/config make start PROFILE=$PROFILE E2E_EXECUTOR=docker DEV_IMAGE=true &
+      - name: Wait for Argo Server to be ready
+        if: ${{ matrix.test != 'test' }}
+        env:
+          GOPATH: /home/runner/go
+        run: make wait
+      - name: Run tests
+        env:
+          GOPATH: /home/runner/go
+        run: |
+          trap 'make test-results/junit.xml' EXIT
+          make ${{ matrix.test }}
+      - name: Upload test results
+        if: failure()
+        uses: actions/upload-artifact@v1
+        with:
+          name: ${{ matrix.test }}
+          path: test-results
+
+  codegen:
+    name: Codegen
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+        with:
+          ref: ${{ github.event.pull_request.head.sha }}
+      - name: Specify branch
+        run: git checkout -b pull/$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')
+      - name: Restore go build cache
+        uses: actions/cache@v1
+        with:
+          path: ~/.cache/go-build
+          key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
+      - name: Setup Golang
+        uses: actions/setup-go@v1
+        with:
+          go-version: '1.13.12'
+      - name: Add bins to PATH
+        run: |
+          echo "::add-path::/home/runner/go/bin"
+          echo "::add-path::/usr/local/bin"
+      - name: Download all Go modules
+        run: |
+          go mod download
+      - name: Install Kustomize
+        run: |
+            cd /usr/local/bin && curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  | sudo bash
+            kustomize version
+      - name: Install protoc
+        run: |
+          set -eux -o pipefail
+          PROTOC_ZIP=protoc-3.11.1-linux-x86_64.zip
+          curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.11.1/$PROTOC_ZIP
+          sudo unzip -o $PROTOC_ZIP -d /usr/local bin/protoc
+          sudo unzip -o $PROTOC_ZIP -d /usr/local 'include/*'
+          sudo chmod +x /usr/local/bin/protoc
+          sudo find /usr/local/include -type f | xargs sudo chmod a+r
+          sudo find /usr/local/include -type d | xargs sudo chmod a+rx
+          rm -f $PROTOC_ZIP
+          ls /usr/local/include/google/protobuf/
+      - name: Make codegen
+        env:
+          GOPATH: /home/runner/go
+        run: |
+          make codegen
+      - name: Make manifests
+        env:
+          GOPATH: /home/runner/go
+        run: |
+          make manifests
+      - name: Make lint
+        env:
+          GOPATH: /home/runner/go
+        run: |
+          make lint
+      - name: Ensure nothing changed
+        run: git diff --exit-code
+
+  ui:
+    name: UI
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+        with:
+          ref: ${{ github.event.pull_request.head.sha }}
+      - name: Specify branch
+        run: git checkout -b pull/$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')
+      - uses: actions/setup-node@v1
+        with:
+          node-version: '14'
+      - name: Restore node dependency cache
+        uses: actions/cache@v1
+        with:
+          path: ui/node_modules
+          key: ${{ runner.os }}-node-dep-v1-${{ hashFiles('**/yarn.lock') }}
+      - name: Install and Lint
+        run: |
+          yarn --cwd ui install
+          yarn --cwd ui lint
+      - name: Ensure nothing changed
+        run: git diff --exit-code
diff --git a/cmd/argo/commands/watch.go b/cmd/argo/commands/watch.go
index c78acfca5..3a12f7120 100644
--- a/cmd/argo/commands/watch.go
+++ b/cmd/argo/commands/watch.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"io"
 	"os"
+	"time"
 
 	"github.com/argoproj/pkg/errors"
 	log "github.com/sirupsen/logrus"
@@ -33,7 +34,6 @@ func NewWatchCommand() *cobra.Command {
 				os.Exit(1)
 			}
 			watchWorkflow(args[0], getArgs)
-
 		},
 	}
 	command.Flags().StringVar(&getArgs.status, "status", "", "Filter by status (Pending, Running, Succeeded, Skipped, Failed, Error)")
@@ -56,19 +56,35 @@ func watchWorkflow(wfName string, getArgs getFlags) {
 	}
 	stream, err := serviceClient.WatchWorkflows(ctx, req)
 	errors.CheckError(err)
-	for {
-		event, err := stream.Recv()
-		if err == io.EOF {
-			log.Debug("Re-establishing workflow watch")
-			stream, err = serviceClient.WatchWorkflows(ctx, req)
+
+	wfChan := make(chan *wfv1.Workflow)
+	go func() {
+		for {
+			event, err := stream.Recv()
+			if err == io.EOF {
+				log.Debug("Re-establishing workflow watch")
+				stream, err = serviceClient.WatchWorkflows(ctx, req)
+				errors.CheckError(err)
+			}
 			errors.CheckError(err)
-			continue
+			wfChan <- event.Object
 		}
-		errors.CheckError(err)
-		wf := event.Object
-		if wf == nil {
-			break
+	}()
+
+	var wf *wfv1.Workflow
+	ticker := time.NewTicker(time.Second)
+	for {
+		select {
+		case newWf := <-wfChan:
+			// If we get a new event, update our workflow
+			if newWf == nil {
+				return
+			}
+			wf = newWf
+		case <-ticker.C:
+			// If we don't, refresh the workflow screen every second
 		}
+
 		printWorkflowStatus(wf, getArgs)
 		if !wf.Status.FinishedAt.IsZero() {
 			return
@@ -77,6 +93,9 @@ func watchWorkflow(wfName string, getArgs getFlags) {
 }
 
 func printWorkflowStatus(wf *wfv1.Workflow, getArgs getFlags) {
+	if wf == nil {
+		return
+	}
 	err := packer.DecompressWorkflow(wf)
 	errors.CheckError(err)
 	print("\033[H\033[2J")
diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go
index 2cf2611ee..b0f22e1ea 100644
--- a/workflow/controller/operator.go
+++ b/workflow/controller/operator.go
@@ -230,7 +230,7 @@ func (woc *wfOperationCtx) operate() {
 			err = woc.failSuspendedNodesAfterDeadlineOrShutdown()
 		}
 		if err != nil {
-			woc.log.Errorf("%s error: %+v", woc.wf.ObjectMeta.Name, err)
+			woc.log.WithError(err).WithField("workflow", woc.wf.ObjectMeta.Name).Error("workflow timeout")
 			woc.eventRecorder.Event(woc.wf, apiv1.EventTypeWarning, "WorkflowTimedOut", "Workflow timed out")
 			// TODO: we need to re-add to the workqueue, but should happen in caller
 			return
@@ -271,26 +271,26 @@ func (woc *wfOperationCtx) operate() {
 
 	err = woc.substituteParamsInVolumes(woc.globalParams)
 	if err != nil {
-		msg := fmt.Sprintf("%s volumes global param substitution error: %+v", woc.wf.ObjectMeta.Name, err)
-		woc.log.Errorf(msg)
+		woc.log.WithError(err).Error("volumes global param substitution error")
 		woc.markWorkflowError(err, true)
 		return
 	}
 
 	err = woc.createPVCs()
 	if err != nil {
-		msg := fmt.Sprintf("%s pvc create error: %+v", woc.wf.ObjectMeta.Name, err)
-		woc.log.Errorf(msg)
+		msg := "pvc create error"
+		woc.log.WithError(err).Error(msg)
 		woc.markWorkflowError(err, true)
-		woc.eventRecorder.Event(woc.wf, apiv1.EventTypeWarning, "WorkflowFailed", msg)
+		woc.eventRecorder.Event(woc.wf, apiv1.EventTypeWarning, "WorkflowFailed", fmt.Sprintf("%s %s: %+v", woc.wf.ObjectMeta.Name, msg, err))
 		return
 	}
 
 	node, err := woc.executeTemplate(woc.wf.ObjectMeta.Name, execTmplRef, tmplCtx, execArgs, &executeTemplateOpts{})
 	if err != nil {
-		msg := fmt.Sprintf("%s error in entry template execution: %+v", woc.wf.Name, err)
 		// the error are handled in the callee so just log it.
-		woc.log.Errorf(msg)
+		msg := "error in entry template execution"
+		woc.log.WithError(err).Error(msg)
+		msg = fmt.Sprintf("%s %s: %+v", woc.wf.Name, msg, err)
 		switch err {
 		case ErrDeadlineExceeded:
 			woc.eventRecorder.Event(woc.wf, apiv1.EventTypeWarning, "WorkflowTimedOut", msg)
@@ -352,13 +352,13 @@ func (woc *wfOperationCtx) operate() {
 
 	err = woc.deletePVCs()
 	if err != nil {
-		msg := fmt.Sprintf("%s error: %+v", woc.wf.ObjectMeta.Name, err)
-		woc.log.Errorf(msg)
+		msg := "failed to delete PVCs"
+		woc.log.WithError(err).Errorf(msg)
 		// Mark the workflow with an error message and return, but intentionally do not
 		// markCompletion so that we can retry PVC deletion (TODO: use workqueue.ReAdd())
 		// This error phase may be cleared if a subsequent delete attempt is successful.
 		woc.markWorkflowError(err, false)
-		woc.eventRecorder.Event(woc.wf, apiv1.EventTypeWarning, "WorkflowFailed", msg)
+		woc.eventRecorder.Event(woc.wf, apiv1.EventTypeWarning, "WorkflowFailed", fmt.Sprintf("%s %s: %+v", woc.wf.ObjectMeta.Name, msg, err))
 		return
 	}
 
@@ -973,13 +973,13 @@ func (woc *wfOperationCtx) assessNodeStatus(pod *apiv1.Pod, node *wfv1.NodeStatu
 			newPhase = wfv1.NodeRunning
 			tmplStr, ok := pod.Annotations[common.AnnotationKeyTemplate]
 			if !ok {
-				log.Warnf("%s missing template annotation", pod.ObjectMeta.Name)
+				log.WithField("pod", pod.ObjectMeta.Name).Warn("missing template annotation")
 				return nil
 			}
 			var tmpl wfv1.Template
 			err := json.Unmarshal([]byte(tmplStr), &tmpl)
 			if err != nil {
-				log.Warnf("%s template annotation unreadable: %v", pod.ObjectMeta.Name, err)
+				log.WithError(err).WithField("pod", pod.ObjectMeta.Name).Warn("template annotation unreadable")
 				return nil
 			}
 			if tmpl.Daemon != nil && *tmpl.Daemon {
@@ -1251,7 +1251,7 @@ func (woc *wfOperationCtx) createPVCs() error {
 		}
 		pvc, err := pvcClient.Create(&pvcTmpl)
 		if err != nil && apierr.IsAlreadyExists(err) {
-			woc.log.Infof("%s pvc already exists. Workflow is re-using it", pvcTmpl.Name)
+			woc.log.WithField("pvc", pvcTmpl.Name).Info("pvc already exists. Workflow is re-using it")
 			pvc, err = pvcClient.Get(pvcTmpl.Name, metav1.GetOptions{})
 			if err != nil {
 				return err
@@ -1762,7 +1762,7 @@ func (woc *wfOperationCtx) onNodeComplete(node *wfv1.NodeStatus) {
 
 // markNodeError is a convenience method to mark a node with an error and set the message from the error
 func (woc *wfOperationCtx) markNodeError(nodeName string, err error) *wfv1.NodeStatus {
-	woc.log.Errorf("Mark error node %s: %+v", nodeName, err)
+	woc.log.WithError(err).WithField("nodeName", nodeName).Error("Mark error node")
 	return woc.markNodePhase(nodeName, wfv1.NodeError, err.Error())
 }
 
diff --git a/workflow/cron/operator.go b/workflow/cron/operator.go
index 1c53562b0..6d9770b50 100644
--- a/workflow/cron/operator.go
+++ b/workflow/cron/operator.go
@@ -115,7 +115,7 @@ func getWorkflowObjectReference(wf *v1alpha1.Workflow, runWf *v1alpha1.Workflow)
 func (woc *cronWfOperationCtx) persistUpdate() {
 	_, err := woc.cronWfIf.Update(woc.cronWf)
 	if err != nil {
-		woc.log.Error(fmt.Sprintf("failed to update CronWorkflow: %s", err))
+		woc.log.WithError(err).Error("failed to update CronWorkflow")
 	}
 }
 
@@ -327,7 +327,7 @@ func (woc *cronWfOperationCtx) deleteOldestWorkflows(jobList []v1alpha1.Workflow
 }
 
 func (woc *cronWfOperationCtx) reportCronWorkflowError(conditionType v1alpha1.ConditionType, errString string) {
-	woc.log.Errorf("%s: %s", conditionType, errString)
+	woc.log.WithField("conditionType", conditionType).Error(errString)
 	woc.cronWf.Status.Conditions.UpsertCondition(v1alpha1.Condition{
 		Type:    conditionType,
 		Message: errString,
diff --git a/workflow/ttlcontroller/ttlcontroller.go b/workflow/ttlcontroller/ttlcontroller.go
index 8e66a7c17..eb18f7f4b 100644
--- a/workflow/ttlcontroller/ttlcontroller.go
+++ b/workflow/ttlcontroller/ttlcontroller.go
@@ -185,45 +185,58 @@ func (c *Controller) deleteWorkflow(key string) error {
 }
 
 func (c *Controller) ttlExpired(wf *wfv1.Workflow) bool {
+	ttlStrategy := getTTLStrategy(wf)
 	// We don't care about the Workflows that are going to be deleted, or the ones that don't need clean up.
-	if wf.DeletionTimestamp != nil || wf.Spec.TTLStrategy == nil || wf.Status.FinishedAt.IsZero() {
+	if wf.DeletionTimestamp != nil || ttlStrategy == nil || wf.Status.FinishedAt.IsZero() {
 		return false
 	}
 	now := c.clock.Now()
-	if wf.Status.Failed() && wf.Spec.TTLStrategy != nil && wf.Spec.TTLStrategy.SecondsAfterFailure != nil {
-		expiry := wf.Status.FinishedAt.Add(time.Second * time.Duration(*wf.Spec.TTLStrategy.SecondsAfterFailure))
+
+	if wf.Status.Failed() && ttlStrategy != nil && ttlStrategy.SecondsAfterFailure != nil {
+		expiry := wf.Status.FinishedAt.Add(time.Second * time.Duration(*ttlStrategy.SecondsAfterFailure))
 		return now.After(expiry)
-	} else if wf.Status.Successful() && wf.Spec.TTLStrategy != nil && wf.Spec.TTLStrategy.SecondsAfterSuccess != nil {
-		expiry := wf.Status.FinishedAt.Add(time.Second * time.Duration(*wf.Spec.TTLStrategy.SecondsAfterSuccess))
+	} else if wf.Status.Successful() && ttlStrategy != nil && ttlStrategy.SecondsAfterSuccess != nil {
+		expiry := wf.Status.FinishedAt.Add(time.Second * time.Duration(*ttlStrategy.SecondsAfterSuccess))
 		return now.After(expiry)
 	} else {
-		expiry := wf.Status.FinishedAt.Add(time.Second * time.Duration(*wf.Spec.TTLStrategy.SecondsAfterCompletion))
+		expiry := wf.Status.FinishedAt.Add(time.Second * time.Duration(*ttlStrategy.SecondsAfterCompletion))
 		return now.After(expiry)
 	}
 }
 
 func timeLeft(wf *wfv1.Workflow, since *time.Time) (*time.Duration, *time.Time) {
-	if wf.DeletionTimestamp != nil || wf.Spec.TTLStrategy == nil || wf.Status.FinishedAt.IsZero() {
+	ttlStrategy := getTTLStrategy(wf)
+	if wf.DeletionTimestamp != nil || ttlStrategy == nil || wf.Status.FinishedAt.IsZero() {
 		return nil, nil
 	}
+
 	sinceUTC := since.UTC()
 	finishAtUTC := wf.Status.FinishedAt.UTC()
 	if finishAtUTC.After(sinceUTC) {
 		log.Infof("Warning: Found Workflow %s/%s finished in the future. This is likely due to time skew in the cluster. Workflow cleanup will be deferred.", wf.Namespace, wf.Name)
 	}
-	if wf.Status.Failed() && wf.Spec.TTLStrategy.SecondsAfterFailure != nil {
-		expireAtUTC := finishAtUTC.Add(time.Duration(*wf.Spec.TTLStrategy.SecondsAfterFailure) * time.Second)
+	if wf.Status.Failed() && ttlStrategy.SecondsAfterFailure != nil {
+		expireAtUTC := finishAtUTC.Add(time.Duration(*ttlStrategy.SecondsAfterFailure) * time.Second)
 		remaining := expireAtUTC.Sub(sinceUTC)
 		return &remaining, &expireAtUTC
-	} else if wf.Status.Successful() && wf.Spec.TTLStrategy.SecondsAfterSuccess != nil {
-		expireAtUTC := finishAtUTC.Add(time.Duration(*wf.Spec.TTLStrategy.SecondsAfterSuccess) * time.Second)
+	} else if wf.Status.Successful() && ttlStrategy.SecondsAfterSuccess != nil {
+		expireAtUTC := finishAtUTC.Add(time.Duration(*ttlStrategy.SecondsAfterSuccess) * time.Second)
 		remaining := expireAtUTC.Sub(sinceUTC)
 		return &remaining, &expireAtUTC
-	} else if wf.Spec.TTLStrategy.SecondsAfterCompletion != nil {
-		expireAtUTC := finishAtUTC.Add(time.Duration(*wf.Spec.TTLStrategy.SecondsAfterCompletion) * time.Second)
+	} else if ttlStrategy.SecondsAfterCompletion != nil {
+		expireAtUTC := finishAtUTC.Add(time.Duration(*ttlStrategy.SecondsAfterCompletion) * time.Second)
 		remaining := expireAtUTC.Sub(sinceUTC)
 		return &remaining, &expireAtUTC
 	} else {
 		return nil, nil
 	}
 }
+
+func getTTLStrategy(wf *wfv1.Workflow) *wfv1.TTLStrategy {
+	ttlStrategy := wf.Spec.TTLStrategy
+
+	if ttlStrategy == nil && wf.Status.StoredWorkflowSpec != nil && wf.Status.StoredWorkflowSpec.TTLStrategy != nil {
+		return wf.Status.StoredWorkflowSpec.TTLStrategy
+	}
+	return ttlStrategy
+}
diff --git a/workflow/validate/validate.go b/workflow/validate/validate.go
index 07642737c..e59a1a7de 100644
--- a/workflow/validate/validate.go
+++ b/workflow/validate/validate.go
@@ -96,6 +96,7 @@ var wfTmplRefAllowedWfSpecValidFields = map[string]bool{
 	"Priority":              true,
 	"Arguments":             true,
 	"WorkflowTemplateRef":   true,
+	"TTLStrategy":           true,
 }
 
 type FakeArguments struct{}


Test patch:
diff --git a/workflow/ttlcontroller/ttlcontroller_test.go b/workflow/ttlcontroller/ttlcontroller_test.go
index d203faabd..a4c515459 100644
--- a/workflow/ttlcontroller/ttlcontroller_test.go
+++ b/workflow/ttlcontroller/ttlcontroller_test.go
@@ -4,15 +4,15 @@ import (
 	"testing"
 	"time"
 
-	wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
-	fakewfclientset "github.com/argoproj/argo/pkg/client/clientset/versioned/fake"
-
 	"github.com/stretchr/testify/assert"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/util/clock"
+	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/workqueue"
 
+	wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
+	fakewfclientset "github.com/argoproj/argo/pkg/client/clientset/versioned/fake"
 	"github.com/argoproj/argo/test"
 	"github.com/argoproj/argo/workflow/util"
 )
@@ -110,11 +110,242 @@ status:
   startedAt: 2018-08-27T20:41:38Z
 `
 
+var wftRefWithTTLinWFT = `
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  creationTimestamp: "2020-06-16T00:57:45Z"
+  generateName: workflow-template-hello-world-
+  generation: 6
+  labels:
+    workflows.argoproj.io/completed: "true"
+    workflows.argoproj.io/phase: Succeeded
+  name: workflow-template-hello-world-k4d26
+  namespace: default
+  resourceVersion: "564446"
+  selfLink: /apis/argoproj.io/v1alpha1/namespaces/argo/workflows/workflow-template-hello-world-k4d26
+  uid: e25cce2d-c71d-4f4e-b016-a0a2e10bf4d1
+spec:
+  arguments:
+    parameters:
+    - name: message
+      value: hello world
+  entrypoint: start
+  templates: null
+  workflowTemplateRef:
+    name: workflow-template-submittable-2.9
+status:
+  conditions:
+  - status: "True"
+    type: Completed
+  finishedAt: "2020-06-16T00:57:51Z"
+  nodes:
+    workflow-template-hello-world-k4d26:
+      displayName: workflow-template-hello-world-k4d26
+      finishedAt: "2020-06-16T00:57:49Z"
+      hostNodeName: docker-desktop
+      id: workflow-template-hello-world-k4d26
+      inputs:
+        parameters:
+        - name: message
+          value: hello world
+      name: workflow-template-hello-world-k4d26
+      outputs:
+        artifacts:
+        - archiveLogs: true
+          name: main-logs
+          s3:
+            accessKeySecret:
+              key: accesskey
+              name: my-minio-cred
+            bucket: my-bucket
+            endpoint: minio:9000
+            insecure: true
+            key: workflow-template-hello-world-k4d26/workflow-template-hello-world-k4d26/main.log
+            secretKeySecret:
+              key: secretkey
+              name: my-minio-cred
+        exitCode: "0"
+      phase: Succeeded
+      resourcesDuration:
+        cpu: 3
+        memory: 1
+      startedAt: "2020-06-16T00:57:45Z"
+      templateRef:
+        name: workflow-template-submittable-2.9
+        template: start
+      templateScope: local/workflow-template-hello-world-k4d26
+      type: Pod
+  phase: Succeeded
+  resourcesDuration:
+    cpu: 3
+    memory: 1
+  startedAt: "2020-06-16T00:57:45Z"
+  storedTemplates:
+    namespaced/workflow-template-submittable-2.9/start:
+      arguments: {}
+      container:
+        args:
+        - '{{inputs.parameters.message}}'
+        command:
+        - echo
+        image: docker/whalesay:latest
+        name: ""
+        resources: {}
+      inputs:
+        parameters:
+        - name: message
+      metadata: {}
+      name: start
+      outputs: {}
+  storedWorkflowTemplateSpec:
+    arguments:
+      parameters:
+      - name: message
+        value: hello world
+    entrypoint: start
+    templates:
+    - arguments: {}
+      container:
+        args:
+        - '{{inputs.parameters.message}}'
+        command:
+        - echo
+        image: docker/whalesay:latest
+        name: ""
+        resources: {}
+      inputs:
+        parameters:
+        - name: message
+      metadata: {}
+      name: start
+      outputs: {}
+    ttlStrategy:
+      secondsAfterCompletion: 10
+`
+var wftRefWithTTLinWF = `
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  creationTimestamp: "2020-06-16T01:00:14Z"
+  generateName: workflow-template-hello-world-
+  generation: 6
+  labels:
+    workflows.argoproj.io/completed: "true"
+    workflows.argoproj.io/phase: Succeeded
+  name: workflow-template-hello-world-jdkdw
+  namespace: default
+  resourceVersion: "564728"
+  selfLink: /apis/argoproj.io/v1alpha1/namespaces/argo/workflows/workflow-template-hello-world-jdkdw
+  uid: 57dac176-2e10-4f1d-b77c-db321d187d83
+spec:
+  arguments:
+    parameters:
+    - name: message
+      value: hello world
+  entrypoint: start
+  templates: null
+  ttlStrategy:
+    secondsAfterCompletion: 10
+  workflowTemplateRef:
+    name: workflow-template-submittable-2.9
+status:
+  conditions:
+  - status: "True"
+    type: Completed
+  finishedAt: "2020-06-16T01:00:19Z"
+  nodes:
+    workflow-template-hello-world-jdkdw:
+      displayName: workflow-template-hello-world-jdkdw
+      finishedAt: "2020-06-16T01:00:17Z"
+      hostNodeName: docker-desktop
+      id: workflow-template-hello-world-jdkdw
+      inputs:
+        parameters:
+        - name: message
+          value: hello world
+      name: workflow-template-hello-world-jdkdw
+      outputs:
+        artifacts:
+        - archiveLogs: true
+          name: main-logs
+          s3:
+            accessKeySecret:
+              key: accesskey
+              name: my-minio-cred
+            bucket: my-bucket
+            endpoint: minio:9000
+            insecure: true
+            key: workflow-template-hello-world-jdkdw/workflow-template-hello-world-jdkdw/main.log
+            secretKeySecret:
+              key: secretkey
+              name: my-minio-cred
+        exitCode: "0"
+      phase: Succeeded
+      resourcesDuration:
+        cpu: 2
+        memory: 1
+      startedAt: "2020-06-16T01:00:14Z"
+      templateRef:
+        name: workflow-template-submittable-2.9
+        template: start
+      templateScope: local/workflow-template-hello-world-jdkdw
+      type: Pod
+  phase: Succeeded
+  resourcesDuration:
+    cpu: 2
+    memory: 1
+  startedAt: "2020-06-16T01:00:14Z"
+  storedTemplates:
+    namespaced/workflow-template-submittable-2.9/start:
+      arguments: {}
+      container:
+        args:
+        - '{{inputs.parameters.message}}'
+        command:
+        - echo
+        image: docker/whalesay:latest
+        name: ""
+        resources: {}
+      inputs:
+        parameters:
+        - name: message
+      metadata: {}
+      name: start
+      outputs: {}
+  storedWorkflowTemplateSpec:
+    arguments:
+      parameters:
+      - name: message
+        value: hello world
+    entrypoint: start
+    templates:
+    - arguments: {}
+      container:
+        args:
+        - '{{inputs.parameters.message}}'
+        command:
+        - echo
+        image: docker/whalesay:latest
+        name: ""
+        resources: {}
+      inputs:
+        parameters:
+        - name: message
+      metadata: {}
+      name: start
+      outputs: {}
+    ttlStrategy:
+      secondsAfterCompletion: 60
+`
+
 func newTTLController() *Controller {
 	clock := clock.NewFakeClock(time.Now())
+	wfclientset := fakewfclientset.NewSimpleClientset()
+	wfInformer := cache.NewSharedIndexInformer(nil, nil, 0, nil)
 	return &Controller{
-		wfclientset: fakewfclientset.NewSimpleClientset(),
-		//wfInformer:   informer,
+		wfclientset:  wfclientset,
+		wfInformer:   wfInformer,
 		resyncPeriod: workflowTTLResyncPeriod,
 		clock:        clock,
 		workqueue:    workqueue.NewNamedDelayingQueue("workflow-ttl"),
@@ -176,6 +407,26 @@ func TestTTLStrategySucceded(t *testing.T) {
 	controller.enqueueWF(un)
 	assert.Equal(t, 1, controller.workqueue.Len())
 
+	wf2 := test.LoadWorkflowFromBytes([]byte(wftRefWithTTLinWFT))
+	wf2.Status.FinishedAt = metav1.Time{Time: controller.clock.Now().Add(-11 * time.Second)}
+	un, err = util.ToUnstructured(wf2)
+	assert.NoError(t, err)
+	_, err = controller.wfclientset.ArgoprojV1alpha1().Workflows("default").Create(wf2)
+	assert.NoError(t, err)
+	controller.enqueueWF(un)
+	controller.processNextWorkItem()
+	assert.Equal(t, 1, controller.workqueue.Len())
+
+	wf3 := test.LoadWorkflowFromBytes([]byte(wftRefWithTTLinWF))
+	wf3.Status.FinishedAt = metav1.Time{Time: controller.clock.Now().Add(-11 * time.Second)}
+	un, err = util.ToUnstructured(wf3)
+	assert.NoError(t, err)
+	_, err = controller.wfclientset.ArgoprojV1alpha1().Workflows("default").Create(wf3)
+	assert.NoError(t, err)
+	controller.enqueueWF(un)
+	controller.processNextWorkItem()
+	assert.Equal(t, 1, controller.workqueue.Len())
+
 }
 
 func TestTTLStrategyFailed(t *testing.T) {
@@ -345,3 +596,32 @@ func TestTTLlExpired(t *testing.T) {
 	wf6.Status.FinishedAt = metav1.Time{Time: controller.clock.Now().Add(-11 * time.Second)}
 	assert.Equal(t, true, controller.ttlExpired(wf6))
 }
+
+func TestGetTTLStrategy(t *testing.T) {
+	var ten int32 = 10
+	wf := test.LoadWorkflowFromBytes([]byte(succeededWf))
+	wf.Spec.TTLStrategy = &wfv1.TTLStrategy{
+		SecondsAfterCompletion: &ten,
+	}
+
+	ttl := getTTLStrategy(wf)
+	assert.NotNil(t, ttl)
+	assert.Equal(t, ten, *ttl.SecondsAfterCompletion)
+
+	wf1 := test.LoadWorkflowFromBytes([]byte(wftRefWithTTLinWF))
+	ttl = getTTLStrategy(wf1)
+	assert.NotNil(t, ttl)
+	assert.Equal(t, ten, *ttl.SecondsAfterCompletion)
+	wf1.Spec.TTLStrategy = nil
+	wf1.Status.StoredWorkflowSpec.TTLStrategy = nil
+	ttl = getTTLStrategy(wf1)
+	assert.Nil(t, ttl)
+
+	wf2 := test.LoadWorkflowFromBytes([]byte(wftRefWithTTLinWFT))
+	ttl = getTTLStrategy(wf2)
+	assert.NotNil(t, ttl)
+	assert.Equal(t, ten, *ttl.SecondsAfterCompletion)
+	wf2.Status.StoredWorkflowSpec.TTLStrategy = nil
+	ttl = getTTLStrategy(wf2)
+	assert.Nil(t, ttl)
+}