Repository: argoproj/argo
Pull request #3249 (2020-06-17 20:57:01)

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



Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 90fc4e238..026ae1d24 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,77 +1,16 @@
 version: 2.1
-orbs:
-  sonarcloud: sonarsource/sonarcloud@1.0.1
-commands:
-  restore_go_mod:
-    steps:
-      - restore_cache:
-          name: Restore go mod cache
-          keys:
-            - go-mod-v3-{{ checksum "go.sum" }}-{{ .Environment.CIRCLE_JOB }}
-  save_go_mod:
-    steps:
-      - save_cache:
-          name: Save go mod cache
-          key:  go-mod-v3-{{ checksum "go.sum" }}-{{ .Environment.CIRCLE_JOB }}
-          paths:
-            - /home/circleci/.go_workspace/go/pkg/mod
-          when: always
-  install_golang:
+jobs:
+  ci:
+    docker:
+      - image: cimg/base:2020.01
     steps:
       - run:
-          name: Install Golang v1.13.4
+          name: hello
           command: |
-            go get golang.org/dl/go1.13.4
-            [ -e /home/circleci/sdk/go1.13.4 ] || go1.13.4 download
-            echo "export GOPATH=/home/circleci/.go_workspace" | tee -a $BASH_ENV
-            echo "export PATH=/home/circleci/sdk/go1.13.4/bin:\$PATH" | tee -a $BASH_ENV
-  save_go_cache:
-    steps:
-      - save_cache:
-          name: Save Golang cache
-          key: go-v7-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}
-          paths:
-            - /home/circleci/.cache/go-build
-            - /home/circleci/sdk/go1.13.4
-          when: always
-  restore_go_cache:
-    steps:
-      - restore_cache:
-          name: Restore Golang cache
-          keys:
-            - go-v7-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}
-            - go-v7-master-{{ .Environment.CIRCLE_JOB }}
-jobs:
-  sonar:
-    working_directory: /home/circleci/.go_workspace/src/github.com/argoproj/argo
-    machine:
-      image: ubuntu-1604:201903-01
-    steps:
-      - checkout
-      - restore_go_cache
-      - install_golang
-      - restore_go_mod
-      - 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
-            echo "sonar.pullrequest.key=$CIRCLE_PR_NUMBER" >> sonar-project.properties
-          fi
-      - sonarcloud/scan
-      - run: cp dist/sonar-project.properties .
-      - store_test_results:
-          path: test-results
-          when: always
-      - store_artifacts:
-          path: test-results
-          destination: .
-          when: always
-      - save_go_cache
-      - save_go_mod
+            echo "Hello, world"
 
 workflows:
   version: 2
-  CI:
+  workflow:
     jobs:
-      - sonar
+      - ci
diff --git a/USERS.md b/USERS.md
index 0c75333a9..dabd6b11e 100644
--- a/USERS.md
+++ b/USERS.md
@@ -25,6 +25,7 @@ Currently, the following organizations are **officially** using Argo Workflows:
 1. [CoreFiling](https://www.corefiling.com/)
 1. [Cratejoy](https://www.cratejoy.com/)
 1. [CoreWeave Cloud](https://www.coreweave.com)
+1. [Cruise](https://getcruise.com/)
 1. [CyberAgent](https://www.cyberagent.co.jp/en/)
 1. [Cyrus Biotechnology](https://cyrusbio.com/)
 1. [Datadog](https://www.datadoghq.com/)
diff --git a/cmd/argo/commands/delete.go b/cmd/argo/commands/delete.go
index bcac0670a..103b2f319 100644
--- a/cmd/argo/commands/delete.go
+++ b/cmd/argo/commands/delete.go
@@ -25,6 +25,13 @@ func NewDeleteCommand() *cobra.Command {
 	var command = &cobra.Command{
 		Use:   "delete [--dry-run] [WORKFLOW...|[--all] [--older] [--completed] [--prefix PREFIX] [--selector SELECTOR]]",
 		Short: "delete workflows",
+		Example: `# Delete a workflow:
+
+  argo delete my-wf
+
+# Delete the latest workflow:
+  argo delete @latest
+`,
 		Run: func(cmd *cobra.Command, args []string) {
 			ctx, apiClient := client.NewAPIClient()
 			serviceClient := apiClient.NewWorkflowServiceClient()
diff --git a/cmd/argo/commands/get.go b/cmd/argo/commands/get.go
index 2e321bbb1..825f41d07 100644
--- a/cmd/argo/commands/get.go
+++ b/cmd/argo/commands/get.go
@@ -60,6 +60,13 @@ func NewGetCommand() *cobra.Command {
 	var command = &cobra.Command{
 		Use:   "get WORKFLOW...",
 		Short: "display details about a workflow",
+		Example: `# Get information about a workflow:
+
+  argo get my-wf
+
+# Get the latest workflow:
+  argo get @latest
+`,
 		Run: func(cmd *cobra.Command, args []string) {
 			if len(args) == 0 {
 				cmd.HelpFunc()(cmd, args)
diff --git a/cmd/argo/commands/logs.go b/cmd/argo/commands/logs.go
index f9c80b219..ae2e25e67 100644
--- a/cmd/argo/commands/logs.go
+++ b/cmd/argo/commands/logs.go
@@ -46,6 +46,8 @@ func NewLogsCommand() *cobra.Command {
 
   argo logs --since=1h my-pod
 
+# Print the logs of the latest workflow:
+  argo logs @latest
 `,
 		Run: func(cmd *cobra.Command, args []string) {
 
diff --git a/cmd/argo/commands/resubmit.go b/cmd/argo/commands/resubmit.go
index 9cc508608..7cd710529 100644
--- a/cmd/argo/commands/resubmit.go
+++ b/cmd/argo/commands/resubmit.go
@@ -16,6 +16,13 @@ func NewResubmitCommand() *cobra.Command {
 	var command = &cobra.Command{
 		Use:   "resubmit [WORKFLOW...]",
 		Short: "resubmit one or more workflows",
+		Example: `# Resubmit a workflow:
+
+  argo resubmit my-wf
+
+# Resubmit the latest workflow:
+  argo resubmit @latest
+`,
 		Run: func(cmd *cobra.Command, args []string) {
 			ctx, apiClient := client.NewAPIClient()
 			serviceClient := apiClient.NewWorkflowServiceClient()
diff --git a/cmd/argo/commands/resume.go b/cmd/argo/commands/resume.go
index 0b765cb11..c8c351963 100644
--- a/cmd/argo/commands/resume.go
+++ b/cmd/argo/commands/resume.go
@@ -23,6 +23,13 @@ func NewResumeCommand() *cobra.Command {
 	var command = &cobra.Command{
 		Use:   "resume WORKFLOW1 WORKFLOW2...",
 		Short: "resume zero or more workflows",
+		Example: `# Resume a workflow that has been stopped or suspended:
+
+  argo resume my-wf
+
+# Resume the latest workflow:
+  argo resume @latest
+`,
 		Run: func(cmd *cobra.Command, args []string) {
 			ctx, apiClient := client.NewAPIClient()
 			serviceClient := apiClient.NewWorkflowServiceClient()
diff --git a/cmd/argo/commands/retry.go b/cmd/argo/commands/retry.go
index c50eaad4b..e990fbcd3 100644
--- a/cmd/argo/commands/retry.go
+++ b/cmd/argo/commands/retry.go
@@ -24,6 +24,16 @@ func NewRetryCommand() *cobra.Command {
 	var command = &cobra.Command{
 		Use:   "retry [WORKFLOW...]",
 		Short: "retry zero or more workflows",
+		Example: `# Retry a workflow:
+
+  argo retry my-wf
+
+# Retry several workflows: 
+  argo retry my-wf my-other-wf my-third-wf
+
+# Retry the latest workflow:
+  argo retry @latest
+`,
 		Run: func(cmd *cobra.Command, args []string) {
 			ctx, apiClient := client.NewAPIClient()
 			serviceClient := apiClient.NewWorkflowServiceClient()
diff --git a/cmd/argo/commands/root.go b/cmd/argo/commands/root.go
index c4954b89b..610878385 100644
--- a/cmd/argo/commands/root.go
+++ b/cmd/argo/commands/root.go
@@ -27,10 +27,9 @@ const (
 // NewCommand returns a new instance of an argo command
 func NewCommand() *cobra.Command {
 	var command = &cobra.Command{
-		Use:   CLIName,
-		Short: "argo is the command line interface to Argo",
-		Example: fmt.Sprintf(`
-If you're using the Argo Server (e.g. because you need large workflow support or workflow archive), please read %s.`, help.CLI),
+		Use:     CLIName,
+		Short:   "argo is the command line interface to Argo",
+		Example: fmt.Sprintf(`If you're using the Argo Server (e.g. because you need large workflow support or workflow archive), please read %s.`, help.CLI),
 		Run: func(cmd *cobra.Command, args []string) {
 			cmd.HelpFunc()(cmd, args)
 		},
diff --git a/cmd/argo/commands/stop.go b/cmd/argo/commands/stop.go
index abb328c68..47cb10af7 100644
--- a/cmd/argo/commands/stop.go
+++ b/cmd/argo/commands/stop.go
@@ -25,6 +25,13 @@ func NewStopCommand() *cobra.Command {
 	var command = &cobra.Command{
 		Use:   "stop WORKFLOW WORKFLOW2...",
 		Short: "stop zero or more workflows",
+		Example: `# Stop about a workflow:
+
+  argo stop my-wf
+
+# Stop the latest workflow:
+  argo stop @latest
+`,
 		Run: func(cmd *cobra.Command, args []string) {
 
 			ctx, apiClient := client.NewAPIClient()
diff --git a/cmd/argo/commands/suspend.go b/cmd/argo/commands/suspend.go
index 3326504de..b15a8d5a1 100644
--- a/cmd/argo/commands/suspend.go
+++ b/cmd/argo/commands/suspend.go
@@ -14,6 +14,13 @@ func NewSuspendCommand() *cobra.Command {
 	var command = &cobra.Command{
 		Use:   "suspend WORKFLOW1 WORKFLOW2...",
 		Short: "suspend zero or more workflow",
+		Example: `# Suspend a workflow:
+
+  argo suspend my-wf
+
+# Suspend the latest workflow:
+  argo suspend @latest
+`,
 		Run: func(cmd *cobra.Command, args []string) {
 			ctx, apiClient := client.NewAPIClient()
 			serviceClient := apiClient.NewWorkflowServiceClient()
diff --git a/cmd/argo/commands/terminate.go b/cmd/argo/commands/terminate.go
index c39a8559a..e1807fd3e 100644
--- a/cmd/argo/commands/terminate.go
+++ b/cmd/argo/commands/terminate.go
@@ -14,6 +14,13 @@ func NewTerminateCommand() *cobra.Command {
 	var command = &cobra.Command{
 		Use:   "terminate WORKFLOW WORKFLOW2...",
 		Short: "terminate zero or more workflows",
+		Example: `# Terminate a workflow:
+
+  argo terminate my-wf
+
+# Terminate the latest workflow:
+  argo terminate @latest
+`,
 		Run: func(cmd *cobra.Command, args []string) {
 
 			ctx, apiClient := client.NewAPIClient()
diff --git a/cmd/argo/commands/wait.go b/cmd/argo/commands/wait.go
index b6b82a98e..34ccdc9e6 100644
--- a/cmd/argo/commands/wait.go
+++ b/cmd/argo/commands/wait.go
@@ -27,6 +27,13 @@ func NewWaitCommand() *cobra.Command {
 	var command = &cobra.Command{
 		Use:   "wait [WORKFLOW...]",
 		Short: "waits for workflows to complete",
+		Example: `# Wait on a workflow:
+
+  argo wait my-wf
+
+# Wait on the latest workflow:
+  argo wait @latest
+`,
 		Run: func(cmd *cobra.Command, args []string) {
 			WaitWorkflows(args, ignoreNotFound, false)
 		},
diff --git a/cmd/argo/commands/watch.go b/cmd/argo/commands/watch.go
index 3a12f7120..a1ee20f5d 100644
--- a/cmd/argo/commands/watch.go
+++ b/cmd/argo/commands/watch.go
@@ -28,6 +28,13 @@ func NewWatchCommand() *cobra.Command {
 	var command = &cobra.Command{
 		Use:   "watch WORKFLOW",
 		Short: "watch a workflow until it completes",
+		Example: `# Watch a workflow:
+
+  argo watch my-wf
+
+# Watch the latest workflow:
+  argo watch @latest
+`,
 		Run: func(cmd *cobra.Command, args []string) {
 			if len(args) != 1 {
 				cmd.HelpFunc()(cmd, args)
diff --git a/config/config.go b/config/config.go
index f55ad6ce6..abde6d582 100644
--- a/config/config.go
+++ b/config/config.go
@@ -2,6 +2,8 @@ package config
 
 import (
 	apiv1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/labels"
 
 	wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
 	"github.com/argoproj/argo/server/auth/sso"
@@ -132,6 +134,8 @@ type PersistConfig struct {
 	NodeStatusOffload bool `json:"nodeStatusOffLoad,omitempty"`
 	// Archive workflows to persistence.
 	Archive bool `json:"archive,omitempty"`
+	// ArchivelabelSelector holds LabelSelector to determine workflow persistence.
+	ArchiveLabelSelector *metav1.LabelSelector `json:"archiveLabelSelector,omitempty"`
 	// in days
 	ArchiveTTL     TTL               `json:"archiveTTL,omitempty"`
 	ClusterName    string            `json:"clusterName,omitempty"`
@@ -140,6 +144,13 @@ type PersistConfig struct {
 	MySQL          *MySQLConfig      `json:"mysql,omitempty"`
 }
 
+func (c PersistConfig) GetArchiveLabelSelector() (labels.Selector, error) {
+	if c.ArchiveLabelSelector == nil {
+		return labels.Everything(), nil
+	}
+	return metav1.LabelSelectorAsSelector(c.ArchiveLabelSelector)
+}
+
 func (c PersistConfig) GetClusterName() string {
 	if c.ClusterName != "" {
 		return c.ClusterName
diff --git a/docs/fields.md b/docs/fields.md
index f69a72d90..9dc48663f 100644
--- a/docs/fields.md
+++ b/docs/fields.md
@@ -3164,6 +3164,8 @@ MetricLabel is a single label for a prometheus metric
 
 - [`dag-enhanced-depends.yaml`](../examples/dag-enhanced-depends.yaml)
 
+- [`hello-world.yaml`](../examples/hello-world.yaml)
+
 - [`pod-metadata.yaml`](../examples/pod-metadata.yaml)
 
 - [`resource-delete-with-flags.yaml`](../examples/resource-delete-with-flags.yaml)
diff --git a/docs/variables.md b/docs/variables.md
index 0f1cf8389..26bd00d0c 100644
--- a/docs/variables.md
+++ b/docs/variables.md
@@ -16,6 +16,7 @@ The following variables are made available to reference various metadata of a wo
 | `steps.<STEPNAME>.status` | Phase status of any previous step |
 | `steps.<STEPNAME>.exitCode` | Exit code of any previous script or container step |
 | `steps.<STEPNAME>.outputs.result` | Output result of any previous container or script step |
+| `steps.<STEPNAME>.outputs.parameters` | When the previous step uses 'withItems', this contains a JSON array of the output parameters of each invocation |
 | `steps.<STEPNAME>.outputs.parameters.<NAME>` | Output parameter of any previous step |
 | `steps.<STEPNAME>.outputs.artifacts.<NAME>` | Output artifact of any previous step |
 
@@ -26,6 +27,7 @@ The following variables are made available to reference various metadata of a wo
 | `tasks.<TASKNAME>.status` | Phase status of any previous task |
 | `tasks.<TASKNAME>.exitCode` | Exit code of any previous script or container task |
 | `tasks.<TASKNAME>.outputs.result` | Output result of any previous container or script task |
+| `tasks.<STEPNAME>.outputs.parameters` | When the previous task uses 'withItems', this contains a JSON array of the output parameters of each invocation |
 | `tasks.<TASKNAME>.outputs.parameters.<NAME>` | Output parameter of any previous task |
 | `tasks.<TASKNAME>.outputs.artifacts.<NAME>` | Output artifact of any previous task |
 
diff --git a/examples/hello-world.yaml b/examples/hello-world.yaml
index 51a0994ad..05690abc4 100644
--- a/examples/hello-world.yaml
+++ b/examples/hello-world.yaml
@@ -2,6 +2,8 @@ apiVersion: argoproj.io/v1alpha1
 kind: Workflow
 metadata:
   generateName: hello-world-
+  labels:
+    workflows.argoproj.io/archive-strategy: false
 spec:
   entrypoint: whalesay
   templates:
diff --git a/workflow/controller/config.go b/workflow/controller/config.go
index 02e6b2682..d14e5b96f 100644
--- a/workflow/controller/config.go
+++ b/workflow/controller/config.go
@@ -58,6 +58,11 @@ func (wfc *WorkflowController) updateConfig(config config.Config) error {
 		}
 		if persistence.Archive {
 			instanceIDService := instanceid.NewService(wfc.Config.InstanceID)
+
+			wfc.archiveLabelSelector, err = persistence.GetArchiveLabelSelector()
+			if err != nil {
+				return err
+			}
 			wfc.wfArchive = sqldb.NewWorkflowArchive(session, persistence.GetClusterName(), wfc.managedNamespace, instanceIDService)
 			log.Info("Workflow archiving is enabled")
 		} else {
diff --git a/workflow/controller/controller.go b/workflow/controller/controller.go
index e6d49b67d..fee629b86 100644
--- a/workflow/controller/controller.go
+++ b/workflow/controller/controller.go
@@ -85,6 +85,7 @@ type WorkflowController struct {
 	wfArchive             sqldb.WorkflowArchive
 	metrics               metrics.Metrics
 	eventRecorder         record.EventRecorder
+	archiveLabelSelector  labels.Selector
 }
 
 const (
@@ -765,3 +766,8 @@ func (wfc *WorkflowController) getMetricsServerConfig() (metrics.ServerConfig, m
 
 	return metricsConfig, telemetryConfig
 }
+
+func (wfc *WorkflowController) isArchivable(wf *wfv1.Workflow) bool {
+	return wfc.archiveLabelSelector.Matches(labels.Set(wf.Labels))
+
+}
diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go
index b0f22e1ea..5fee42038 100644
--- a/workflow/controller/operator.go
+++ b/workflow/controller/operator.go
@@ -831,6 +831,9 @@ func (woc *wfOperationCtx) podReconciliation() error {
 
 			node.Message = "pod deleted"
 			node.Phase = wfv1.NodeError
+			// FinishedAt must be set since retry strategy depends on it to determine the backoff duration.
+			// See processNodeRetries for more details.
+			node.FinishedAt = metav1.Time{Time: time.Now().UTC()}
 			woc.wf.Status.Nodes[nodeID] = node
 			woc.log.Warnf("pod %s deleted", nodeID)
 			woc.updated = true
@@ -1600,9 +1603,14 @@ func (woc *wfOperationCtx) markWorkflowPhase(phase wfv1.NodePhase, markCompleted
 				woc.updated = true
 				woc.wf.Status.Message = err.Error()
 			}
-			err = woc.controller.wfArchive.ArchiveWorkflow(woc.wf)
-			if err != nil {
-				woc.log.WithField("err", err).Error("Failed to archive workflow")
+
+			if woc.controller.isArchivable(woc.wf) {
+				err = woc.controller.wfArchive.ArchiveWorkflow(woc.wf)
+				if err != nil {
+					woc.log.WithField("err", err).Error("Failed to archive workflow")
+				}
+			} else {
+				woc.log.Infof("Does't match with archive label selector. Skipping Archive")
 			}
 			woc.updated = true
 		}


Test patch:
diff --git a/workflow/controller/controller_test.go b/workflow/controller/controller_test.go
index 185f7eedc..bdb6f737b 100644
--- a/workflow/controller/controller_test.go
+++ b/workflow/controller/controller_test.go
@@ -5,6 +5,8 @@ import (
 	"testing"
 	"time"
 
+	"k8s.io/apimachinery/pkg/labels"
+
 	"github.com/stretchr/testify/assert"
 	authorizationv1 "k8s.io/api/authorization/v1"
 	apiv1 "k8s.io/api/core/v1"
@@ -120,18 +122,19 @@ func newController(objects ...runtime.Object) (context.CancelFunc, *WorkflowCont
 		Config: config.Config{
 			ExecutorImage: "executor:latest",
 		},
-		kubeclientset:   kube,
-		wfclientset:     wfclientset,
-		completedPods:   make(chan string, 16),
-		wfInformer:      wfInformer,
-		wftmplInformer:  wftmplInformer,
-		cwftmplInformer: cwftmplInformer,
-		wfQueue:         workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
-		podQueue:        workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
-		wfArchive:       sqldb.NullWorkflowArchive,
-		hydrator:        hydratorfake.Noop,
-		metrics:         metrics.New(metrics.ServerConfig{}, metrics.ServerConfig{}),
-		eventRecorder:   record.NewFakeRecorder(16),
+		kubeclientset:        kube,
+		wfclientset:          wfclientset,
+		completedPods:        make(chan string, 16),
+		wfInformer:           wfInformer,
+		wftmplInformer:       wftmplInformer,
+		cwftmplInformer:      cwftmplInformer,
+		wfQueue:              workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
+		podQueue:             workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
+		wfArchive:            sqldb.NullWorkflowArchive,
+		hydrator:             hydratorfake.Noop,
+		metrics:              metrics.New(metrics.ServerConfig{}, metrics.ServerConfig{}),
+		eventRecorder:        record.NewFakeRecorder(16),
+		archiveLabelSelector: labels.Everything(),
 	}
 	return cancel, controller
 }
@@ -382,3 +385,29 @@ func TestCheckAndInitWorkflowTmplRef(t *testing.T) {
 
 	})
 }
+
+func TestIsArchivable(t *testing.T) {
+	_, controller := newController()
+	var lblSelector metav1.LabelSelector
+	lblSelector.MatchLabels = make(map[string]string)
+	lblSelector.MatchLabels["workflows.argoproj.io/archive-strategy"] = "true"
+
+	workflow := unmarshalWF(helloWorldWf)
+	t.Run("EverythingSelector", func(t *testing.T) {
+		controller.archiveLabelSelector = labels.Everything()
+		assert.True(t, controller.isArchivable(workflow))
+	})
+	t.Run("NothingSelector", func(t *testing.T) {
+		controller.archiveLabelSelector = labels.Nothing()
+		assert.False(t, controller.isArchivable(workflow))
+	})
+	t.Run("ConfiguredSelector", func(t *testing.T) {
+		selector, err := metav1.LabelSelectorAsSelector(&lblSelector)
+		assert.NoError(t, err)
+		controller.archiveLabelSelector = selector
+		assert.False(t, controller.isArchivable(workflow))
+		workflow.Labels = make(map[string]string)
+		workflow.Labels["workflows.argoproj.io/archive-strategy"] = "true"
+		assert.True(t, controller.isArchivable(workflow))
+	})
+}