Repository: elastic/ecctl
Pull request #239 (2020-04-02 01:10:27)

cmd: Temporarily remove ability to create a flag based deployment
<!--- Provide a general summary of your changes in the title above. -->

## Description
This change temporarily removes the ability to create a flag based deployment until reads to deployment templates are available to all users.

## Motivation and Context
Currently only ECE users have permissions to perform reads on deployment templates. To create a flag based deployment all users would need these permissions. We will wait until these endpoints are available to all to reinstate flag based deployment creation.

## How Has This Been Tested?
Manually by running the command and by running `make docs`

## Types of Changes
<!--- What types of changes does your code introduce? Put an `x` in -->
<!--- all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Refactoring (improves code quality but has no user-facing effect)
- [x] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation

## Readiness Checklist
<!--- Go over all the following points, and put an `x` in all the boxes -->
<!--- that apply.  If you're unsure about any of these, don't hesitate -->
<!--- to ask. We're here to help! -->
- [x] My code follows the code style of this project
- [x] My change requires a change to the documentation
- [x] I have updated the documentation accordingly
- [ ] I have added tests to cover my changes
- [x] All new and existing tests passed



Patch:
diff --git a/cmd/deployment/apm/create.go b/cmd/deployment/apm/create.go
index eab12e2..a076ea7 100644
--- a/cmd/deployment/apm/create.go
+++ b/cmd/deployment/apm/create.go
@@ -106,17 +106,12 @@ var createApmCmd = &cobra.Command{
 		}
 
 		var track, _ = cmd.Flags().GetBool("track")
-		return cmdutil.Track(cmdutil.TrackParams{
-			TrackResourcesParams: depresource.TrackResourcesParams{
-				API:          ecctl.Get().API,
-				Resources:    res.Resources,
-				Orphaned:     res.ShutdownResources,
-				OutputDevice: ecctl.Get().Config.OutputDevice,
-			},
-			Formatter: ecctl.Get().Formatter,
-			Track:     track,
-			Response:  res,
-		})
+		return cmdutil.Track(cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+			App:          ecctl.Get(),
+			DeploymentID: id,
+			Track:        track,
+			Response:     res,
+		}))
 	},
 }
 
diff --git a/cmd/deployment/apm/delete.go b/cmd/deployment/apm/delete.go
index f89d23e..2bc5913 100644
--- a/cmd/deployment/apm/delete.go
+++ b/cmd/deployment/apm/delete.go
@@ -18,9 +18,10 @@
 package cmdapm
 
 import (
-	"github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
+	sdkcmdutil "github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
 	"github.com/spf13/cobra"
 
+	cmdutil "github.com/elastic/ecctl/cmd/util"
 	"github.com/elastic/ecctl/pkg/deployment/apm"
 	"github.com/elastic/ecctl/pkg/ecctl"
 	"github.com/elastic/ecctl/pkg/util"
@@ -29,16 +30,19 @@ import (
 var deleteApmCmd = &cobra.Command{
 	Use:     "delete <apm deployment id>",
 	Short:   "Deletes an APM deployment",
-	PreRunE: cmdutil.MinimumNArgsAndUUID(1),
+	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		if stop, _ := cmd.Flags().GetBool("stop"); stop {
 			err := apm.Shutdown(apm.ShutdownParams{
-				API: ecctl.Get().API,
-				ID:  args[0],
-				TrackParams: util.TrackParams{
-					Track:  true,
-					Output: ecctl.Get().Config.OutputDevice,
-				},
+				API:   ecctl.Get().API,
+				ID:    args[0],
+				Track: true,
+				TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+					App:        ecctl.Get(),
+					ResourceID: args[0],
+					Kind:       util.Apm,
+					Track:      true,
+				}).TrackChangeParams,
 			})
 			if err != nil {
 				return err
diff --git a/cmd/deployment/apm/plan/cancel.go b/cmd/deployment/apm/plan/cancel.go
index cbd87ec..6a74eee 100644
--- a/cmd/deployment/apm/plan/cancel.go
+++ b/cmd/deployment/apm/plan/cancel.go
@@ -18,8 +18,6 @@
 package cmdapmplan
 
 import (
-	"fmt"
-
 	sdkcmdutil "github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
 	"github.com/spf13/cobra"
 
@@ -35,20 +33,17 @@ var cancelPlanCmd = &cobra.Command{
 	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		track, _ := cmd.Flags().GetBool("track")
-		err := apm.CancelPlan(apm.PlanParams{
-			API: ecctl.Get().API,
-			ID:  args[0],
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
+		return apm.CancelPlan(apm.PlanParams{
+			API:   ecctl.Get().API,
+			ID:    args[0],
+			Track: track,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Apm,
+				Track:      track,
+			}).TrackChangeParams,
 		})
-		if err != nil {
-			return err
-		}
-
-		fmt.Printf("Cluster [%s][Apm]: pending plan canceled\n", args[0])
-		return nil
 	},
 }
 
diff --git a/cmd/deployment/apm/plan/history.go b/cmd/deployment/apm/plan/history.go
index bf5a440..1d7d91b 100644
--- a/cmd/deployment/apm/plan/history.go
+++ b/cmd/deployment/apm/plan/history.go
@@ -24,10 +24,8 @@ import (
 	sdkcmdutil "github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
 	"github.com/spf13/cobra"
 
-	cmdutil "github.com/elastic/ecctl/cmd/util"
 	"github.com/elastic/ecctl/pkg/deployment/apm"
 	"github.com/elastic/ecctl/pkg/ecctl"
-	"github.com/elastic/ecctl/pkg/util"
 )
 
 var listPlansCmd = &cobra.Command{
@@ -36,14 +34,9 @@ var listPlansCmd = &cobra.Command{
 	Aliases: []string{"attempts"},
 	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
-		track, _ := cmd.Flags().GetBool("track")
 		p, err := apm.ListPlanHistory(apm.PlanParams{
 			API: ecctl.Get().API,
 			ID:  args[0],
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
 		})
 		if err != nil {
 			return nil
@@ -56,7 +49,3 @@ var listPlansCmd = &cobra.Command{
 		)
 	},
 }
-
-func init() {
-	listPlansCmd.Flags().Bool("track", false, cmdutil.TrackFlagMessage)
-}
diff --git a/cmd/deployment/apm/plan/monitor.go b/cmd/deployment/apm/plan/monitor.go
index 2ff4df0..c897f44 100644
--- a/cmd/deployment/apm/plan/monitor.go
+++ b/cmd/deployment/apm/plan/monitor.go
@@ -18,9 +18,6 @@
 package cmdapmplan
 
 import (
-	"time"
-
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
 	sdkcmdutil "github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
 	"github.com/spf13/cobra"
 
@@ -35,18 +32,16 @@ var monitorPlanCmd = &cobra.Command{
 	Short:   "Monitors the pending plan",
 	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
-		return util.TrackCluster(util.TrackClusterParams{
-			TrackParams: plan.TrackParams{
-				ID:   args[0],
-				API:  ecctl.Get().API,
-				Kind: "apm",
-			},
-			Output: ecctl.Get().Config.OutputDevice,
-		})
+		return cmdutil.Track(cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+			App:          ecctl.Get(),
+			DeploymentID: args[0],
+			Kind:         util.Apm,
+			Track:        true,
+		}))
 	},
 }
 
 func init() {
-	monitorPlanCmd.Flags().Duration("poll-interval", time.Second*2, "Monitor poll interval")
-	monitorPlanCmd.Flags().Uint8("retries", 3, cmdutil.PlanRetriesFlagMessage)
+	monitorPlanCmd.Flags().Duration("poll-interval", util.DefaultPollFrequency, "Monitor poll interval")
+	monitorPlanCmd.Flags().Uint8("retries", util.DefaultRetries, cmdutil.PlanRetriesFlagMessage)
 }
diff --git a/cmd/deployment/apm/plan/reapply.go b/cmd/deployment/apm/plan/reapply.go
index a120e1c..f61f267 100644
--- a/cmd/deployment/apm/plan/reapply.go
+++ b/cmd/deployment/apm/plan/reapply.go
@@ -35,27 +35,30 @@ var reapplyLatestPlanCmd = &cobra.Command{
 	Short:   "Reapplies the latest plan attempt, resetting all transient settings",
 	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
-		var reparams = &planutil.ReapplyParams{ID: args[0]}
+		var reparams = planutil.ReapplyParams{ID: args[0]}
 		// Obtains the parameter's value and sets it in params
 		for name := range util.FieldsOfStruct(reparams) {
 			val, err := cmd.Flags().GetBool(name)
 			if err != nil {
 				return err
 			}
-			util.Set(reparams, name, val)
+			util.Set(&reparams, name, val)
 		}
 
 		track, _ := cmd.Flags().GetBool("track")
 		p, err := apm.ReapplyLatestPlanAttempt(
 			apm.PlanParams{
-				TrackParams: util.TrackParams{
-					Track:  track,
-					Output: ecctl.Get().Config.OutputDevice,
-				},
+				Track: track,
+				TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+					App:        ecctl.Get(),
+					ResourceID: args[0],
+					Kind:       util.Apm,
+					Track:      track,
+				}).TrackChangeParams,
 				API: ecctl.Get().API,
 				ID:  args[0],
 			},
-			*reparams,
+			reparams,
 		)
 		if err != nil {
 			return err
diff --git a/cmd/deployment/apm/restart.go b/cmd/deployment/apm/restart.go
index 4a08e56..9fac7ef 100644
--- a/cmd/deployment/apm/restart.go
+++ b/cmd/deployment/apm/restart.go
@@ -39,10 +39,13 @@ var restartApmCmd = &cobra.Command{
 			API:   ecctl.Get().API,
 			ID:    args[0],
 			Force: force,
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
+			Track: track,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Apm,
+				Track:      track,
+			}).TrackChangeParams,
 		})
 	},
 }
diff --git a/cmd/deployment/apm/stop.go b/cmd/deployment/apm/stop.go
index b340b9f..a1511e3 100644
--- a/cmd/deployment/apm/stop.go
+++ b/cmd/deployment/apm/stop.go
@@ -36,13 +36,16 @@ var stopApmCmd = &cobra.Command{
 		track, _ := cmd.Flags().GetBool("track")
 		hide, _ := cmd.Flags().GetBool("hide")
 		return apm.Shutdown(apm.ShutdownParams{
-			API:  ecctl.Get().API,
-			ID:   args[0],
-			Hide: hide,
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
+			API:   ecctl.Get().API,
+			ID:    args[0],
+			Hide:  hide,
+			Track: track,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Apm,
+				Track:      track,
+			}).TrackChangeParams,
 		})
 	},
 }
diff --git a/cmd/deployment/apm/upgrade.go b/cmd/deployment/apm/upgrade.go
index 065da81..e35a346 100644
--- a/cmd/deployment/apm/upgrade.go
+++ b/cmd/deployment/apm/upgrade.go
@@ -36,17 +36,20 @@ var upgradeApmCmd = &cobra.Command{
 	RunE: func(cmd *cobra.Command, args []string) error {
 		track, _ := cmd.Flags().GetBool("track")
 		res, err := apm.Upgrade(apm.UpgradeParams{
-			API: ecctl.Get().API,
-			ID:  args[0],
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
+			API:   ecctl.Get().API,
+			ID:    args[0],
+			Track: track,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Apm,
+				Track:      track,
+			}).TrackChangeParams,
 		})
 		if err != nil {
 			return err
 		}
-		return ecctl.Get().Formatter.Format(filepath.Join("apm", "upgrade"), res)
+		return ecctl.Get().Formatter.Format(filepath.Join(util.Apm, "upgrade"), res)
 	},
 }
 
diff --git a/cmd/deployment/appsearch/create.go b/cmd/deployment/appsearch/create.go
index ab41e82..86b7ee3 100644
--- a/cmd/deployment/appsearch/create.go
+++ b/cmd/deployment/appsearch/create.go
@@ -106,17 +106,12 @@ var createAppSearchCmd = &cobra.Command{
 		}
 
 		var track, _ = cmd.Flags().GetBool("track")
-		return cmdutil.Track(cmdutil.TrackParams{
-			TrackResourcesParams: depresource.TrackResourcesParams{
-				API:          ecctl.Get().API,
-				Resources:    res.Resources,
-				Orphaned:     res.ShutdownResources,
-				OutputDevice: ecctl.Get().Config.OutputDevice,
-			},
-			Formatter: ecctl.Get().Formatter,
-			Track:     track,
-			Response:  res,
-		})
+		return cmdutil.Track(cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+			App:          ecctl.Get(),
+			DeploymentID: id,
+			Track:        track,
+			Response:     res,
+		}))
 	},
 }
 
diff --git a/cmd/deployment/appsearch/upgrade.go b/cmd/deployment/appsearch/upgrade.go
index faf1af9..811dc10 100644
--- a/cmd/deployment/appsearch/upgrade.go
+++ b/cmd/deployment/appsearch/upgrade.go
@@ -18,9 +18,7 @@
 package cmdappsearch
 
 import (
-	"github.com/elastic/cloud-sdk-go/pkg/models"
 	sdkcmdutil "github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
-	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	"github.com/spf13/cobra"
 
 	cmdutil "github.com/elastic/ecctl/cmd/util"
@@ -49,19 +47,13 @@ var upgradeCmd = &cobra.Command{
 			return err
 		}
 
-		if track, _ := cmd.Flags().GetBool("track"); !track {
-			return nil
-		}
-
-		return depresource.TrackResources(depresource.TrackResourcesParams{
-			API:          ecctl.Get().API,
-			OutputDevice: ecctl.Get().Config.OutputDevice,
-			Resources: []*models.DeploymentResource{{
-				ID:    ec.String(res.ResourceID),
-				Kind:  ec.String(resKind),
-				RefID: ec.String(refID),
-			}},
-		})
+		track, _ := cmd.Flags().GetBool("track")
+		return cmdutil.Track(cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+			App:          ecctl.Get(),
+			DeploymentID: args[0],
+			Track:        track,
+			Response:     res,
+		}))
 	},
 }
 
diff --git a/cmd/deployment/create.go b/cmd/deployment/create.go
index eaca324..29a17b5 100644
--- a/cmd/deployment/create.go
+++ b/cmd/deployment/create.go
@@ -36,8 +36,9 @@ var createCmd = &cobra.Command{
 	Use:     "create {--file | --size <int> --zones <string> | --topology-element <obj>}",
 	Short:   "Creates a deployment",
 	PreRunE: cobra.MaximumNArgs(0),
-	Long:    createLong,
-	Example: createExample,
+	// Switch back to non-temp constants when reads for deployment templates are available on ESS
+	Long:    createLongTemp,
+	Example: createExampleTemp,
 	RunE: func(cmd *cobra.Command, args []string) error {
 		var track, _ = cmd.Flags().GetBool("track")
 		var generatePayload, _ = cmd.Flags().GetBool("generate-payload")
@@ -45,9 +46,9 @@ var createCmd = &cobra.Command{
 		var version, _ = cmd.Flags().GetString("version")
 		var dt, _ = cmd.Flags().GetString("deployment-template")
 
-		var zoneCount, _ = cmd.Flags().GetInt32("zones")
-		var size, _ = cmd.Flags().GetInt32("size")
-		var esRefID, _ = cmd.Flags().GetString("ref-id")
+		var esZoneCount, _ = cmd.Flags().GetInt32("es-zones")
+		var esSize, _ = cmd.Flags().GetInt32("es-size")
+		var esRefID, _ = cmd.Flags().GetString("es-ref-id")
 		var te, _ = cmd.Flags().GetStringArray("topology-element")
 		var plugin, _ = cmd.Flags().GetStringSlice("plugin")
 
@@ -93,8 +94,8 @@ var createCmd = &cobra.Command{
 				AppsearchEnable:      appsearchEnable,
 				ElasticsearchInstance: depresource.InstanceParams{
 					RefID:     esRefID,
-					Size:      size,
-					ZoneCount: zoneCount,
+					Size:      esSize,
+					ZoneCount: esZoneCount,
 				},
 				KibanaInstance: depresource.InstanceParams{
 					RefID:     kibanaRefID,
@@ -142,28 +143,20 @@ var createCmd = &cobra.Command{
 			return err
 		}
 
-		if err := ecctl.Get().Formatter.Format("", res); err != nil {
-			if !track {
-				return err
-			}
-			fmt.Fprintln(ecctl.Get().Config.OutputDevice, err)
-		}
-
-		if !track {
-			return nil
-		}
-
-		return depresource.TrackResources(depresource.TrackResourcesParams{
-			API:          ecctl.Get().API,
-			Resources:    res.Resources,
-			OutputDevice: ecctl.Get().Config.OutputDevice,
-		})
+		return cmdutil.Track(cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+			App:          ecctl.Get(),
+			DeploymentID: *res.ID,
+			Track:        track,
+			Response:     res,
+		}))
 	},
 }
 
 func init() {
 	Command.AddCommand(createCmd)
 	createCmd.Flags().StringP("file", "f", "", "DeploymentCreateRequest file definition. See help for more information")
+	// Remove when reads for deployment templates are available on ESS
+	createCmd.MarkFlagRequired("file")
 	createCmd.Flags().String("deployment-template", "default", "Deployment template ID on which to base the deployment from")
 	createCmd.Flags().String("version", "", "Version to use, if not specified, the latest available stack version will be used")
 	createCmd.Flags().String("name", "", "Optional name for the deployment")
@@ -171,9 +164,9 @@ func init() {
 	createCmd.Flags().Bool("generate-payload", false, "Returns the deployment payload without actually creating the deployment resources")
 	createCmd.Flags().String("request-id", "", "Optional idempotency token - Can be found in the Stderr device when a previous deployment creation failed, for more information see the examples in the help command page")
 
-	createCmd.Flags().String("ref-id", "main-elasticsearch", "Optional RefId for the Elasticsearch deployment")
-	createCmd.Flags().Int32("zones", 1, "Number of zones the Elasticsearch instances will span")
-	createCmd.Flags().Int32("size", 4096, "Memory (RAM) in MB that each of the Elasticsearch instances will have")
+	createCmd.Flags().String("es-ref-id", "main-elasticsearch", "Optional RefId for the Elasticsearch deployment")
+	createCmd.Flags().Int32("es-zones", 1, "Number of zones the Elasticsearch instances will span")
+	createCmd.Flags().Int32("es-size", 4096, "Memory (RAM) in MB that each of the Elasticsearch instances will have")
 	createCmd.Flags().StringArrayP("topology-element", "e", nil, "Optional Elasticsearch topology element definition. See help for more information")
 	createCmd.Flags().StringSlice("plugin", nil, "Additional plugins to add to the Elasticsearch deployment")
 
@@ -190,4 +183,26 @@ func init() {
 	createCmd.Flags().String("appsearch-ref-id", "main-appsearch", "Optional RefId for the AppSearch deployment")
 	createCmd.Flags().Int32("appsearch-zones", 1, "Number of zones the AppSearch instances will span")
 	createCmd.Flags().Int32("appsearch-size", 2048, "Memory (RAM) in MB that each of the AppSearch instances will have")
+
+	// The following flags will remain hidden until reads for deployment templates are available on ESS
+	createCmd.Flags().MarkHidden("deployment-template")
+	createCmd.Flags().MarkHidden("version")
+	createCmd.Flags().MarkHidden("name")
+	createCmd.Flags().MarkHidden("generate-payload")
+	createCmd.Flags().MarkHidden("es-ref-id")
+	createCmd.Flags().MarkHidden("es-zones")
+	createCmd.Flags().MarkHidden("es-size")
+	createCmd.Flags().MarkHidden("topology-element")
+	createCmd.Flags().MarkHidden("plugin")
+	createCmd.Flags().MarkHidden("kibana-ref-id")
+	createCmd.Flags().MarkHidden("kibana-zones")
+	createCmd.Flags().MarkHidden("kibana-size")
+	createCmd.Flags().MarkHidden("apm")
+	createCmd.Flags().MarkHidden("apm-ref-id")
+	createCmd.Flags().MarkHidden("apm-zones")
+	createCmd.Flags().MarkHidden("apm-size")
+	createCmd.Flags().MarkHidden("appsearch")
+	createCmd.Flags().MarkHidden("appsearch-ref-id")
+	createCmd.Flags().MarkHidden("appsearch-zones")
+	createCmd.Flags().MarkHidden("appsearch-size")
 }
diff --git a/cmd/deployment/create_help.go b/cmd/deployment/create_help.go
index 0d3391f..7228e69 100644
--- a/cmd/deployment/create_help.go
+++ b/cmd/deployment/create_help.go
@@ -18,6 +18,7 @@
 package cmddeployment
 
 const (
+	// nolint
 	createLong = `Creates a deployment which can be defined through flags or from a file definition.
 Sane default values are provided, making the command work out of the box even when no parameters are set. 
 When version is not specified, the latest available stack version will automatically be used. 
@@ -38,6 +39,7 @@ These are the available options:
 As an option "--generate-payload" can be used in order to obtain the generated payload that would be sent as a request. 
 Save it, update or extend the topology and create a deployment using the saved payload with the "--file" flag.`
 
+	// nolint
 	createExample = `## Create a deployment with the default values for Elasticsearch, a Kibana instance with a modified size, 
 and a default APM instance. While Elasticsearch and Kibana come enabled by default, both APM and AppSearch need to be 
 enabled by using the "--apm" and "--appsearch" flags. The command will exit after the API response has been returned, without 
@@ -48,8 +50,8 @@ $ ecctl deployment create --name my-deployment --zones 2 --kibana-size 2048 --ap
 the current stage on which the deployment resources are in.
 $ deployment create --name my-deployment --track
 [...]
-Cluster [38e0ff5b58a9483c96a98c923b22194e][Elasticsearch]: finished running all the plan steps (Total plan duration: 1m0.911628175s)
-Cluster [51178ffc645d48b7859dbf17388a6c35][Kibana]: finished running all the plan steps (Total plan duration: 1m11.246662764s)
+Deployment [b6ecbea3d5c84124b7dca457f2892086] - [Elasticsearch][b6ecbea3d5c84124b7dca457f2892086]: finished running all the plan steps (Total plan duration: 5m11.s)
+Deployment [91c4d60acb804ba0a27651fac02780ec] - [Kibana][8a9d9916cd6e46a7bb0912211d76e2af]: finished running all the plan steps (Total plan duration: 4m29.58s)
 
 ## Additionally, a more advanced topology for Elasticsearch can be created through "--topology-element" or shorthand "-e".
 The following command will create a deployment with two 1GB Elasticsearch instances of the type "data" and 
@@ -57,15 +59,14 @@ one 1GB Elasticsearch instance of the type "ml".
 $ ecctl deployment create --name my-deployment --topology-element '{"size": 1024, "zone_count": 2, "name": "data"}' --topology-element '{"size": 1024, "zone_count": 1, "name": "ml"}'
 
 ## In order to use the "--deployment-template" flag, you'll need to know which deployment templates ara available to you.
-Visit https://elastic.co/guide/en/cloud/current/ec-regions-templates-instances.html.
-If you are an Elastic Cloud Enterprise customer, you'll need to run the following command to view your deployment templates:
+You'll need to run the following command to view your deployment templates:
 $ ecctl platform deployment-template list
 
 ## Use the "--generate-payload" flag to save the definition to a file for later use.
-$ ecctl deployment create --name my-deployment --size 1024 --track --generate-payload --zones 2 > elasticsearch_create_example.json
+$ ecctl deployment create --name my-deployment --size 1024 --track --generate-payload --zones 2 > create_example.json
 
 ## Create a deployment through the file definition.
-$ ecctl deployment create --file elasticsearch_create_example.json --track
+$ ecctl deployment create --file create_example.json --track
 
 ## To retry a when the previous deployment creation failed:
 $ ecctl deployment create
@@ -74,4 +75,25 @@ to recreate the deployment resources
 Idempotency token: GMZPMRrcMYqHdmxjIQkHbdjnhPIeBElcwrHwzVlhGUSMXrEIzVXoBykSVRsKncNb
 unknown error (status 500)
 $ ecctl deployment create --request-id=GMZPMRrcMYqHdmxjIQkHbdjnhPIeBElcwrHwzVlhGUSMXrEIzVXoBykSVRsKncNb`
+
+	// Remove temporary constants when reads for deployment templates are available on ESS
+	createLongTemp = `Creates a deployment which is defined from a file definition using the --file=<file path> (shorthand: -f) flag.
+
+You can create a definition by using the sample JSON seen here:
+  https://elastic.co/guide/en/cloud/current/ec-api-deployment-crud.html#ec_create_a_deployment`
+
+	createExampleTemp = `## To make the command wait until the resources have been created use the "--track" flag, which will output 
+the current stage on which the deployment resources are in.
+$ deployment create --file create_example.json --track
+[...]
+Deployment [b6ecbea3d5c84124b7dca457f2892086] - [Elasticsearch][b6ecbea3d5c84124b7dca457f2892086]: finished running all the plan steps (Total plan duration: 5m11.s)
+Deployment [91c4d60acb804ba0a27651fac02780ec] - [Kibana][8a9d9916cd6e46a7bb0912211d76e2af]: finished running all the plan steps (Total plan duration: 4m29.58s)
+
+## To retry a when the previous deployment creation failed:
+$ ecctl deployment create --file create_example.json
+The deployment creation returned with an error, please use the displayed idempotency token
+to recreate the deployment resources
+Idempotency token: GMZPMRrcMYqHdmxjIQkHbdjnhPIeBElcwrHwzVlhGUSMXrEIzVXoBykSVRsKncNb
+unknown error (status 500)
+$ ecctl deployment create --file create_example.json --request-id=GMZPMRrcMYqHdmxjIQkHbdjnhPIeBElcwrHwzVlhGUSMXrEIzVXoBykSVRsKncNb`
 )
diff --git a/cmd/deployment/elasticsearch/delete.go b/cmd/deployment/elasticsearch/delete.go
index 7d26866..0ff09b9 100644
--- a/cmd/deployment/elasticsearch/delete.go
+++ b/cmd/deployment/elasticsearch/delete.go
@@ -18,9 +18,10 @@
 package cmdelasticsearch
 
 import (
-	"github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
+	sdkcmdutil "github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
 	"github.com/spf13/cobra"
 
+	cmdutil "github.com/elastic/ecctl/cmd/util"
 	"github.com/elastic/ecctl/pkg/deployment/elasticsearch"
 	"github.com/elastic/ecctl/pkg/ecctl"
 	"github.com/elastic/ecctl/pkg/util"
@@ -29,8 +30,7 @@ import (
 var deleteElasticsearchCmd = &cobra.Command{
 	Use:     "delete <cluster id>",
 	Short:   "Deletes an Elasticsearch cluster",
-	PreRunE: cmdutil.MinimumNArgsAndUUID(1),
-
+	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		if stop, _ := cmd.Flags().GetBool("stop"); stop {
 			skipSnapshot, _ := cmd.Flags().GetBool("skip-snapshot")
@@ -39,10 +39,13 @@ var deleteElasticsearchCmd = &cobra.Command{
 					ClusterID: args[0],
 					API:       ecctl.Get().API,
 				},
-				TrackParams: util.TrackParams{
-					Track:  true,
-					Output: ecctl.Get().Config.OutputDevice,
-				},
+				Track: true,
+				TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+					App:        ecctl.Get(),
+					ResourceID: args[0],
+					Kind:       util.Elasticsearch,
+					Track:      true,
+				}).TrackChangeParams,
 				SkipSnapshot: skipSnapshot,
 			})
 			if err != nil {
diff --git a/cmd/deployment/elasticsearch/plan/monitor.go b/cmd/deployment/elasticsearch/plan/monitor.go
index 2224927..5ba2da8 100644
--- a/cmd/deployment/elasticsearch/plan/monitor.go
+++ b/cmd/deployment/elasticsearch/plan/monitor.go
@@ -20,7 +20,6 @@ package cmdelasticsearchplan
 import (
 	"time"
 
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
 	sdkcmdutil "github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
 	"github.com/spf13/cobra"
 
@@ -35,14 +34,12 @@ var monitorPlanCmd = &cobra.Command{
 	Short:   "Monitors the pending plan",
 	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
-		return util.TrackCluster(util.TrackClusterParams{
-			TrackParams: plan.TrackParams{
-				ID:   args[0],
-				API:  ecctl.Get().API,
-				Kind: "elasticsearch",
-			},
-			Output: ecctl.Get().Config.OutputDevice,
-		})
+		return cmdutil.Track(cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+			App:          ecctl.Get(),
+			DeploymentID: args[0],
+			Kind:         util.Elasticsearch,
+			Track:        true,
+		}))
 	},
 }
 
diff --git a/cmd/deployment/elasticsearch/plan/reapply.go b/cmd/deployment/elasticsearch/plan/reapply.go
index 5649e7b..66aa06a 100644
--- a/cmd/deployment/elasticsearch/plan/reapply.go
+++ b/cmd/deployment/elasticsearch/plan/reapply.go
@@ -22,6 +22,7 @@ import (
 
 	"github.com/spf13/cobra"
 
+	cmdutil "github.com/elastic/ecctl/cmd/util"
 	"github.com/elastic/ecctl/pkg/deployment/elasticsearch/plan"
 	"github.com/elastic/ecctl/pkg/deployment/planutil"
 	"github.com/elastic/ecctl/pkg/ecctl"
@@ -32,7 +33,6 @@ var reapplyLatestElasticsearchPlanCmd = &cobra.Command{
 	Use:     "reapply <cluster id>",
 	Short:   "Reapplies the latest plan attempt, resetting all transient settings",
 	PreRunE: cobra.MinimumNArgs(1),
-
 	RunE: func(cmd *cobra.Command, args []string) error {
 		commonParams := &planutil.ReapplyParams{ID: args[0]}
 		for name := range util.FieldsOfStruct(commonParams) {
@@ -46,10 +46,14 @@ var reapplyLatestElasticsearchPlanCmd = &cobra.Command{
 		var params = &plan.ReapplyParams{
 			ReapplyParams: *commonParams,
 			API:           ecctl.Get().API,
-			TrackParams: util.TrackParams{
-				Track:  true,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
+			Output:        cmd.OutOrStdout(),
+			Track:         true,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Elasticsearch,
+				Track:      true,
+			}).TrackChangeParams,
 		}
 		for name := range util.FieldsOfStruct(params) {
 			val, err := cmd.Flags().GetBool(name)
diff --git a/cmd/deployment/elasticsearch/plan/update.go b/cmd/deployment/elasticsearch/plan/update.go
index 24efee6..4fa2b35 100644
--- a/cmd/deployment/elasticsearch/plan/update.go
+++ b/cmd/deployment/elasticsearch/plan/update.go
@@ -65,17 +65,20 @@ The plan must be a valid ElasticsearchClusterPlan and will be validated against
 			ID:           args[0],
 			ValidateOnly: validate,
 			Plan:         planModel,
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
+			Track:        track,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Elasticsearch,
+				Track:      track,
+			}).TrackChangeParams,
 		})
 		if err != nil {
 			return err
 		}
 
 		return ecctl.Get().Formatter.Format(
-			filepath.Join("elasticsearch", "update"),
+			filepath.Join(util.Elasticsearch, "update"),
 			p,
 		)
 	},
diff --git a/cmd/deployment/elasticsearch/reallocate.go b/cmd/deployment/elasticsearch/reallocate.go
index 7483964..46bbeaf 100644
--- a/cmd/deployment/elasticsearch/reallocate.go
+++ b/cmd/deployment/elasticsearch/reallocate.go
@@ -18,9 +18,10 @@
 package cmdelasticsearch
 
 import (
-	"github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
+	sdkcmdutil "github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
 	"github.com/spf13/cobra"
 
+	cmdutil "github.com/elastic/ecctl/cmd/util"
 	"github.com/elastic/ecctl/pkg/deployment/elasticsearch"
 	"github.com/elastic/ecctl/pkg/ecctl"
 	"github.com/elastic/ecctl/pkg/util"
@@ -30,14 +31,14 @@ var reallocateESClusterCmd = &cobra.Command{
 	Use:     "reallocate <cluster id>",
 	Short:   "Reallocates the Elasticsearch cluster nodes",
 	Long:    "Reallocates the Elasticsearch cluster nodes. If no \"--instances\" are specified all of the nodes will be restarted",
-	PreRunE: cmdutil.MinimumNArgsAndUUID(1),
+	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		instances, err := cmd.Flags().GetStringSlice("instances")
 		if err != nil {
 			return err
 		}
 
-		instancesDown, err := cmdutil.ParseBoolP(cmd, "instances-down")
+		instancesDown, err := sdkcmdutil.ParseBoolP(cmd, "instances-down")
 		if err != nil {
 			return err
 		}
@@ -50,14 +51,18 @@ var reallocateESClusterCmd = &cobra.Command{
 			User:          ecctl.Get().Config.User,
 			InstancesDown: instancesDown,
 			Instances:     instances,
-			OutputDevice:  ecctl.Get().Config.OutputDevice,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Elasticsearch,
+				Track:      true,
+			}).TrackChangeParams,
 		})
 	},
 }
 
 func init() {
 	Command.AddCommand(reallocateESClusterCmd)
-
 	reallocateESClusterCmd.Flags().StringSliceP("instances", "i", nil, "Reallocates only specific instances")
 	reallocateESClusterCmd.Flags().String("instances-down", "", "Overwrites the default if set to [true|false], marking the instances as 'down'")
 }
diff --git a/cmd/deployment/elasticsearch/restart.go b/cmd/deployment/elasticsearch/restart.go
index fb8501c..c8d0463 100644
--- a/cmd/deployment/elasticsearch/restart.go
+++ b/cmd/deployment/elasticsearch/restart.go
@@ -34,7 +34,6 @@ var restartElasticsearchCmd = &cobra.Command{
 	Use:     "restart <cluster id>",
 	Short:   "Restarts an Elasticsearch cluster",
 	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
-
 	RunE: func(cmd *cobra.Command, args []string) error {
 		track, _ := cmd.Flags().GetBool("track")
 		rollingByName, _ := cmd.Flags().GetBool("rolling-by-name")
@@ -46,10 +45,13 @@ var restartElasticsearchCmd = &cobra.Command{
 				ClusterID: args[0],
 				API:       ecctl.Get().API,
 			},
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
+			Track: track,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Elasticsearch,
+				Track:      track,
+			}).TrackChangeParams,
 			RollingByName:     rollingByName,
 			RollingByZone:     rollingByZone,
 			SkipSnapshot:      skipSnapshot,
diff --git a/cmd/deployment/elasticsearch/shutdown.go b/cmd/deployment/elasticsearch/shutdown.go
index 3fa8245..aaf9ffa 100644
--- a/cmd/deployment/elasticsearch/shutdown.go
+++ b/cmd/deployment/elasticsearch/shutdown.go
@@ -43,10 +43,13 @@ var shutdownElasticsearchCmd = &cobra.Command{
 				ClusterID: args[0],
 				API:       ecctl.Get().API,
 			},
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
+			Track: track,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Elasticsearch,
+				Track:      track,
+			}).TrackChangeParams,
 			Hide:         hide,
 			SkipSnapshot: skipSnapshot,
 		})
diff --git a/cmd/deployment/elasticsearch/start.go b/cmd/deployment/elasticsearch/start.go
index ea7515c..519e2aa 100644
--- a/cmd/deployment/elasticsearch/start.go
+++ b/cmd/deployment/elasticsearch/start.go
@@ -33,7 +33,6 @@ var startElasticsearchCmd = &cobra.Command{
 	Use:     "start <cluster id>",
 	Short:   "Starts a stopped Elasticsearch cluster",
 	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
-
 	RunE: func(cmd *cobra.Command, args []string) error {
 		c, err := elasticsearch.GetCluster(elasticsearch.GetClusterParams{
 			ClusterParams: util.ClusterParams{
@@ -55,10 +54,13 @@ var startElasticsearchCmd = &cobra.Command{
 				ClusterID: args[0],
 				API:       ecctl.Get().API,
 			},
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
+			Track: track,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Elasticsearch,
+				Track:      track,
+			}).TrackChangeParams,
 		}); err != nil {
 			return err
 		}
diff --git a/cmd/deployment/elasticsearch/upgrade.go b/cmd/deployment/elasticsearch/upgrade.go
index 2f4e072..2a01216 100644
--- a/cmd/deployment/elasticsearch/upgrade.go
+++ b/cmd/deployment/elasticsearch/upgrade.go
@@ -28,24 +28,24 @@ import (
 )
 
 var upgradeElasticsearchVersionCmd = &cobra.Command{
-	Use:   "upgrade <cluster id> --version=<version>",
-	Short: "Upgrades the cluster to the specified version",
-
+	Use:     "upgrade <cluster id> --version=<version>",
+	Short:   "Upgrades the cluster to the specified version",
 	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		version := cmd.Flag("version").Value.String()
 		track, _ := cmd.Flags().GetBool("track")
-
 		_, err := elasticsearch.Upgrade(elasticsearch.UpgradeParams{
 			ClusterParams: util.ClusterParams{
 				ClusterID: args[0],
 				API:       ecctl.Get().API,
 			},
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
-
+			Track: track,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Elasticsearch,
+				Track:      track,
+			}).TrackChangeParams,
 			Version: version,
 		})
 
@@ -55,7 +55,6 @@ var upgradeElasticsearchVersionCmd = &cobra.Command{
 
 func init() {
 	Command.AddCommand(upgradeElasticsearchVersionCmd)
-
 	upgradeElasticsearchVersionCmd.Flags().StringP("version", "v", "", "Version to upgrade to (required)")
 	upgradeElasticsearchVersionCmd.Flags().BoolP("track", "t", false, cmdutil.TrackFlagMessage)
 	cobra.MarkFlagRequired(upgradeElasticsearchVersionCmd.Flags(), "version")
diff --git a/cmd/deployment/kibana/create.go b/cmd/deployment/kibana/create.go
index d8fe096..0191496 100644
--- a/cmd/deployment/kibana/create.go
+++ b/cmd/deployment/kibana/create.go
@@ -106,17 +106,12 @@ var createKibanaCmd = &cobra.Command{
 			return err
 		}
 
-		return cmdutil.Track(cmdutil.TrackParams{
-			TrackResourcesParams: depresource.TrackResourcesParams{
-				API:          ecctl.Get().API,
-				Resources:    res.Resources,
-				Orphaned:     res.ShutdownResources,
-				OutputDevice: ecctl.Get().Config.OutputDevice,
-			},
-			Formatter: ecctl.Get().Formatter,
-			Track:     track,
-			Response:  res,
-		})
+		return cmdutil.Track(cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+			App:          ecctl.Get(),
+			DeploymentID: *res.ID,
+			Track:        track,
+			Response:     res,
+		}))
 	},
 }
 
diff --git a/cmd/deployment/kibana/delete.go b/cmd/deployment/kibana/delete.go
index 5add2ff..0647fbf 100644
--- a/cmd/deployment/kibana/delete.go
+++ b/cmd/deployment/kibana/delete.go
@@ -33,15 +33,20 @@ var deleteKibanaCmd = &cobra.Command{
 	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
 
 	RunE: func(cmd *cobra.Command, args []string) error {
+		track, _ := cmd.Flags().GetBool("track")
+		trackParams := cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+			App:        ecctl.Get(),
+			ResourceID: args[0],
+			Kind:       util.Kibana,
+			Track:      track,
+		}).TrackChangeParams
 		if stop, _ := cmd.Flags().GetBool("stop"); stop {
 			err := kibana.Stop(kibana.StopParams{
 				DeploymentParams: kibana.DeploymentParams{
-					API: ecctl.Get().API,
-					ID:  args[0],
-					TrackParams: util.TrackParams{
-						Track:  true,
-						Output: ecctl.Get().Config.OutputDevice,
-					},
+					API:               ecctl.Get().API,
+					ID:                args[0],
+					Track:             true,
+					TrackChangeParams: trackParams,
 				},
 			})
 			if err != nil {
@@ -49,14 +54,11 @@ var deleteKibanaCmd = &cobra.Command{
 			}
 		}
 
-		track, _ := cmd.Flags().GetBool("track")
 		return kibana.Delete(kibana.DeploymentParams{
-			API: ecctl.Get().API,
-			ID:  args[0],
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
+			API:               ecctl.Get().API,
+			ID:                args[0],
+			Track:             track,
+			TrackChangeParams: trackParams,
 		})
 	},
 }
diff --git a/cmd/deployment/kibana/plan/monitor.go b/cmd/deployment/kibana/plan/monitor.go
index 55f7ba5..64a47dc 100644
--- a/cmd/deployment/kibana/plan/monitor.go
+++ b/cmd/deployment/kibana/plan/monitor.go
@@ -20,13 +20,11 @@ package cmdkibanaplan
 import (
 	"time"
 
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
 	sdkcmdutil "github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
 	"github.com/spf13/cobra"
 
 	cmdutil "github.com/elastic/ecctl/cmd/util"
 	"github.com/elastic/ecctl/pkg/ecctl"
-	"github.com/elastic/ecctl/pkg/util"
 )
 
 var monitorPlanCmd = &cobra.Command{
@@ -35,18 +33,16 @@ var monitorPlanCmd = &cobra.Command{
 	Short:   "Monitors the pending plan",
 	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
-		return util.TrackCluster(util.TrackClusterParams{
-			TrackParams: plan.TrackParams{
-				ID:   args[0],
-				API:  ecctl.Get().API,
-				Kind: "kibana",
-			},
-			Output: ecctl.Get().Config.OutputDevice,
-		})
+		return cmdutil.Track(cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+			App:          ecctl.Get(),
+			DeploymentID: args[0],
+			Kind:         "kibana",
+			Track:        true,
+		}))
 	},
 }
 
 func init() {
-	monitorPlanCmd.Flags().Duration("poll-interval", time.Second*2, "Monitor poll interval")
+	monitorPlanCmd.Flags().Duration("poll-interval", time.Second*5, "Monitor poll interval")
 	monitorPlanCmd.Flags().Uint8("retries", 3, cmdutil.PlanRetriesFlagMessage)
 }
diff --git a/cmd/deployment/kibana/reallocate.go b/cmd/deployment/kibana/reallocate.go
index cf4410e..a09cdf5 100644
--- a/cmd/deployment/kibana/reallocate.go
+++ b/cmd/deployment/kibana/reallocate.go
@@ -18,26 +18,34 @@
 package cmdkibana
 
 import (
-	"github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
+	sdkcmdutil "github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
 	"github.com/spf13/cobra"
 
+	cmdutil "github.com/elastic/ecctl/cmd/util"
 	"github.com/elastic/ecctl/pkg/deployment/kibana"
 	"github.com/elastic/ecctl/pkg/ecctl"
+	"github.com/elastic/ecctl/pkg/util"
 )
 
 var reallocateKibanaClusterCmd = &cobra.Command{
 	Use:     "reallocate <cluster id>",
 	Short:   "Reallocates Kibana instances",
 	Long:    "Reallocates Kibana instances. If no \"--instances\" are specified all of the nodes will be restarted",
-	PreRunE: cmdutil.MinimumNArgsAndUUID(1),
+	PreRunE: sdkcmdutil.MinimumNArgsAndUUID(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		instances, _ := cmd.Flags().GetStringSlice("instances")
 		return kibana.Reallocate(kibana.ReallocateParams{
 			DeploymentParams: kibana.DeploymentParams{
-				ID:  args[0],
-				API: ecctl.Get().API,
+				ID:    args[0],
+				API:   ecctl.Get().API,
+				Track: true,
+				TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+					App:        ecctl.Get(),
+					ResourceID: args[0],
+					Kind:       util.Kibana,
+					Track:      true,
+				}).TrackChangeParams,
 			},
-			Output:    ecctl.Get().Config.OutputDevice,
 			Instances: instances,
 		})
 	},
diff --git a/cmd/deployment/kibana/restart.go b/cmd/deployment/kibana/restart.go
index 5225a57..7e95ba5 100644
--- a/cmd/deployment/kibana/restart.go
+++ b/cmd/deployment/kibana/restart.go
@@ -34,12 +34,15 @@ var restartKibanaCmd = &cobra.Command{
 	RunE: func(cmd *cobra.Command, args []string) error {
 		track, _ := cmd.Flags().GetBool("track")
 		return kibana.Restart(kibana.DeploymentParams{
-			API: ecctl.Get().API,
-			ID:  args[0],
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
+			API:   ecctl.Get().API,
+			ID:    args[0],
+			Track: track,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Kibana,
+				Track:      track,
+			}).TrackChangeParams,
 		})
 	},
 }
diff --git a/cmd/deployment/kibana/start.go b/cmd/deployment/kibana/start.go
index 7d27a61..7ea7da9 100644
--- a/cmd/deployment/kibana/start.go
+++ b/cmd/deployment/kibana/start.go
@@ -24,7 +24,6 @@ import (
 	"github.com/spf13/cobra"
 
 	cmdutil "github.com/elastic/ecctl/cmd/util"
-	"github.com/elastic/ecctl/pkg/deployment/deputil"
 	"github.com/elastic/ecctl/pkg/deployment/kibana"
 	"github.com/elastic/ecctl/pkg/ecctl"
 	"github.com/elastic/ecctl/pkg/util"
@@ -38,15 +37,9 @@ var startKibanaCmd = &cobra.Command{
 		c, err := kibana.Get(kibana.ClusterParams{
 			DeploymentParams: kibana.DeploymentParams{
 				API: ecctl.Get().API,
-				ID:  args[0]},
-			QueryParams: deputil.QueryParams{
-				ShowMetadata:     false,
-				ShowPlanDefaults: false,
-				ShowPlans:        false,
-				ShowPlanLogs:     false,
-				ShowSettings:     false,
-			}},
-		)
+				ID:  args[0],
+			},
+		})
 		if err != nil {
 			return err
 		}
@@ -57,12 +50,15 @@ var startKibanaCmd = &cobra.Command{
 
 		track, _ := cmd.Flags().GetBool("track")
 		return kibana.Restart(kibana.DeploymentParams{
-			API: ecctl.Get().API,
-			ID:  args[0],
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
+			API:   ecctl.Get().API,
+			ID:    args[0],
+			Track: track,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Kibana,
+				Track:      track,
+			}).TrackChangeParams,
 		})
 	},
 }
diff --git a/cmd/deployment/kibana/stop.go b/cmd/deployment/kibana/stop.go
index 780c504..50f6454 100644
--- a/cmd/deployment/kibana/stop.go
+++ b/cmd/deployment/kibana/stop.go
@@ -35,16 +35,18 @@ var stopKibanaCmd = &cobra.Command{
 		track, _ := cmd.Flags().GetBool("track")
 		return kibana.Stop(kibana.StopParams{
 			DeploymentParams: kibana.DeploymentParams{
-				API: ecctl.Get().API,
-				ID:  args[0],
-				TrackParams: util.TrackParams{
-					Track:  track,
-					Output: ecctl.Get().Config.OutputDevice,
-				},
+				API:   ecctl.Get().API,
+				ID:    args[0],
+				Track: track,
+				TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+					App:        ecctl.Get(),
+					ResourceID: args[0],
+					Kind:       util.Kibana,
+					Track:      track,
+				}).TrackChangeParams,
 			},
 			Hide: false,
-		},
-		)
+		})
 	},
 }
 
diff --git a/cmd/deployment/kibana/upgrade.go b/cmd/deployment/kibana/upgrade.go
index 5b84e4b..2223261 100644
--- a/cmd/deployment/kibana/upgrade.go
+++ b/cmd/deployment/kibana/upgrade.go
@@ -34,12 +34,15 @@ var upgradeKibanaCmd = &cobra.Command{
 	RunE: func(cmd *cobra.Command, args []string) error {
 		track, _ := cmd.Flags().GetBool("track")
 		_, err := kibana.Upgrade(kibana.DeploymentParams{
-			API: ecctl.Get().API,
-			ID:  args[0],
-			TrackParams: util.TrackParams{
-				Track:  track,
-				Output: ecctl.Get().Config.OutputDevice,
-			},
+			API:   ecctl.Get().API,
+			ID:    args[0],
+			Track: track,
+			TrackChangeParams: cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+				App:        ecctl.Get(),
+				ResourceID: args[0],
+				Kind:       util.Kibana,
+				Track:      track,
+			}).TrackChangeParams,
 		})
 
 		return err
diff --git a/cmd/deployment/resource/upgrade.go b/cmd/deployment/resource/upgrade.go
index 255f2c1..37559fd 100644
--- a/cmd/deployment/resource/upgrade.go
+++ b/cmd/deployment/resource/upgrade.go
@@ -18,9 +18,7 @@
 package cmddeploymentresource
 
 import (
-	"github.com/elastic/cloud-sdk-go/pkg/models"
 	sdkcmdutil "github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
-	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	"github.com/spf13/cobra"
 
 	cmdutil "github.com/elastic/ecctl/cmd/util"
@@ -50,19 +48,13 @@ var upgradeCmd = &cobra.Command{
 			return err
 		}
 
-		if track, _ := cmd.Flags().GetBool("track"); !track {
-			return nil
-		}
-
-		return depresource.TrackResources(depresource.TrackResourcesParams{
-			API:          ecctl.Get().API,
-			OutputDevice: ecctl.Get().Config.OutputDevice,
-			Resources: []*models.DeploymentResource{{
-				ID:    ec.String(res.ResourceID),
-				Kind:  ec.String(resKind),
-				RefID: ec.String(refID),
-			}},
-		})
+		track, _ := cmd.Flags().GetBool("track")
+		return cmdutil.Track(cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+			App:          ecctl.Get(),
+			DeploymentID: args[0],
+			Track:        track,
+			Response:     res,
+		}))
 	},
 }
 
diff --git a/cmd/deployment/shutdown.go b/cmd/deployment/shutdown.go
index e57d663..14ee23e 100644
--- a/cmd/deployment/shutdown.go
+++ b/cmd/deployment/shutdown.go
@@ -25,7 +25,6 @@ import (
 
 	cmdutil "github.com/elastic/ecctl/cmd/util"
 	"github.com/elastic/ecctl/pkg/deployment"
-	"github.com/elastic/ecctl/pkg/deployment/depresource"
 	"github.com/elastic/ecctl/pkg/ecctl"
 )
 
@@ -54,17 +53,13 @@ var shutdownCmd = &cobra.Command{
 		}
 
 		var track, _ = cmd.Flags().GetBool("track")
-		return cmdutil.Track(cmdutil.TrackParams{
-			Template: "deployment/shutdown",
-			TrackResourcesParams: depresource.TrackResourcesParams{
-				API:          ecctl.Get().API,
-				Orphaned:     res.Orphaned,
-				OutputDevice: ecctl.Get().Config.OutputDevice,
-			},
-			Formatter: ecctl.Get().Formatter,
-			Track:     track,
-			Response:  res,
-		})
+		return cmdutil.Track(cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+			App:          ecctl.Get(),
+			DeploymentID: args[0],
+			Track:        track,
+			Response:     res,
+			Template:     "deployment/shutdown",
+		}))
 	},
 }
 
diff --git a/cmd/deployment/update.go b/cmd/deployment/update.go
index 6490a91..823ba01 100644
--- a/cmd/deployment/update.go
+++ b/cmd/deployment/update.go
@@ -26,7 +26,6 @@ import (
 
 	cmdutil "github.com/elastic/ecctl/cmd/util"
 	"github.com/elastic/ecctl/pkg/deployment"
-	"github.com/elastic/ecctl/pkg/deployment/depresource"
 	"github.com/elastic/ecctl/pkg/ecctl"
 )
 
@@ -168,17 +167,12 @@ var updateCmd = &cobra.Command{
 		}
 
 		var track, _ = cmd.Flags().GetBool("track")
-		return cmdutil.Track(cmdutil.TrackParams{
-			TrackResourcesParams: depresource.TrackResourcesParams{
-				API:          ecctl.Get().API,
-				Resources:    res.Resources,
-				Orphaned:     res.ShutdownResources,
-				OutputDevice: ecctl.Get().Config.OutputDevice,
-			},
-			Formatter: ecctl.Get().Formatter,
-			Track:     track,
-			Response:  res,
-		})
+		return cmdutil.Track(cmdutil.NewTrackParams(cmdutil.TrackParamsConfig{
+			App:          ecctl.Get(),
+			DeploymentID: args[0],
+			Track:        track,
+			Response:     res,
+		}))
 	},
 }
 
diff --git a/cmd/platform/allocator/vacate.go b/cmd/platform/allocator/vacate.go
index ad49216..e6c2fac 100644
--- a/cmd/platform/allocator/vacate.go
+++ b/cmd/platform/allocator/vacate.go
@@ -165,6 +165,7 @@ var vacateAllocatorCmd = &cobra.Command{
 			KindFilter:          kind,
 			Concurrency:         uint16(concurrency),
 			Output:              ecctl.Get().Config.OutputDevice,
+			OutputFormat:        ecctl.Get().Config.Output,
 			MoveOnly:            ec.Bool(moveOnly),
 			SkipTracking:        skipTracking,
 			PlanOverrides: allocator.PlanOverrides{
diff --git a/cmd/util/track.go b/cmd/util/track.go
index 3689c5a..af1db35 100644
--- a/cmd/util/track.go
+++ b/cmd/util/track.go
@@ -21,15 +21,15 @@ import (
 	"errors"
 	"fmt"
 
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	"github.com/hashicorp/go-multierror"
 
-	"github.com/elastic/ecctl/pkg/deployment/depresource"
 	"github.com/elastic/ecctl/pkg/formatter"
 )
 
 // TrackParams is consumed by Track.
 type TrackParams struct {
-	depresource.TrackResourcesParams
+	planutil.TrackChangeParams
 
 	// Formatter used to print the structure.
 	Formatter formatter.Formatter
@@ -67,8 +67,8 @@ func Track(params TrackParams) error {
 		if !params.Track {
 			return err
 		}
-		if params.TrackResourcesParams.OutputDevice != nil {
-			fmt.Fprintln(params.OutputDevice, err)
+		if params.TrackChangeParams.Writer != nil {
+			fmt.Fprintln(params.Writer, err)
 		}
 	}
 
@@ -76,5 +76,5 @@ func Track(params TrackParams) error {
 		return nil
 	}
 
-	return depresource.TrackResources(params.TrackResourcesParams)
+	return planutil.TrackChange(params.TrackChangeParams)
 }
diff --git a/cmd/util/track_params.go b/cmd/util/track_params.go
new file mode 100644
index 0000000..2ead6b7
--- /dev/null
+++ b/cmd/util/track_params.go
@@ -0,0 +1,74 @@
+// Licensed to Elasticsearch B.V. under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Elasticsearch B.V. licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package cmdutil
+
+import (
+	"time"
+
+	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
+
+	"github.com/elastic/ecctl/pkg/ecctl"
+)
+
+// DefaultTrackFrequencyConfig provides sane defaults for Plan change tracking.
+var DefaultTrackFrequencyConfig = plan.TrackFrequencyConfig{
+	PollFrequency: time.Second * 5,
+	MaxRetries:    3,
+}
+
+// TrackParamsConfig is used to create TrackParams which print the output / track.
+type TrackParamsConfig struct {
+	App             *ecctl.App
+	DeploymentID    string
+	ResourceID      string
+	Kind            string
+	Template        string
+	Response        interface{}
+	Track           bool
+	FrequencyConfig *plan.TrackFrequencyConfig
+}
+
+// NewTrackParams creates a TrackParams structure from the config.
+func NewTrackParams(params TrackParamsConfig) TrackParams {
+	if params.FrequencyConfig == nil {
+		params.FrequencyConfig = &DefaultTrackFrequencyConfig
+	}
+
+	if params.App == nil {
+		params.App = ecctl.Get()
+	}
+
+	return TrackParams{
+		TrackChangeParams: planutil.TrackChangeParams{
+			Writer: params.App.Config.OutputDevice,
+			Format: params.App.Config.Output,
+			TrackChangeParams: plan.TrackChangeParams{
+				API:          params.App.API,
+				DeploymentID: params.DeploymentID,
+				ResourceID:   params.ResourceID,
+				Kind:         params.Kind,
+				Config:       *params.FrequencyConfig,
+			},
+		},
+		Formatter: params.App.Formatter,
+		Track:     params.Track,
+		Response:  params.Response,
+		Template:  params.Template,
+	}
+}
diff --git a/docs/ecctl_deployment_apm_plan_history.md b/docs/ecctl_deployment_apm_plan_history.md
index a0acdf7..dea90a4 100644
--- a/docs/ecctl_deployment_apm_plan_history.md
+++ b/docs/ecctl_deployment_apm_plan_history.md
@@ -13,8 +13,7 @@ ecctl deployment apm plan history <cluster id> [flags]
 ### Options
 
 ```
-  -h, --help    help for history
-      --track   Tracks the progress of the performed task
+  -h, --help   help for history
 ```
 
 ### Options inherited from parent commands
diff --git a/docs/ecctl_deployment_apm_plan_monitor.md b/docs/ecctl_deployment_apm_plan_monitor.md
index 86bce09..ce39e13 100644
--- a/docs/ecctl_deployment_apm_plan_monitor.md
+++ b/docs/ecctl_deployment_apm_plan_monitor.md
@@ -14,8 +14,8 @@ ecctl deployment apm plan monitor <cluster id> [flags]
 
 ```
   -h, --help                     help for monitor
-      --poll-interval duration   Monitor poll interval (default 2s)
-      --retries uint8            Number of times monitoring will be retried. Meaning it accepts an N number of API errors (default 3)
+      --poll-interval duration   Monitor poll interval (default 10s)
+      --retries uint8            Number of times monitoring will be retried. Meaning it accepts an N number of API errors (default 2)
 ```
 
 ### Options inherited from parent commands
diff --git a/docs/ecctl_deployment_create.md b/docs/ecctl_deployment_create.md
index 847809d..bae6f01 100644
--- a/docs/ecctl_deployment_create.md
+++ b/docs/ecctl_deployment_create.md
@@ -4,25 +4,10 @@ Creates a deployment
 
 ### Synopsis
 
-Creates a deployment which can be defined through flags or from a file definition.
-Sane default values are provided, making the command work out of the box even when no parameters are set. 
-When version is not specified, the latest available stack version will automatically be used. 
-These are the available options:
+Creates a deployment which is defined from a file definition using the --file=<file path> (shorthand: -f) flag.
 
-  * Simplified flags to set size and zone count for each instance type (Elasticsearch, Kibana, APM and AppSearch). 
-  * Advanced flag for different Elasticsearch node types: --topology-element <json obj> (shorthand: -e).
-    Note that the flag can be specified multiple times for complex topologies.
-    The JSON object has the following format:
-    {
-      "name": "["data", "master", "ml"]" # type string
-      "size": 1024 # type int32
-      "zone_count": 1 # type int32
-    }
-  * File definition: --file=<file path> (shorthand: -f). You can create a definition by using the sample JSON seen here:
-    https://elastic.co/guide/en/cloud/current/ec-api-deployment-crud.html#ec_create_a_deployment
-
-As an option "--generate-payload" can be used in order to obtain the generated payload that would be sent as a request. 
-Save it, update or extend the topology and create a deployment using the saved payload with the "--file" flag.
+You can create a definition by using the sample JSON seen here:
+  https://elastic.co/guide/en/cloud/current/ec-api-deployment-crud.html#ec_create_a_deployment
 
 ```
 ecctl deployment create {--file | --size <int> --zones <string> | --topology-element <obj>} [flags]
@@ -31,71 +16,29 @@ ecctl deployment create {--file | --size <int> --zones <string> | --topology-ele
 ### Examples
 
 ```
-## Create a deployment with the default values for Elasticsearch, a Kibana instance with a modified size, 
-and a default APM instance. While Elasticsearch and Kibana come enabled by default, both APM and AppSearch need to be 
-enabled by using the "--apm" and "--appsearch" flags. The command will exit after the API response has been returned, without 
-waiting until the deployment resources have been created. 
-$ ecctl deployment create --name my-deployment --zones 2 --kibana-size 2048 --apm --apm-size 1024
-
 ## To make the command wait until the resources have been created use the "--track" flag, which will output 
 the current stage on which the deployment resources are in.
-$ deployment create --name my-deployment --track
+$ deployment create --file create_example.json --track
 [...]
-Cluster [38e0ff5b58a9483c96a98c923b22194e][Elasticsearch]: finished running all the plan steps (Total plan duration: 1m0.911628175s)
-Cluster [51178ffc645d48b7859dbf17388a6c35][Kibana]: finished running all the plan steps (Total plan duration: 1m11.246662764s)
-
-## Additionally, a more advanced topology for Elasticsearch can be created through "--topology-element" or shorthand "-e".
-The following command will create a deployment with two 1GB Elasticsearch instances of the type "data" and 
-one 1GB Elasticsearch instance of the type "ml".
-$ ecctl deployment create --name my-deployment --topology-element '{"size": 1024, "zone_count": 2, "name": "data"}' --topology-element '{"size": 1024, "zone_count": 1, "name": "ml"}'
-
-## In order to use the "--deployment-template" flag, you'll need to know which deployment templates ara available to you.
-Visit https://elastic.co/guide/en/cloud/current/ec-regions-templates-instances.html.
-If you are an Elastic Cloud Enterprise customer, you'll need to run the following command to view your deployment templates:
-$ ecctl platform deployment-template list
-
-## Use the "--generate-payload" flag to save the definition to a file for later use.
-$ ecctl deployment create --name my-deployment --size 1024 --track --generate-payload --zones 2 > elasticsearch_create_example.json
-
-## Create a deployment through the file definition.
-$ ecctl deployment create --file elasticsearch_create_example.json --track
+Deployment [b6ecbea3d5c84124b7dca457f2892086] - [Elasticsearch][b6ecbea3d5c84124b7dca457f2892086]: finished running all the plan steps (Total plan duration: 5m11.s)
+Deployment [91c4d60acb804ba0a27651fac02780ec] - [Kibana][8a9d9916cd6e46a7bb0912211d76e2af]: finished running all the plan steps (Total plan duration: 4m29.58s)
 
 ## To retry a when the previous deployment creation failed:
-$ ecctl deployment create
+$ ecctl deployment create --file create_example.json
 The deployment creation returned with an error, please use the displayed idempotency token
 to recreate the deployment resources
 Idempotency token: GMZPMRrcMYqHdmxjIQkHbdjnhPIeBElcwrHwzVlhGUSMXrEIzVXoBykSVRsKncNb
 unknown error (status 500)
-$ ecctl deployment create --request-id=GMZPMRrcMYqHdmxjIQkHbdjnhPIeBElcwrHwzVlhGUSMXrEIzVXoBykSVRsKncNb
+$ ecctl deployment create --file create_example.json --request-id=GMZPMRrcMYqHdmxjIQkHbdjnhPIeBElcwrHwzVlhGUSMXrEIzVXoBykSVRsKncNb
 ```
 
 ### Options
 
 ```
-      --apm                            Enables APM for the deployment
-      --apm-ref-id string              Optional RefId for the APM deployment (default "main-apm")
-      --apm-size int32                 Memory (RAM) in MB that each of the APM instances will have (default 512)
-      --apm-zones int32                Number of zones the APM instances will span (default 1)
-      --appsearch                      Enables AppSearch for the deployment
-      --appsearch-ref-id string        Optional RefId for the AppSearch deployment (default "main-appsearch")
-      --appsearch-size int32           Memory (RAM) in MB that each of the AppSearch instances will have (default 2048)
-      --appsearch-zones int32          Number of zones the AppSearch instances will span (default 1)
-      --deployment-template string     Deployment template ID on which to base the deployment from (default "default")
-  -f, --file string                    DeploymentCreateRequest file definition. See help for more information
-      --generate-payload               Returns the deployment payload without actually creating the deployment resources
-  -h, --help                           help for create
-      --kibana-ref-id string           Optional RefId for the Kibana deployment (default "main-kibana")
-      --kibana-size int32              Memory (RAM) in MB that each of the Kibana instances will have (default 1024)
-      --kibana-zones int32             Number of zones the Kibana instances will span (default 1)
-      --name string                    Optional name for the deployment
-      --plugin strings                 Additional plugins to add to the Elasticsearch deployment
-      --ref-id string                  Optional RefId for the Elasticsearch deployment (default "main-elasticsearch")
-      --request-id string              Optional idempotency token - Can be found in the Stderr device when a previous deployment creation failed, for more information see the examples in the help command page
-      --size int32                     Memory (RAM) in MB that each of the Elasticsearch instances will have (default 4096)
-  -e, --topology-element stringArray   Optional Elasticsearch topology element definition. See help for more information
-  -t, --track                          Tracks the progress of the performed task
-      --version string                 Version to use, if not specified, the latest available stack version will be used
-      --zones int32                    Number of zones the Elasticsearch instances will span (default 1)
+  -f, --file string         DeploymentCreateRequest file definition. See help for more information
+  -h, --help                help for create
+      --request-id string   Optional idempotency token - Can be found in the Stderr device when a previous deployment creation failed, for more information see the examples in the help command page
+  -t, --track               Tracks the progress of the performed task
 ```
 
 ### Options inherited from parent commands
diff --git a/docs/ecctl_deployment_kibana_plan_monitor.md b/docs/ecctl_deployment_kibana_plan_monitor.md
index c6cf4f3..f5fd0a0 100644
--- a/docs/ecctl_deployment_kibana_plan_monitor.md
+++ b/docs/ecctl_deployment_kibana_plan_monitor.md
@@ -14,7 +14,7 @@ ecctl deployment kibana plan monitor <cluster id> [flags]
 
 ```
   -h, --help                     help for monitor
-      --poll-interval duration   Monitor poll interval (default 2s)
+      --poll-interval duration   Monitor poll interval (default 5s)
       --retries uint8            Number of times monitoring will be retried. Meaning it accepts an N number of API errors (default 3)
 ```
 
diff --git a/go.mod b/go.mod
index 366f663..032b80d 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/chzyer/logex v1.1.10 // indirect
 	github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
 	github.com/davecgh/go-spew v1.1.1
-	github.com/elastic/cloud-sdk-go v1.0.0-beta2
+	github.com/elastic/cloud-sdk-go v1.0.0-beta2.0.20200325215536-c64d182f57e4
 	github.com/ghodss/yaml v1.0.0
 	github.com/go-openapi/runtime v0.19.15
 	github.com/go-openapi/strfmt v0.19.5
diff --git a/go.sum b/go.sum
index 6c830a1..c9418c8 100644
--- a/go.sum
+++ b/go.sum
@@ -50,8 +50,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/elastic/cloud-sdk-go v1.0.0-beta2 h1:INSj4Z3Xdm3Jn8Vl8/CtSe+ImBT2PZ6HHp0oAM6ZfRw=
-github.com/elastic/cloud-sdk-go v1.0.0-beta2/go.mod h1:kAQwliTmnTjeHdIt5nk8Y24IfegfVhlDEy8oWlIod3A=
+github.com/elastic/cloud-sdk-go v1.0.0-beta2.0.20200325215536-c64d182f57e4 h1:FoEY4ineL6dHxY1pGu1DKfdU1fd+VfdEB0A9E6d0PfE=
+github.com/elastic/cloud-sdk-go v1.0.0-beta2.0.20200325215536-c64d182f57e4/go.mod h1:dxje+iaUThzYjrFaWhzpXf2TpziA4yBN47igBJ7GeV8=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fsnotify/fsnotify v0.0.0-20170329110642-4da3e2cfbabc/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
@@ -105,8 +105,6 @@ github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6
 github.com/go-openapi/runtime v0.19.0 h1:sU6pp4dSV2sGlNKKyHxZzi1m1kG4WnYtWcJ+HYbygjE=
 github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
 github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
-github.com/go-openapi/runtime v0.19.12 h1:5lQlCr1HL0ZVRPL+0PHKhRpMCSPMV2qF842rhQx8CYY=
-github.com/go-openapi/runtime v0.19.12/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
 github.com/go-openapi/runtime v0.19.14 h1:/k1/W4Cgs73kOdlPCaZCu77jC1FHhNY6sUOnVvtOX94=
 github.com/go-openapi/runtime v0.19.14/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
 github.com/go-openapi/runtime v0.19.15 h1:2GIefxs9Rx1vCDNghRtypRq+ig8KSLrjHbAYI/gCLCM=
diff --git a/pkg/deployment/apm/delete.go b/pkg/deployment/apm/delete.go
index 60a5e2f..95470d0 100644
--- a/pkg/deployment/apm/delete.go
+++ b/pkg/deployment/apm/delete.go
@@ -22,7 +22,7 @@ import (
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_apm"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 
@@ -74,7 +74,8 @@ type ShutdownParams struct {
 	// Stops the cluster and hides it from the user.
 	Hide bool
 
-	util.TrackParams
+	Track bool
+	planutil.TrackChangeParams
 }
 
 // Validate ensures that the parameters are usable by the consuming function.
@@ -88,7 +89,6 @@ func (params ShutdownParams) Validate() error {
 		merr = multierror.Append(merr, deputil.NewInvalidDeploymentIDError(params.ID))
 	}
 
-	merr = multierror.Append(merr, params.TrackParams.Validate())
 	return merr.ErrorOrNil()
 }
 
@@ -112,14 +112,7 @@ func Shutdown(params ShutdownParams) error {
 		return nil
 	}
 
-	return util.TrackCluster(util.TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			API:           params.API,
-			PollFrequency: params.PollFrequency,
-			MaxRetries:    params.MaxRetries,
-			ID:            params.ID,
-			Kind:          "apm",
-		},
-	})
+	return planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ID, util.Apm,
+	))
 }
diff --git a/pkg/deployment/apm/plan.go b/pkg/deployment/apm/plan.go
index fa1754d..0f35312 100644
--- a/pkg/deployment/apm/plan.go
+++ b/pkg/deployment/apm/plan.go
@@ -24,12 +24,12 @@ import (
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_apm"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/deployment/deputil"
-	"github.com/elastic/ecctl/pkg/deployment/planutil"
+	deploymentplanutil "github.com/elastic/ecctl/pkg/deployment/planutil"
 	"github.com/elastic/ecctl/pkg/util"
 )
 
@@ -39,14 +39,15 @@ type PlanParams struct {
 	// ID represents the deployment ID.
 	ID           string
 	PlanDefaults bool
-	util.TrackParams
+
+	Track bool
+	planutil.TrackChangeParams
 }
 
 // Validate ensures that the parameters are usable by the consuming function.
 func (params PlanParams) Validate() error {
 	var err = multierror.Append(new(multierror.Error),
 		deputil.ValidateParams(&params),
-		params.TrackParams.Validate(),
 	)
 	return err.ErrorOrNil()
 }
@@ -71,16 +72,9 @@ func GetPlan(params PlanParams) (*models.ApmPlansInfo, error) {
 		return res.Payload, nil
 	}
 
-	return res.Payload, util.TrackCluster(util.TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			API:           params.API,
-			PollFrequency: params.PollFrequency,
-			MaxRetries:    params.MaxRetries,
-			ID:            params.ID,
-			Kind:          "apm",
-		},
-	})
+	return res.Payload, planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ID, util.Apm,
+	))
 }
 
 // CancelPlan cancels the pending plan on the specified cluster.
@@ -102,16 +96,9 @@ func CancelPlan(params PlanParams) error {
 		return nil
 	}
 
-	return util.TrackCluster(util.TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			API:           params.API,
-			PollFrequency: params.PollFrequency,
-			MaxRetries:    params.MaxRetries,
-			ID:            params.ID,
-			Kind:          "apm",
-		},
-	})
+	return planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ID, util.Apm,
+	))
 }
 
 // ListPlanHistory returns the historic plan list
@@ -133,13 +120,13 @@ func newDefaultTransientPlanConfiguration() *models.ApmPlanControlConfiguration
 
 type computeTransientParams struct {
 	plan      models.ApmPlan
-	transient planutil.ReapplyParams
+	transient deploymentplanutil.ReapplyParams
 }
 
 // ReapplyLatestPlanAttempt will obtain the latest plan attempt and reapply it
 // resetting all of the transient settings, Any setting can be overridden if
 // specified in the params.
-func ReapplyLatestPlanAttempt(params PlanParams, reparams planutil.ReapplyParams) (*models.ApmCrudResponse, error) {
+func ReapplyLatestPlanAttempt(params PlanParams, reparams deploymentplanutil.ReapplyParams) (*models.ApmCrudResponse, error) {
 	if err := reparams.Validate(); err != nil {
 		return nil, err
 	}
@@ -159,7 +146,7 @@ func ReapplyLatestPlanAttempt(params PlanParams, reparams planutil.ReapplyParams
 		transient: reparams,
 	})
 	if !reparams.HidePlan {
-		enc := json.NewEncoder(params.TrackParams.Output)
+		enc := json.NewEncoder(params.TrackChangeParams.Writer)
 		enc.SetIndent("", "  ")
 
 		if err := enc.Encode(latestAttempt); err != nil {
@@ -181,16 +168,9 @@ func ReapplyLatestPlanAttempt(params PlanParams, reparams planutil.ReapplyParams
 		return res.Payload, nil
 	}
 
-	return res.Payload, util.TrackCluster(util.TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			API:           params.API,
-			PollFrequency: params.PollFrequency,
-			MaxRetries:    params.MaxRetries,
-			ID:            res.Payload.ApmID,
-			Kind:          "apm",
-		},
-	})
+	return res.Payload, planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ID, util.Apm,
+	))
 }
 
 // computeTransientSettings resets the transient.PlanConfiguration to a set of
diff --git a/pkg/deployment/apm/restart.go b/pkg/deployment/apm/restart.go
index 6918b42..3833bfe 100644
--- a/pkg/deployment/apm/restart.go
+++ b/pkg/deployment/apm/restart.go
@@ -20,7 +20,7 @@ package apm
 import (
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_apm"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 
@@ -35,14 +35,15 @@ type RestartParams struct {
 
 	// Force cancels any pending plans that might be in progress.
 	Force bool
-	util.TrackParams
+
+	Track bool
+	planutil.TrackChangeParams
 }
 
 // Validate ensures that the parameters are usable by the consuming function.
 func (params RestartParams) Validate() error {
 	var err = multierror.Append(new(multierror.Error),
 		deputil.ValidateParams(&params),
-		params.TrackParams.Validate(),
 	)
 	return err.ErrorOrNil()
 }
@@ -69,14 +70,7 @@ func Restart(params RestartParams) error {
 		return nil
 	}
 
-	return util.TrackCluster(util.TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			Kind:          "apm",
-			API:           params.API,
-			ID:            params.ID,
-			PollFrequency: params.PollFrequency,
-			MaxRetries:    params.MaxRetries,
-		},
-	})
+	return planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ID, util.Apm,
+	))
 }
diff --git a/pkg/deployment/apm/upgrade.go b/pkg/deployment/apm/upgrade.go
index 670f446..2d47497 100644
--- a/pkg/deployment/apm/upgrade.go
+++ b/pkg/deployment/apm/upgrade.go
@@ -21,7 +21,7 @@ import (
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_apm"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/deployment/deputil"
@@ -33,14 +33,14 @@ type UpgradeParams struct {
 	*api.API
 	ID string
 
-	util.TrackParams
+	Track bool
+	planutil.TrackChangeParams
 }
 
 // Validate ensures that the parameters are usable by the consuming function.
 func (params UpgradeParams) Validate() error {
 	var err = multierror.Append(new(multierror.Error),
 		deputil.ValidateParams(&params),
-		params.TrackParams.Validate(),
 	)
 	return err.ErrorOrNil()
 }
@@ -64,14 +64,7 @@ func Upgrade(params UpgradeParams) (*models.ClusterUpgradeInfo, error) {
 		return res.Payload, nil
 	}
 
-	return res.Payload, util.TrackCluster(util.TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			API:           params.API,
-			PollFrequency: params.PollFrequency,
-			MaxRetries:    params.MaxRetries,
-			ID:            params.ID,
-			Kind:          "apm",
-		},
-	})
+	return res.Payload, planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ID, util.Apm,
+	))
 }
diff --git a/pkg/deployment/depresource/track.go b/pkg/deployment/depresource/track.go
deleted file mode 100644
index 6f665d5..0000000
--- a/pkg/deployment/depresource/track.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// Licensed to Elasticsearch B.V. under one or more contributor
-// license agreements. See the NOTICE file distributed with
-// this work for additional information regarding copyright
-// ownership. Elasticsearch B.V. licenses this file to you under
-// the Apache License, Version 2.0 (the "License"); you may
-// not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package depresource
-
-import (
-	"errors"
-	"fmt"
-	"sync"
-
-	"github.com/elastic/cloud-sdk-go/pkg/api"
-	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
-	"github.com/hashicorp/go-multierror"
-
-	"github.com/elastic/ecctl/pkg/util"
-)
-
-// TrackResourcesParams is consumed by TrackResources.
-type TrackResourcesParams struct {
-	*api.API
-
-	// List of current "live" resources,
-	Resources []*models.DeploymentResource
-	// Orphaned are the resources that will become orphaned after a deployment
-	// update.
-	Orphaned *models.Orphaned
-
-	OutputDevice *output.Device
-}
-
-type resource struct {
-	id, kind string
-}
-
-// Validate ensures the parameters are usable by the consuming function.
-func (params TrackResourcesParams) Validate() error {
-	var merr = new(multierror.Error)
-	if params.API == nil {
-		merr = multierror.Append(merr, util.ErrAPIReq)
-	}
-
-	if params.OutputDevice == nil {
-		merr = multierror.Append(merr, errors.New("resource track: output device cannot be nil"))
-	}
-
-	return merr.ErrorOrNil()
-}
-
-// TrackResources will track changes made to a deployment's resources. Creating
-// a gorouine for each cluster which is going to be tracked.
-// The returned error is a multierror, containing all the encountered errors.
-// This function is rather a stop-gap mechanism to be able to easily track
-// responses returned by the Deployments API but it suffice going forward.
-// WARNING: Does not support tracking "appsearch" resource changes.
-func TrackResources(params TrackResourcesParams) error {
-	if err := params.Validate(); err != nil {
-		return err
-	}
-
-	var resources = make([]resource, 0, len(params.Resources))
-	for _, r := range params.Resources {
-		resources = append(resources, resource{
-			id:   *r.ID,
-			kind: *r.Kind,
-		})
-	}
-
-	if params.Orphaned != nil {
-		for _, id := range params.Orphaned.Apm {
-			resources = append(resources, resource{
-				id:   id,
-				kind: "apm",
-			})
-		}
-		for _, id := range params.Orphaned.Appsearch {
-			resources = append(resources, resource{
-				id:   id,
-				kind: "appsearch",
-			})
-		}
-		for _, id := range params.Orphaned.Kibana {
-			resources = append(resources, resource{
-				id:   id,
-				kind: "kibana",
-			})
-		}
-		for _, es := range params.Orphaned.Elasticsearch {
-			resources = append(resources, resource{
-				id:   *es.ID,
-				kind: "elasticsearch",
-			})
-		}
-	}
-
-	var merr = new(multierror.Error)
-	var errChan = make(chan error)
-	var wg sync.WaitGroup
-	for _, r := range resources {
-		wg.Add(1)
-		go trackResource(params.API, params.OutputDevice, r, &wg, errChan)
-	}
-
-	// Close the channel once all the errors have been received.
-	go func(w *sync.WaitGroup) {
-		w.Wait()
-		close(errChan)
-	}(&wg)
-
-	for err := range errChan {
-		merr = multierror.Append(merr, err)
-	}
-
-	return merr.ErrorOrNil()
-}
-
-func trackResource(a *api.API, out *output.Device, r resource, w *sync.WaitGroup, errChan chan error) {
-	defer w.Done()
-	if r.kind == "appsearch" {
-		errChan <- fmt.Errorf("cannot track appsearch resource id \"%s\"", r.id)
-		return
-	}
-
-	errChan <- util.TrackCluster(util.TrackClusterParams{
-		TrackParams: plan.TrackParams{
-			API:  a,
-			ID:   r.id,
-			Kind: r.kind,
-		},
-		Output: out,
-	})
-}
diff --git a/pkg/deployment/elasticsearch/plan/reapply.go b/pkg/deployment/elasticsearch/plan/reapply.go
index dfeca87..8cad4f5 100644
--- a/pkg/deployment/elasticsearch/plan/reapply.go
+++ b/pkg/deployment/elasticsearch/plan/reapply.go
@@ -21,14 +21,16 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"io"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_elasticsearch"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 
-	"github.com/elastic/ecctl/pkg/deployment/planutil"
+	depplanutil "github.com/elastic/ecctl/pkg/deployment/planutil"
 	"github.com/elastic/ecctl/pkg/util"
 )
 
@@ -39,9 +41,11 @@ var (
 // ReapplyParams contains the parameters required to call a plan reapply action
 type ReapplyParams struct {
 	*api.API
-	planutil.ReapplyParams
-	util.TrackParams
+	depplanutil.ReapplyParams
 
+	Output io.Writer
+	planutil.TrackChangeParams
+	Track                bool
 	SkipSnapshot         bool `kebabcase:"Skips snapshot on the reapplied plan"`
 	SkipDataMigration    bool `kebabcase:"Bypasses the need to wait for data to be migrated from old instances to new instances before continuing the plan (potentially deleting the old instances and losing data)"`
 	SkipPostUpgradeSteps bool `kebabcase:"Bypasses 2.x->5.x operations for any plan change ending with a 5.x cluster (eg apply a cluster license, ensure Monitoring is configured)"`
@@ -55,10 +59,6 @@ func (params ReapplyParams) Validate() error {
 		err = multierror.Append(err, util.ErrAPIReq)
 	}
 
-	if params.TrackParams.Output == nil && !params.ReapplyParams.HidePlan {
-		err = multierror.Append(err, errors.New("output cannot be nil when hide is false"))
-	}
-
 	err = multierror.Append(err, params.ReapplyParams.Validate())
 
 	return err.ErrorOrNil()
@@ -122,7 +122,7 @@ func Reapply(params ReapplyParams) (*models.ClusterCrudResponse, error) {
 	}
 
 	if !params.HidePlan {
-		enc := json.NewEncoder(params.TrackParams.Output)
+		enc := json.NewEncoder(params.Output)
 		enc.SetIndent("", "  ")
 		if err := enc.Encode(body); err != nil {
 			return nil, err
@@ -137,11 +137,11 @@ func Reapply(params ReapplyParams) (*models.ClusterCrudResponse, error) {
 	)
 
 	return util.ParseCUResponse(util.ParseCUResponseParams{
-		API:            params.API,
-		UpdateResponse: res2,
-		CreateResponse: res,
-		Err:            err,
-		TrackParams:    params.TrackParams,
+		UpdateResponse:    res2,
+		CreateResponse:    res,
+		Err:               err,
+		Track:             params.Track,
+		TrackChangeParams: params.TrackChangeParams,
 	})
 }
 
diff --git a/pkg/deployment/elasticsearch/plan/update.go b/pkg/deployment/elasticsearch/plan/update.go
index c731185..51ff9d2 100644
--- a/pkg/deployment/elasticsearch/plan/update.go
+++ b/pkg/deployment/elasticsearch/plan/update.go
@@ -21,7 +21,7 @@ import (
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_elasticsearch"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/deployment/deputil"
@@ -30,19 +30,18 @@ import (
 
 // UpdateParams is consumed by Update
 type UpdateParams struct {
+	planutil.TrackChangeParams
+	Plan models.ElasticsearchClusterPlan
 	*api.API
 	ID           string
 	ValidateOnly bool
-	Plan         models.ElasticsearchClusterPlan
-
-	util.TrackParams
+	Track        bool
 }
 
 // Validate ensures that the parameters are usable by the consuming function.
 func (params UpdateParams) Validate() error {
 	var err = multierror.Append(new(multierror.Error),
 		deputil.ValidateParams(&params),
-		params.TrackParams.Validate(),
 	)
 	return err.ErrorOrNil()
 }
@@ -73,14 +72,7 @@ func Update(params UpdateParams) (*models.ClusterCrudResponse, error) {
 		return planAccepted.Payload, nil
 	}
 
-	return planAccepted.Payload, util.TrackCluster(util.TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			API:           params.API,
-			PollFrequency: params.PollFrequency,
-			MaxRetries:    params.MaxRetries,
-			ID:            params.ID,
-			Kind:          "elasticsearch",
-		},
-	})
+	return planAccepted.Payload, planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ID, util.Elasticsearch,
+	))
 }
diff --git a/pkg/deployment/elasticsearch/reallocate.go b/pkg/deployment/elasticsearch/reallocate.go
index 4cc9124..a7f554f 100644
--- a/pkg/deployment/elasticsearch/reallocate.go
+++ b/pkg/deployment/elasticsearch/reallocate.go
@@ -18,15 +18,12 @@
 package elasticsearch
 
 import (
-	"errors"
 	"fmt"
 	"strings"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_elasticsearch"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/deployment"
@@ -45,19 +42,14 @@ type ReallocateParams struct {
 	// Commenting user
 	User          string
 	InstancesDown *bool
-	OutputDevice  *output.Device
-	PollFrequency time.Duration
-	MaxRetries    uint8
+
+	planutil.TrackChangeParams
 }
 
 // Validate ensures that the parameters are usable by the consuming function.
 func (params ReallocateParams) Validate() error {
 	var err = multierror.Append(new(multierror.Error), params.ClusterParams.Validate())
 
-	if params.OutputDevice == nil {
-		err = multierror.Append(err, errors.New("output device cannot be nil"))
-	}
-
 	return err.ErrorOrNil()
 }
 
@@ -90,10 +82,10 @@ func Reallocate(params ReallocateParams) error {
 	//nolint
 	note.Add(note.AddParams{
 		Params: note.Params{
-		Params: deployment.Params{
-			API:         params.API,
-			ID:          params.ClusterID,
-		}},
+			Params: deployment.Params{
+				API: params.API,
+				ID:  params.ClusterID,
+			}},
 		Message:     fmt.Sprintf(reallocateMessage, "elasticsearch", strings.Join(params.Instances, " ")),
 		UserID:      params.User,
 		Commentator: ecctl.GetOperationInstance(),
@@ -109,14 +101,7 @@ func Reallocate(params ReallocateParams) error {
 		return api.UnwrapError(err)
 	}
 
-	return util.TrackCluster(util.TrackClusterParams{
-		Output: params.OutputDevice,
-		TrackParams: plan.TrackParams{
-			API:           params.API,
-			ID:            params.ClusterID,
-			Kind:          "elasticsearch",
-			PollFrequency: params.PollFrequency,
-			MaxRetries:    params.MaxRetries,
-		},
-	})
+	return planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ClusterID, util.Elasticsearch,
+	))
 }
diff --git a/pkg/deployment/elasticsearch/restart.go b/pkg/deployment/elasticsearch/restart.go
index 5ced175..747f4d2 100644
--- a/pkg/deployment/elasticsearch/restart.go
+++ b/pkg/deployment/elasticsearch/restart.go
@@ -22,22 +22,24 @@ import (
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_elasticsearch"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 
-	"github.com/elastic/ecctl/pkg/deployment/planutil"
+	depplanutil "github.com/elastic/ecctl/pkg/deployment/planutil"
 	"github.com/elastic/ecctl/pkg/util"
 )
 
 // RestartClusterParams is used to restart clusters with specific parameters / options
 type RestartClusterParams struct {
 	util.ClusterParams
-	util.TrackParams
 
 	ShardInitWaitTime time.Duration
 
 	RollingByName, RollingByZone, SkipSnapshot, RestoreSnapshot bool
+
+	Track bool
+	planutil.TrackChangeParams
 }
 
 // Validate ensures the parameters are usable by ShutdownCluster.
@@ -45,7 +47,6 @@ func (params RestartClusterParams) Validate() error {
 	var err = new(multierror.Error)
 
 	err = multierror.Append(err, params.ClusterParams.Validate())
-	err = multierror.Append(err, params.TrackParams.Validate())
 
 	return err.ErrorOrNil()
 }
@@ -59,12 +60,12 @@ func RestartCluster(params RestartClusterParams) error {
 		return err
 	}
 
-	groupingStrategy := planutil.AllGroupAttribute
+	groupingStrategy := depplanutil.AllGroupAttribute
 	if params.RollingByName {
-		groupingStrategy = planutil.NameGroupAttribute
+		groupingStrategy = depplanutil.NameGroupAttribute
 	}
 	if params.RollingByZone {
-		groupingStrategy = planutil.ZoneGroupAttribute
+		groupingStrategy = depplanutil.ZoneGroupAttribute
 	}
 
 	shardInitWaitTime := int64(params.ShardInitWaitTime.Seconds())
@@ -85,14 +86,7 @@ func RestartCluster(params RestartClusterParams) error {
 		return nil
 	}
 
-	return util.TrackCluster(util.TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			API:           params.API,
-			ID:            params.ClusterID,
-			MaxRetries:    params.MaxRetries,
-			PollFrequency: params.PollFrequency,
-			Kind:          "elasticsearch",
-		},
-	})
+	return planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ClusterID, util.Elasticsearch,
+	))
 }
diff --git a/pkg/deployment/elasticsearch/shutdown.go b/pkg/deployment/elasticsearch/shutdown.go
index 2ba1be1..b87e5fa 100644
--- a/pkg/deployment/elasticsearch/shutdown.go
+++ b/pkg/deployment/elasticsearch/shutdown.go
@@ -20,7 +20,7 @@ package elasticsearch
 import (
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_elasticsearch"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/util"
@@ -29,11 +29,13 @@ import (
 // ShutdownClusterParams used to shut down an elasticsearch cluster.
 type ShutdownClusterParams struct {
 	util.ClusterParams
-	util.TrackParams
 
 	// Hides the cluster from the user console if specified.
 	Hide         bool
 	SkipSnapshot bool
+
+	Track bool
+	planutil.TrackChangeParams
 }
 
 // Validate ensures the parameters are usable by ShutdownCluster.
@@ -41,7 +43,6 @@ func (params ShutdownClusterParams) Validate() error {
 	var err = new(multierror.Error)
 
 	err = multierror.Append(err, params.ClusterParams.Validate())
-	err = multierror.Append(err, params.TrackParams.Validate())
 
 	return err.ErrorOrNil()
 }
@@ -67,14 +68,7 @@ func ShutdownCluster(params ShutdownClusterParams) error {
 		return nil
 	}
 
-	return util.TrackCluster(util.TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			API:           params.API,
-			ID:            params.ClusterID,
-			PollFrequency: params.PollFrequency,
-			MaxRetries:    params.MaxRetries,
-			Kind:          "elasticsearch",
-		},
-	})
+	return planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ClusterID, util.Elasticsearch,
+	))
 }
diff --git a/pkg/deployment/elasticsearch/upgrade.go b/pkg/deployment/elasticsearch/upgrade.go
index 5a7ec4e..042226f 100644
--- a/pkg/deployment/elasticsearch/upgrade.go
+++ b/pkg/deployment/elasticsearch/upgrade.go
@@ -24,9 +24,10 @@ import (
 	"github.com/blang/semver"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_elasticsearch"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	multierror "github.com/hashicorp/go-multierror"
 
-	"github.com/elastic/ecctl/pkg/deployment/planutil"
+	depplanutil "github.com/elastic/ecctl/pkg/deployment/planutil"
 	"github.com/elastic/ecctl/pkg/util"
 )
 
@@ -47,16 +48,17 @@ const (
 // UpgradeParams is consumed by Upgrade.
 type UpgradeParams struct {
 	util.ClusterParams
-	util.TrackParams
 
 	Version string
+
+	Track bool
+	planutil.TrackChangeParams
 }
 
 // Validate ensures that the parameters are usable by Upgrade.
 func (params UpgradeParams) Validate() error {
 	var merr = new(multierror.Error)
 	merr = multierror.Append(merr, params.ClusterParams.Validate())
-	merr = multierror.Append(merr, params.TrackParams.Validate())
 
 	if _, e := semver.Parse(params.Version); e != nil {
 		merr = multierror.Append(merr, errors.New(strings.ToLower(e.Error())))
@@ -97,11 +99,10 @@ func Upgrade(params UpgradeParams) (*models.ClusterCrudResponse, error) {
 	)
 
 	return util.ParseCUResponse(util.ParseCUResponseParams{
-		API:            params.API,
-		UpdateResponse: res2,
-		CreateResponse: res,
-		Err:            err,
-		TrackParams:    params.TrackParams,
+		UpdateResponse:    res2,
+		CreateResponse:    res,
+		Err:               err,
+		TrackChangeParams: params.TrackChangeParams,
 	})
 }
 
@@ -112,7 +113,7 @@ func getUpgradePlan(cluster *models.ElasticsearchClusterInfo, version string) *m
 
 	// Default Plan for Minor upgrades
 	newPlan.Transient = &models.TransientElasticsearchPlanConfiguration{
-		Strategy: planutil.DefaultPlanStrategy,
+		Strategy: depplanutil.DefaultPlanStrategy,
 	}
 
 	previousVer := semver.MustParse(currentVersion)
@@ -121,7 +122,7 @@ func getUpgradePlan(cluster *models.ElasticsearchClusterInfo, version string) *m
 	// The only valid Strategy for Major version upgrades is all performing the
 	// change to all instances at the same time, resulting in downtime
 	if previousVer.Major != nextVersion.Major {
-		newPlan.Transient.Strategy = planutil.MajorUpgradeStrategy
+		newPlan.Transient.Strategy = depplanutil.MajorUpgradeStrategy
 		// This part ensures that upgrades from 2.x to 5.x succeed. The setting
 		// is enforced from the API to be unused, else it would error.
 		if newPlan.Elasticsearch.SystemSettings != nil {
@@ -161,7 +162,7 @@ func getUpgradePlan(cluster *models.ElasticsearchClusterInfo, version string) *m
 		// Asserts wether it complies with the criteria
 		needsRollingStrategyGroupedByName := memoryPressureThresholdExceeded || percentageDiskThresholdExceeded || hardDiskThresholdExceeded
 		if needsRollingStrategyGroupedByName {
-			newPlan.Transient.Strategy = planutil.RollingByNameStrategy
+			newPlan.Transient.Strategy = depplanutil.RollingByNameStrategy
 		}
 	}
 	return newPlan
diff --git a/pkg/deployment/get_resource.go b/pkg/deployment/get_resource.go
index 7ed7240..97f4436 100644
--- a/pkg/deployment/get_resource.go
+++ b/pkg/deployment/get_resource.go
@@ -19,6 +19,8 @@ package deployment
 
 import (
 	"fmt"
+
+	"github.com/elastic/ecctl/pkg/util"
 )
 
 // GetResourceParams is consumed by GetResource.
@@ -47,13 +49,13 @@ func GetResource(params GetResourceParams) (interface{}, error) {
 	}
 
 	switch params.Kind {
-	case "apm":
+	case util.Apm:
 		return GetApm(params.GetParams)
-	case "kibana":
+	case util.Kibana:
 		return GetKibana(params.GetParams)
-	case "elasticsearch":
+	case util.Elasticsearch:
 		return GetElasticsearch(params.GetParams)
-	case "appsearch":
+	case util.Appsearch:
 		return GetAppSearch(params.GetParams)
 	default:
 		// If the is specified but not supported, return an error.
@@ -76,19 +78,19 @@ func GetKindRefID(params GetResourceParams) (string, error) {
 
 	var refID string
 	switch params.Kind {
-	case "apm":
+	case util.Apm:
 		for _, resource := range res.Resources.Apm {
 			refID = *resource.RefID
 		}
-	case "kibana":
+	case util.Kibana:
 		for _, resource := range res.Resources.Kibana {
 			refID = *resource.RefID
 		}
-	case "elasticsearch":
+	case util.Elasticsearch:
 		for _, resource := range res.Resources.Elasticsearch {
 			refID = *resource.RefID
 		}
-	case "appsearch":
+	case util.Appsearch:
 		for _, resource := range res.Resources.Appsearch {
 			refID = *resource.RefID
 		}
diff --git a/pkg/deployment/kibana/params.go b/pkg/deployment/kibana/params.go
index 714c37a..2f1e5da 100644
--- a/pkg/deployment/kibana/params.go
+++ b/pkg/deployment/kibana/params.go
@@ -19,10 +19,10 @@ package kibana
 
 import (
 	"github.com/elastic/cloud-sdk-go/pkg/api"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/deployment/deputil"
-	"github.com/elastic/ecctl/pkg/util"
 )
 
 // stoppedState represents the stopped state string for a cluster
@@ -33,14 +33,15 @@ type DeploymentParams struct {
 	API *api.API
 	// ID represents the deployment ID.
 	ID string
-	util.TrackParams
+
+	Track bool
+	planutil.TrackChangeParams
 }
 
 // Validate ensures that the parameters are usable by the consuming function.
 func (params *DeploymentParams) Validate() error {
 	var err = multierror.Append(new(multierror.Error),
 		deputil.ValidateParams(params),
-		params.TrackParams.Validate(),
 	)
 	return err.ErrorOrNil()
 }
diff --git a/pkg/deployment/kibana/reallocate.go b/pkg/deployment/kibana/reallocate.go
index 26adce1..f454463 100644
--- a/pkg/deployment/kibana/reallocate.go
+++ b/pkg/deployment/kibana/reallocate.go
@@ -18,13 +18,9 @@
 package kibana
 
 import (
-	"errors"
-
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_kibana"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
-	multierror "github.com/hashicorp/go-multierror"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 
 	"github.com/elastic/ecctl/pkg/deployment/deputil"
 	"github.com/elastic/ecctl/pkg/util"
@@ -34,19 +30,6 @@ import (
 type ReallocateParams struct {
 	DeploymentParams
 	Instances []string
-	Output    *output.Device
-}
-
-// Validate ensures that the parameters are usable by the consuming function.
-func (params ReallocateParams) Validate() error {
-	var merr = new(multierror.Error)
-
-	merr = multierror.Append(merr, params.DeploymentParams.Validate())
-
-	if params.Output == nil {
-		merr = multierror.Append(merr, errors.New("track: Output cannot be empty"))
-	}
-	return merr.ErrorOrNil()
 }
 
 // Reallocate will reallocate the Kibana instance instances, if no
@@ -87,12 +70,7 @@ func Reallocate(params ReallocateParams) error {
 		return api.UnwrapError(err)
 	}
 
-	return util.TrackCluster(util.TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			API:  params.API,
-			ID:   params.ID,
-			Kind: "kibana",
-		},
-	})
+	return planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ID, util.Kibana,
+	))
 }
diff --git a/pkg/deployment/kibana/restart.go b/pkg/deployment/kibana/restart.go
index c451f7d..de65da1 100644
--- a/pkg/deployment/kibana/restart.go
+++ b/pkg/deployment/kibana/restart.go
@@ -20,7 +20,7 @@ package kibana
 import (
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_kibana"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 
 	"github.com/elastic/ecctl/pkg/util"
 )
@@ -43,14 +43,7 @@ func Restart(params DeploymentParams) error {
 		return nil
 	}
 
-	return util.TrackCluster(util.TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			API:           params.API,
-			PollFrequency: params.PollFrequency,
-			MaxRetries:    params.MaxRetries,
-			ID:            params.ID,
-			Kind:          "kibana",
-		},
-	})
+	return planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ID, util.Kibana,
+	))
 }
diff --git a/pkg/deployment/kibana/stop.go b/pkg/deployment/kibana/stop.go
index 7e278da..ea10793 100644
--- a/pkg/deployment/kibana/stop.go
+++ b/pkg/deployment/kibana/stop.go
@@ -20,7 +20,7 @@ package kibana
 import (
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_kibana"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/util"
@@ -61,14 +61,7 @@ func Stop(params StopParams) error {
 		return nil
 	}
 
-	return util.TrackCluster(util.TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			API:           params.API,
-			PollFrequency: params.PollFrequency,
-			MaxRetries:    params.MaxRetries,
-			ID:            params.ID,
-			Kind:          "kibana",
-		},
-	})
+	return planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ID, util.Kibana,
+	))
 }
diff --git a/pkg/deployment/kibana/upgrade.go b/pkg/deployment/kibana/upgrade.go
index db52ce5..79cafee 100644
--- a/pkg/deployment/kibana/upgrade.go
+++ b/pkg/deployment/kibana/upgrade.go
@@ -21,7 +21,7 @@ import (
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_kibana"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 
 	"github.com/elastic/ecctl/pkg/util"
 )
@@ -45,14 +45,7 @@ func Upgrade(params DeploymentParams) (*models.ClusterUpgradeInfo, error) {
 		return res.Payload, nil
 	}
 
-	return res.Payload, util.TrackCluster(util.TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			API:           params.API,
-			PollFrequency: params.PollFrequency,
-			MaxRetries:    params.MaxRetries,
-			ID:            params.ID,
-			Kind:          "kibana",
-		},
-	})
+	return res.Payload, planutil.TrackChange(util.SetClusterTracking(
+		params.TrackChangeParams, params.ID, util.Kibana,
+	))
 }
diff --git a/pkg/platform/allocator/vacate.go b/pkg/platform/allocator/vacate.go
index 8cd1f74..55e9245 100644
--- a/pkg/platform/allocator/vacate.go
+++ b/pkg/platform/allocator/vacate.go
@@ -25,6 +25,7 @@ import (
 	"github.com/elastic/cloud-sdk-go/pkg/client/platform_infrastructure"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
 	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	"github.com/elastic/cloud-sdk-go/pkg/sync/pool"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	"github.com/elastic/cloud-sdk-go/pkg/util/slice"
@@ -164,7 +165,7 @@ func waitVacateCompletion(p *pool.Pool, hasWork bool) error {
 	for _, lover := range leftovers {
 		if params, ok := lover.(*VacateClusterParams); ok {
 			errs = multierror.Append(errs, fmt.Errorf(
-				"allocator %s: cluster [%s][%s]: %s",
+				"allocator %s: resource id [%s][%s]: %s",
 				params.ID, params.ClusterID, params.Kind,
 				"was either cancelled or not processed, follow up accordingly",
 			))
@@ -191,7 +192,7 @@ func addAllocatorMovesToPool(params addAllocatorMovesToPoolParams) ([]pool.Valid
 			continue
 		}
 
-		var kind = "elasticsearch"
+		var kind = util.Elasticsearch
 		if kindFilter != "" && kind != kindFilter {
 			break
 		}
@@ -203,7 +204,7 @@ func addAllocatorMovesToPool(params addAllocatorMovesToPoolParams) ([]pool.Valid
 			continue
 		}
 
-		var kind = "kibana"
+		var kind = util.Kibana
 		if kindFilter != "" && kind != kindFilter {
 			break
 		}
@@ -215,7 +216,7 @@ func addAllocatorMovesToPool(params addAllocatorMovesToPoolParams) ([]pool.Valid
 			continue
 		}
 
-		var kind = "apm"
+		var kind = util.Apm
 		if kindFilter != "" && kind != kindFilter {
 			break
 		}
@@ -227,7 +228,7 @@ func addAllocatorMovesToPool(params addAllocatorMovesToPoolParams) ([]pool.Valid
 			continue
 		}
 
-		var kind = "appsearch"
+		var kind = util.Appsearch
 		if kindFilter != "" && kind != kindFilter {
 			break
 		}
@@ -253,6 +254,7 @@ func newVacateClusterParams(params addAllocatorMovesToPoolParams, id, kind strin
 		MaxPollRetries:      params.VacateParams.MaxPollRetries,
 		TrackFrequency:      params.VacateParams.TrackFrequency,
 		Output:              params.VacateParams.Output,
+		OutputFormat:        params.VacateParams.OutputFormat,
 		MoveOnly:            params.VacateParams.MoveOnly,
 		PlanOverrides:       params.VacateParams.PlanOverrides,
 	}
@@ -287,7 +289,20 @@ func VacateCluster(params *VacateClusterParams) error {
 		return nil
 	}
 
-	return trackMovedCluster(params)
+	return planutil.TrackChange(planutil.TrackChangeParams{
+		TrackChangeParams: plan.TrackChangeParams{
+			API:              params.API,
+			ResourceID:       params.ClusterID,
+			Kind:             params.Kind,
+			IgnoreDownstream: true,
+			Config: plan.TrackFrequencyConfig{
+				PollFrequency: params.TrackFrequency,
+				MaxRetries:    int(params.MaxPollRetries),
+			},
+		},
+		Writer: params.Output,
+		Format: params.OutputFormat,
+	})
 }
 
 // fillVacateClusterParams validates the parameters and fills any missing
@@ -307,7 +322,9 @@ func fillVacateClusterParams(params *VacateClusterParams) (*VacateClusterParams,
 		if err != nil {
 			return nil, errors.Wrap(err, vacateWrapMessage(params, "allocator health autodiscovery"))
 		}
-		params.AllocatorDown = ec.Bool(!*alloc.Status.Connected || !*alloc.Status.Healthy)
+		if alloc.Status != nil {
+			params.AllocatorDown = ec.Bool(!*alloc.Status.Connected || !*alloc.Status.Healthy)
+		}
 	}
 
 	if params.MaxPollRetries == 0 {
@@ -326,7 +343,7 @@ func vacateWrapMessage(params *VacateClusterParams, ctx string) string {
 	if params == nil {
 		return ""
 	}
-	return fmt.Sprintf("allocator %s: cluster [%s][%s]: %s",
+	return fmt.Sprintf("allocator %s: resource id [%s][%s]: %s",
 		params.ID, params.ClusterID, params.Kind, ctx,
 	)
 }
@@ -357,19 +374,19 @@ func newMoveClusterParams(params *VacateClusterParams) (*platform_infrastructure
 		WithBody(req)
 
 	if len(req.ElasticsearchClusters) > 0 {
-		moveParams.SetClusterType(ec.String("elasticsearch"))
+		moveParams.SetClusterType(ec.String(util.Elasticsearch))
 	}
 
 	if len(req.KibanaClusters) > 0 {
-		moveParams.SetClusterType(ec.String("kibana"))
+		moveParams.SetClusterType(ec.String(util.Kibana))
 	}
 
 	if len(req.ApmClusters) > 0 {
-		moveParams.SetClusterType(ec.String("apm"))
+		moveParams.SetClusterType(ec.String(util.Apm))
 	}
 
 	if len(req.AppsearchClusters) > 0 {
-		moveParams.SetClusterType(ec.String("appsearch"))
+		moveParams.SetClusterType(ec.String(util.Appsearch))
 	}
 
 	return moveParams, nil
@@ -388,34 +405,12 @@ func moveClusterByType(params *VacateClusterParams) error {
 	)
 
 	if err != nil {
-		return errors.Wrap(api.UnwrapError(err), vacateWrapMessage(params, "cluster move"))
+		return errors.Wrap(api.UnwrapError(err), vacateWrapMessage(params, "resource move"))
 	}
 
 	return CheckVacateFailures(res.Payload.Failures, params.ClusterFilter)
 }
 
-func trackMovedCluster(params *VacateClusterParams) error {
-	channel, err := plan.Track(plan.TrackParams{
-		API:           params.API,
-		ID:            params.ClusterID,
-		Kind:          params.Kind,
-		PollFrequency: params.TrackFrequency,
-		MaxRetries:    params.MaxPollRetries,
-	})
-	if err != nil {
-		// Remove this after AppSearch is supported in the plan tracker
-		// https://github.com/elastic/cloud-sdk-go/issues/34
-		if params.Kind == "appsearch" {
-			wrapMessage := "instance is being moved but tracking is not supported, please check the vacate progress from the UI"
-			return errors.New(vacateWrapMessage(params, wrapMessage))
-		}
-		wrapMessage := "plan tracker returned an error but vacate is in progress"
-		return errors.Wrap(err, vacateWrapMessage(params, wrapMessage))
-	}
-
-	return plan.Stream(channel, params.Output)
-}
-
 // CheckVacateFailures iterates over the list of failures returning
 // a multierror with any of the failures found.
 //
@@ -438,7 +433,7 @@ func CheckVacateFailures(failures *models.MoveClustersDetails, filter []string)
 		}
 		if !strings.Contains(ferr.Error(), PlanPendingMessage) {
 			merr = multierror.Append(merr,
-				fmt.Errorf("cluster [%s][elasticsearch] failed vacating, reason: %s", *failure.ClusterID, ferr),
+				fmt.Errorf("resource id [%s][elasticsearch] failed vacating, reason: %s", *failure.ClusterID, ferr),
 			)
 		}
 	}
@@ -456,7 +451,7 @@ func CheckVacateFailures(failures *models.MoveClustersDetails, filter []string)
 
 		if !strings.Contains(ferr.Error(), PlanPendingMessage) {
 			merr = multierror.Append(merr,
-				fmt.Errorf("cluster [%s][kibana] failed vacating, reason: %s", *failure.ClusterID, ferr),
+				fmt.Errorf("resource id [%s][kibana] failed vacating, reason: %s", *failure.ClusterID, ferr),
 			)
 		}
 	}
@@ -474,7 +469,7 @@ func CheckVacateFailures(failures *models.MoveClustersDetails, filter []string)
 
 		if !strings.Contains(ferr.Error(), PlanPendingMessage) {
 			merr = multierror.Append(merr,
-				fmt.Errorf("cluster [%s][apm] failed vacating, reason: %s", *failure.ClusterID, ferr),
+				fmt.Errorf("resource id [%s][apm] failed vacating, reason: %s", *failure.ClusterID, ferr),
 			)
 		}
 	}
@@ -492,7 +487,7 @@ func CheckVacateFailures(failures *models.MoveClustersDetails, filter []string)
 
 		if !strings.Contains(ferr.Error(), PlanPendingMessage) {
 			merr = multierror.Append(merr,
-				fmt.Errorf("cluster [%s][appsearch] failed vacating, reason: %s", *failure.ClusterID, ferr),
+				fmt.Errorf("resource id [%s][appsearch] failed vacating, reason: %s", *failure.ClusterID, ferr),
 			)
 		}
 	}
diff --git a/pkg/platform/allocator/vacate_params.go b/pkg/platform/allocator/vacate_params.go
index 113107d..b2bbbf7 100644
--- a/pkg/platform/allocator/vacate_params.go
+++ b/pkg/platform/allocator/vacate_params.go
@@ -40,7 +40,7 @@ var (
 	errOutputDeviceCannotBeNil      = errors.New("output device cannot be nil")
 	errCannotOverrideAllocatorDown  = errors.New("cannot set the AllocatorDown when multiple allocators are specified")
 
-	allowedClusterKinds = []string{"apm", "elasticsearch", "kibana", "appsearch"}
+	allowedClusterKinds = []string{util.Apm, util.Elasticsearch, util.Kibana, util.Appsearch}
 )
 
 // VacateParams used to vacate N allocators or clusters.
@@ -68,6 +68,9 @@ type VacateParams struct {
 	// Output device where the progress will be sent.
 	Output *output.Device
 
+	// OutputFormat to use
+	OutputFormat string
+
 	// Maximum number of errors to allow the plan status poller to tolerate.
 	MaxPollRetries uint8
 
@@ -151,6 +154,7 @@ type VacateClusterParams struct {
 	AllocatorDown  *bool
 	MoveOnly       *bool
 	Output         *output.Device
+	OutputFormat   string
 	MaxPollRetries uint8
 	SkipTracking   bool
 }
diff --git a/pkg/util/kinds.go b/pkg/util/kinds.go
new file mode 100644
index 0000000..77f0a55
--- /dev/null
+++ b/pkg/util/kinds.go
@@ -0,0 +1,32 @@
+// Licensed to Elasticsearch B.V. under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Elasticsearch B.V. licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package util
+
+const (
+	// Apm kind
+	Apm = "apm"
+
+	// Appsearch kind
+	Appsearch = "appsearch"
+
+	// Elasticsearch kind
+	Elasticsearch = "elasticsearch"
+
+	// Kibana kind
+	Kibana = "kibana"
+)
diff --git a/pkg/util/response_parse.go b/pkg/util/response_parse.go
index ddeba84..b2a6d32 100644
--- a/pkg/util/response_parse.go
+++ b/pkg/util/response_parse.go
@@ -23,17 +23,17 @@ import (
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	multierror "github.com/hashicorp/go-multierror"
 )
 
 // ParseCUResponseParams is used by ParseCUResponse.
 type ParseCUResponseParams struct {
-	*api.API
 	CreateResponse interface{}
 	UpdateResponse interface{}
 	Err            error
-	TrackParams
+	Track          bool
+	planutil.TrackChangeParams
 }
 
 // Validate ensures that the parameters are usable by the consuming function.
@@ -43,17 +43,13 @@ func (params ParseCUResponseParams) Validate() error {
 	}
 
 	var merr = new(multierror.Error)
-	if params.API == nil {
-		merr = multierror.Append(merr, errors.New("parse response: API cannot be empty"))
-	}
-
 	if params.CreateResponse == nil && params.UpdateResponse == nil {
 		merr = multierror.Append(merr,
 			errors.New("parse response: One of Create or Update response must be populated"),
 		)
 	}
 
-	return multierror.Append(merr, params.TrackParams.Validate()).ErrorOrNil()
+	return merr.ErrorOrNil()
 }
 
 // ParseCUResponse parses the create / update response
@@ -81,20 +77,17 @@ func ParseCUResponse(params ParseCUResponseParams) (*models.ClusterCrudResponse,
 		return response, nil
 	}
 
-	var id, kind = response.ElasticsearchClusterID, "elasticsearch"
+	var id, kind = response.ElasticsearchClusterID, Elasticsearch
 	if id == "" {
 		id = response.KibanaClusterID
-		kind = "kibana"
+		kind = Kibana
+	}
+
+	if !params.Track {
+		return response, nil
 	}
 
-	return response, TrackCluster(TrackClusterParams{
-		Output: params.Output,
-		TrackParams: plan.TrackParams{
-			API:           params.API,
-			PollFrequency: params.PollFrequency,
-			MaxRetries:    params.MaxRetries,
-			ID:            id,
-			Kind:          kind,
-		},
-	})
+	return response, planutil.TrackChange(SetClusterTracking(
+		params.TrackChangeParams, id, kind,
+	))
 }
diff --git a/pkg/util/track.go b/pkg/util/track.go
index 682bc64..ff8d9dc 100644
--- a/pkg/util/track.go
+++ b/pkg/util/track.go
@@ -18,11 +18,9 @@
 package util
 
 import (
-	"errors"
 	"time"
 
-	"github.com/elastic/cloud-sdk-go/pkg/output"
-	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 )
 
 const (
@@ -40,55 +38,11 @@ const (
 	DefaultPollFrequency = time.Second * 10
 )
 
-// TrackParams are intended to be used as a field in other params structs
-// that aim to perform plan tracking.
-type TrackParams struct {
-	Output        *output.Device
-	PollFrequency time.Duration
-	Track         bool
-	MaxRetries    uint8
-}
-
-// TrackClusterParams is consumed by TrackCluster
-type TrackClusterParams struct {
-	plan.TrackParams
-	Output *output.Device
-}
-
-// Validate ensures that the parameters are usable by the consuming function.
-func (params TrackClusterParams) Validate() error {
-	if params.Output == nil {
-		return errors.New("track: Output cannot be empty")
-	}
-	return params.TrackParams.Validate()
-}
-
-// Validate ensures that the parameters are usable by the consuming function.
-func (params TrackParams) Validate() error {
-	if params.Track && params.Output == nil {
-		return errors.New("track params: output device cannot be empty")
-	}
-	return nil
-}
-
-// TrackCluster tracks a cluster by its ID and kind to a specified output.
-func TrackCluster(params TrackClusterParams) error {
-	if params.MaxRetries == 0 {
-		params.MaxRetries = DefaultRetries
-	}
-
-	if params.PollFrequency.Nanoseconds() == 0 {
-		params.PollFrequency = DefaultPollFrequency
-	}
-
-	if err := params.Validate(); err != nil {
-		return err
-	}
-
-	c, err := plan.Track(params.TrackParams)
-	if err != nil {
-		return err
-	}
-
-	return plan.Stream(c, params.Output)
+// SetClusterTracking modifies the TrackChangeParams to track a specific id and
+// kind ignoring downstream changes.
+func SetClusterTracking(params planutil.TrackChangeParams, id, kind string) planutil.TrackChangeParams {
+	params.TrackChangeParams.ResourceID = id
+	params.TrackChangeParams.Kind = kind
+	params.TrackChangeParams.IgnoreDownstream = true
+	return params
 }
diff --git a/pkg/util/type.go b/pkg/util/type.go
index 25abbc9..cfe28a0 100644
--- a/pkg/util/type.go
+++ b/pkg/util/type.go
@@ -27,10 +27,10 @@ import (
 var (
 	// ValidTypes exposes a list of the valid Elastic Cloud workload Types.
 	ValidTypes = []string{
-		"elasticsearch",
-		"kibana",
-		"apm",
-		"appsearch",
+		Elasticsearch,
+		Kibana,
+		Apm,
+		Appsearch,
 	}
 )
 


Test patch:
diff --git a/cmd/util/track_test.go b/cmd/util/track_test.go
index 757f265..a194980 100644
--- a/cmd/util/track_test.go
+++ b/cmd/util/track_test.go
@@ -26,10 +26,10 @@ import (
 
 	"github.com/elastic/cloud-sdk-go/pkg/models"
 	"github.com/elastic/cloud-sdk-go/pkg/output"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	"github.com/hashicorp/go-multierror"
 
-	"github.com/elastic/ecctl/pkg/deployment/depresource"
 	"github.com/elastic/ecctl/pkg/formatter"
 )
 
@@ -93,15 +93,16 @@ func TestTrack(t *testing.T) {
 			}, buf: secondOutputBuf},
 			wantOut: wantOutTokenValue,
 			err: &multierror.Error{Errors: []error{
-				errors.New("api reference is required for command"),
-				errors.New("resource track: output device cannot be nil"),
+				errors.New("plan track change: API cannot be nil"),
+				errors.New("plan track change: one of DeploymentID or ResourceID must be specified"),
+				errors.New("plan track change: Kind cannot be empty"),
 			}},
 		},
 		{
 			name: "prints the first error when track is set to true and also returns the formatter errors",
 			args: args{params: TrackParams{
-				TrackResourcesParams: depresource.TrackResourcesParams{
-					OutputDevice: output.NewDevice(thirdOutputBuf),
+				TrackChangeParams: planutil.TrackChangeParams{
+					Writer: output.NewDevice(thirdOutputBuf),
 				},
 				Track: true,
 				Formatter: formatter.NewText(&formatter.TextConfig{
@@ -113,7 +114,9 @@ func TestTrack(t *testing.T) {
 			}, buf: thirdOutputBuf},
 			wantOut: `template: no template "default" associated with template "text"` + "\n",
 			err: &multierror.Error{Errors: []error{
-				errors.New("api reference is required for command"),
+				errors.New("plan track change: API cannot be nil"),
+				errors.New("plan track change: one of DeploymentID or ResourceID must be specified"),
+				errors.New("plan track change: Kind cannot be empty"),
 			}},
 		},
 	}
diff --git a/pkg/deployment/apm/delete_test.go b/pkg/deployment/apm/delete_test.go
index b1d0157..c2abc79 100644
--- a/pkg/deployment/apm/delete_test.go
+++ b/pkg/deployment/apm/delete_test.go
@@ -18,17 +18,14 @@
 package apm
 
 import (
-	"bytes"
 	"errors"
 	"net/http"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 
@@ -123,17 +120,13 @@ func TestShutdown(t *testing.T) {
 		{
 			name: "Succeeds with tracking",
 			args: args{params: ShutdownParams{
-				TrackParams: util.TrackParams{
-					Track:         true,
-					Output:        output.NewDevice(new(bytes.Buffer)),
-					PollFrequency: time.Millisecond,
-					MaxRetries:    1,
-				},
-				ID: "d324608c97154bdba2dff97511d40368",
-				API: api.NewMock(util.AppendTrackResponses(mock.Response{Response: http.Response{
+				Track:             true,
+				TrackChangeParams: util.NewMockTrackChangeParams(""),
+				ID:                "d324608c97154bdba2dff97511d40368",
+				API: api.NewMock(mock.Response{Response: http.Response{
 					Body:       mock.NewStructBody(models.ClusterCommandResponse{}),
 					StatusCode: 202,
-				}})...),
+				}}),
 			}},
 		},
 	}
diff --git a/pkg/deployment/apm/plan_test.go b/pkg/deployment/apm/plan_test.go
index bbe2ea9..423e6e1 100644
--- a/pkg/deployment/apm/plan_test.go
+++ b/pkg/deployment/apm/plan_test.go
@@ -18,22 +18,19 @@
 package apm
 
 import (
-	"bytes"
 	"errors"
 	"net/http"
 	"net/url"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 
-	"github.com/elastic/ecctl/pkg/deployment/planutil"
+	depplanutil "github.com/elastic/ecctl/pkg/deployment/planutil"
 	"github.com/elastic/ecctl/pkg/util"
 )
 
@@ -131,19 +128,14 @@ func TestGetPlan(t *testing.T) {
 		{
 			name: "Succeeds getting plan with tracking",
 			args: args{params: PlanParams{
-				TrackParams: util.TrackParams{
-					Track:         true,
-					Output:        output.NewDevice(new(bytes.Buffer)),
-					PollFrequency: time.Millisecond,
-					MaxRetries:    1,
-				},
-				ID: "86d2ec6217774eedb93ba38483141997",
-				API: api.NewMock(util.AppendTrackResponses(mock.Response{Response: http.Response{
+				TrackChangeParams: util.NewMockTrackChangeParams("86d2ec6217774eedb93ba38483141997"),
+				ID:                "86d2ec6217774eedb93ba38483141997",
+				API: api.NewMock(mock.Response{Response: http.Response{
 					Body: mock.NewStructBody(models.ApmPlansInfo{
 						Healthy: ec.Bool(true),
 					}),
 					StatusCode: 200,
-				}})...),
+				}}),
 			}},
 			want: &models.ApmPlansInfo{
 				Healthy: ec.Bool(true),
@@ -231,13 +223,8 @@ func TestCancelPlan(t *testing.T) {
 		{
 			name: "Succeeds with tracking",
 			args: args{params: PlanParams{
-				TrackParams: util.TrackParams{
-					Track:         true,
-					Output:        output.NewDevice(new(bytes.Buffer)),
-					PollFrequency: time.Millisecond,
-					MaxRetries:    1,
-				},
-				ID: "d324608c97154bdba2dff97511d40368",
+				TrackChangeParams: util.NewMockTrackChangeParams("d324608c97154bdba2dff97511d40368"),
+				ID:                "d324608c97154bdba2dff97511d40368",
 				API: api.NewMock(util.AppendTrackResponses(mock.Response{Response: http.Response{
 					Body: mock.NewStructBody(models.ApmInfo{
 						Status: ec.String("success"),
@@ -324,7 +311,7 @@ func TestListPlanHistory(t *testing.T) {
 func TestReapplyLatestPlanAttempt(t *testing.T) {
 	type args struct {
 		params   PlanParams
-		reparams planutil.ReapplyParams
+		reparams depplanutil.ReapplyParams
 	}
 	tests := []struct {
 		name    string
@@ -351,7 +338,7 @@ func TestReapplyLatestPlanAttempt(t *testing.T) {
 					}},
 				),
 			},
-				reparams: planutil.ReapplyParams{
+				reparams: depplanutil.ReapplyParams{
 					ID:                   "181a0cc28c9143b5a0bda51cd65676b3",
 					Default:              true,
 					Rolling:              false,
@@ -469,7 +456,7 @@ func TestComputeTransientSettings(t *testing.T) {
 			args: args{
 				params: computeTransientParams{
 					plan: models.ApmPlan{},
-					transient: planutil.ReapplyParams{
+					transient: depplanutil.ReapplyParams{
 						ExtendedMaintenance: true,
 					},
 				},
@@ -508,7 +495,7 @@ func TestComputeTransientSettings(t *testing.T) {
 					plan: models.ApmPlan{
 						Transient: &models.TransientApmPlanConfiguration{},
 					},
-					transient: planutil.ReapplyParams{
+					transient: depplanutil.ReapplyParams{
 						ExtendedMaintenance: true,
 					},
 				},
@@ -533,7 +520,7 @@ func TestComputeTransientSettings(t *testing.T) {
 							},
 						},
 					},
-					transient: planutil.ReapplyParams{
+					transient: depplanutil.ReapplyParams{
 						ExtendedMaintenance: true,
 					},
 				},
diff --git a/pkg/deployment/apm/restart_test.go b/pkg/deployment/apm/restart_test.go
index 9109841..1ffaaea 100644
--- a/pkg/deployment/apm/restart_test.go
+++ b/pkg/deployment/apm/restart_test.go
@@ -18,17 +18,14 @@
 package apm
 
 import (
-	"bytes"
 	"errors"
 	"net/http"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/util"
@@ -64,13 +61,8 @@ func TestRestart(t *testing.T) {
 		{
 			name: "Succeeds with tracking",
 			args: args{params: RestartParams{
-				TrackParams: util.TrackParams{
-					Track:         true,
-					Output:        output.NewDevice(new(bytes.Buffer)),
-					PollFrequency: time.Millisecond,
-					MaxRetries:    1,
-				},
-				ID: "d324608c97154bdba2dff97511d40368",
+				TrackChangeParams: util.NewMockTrackChangeParams("d324608c97154bdba2dff97511d40368"),
+				ID:                "d324608c97154bdba2dff97511d40368",
 				API: api.NewMock(util.AppendTrackResponses(mock.Response{Response: http.Response{
 					Body:       mock.NewStructBody(models.ClusterCommandResponse{}),
 					StatusCode: 202,
diff --git a/pkg/deployment/apm/upgrade_test.go b/pkg/deployment/apm/upgrade_test.go
index fb84005..4808e52 100644
--- a/pkg/deployment/apm/upgrade_test.go
+++ b/pkg/deployment/apm/upgrade_test.go
@@ -18,17 +18,14 @@
 package apm
 
 import (
-	"bytes"
 	"errors"
 	"net/http"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 
@@ -73,13 +70,8 @@ func TestUpgrade(t *testing.T) {
 		{
 			name: "Succeeds with tracking",
 			args: args{params: UpgradeParams{
-				TrackParams: util.TrackParams{
-					Track:         true,
-					Output:        output.NewDevice(new(bytes.Buffer)),
-					PollFrequency: time.Millisecond,
-					MaxRetries:    1,
-				},
-				ID: "d324608c97154bdba2dff97511d40368",
+				TrackChangeParams: util.NewMockTrackChangeParams("d324608c97154bdba2dff97511d40368"),
+				ID:                "d324608c97154bdba2dff97511d40368",
 				API: api.NewMock(util.AppendTrackResponses(mock.Response{Response: http.Response{
 					Body: mock.NewStructBody(models.ClusterUpgradeInfo{
 						ClusterID:      ec.String("d324608c97154bdba2dff97511d40368"),
diff --git a/pkg/deployment/depresource/delete_stateless_test.go b/pkg/deployment/depresource/delete_stateless_test.go
index 1709aa3..3553c33 100644
--- a/pkg/deployment/depresource/delete_stateless_test.go
+++ b/pkg/deployment/depresource/delete_stateless_test.go
@@ -104,8 +104,8 @@ func TestDeleteStateless(t *testing.T) {
 				ResourceParams: deployment.ResourceParams{
 					API:          api.NewMock(mock.New404Response(mock.NewStructBody(internalError))),
 					DeploymentID: util.ValidClusterID,
-					RefID:        "apm",
-					Kind:         "apm",
+					RefID:        util.Apm,
+					Kind:         util.Apm,
 				},
 			}},
 			err: errors.New(string(internalErrorBytes)),
@@ -116,8 +116,8 @@ func TestDeleteStateless(t *testing.T) {
 				ResourceParams: deployment.ResourceParams{
 					API:          api.NewMock(mock.New200Response(mock.NewStringBody(""))),
 					DeploymentID: util.ValidClusterID,
-					RefID:        "apm",
-					Kind:         "apm",
+					RefID:        util.Apm,
+					Kind:         util.Apm,
 				},
 			}},
 		},
@@ -132,14 +132,14 @@ func TestDeleteStateless(t *testing.T) {
 							Resources: &models.DeploymentResources{
 								Apm: []*models.ApmResourceInfo{{
 									ID:    ec.String(util.ValidClusterID),
-									RefID: ec.String("apm"),
+									RefID: ec.String(util.Apm),
 								}},
 							},
 						})),
 						mock.New200Response(mock.NewStringBody("")),
 					),
 					DeploymentID: util.ValidClusterID,
-					Kind:         "apm",
+					Kind:         util.Apm,
 				},
 			}},
 		},
diff --git a/pkg/deployment/depresource/restore_test.go b/pkg/deployment/depresource/restore_test.go
index db75f36..de16ced 100644
--- a/pkg/deployment/depresource/restore_test.go
+++ b/pkg/deployment/depresource/restore_test.go
@@ -77,8 +77,8 @@ func TestRestore(t *testing.T) {
 				ResourceParams: deployment.ResourceParams{
 					API:          api.NewMock(mock.New404Response(mock.NewStructBody(internalError))),
 					DeploymentID: util.ValidClusterID,
-					RefID:        "apm",
-					Kind:         "apm",
+					RefID:        util.Apm,
+					Kind:         util.Apm,
 				},
 			}},
 			err: errors.New(string(internalErrorBytes)),
@@ -112,8 +112,8 @@ func TestRestore(t *testing.T) {
 				ResourceParams: deployment.ResourceParams{
 					API:          api.NewMock(mock.New200Response(mock.NewStringBody(""))),
 					DeploymentID: util.ValidClusterID,
-					RefID:        "apm",
-					Kind:         "apm",
+					RefID:        util.Apm,
+					Kind:         util.Apm,
 				},
 			}},
 		},
diff --git a/pkg/deployment/depresource/shutdown_test.go b/pkg/deployment/depresource/shutdown_test.go
index 172126a..a465468 100644
--- a/pkg/deployment/depresource/shutdown_test.go
+++ b/pkg/deployment/depresource/shutdown_test.go
@@ -77,8 +77,8 @@ func TestShutdown(t *testing.T) {
 				ResourceParams: deployment.ResourceParams{
 					API:          api.NewMock(mock.New404Response(mock.NewStructBody(error404))),
 					DeploymentID: util.ValidClusterID,
-					RefID:        "apm",
-					Kind:         "apm",
+					RefID:        util.Apm,
+					Kind:         util.Apm,
 				},
 			}},
 			err: errors.New(string(error404Byte)),
@@ -147,8 +147,8 @@ func TestShutdown(t *testing.T) {
 				ResourceParams: deployment.ResourceParams{
 					API:          api.NewMock(mock.New200Response(mock.NewStructBody(struct{}{}))),
 					DeploymentID: util.ValidClusterID,
-					RefID:        "apm",
-					Kind:         "apm",
+					RefID:        util.Apm,
+					Kind:         util.Apm,
 				},
 			}},
 		},
diff --git a/pkg/deployment/depresource/track_test.go b/pkg/deployment/depresource/track_test.go
deleted file mode 100644
index 8bfdbc5..0000000
--- a/pkg/deployment/depresource/track_test.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// Licensed to Elasticsearch B.V. under one or more contributor
-// license agreements. See the NOTICE file distributed with
-// this work for additional information regarding copyright
-// ownership. Elasticsearch B.V. licenses this file to you under
-// the Apache License, Version 2.0 (the "License"); you may
-// not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package depresource
-
-import (
-	"errors"
-	"fmt"
-	"io/ioutil"
-	"reflect"
-	"testing"
-
-	"github.com/elastic/cloud-sdk-go/pkg/api"
-	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
-	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
-	"github.com/hashicorp/go-multierror"
-)
-
-// Note: This test is not fully testing the track behaviour since that covered
-// in another package already. This focuses on covering the error paths.
-func TestTrackResources(t *testing.T) {
-	type args struct {
-		params TrackResourcesParams
-	}
-	tests := []struct {
-		name string
-		args args
-		err  error
-	}{
-		{
-			name: "fails on parameter validation",
-			err: &multierror.Error{Errors: []error{
-				errors.New("api reference is required for command"),
-				errors.New("resource track: output device cannot be nil"),
-			}},
-		},
-		{
-			name: "returns without an error if the resources are nil",
-			args: args{params: TrackResourcesParams{
-				API:          api.NewMock(),
-				OutputDevice: output.NewDevice(ioutil.Discard),
-			}},
-		},
-		{
-			name: "returns with fail to track when the resource is of kind appsearch",
-			args: args{params: TrackResourcesParams{
-				API:          api.NewMock(),
-				OutputDevice: output.NewDevice(ioutil.Discard),
-				Resources: []*models.DeploymentResource{
-					{ID: ec.String("an id"), Kind: ec.String("appsearch")},
-				},
-			}},
-			err: &multierror.Error{Errors: []error{
-				fmt.Errorf("cannot track appsearch resource id \"%s\"", "an id"),
-			}},
-		},
-		{
-			name: "returns an error due to parameter validation",
-			args: args{params: TrackResourcesParams{
-				API:          api.NewMock(),
-				OutputDevice: output.NewDevice(ioutil.Discard),
-				Resources: []*models.DeploymentResource{
-					{ID: ec.String("invalid id"), Kind: ec.String("elasticsearch")},
-				},
-			}},
-			err: &multierror.Error{Errors: []error{
-				fmt.Errorf("plan Track: invalid ID"),
-			}},
-		},
-		{
-			name: "returns an error due to parameter validation (Orphaned Appsearch)",
-			args: args{params: TrackResourcesParams{
-				API:          api.NewMock(),
-				OutputDevice: output.NewDevice(ioutil.Discard),
-				Orphaned:     &models.Orphaned{Appsearch: []string{"invalid id"}},
-			}},
-			err: &multierror.Error{Errors: []error{
-				fmt.Errorf("cannot track appsearch resource id \"invalid id\""),
-			}},
-		},
-		{
-			name: "returns an error due to parameter validation (Orphaned and Resources)",
-			args: args{params: TrackResourcesParams{
-				API:          api.NewMock(),
-				OutputDevice: output.NewDevice(ioutil.Discard),
-				Resources: []*models.DeploymentResource{
-					{ID: ec.String("invalid id"), Kind: ec.String("elasticsearch")},
-					{ID: ec.String("invalid id"), Kind: ec.String("kibana")},
-					{ID: ec.String("invalid id"), Kind: ec.String("apm")},
-				},
-				Orphaned: &models.Orphaned{
-					Apm:    []string{"invalid id"},
-					Kibana: []string{"invalid id"},
-					Elasticsearch: []*models.OrphanedElasticsearch{
-						{ID: ec.String("invalid id")},
-					},
-				},
-			}},
-			err: &multierror.Error{Errors: []error{
-				fmt.Errorf("plan Track: invalid ID"),
-				fmt.Errorf("plan Track: invalid ID"),
-				fmt.Errorf("plan Track: invalid ID"),
-				fmt.Errorf("plan Track: invalid ID"),
-				fmt.Errorf("plan Track: invalid ID"),
-				fmt.Errorf("plan Track: invalid ID"),
-			}},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if err := TrackResources(tt.args.params); !reflect.DeepEqual(err, tt.err) {
-				t.Errorf("TrackResources() error = %v, wantErr %v", err, tt.err)
-			}
-		})
-	}
-}
diff --git a/pkg/deployment/elasticsearch/plan/reapply_test.go b/pkg/deployment/elasticsearch/plan/reapply_test.go
index 610ebf9..6eb5cd4 100644
--- a/pkg/deployment/elasticsearch/plan/reapply_test.go
+++ b/pkg/deployment/elasticsearch/plan/reapply_test.go
@@ -27,7 +27,6 @@ import (
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 
@@ -381,44 +380,9 @@ func TestReapply(t *testing.T) {
 			args: args{params: ReapplyParams{}},
 			err: &multierror.Error{Errors: []error{
 				util.ErrAPIReq,
-				errors.New("output cannot be nil when hide is false"),
 				errors.New("cluster id cannot be empty"),
 			}},
 		},
-		{
-			name: "Fails due to parameter validation (missing Writer)",
-			args: args{params: ReapplyParams{
-				API:           new(api.API),
-				ReapplyParams: planutil.ReapplyParams{ID: "8b43ed5e277f7ea6f13606fcf4027f9c"},
-			}},
-			err: &multierror.Error{Errors: []error{
-				errors.New("output cannot be nil when hide is false"),
-			}},
-		},
-		{
-			name: "Fails due to parameter validation (missing Cluster ID)",
-			args: args{params: ReapplyParams{
-				API: new(api.API),
-				TrackParams: util.TrackParams{
-					Output: output.NewDevice(new(bytes.Buffer)),
-				},
-			}},
-			err: &multierror.Error{Errors: []error{
-				errors.New("cluster id cannot be empty"),
-			}},
-		},
-		{
-			name: "Fails due to parameter validation (missing API)",
-			args: args{params: ReapplyParams{
-				TrackParams: util.TrackParams{
-					Output: output.NewDevice(new(bytes.Buffer)),
-				},
-				ReapplyParams: planutil.ReapplyParams{ID: "8b43ed5e277f7ea6f13606fcf4027f9c"},
-			}},
-			err: &multierror.Error{Errors: []error{
-				util.ErrAPIReq,
-			}},
-		},
 		{
 			name: "Fails to obtain latest plan attempt without Tracking",
 			args: args{params: ReapplyParams{
@@ -429,17 +393,11 @@ func TestReapply(t *testing.T) {
 						Status:     http.StatusText(http.StatusOK),
 						Body: mock.NewStructBody(models.ElasticsearchClusterPlansInfo{
 							History: []*models.ElasticsearchClusterPlanInfo{
-								{
-									PlanAttemptID: "someID",
-								},
+								{PlanAttemptID: "someID"},
 							},
 						}),
 					}},
 				),
-				TrackParams: util.TrackParams{
-					Output: output.NewDevice(new(bytes.Buffer)),
-					Track:  false,
-				},
 				ReapplyParams: planutil.ReapplyParams{
 					ID:      "b786acd298292c2d521c0e8741761b4d",
 					Default: true,
@@ -448,22 +406,20 @@ func TestReapply(t *testing.T) {
 			err: errors.New("unable to obtain latest plan attempt"),
 		},
 		{
-			name: "Succeeds without Tracking",
+			name: "Succeeds with Tracking",
 			args: args{params: ReapplyParams{
-				SkipSnapshot: true,
+				Output:            new(bytes.Buffer),
+				Track:             true,
+				TrackChangeParams: util.NewMockTrackChangeParams(""),
+				SkipSnapshot:      true,
 				API: api.NewMock(
 					mock.Response{Response: http.Response{
 						StatusCode: http.StatusOK,
 						Status:     http.StatusText(http.StatusOK),
 						Body: mock.NewStructBody(models.ElasticsearchClusterPlansInfo{
 							History: []*models.ElasticsearchClusterPlanInfo{
-								{
-									PlanAttemptID: "someID",
-								},
-								{
-									PlanAttemptID: "someID",
-									Plan:          &models.ElasticsearchClusterPlan{},
-								},
+								{PlanAttemptID: "someID"},
+								{PlanAttemptID: "someID", Plan: &models.ElasticsearchClusterPlan{}},
 							},
 						}),
 					}},
@@ -475,10 +431,6 @@ func TestReapply(t *testing.T) {
 						}),
 					}},
 				),
-				TrackParams: util.TrackParams{
-					Output: output.NewDevice(new(bytes.Buffer)),
-					Track:  false,
-				},
 				ReapplyParams: planutil.ReapplyParams{
 					ID:      "b786acd298292c2d521c0e8741761b4d",
 					Default: true,
diff --git a/pkg/deployment/elasticsearch/plan/update_test.go b/pkg/deployment/elasticsearch/plan/update_test.go
index d722ce2..2b54374 100644
--- a/pkg/deployment/elasticsearch/plan/update_test.go
+++ b/pkg/deployment/elasticsearch/plan/update_test.go
@@ -18,17 +18,14 @@
 package plan
 
 import (
-	"bytes"
 	"errors"
 	"net/http"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/util"
@@ -71,14 +68,10 @@ func TestUpdate(t *testing.T) {
 		{
 			name: "Successfully validates a plan, ignores requested tracking",
 			args: args{params: UpdateParams{
-				ID:           "d324608c97154bdba2dff97511d40368",
-				ValidateOnly: true,
-				TrackParams: util.TrackParams{
-					Track:         true,
-					Output:        output.NewDevice(new(bytes.Buffer)),
-					PollFrequency: time.Millisecond,
-					MaxRetries:    1,
-				},
+				ID:                "d324608c97154bdba2dff97511d40368",
+				ValidateOnly:      true,
+				Track:             true,
+				TrackChangeParams: util.NewMockTrackChangeParams(""),
 				API: api.NewMock(mock.Response{Response: http.Response{
 					Body: mock.NewStructBody(models.ClusterCrudResponse{
 						ElasticsearchClusterID: "d324608c97154bdba2dff97511d40368",
@@ -108,13 +101,9 @@ func TestUpdate(t *testing.T) {
 		{
 			name: "Successfully submits the new plan with tracking",
 			args: args{params: UpdateParams{
-				ID: "d324608c97154bdba2dff97511d40368",
-				TrackParams: util.TrackParams{
-					Track:         true,
-					Output:        output.NewDevice(new(bytes.Buffer)),
-					PollFrequency: time.Millisecond,
-					MaxRetries:    1,
-				},
+				ID:                "d324608c97154bdba2dff97511d40368",
+				Track:             true,
+				TrackChangeParams: util.NewMockTrackChangeParams(""),
 				API: api.NewMock(util.AppendTrackResponses(mock.Response{Response: http.Response{
 					Body: mock.NewStructBody(models.ClusterCrudResponse{
 						ElasticsearchClusterID: "d324608c97154bdba2dff97511d40368",
diff --git a/pkg/deployment/elasticsearch/reallocate_test.go b/pkg/deployment/elasticsearch/reallocate_test.go
index 82256c6..638a047 100644
--- a/pkg/deployment/elasticsearch/reallocate_test.go
+++ b/pkg/deployment/elasticsearch/reallocate_test.go
@@ -18,17 +18,13 @@
 package elasticsearch
 
 import (
-	"bytes"
 	"errors"
-	"net/http"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 
@@ -51,13 +47,11 @@ func TestReallocate(t *testing.T) {
 			}},
 			err: &multierror.Error{Errors: []error{
 				errors.New("cluster id should have a length of 32 characters"),
-				errors.New("output device cannot be nil"),
 			}},
 		},
 		{
 			name: "Fails obtaining the Elasticsearch instances",
 			args: args{params: ReallocateParams{
-				OutputDevice: output.NewDevice(new(bytes.Buffer)),
 				ClusterParams: util.ClusterParams{
 					ClusterID: util.ValidClusterID,
 					API:       api.NewMock(mock.New500Response(mock.NewStringBody(`{"error": "failed obtaining instances"}`))),
@@ -68,23 +62,19 @@ func TestReallocate(t *testing.T) {
 		{
 			name: "Fails performing the move operation",
 			args: args{params: ReallocateParams{
-				OutputDevice: output.NewDevice(new(bytes.Buffer)),
+				TrackChangeParams: util.NewMockTrackChangeParams(""),
 				ClusterParams: util.ClusterParams{
 					ClusterID: util.ValidClusterID,
-					API: api.NewMock(mock.Response{
-						Response: http.Response{
-							StatusCode: 200,
-							Body: mock.NewStructBody(models.ElasticsearchClusterInfo{
-								Topology: &models.ClusterTopologyInfo{
-									Instances: []*models.ClusterInstanceInfo{
-										{InstanceName: ec.String("instance-00000001")},
-									},
+					API: api.NewMock(
+						mock.New200StructResponse(models.ElasticsearchClusterInfo{
+							Topology: &models.ClusterTopologyInfo{
+								Instances: []*models.ClusterInstanceInfo{
+									{InstanceName: ec.String("instance-00000001")},
 								},
-							}),
-						},
-					}, mock.New500Response( // MOVE
-						mock.NewStringBody(`{"error": "failed moving instances"}`),
-					)),
+							},
+						}),
+						mock.New500Response(mock.NewStringBody(`{"error": "failed moving instances"}`)),
+					),
 				},
 			}},
 			err: errors.New(`{"error": "failed moving instances"}`),
@@ -92,52 +82,19 @@ func TestReallocate(t *testing.T) {
 		{
 			name: "Succeeds when no instances are specified",
 			args: args{params: ReallocateParams{
-				OutputDevice:  output.NewDevice(new(bytes.Buffer)),
-				MaxRetries:    1,
-				PollFrequency: time.Nanosecond,
+				TrackChangeParams: util.NewMockTrackChangeParams(""),
 				ClusterParams: util.ClusterParams{
 					ClusterID: util.ValidClusterID,
-					API: api.NewMock(mock.Response{
-						Response: http.Response{
-							StatusCode: 200,
-							Body: mock.NewStructBody(models.ElasticsearchClusterInfo{
-								Topology: &models.ClusterTopologyInfo{
-									Instances: []*models.ClusterInstanceInfo{
-										{InstanceName: ec.String("instance-00000001")},
-									},
+					API: api.NewMock(
+						mock.New200StructResponse(models.ElasticsearchClusterInfo{
+							Topology: &models.ClusterTopologyInfo{
+								Instances: []*models.ClusterInstanceInfo{
+									{InstanceName: ec.String("instance-00000001")},
 								},
-							}),
-						},
-					}, mock.Response{ // MOVE
-						Response: http.Response{
-							StatusCode: 202,
-							Body:       mock.NewStringBody(`{}`),
-						},
-					}, mock.Response{ // Tracking
-						Response: http.Response{
-							StatusCode: 404,
-							Body:       mock.NewStringBody(`{}`),
-						},
-					}, mock.Response{ // Tracking
-						Response: http.Response{
-							StatusCode: 404,
-							Body:       mock.NewStringBody(`{}`),
-						},
-					}, mock.Response{ // Tracking
-						Response: http.Response{
-							StatusCode: 200,
-							Body: mock.NewStructBody(models.ElasticsearchClusterPlansInfo{
-								Current: &models.ElasticsearchClusterPlanInfo{
-									PlanAttemptLog: []*models.ClusterPlanStepInfo{
-										{
-											StepID: ec.String("an id"),
-											Status: ec.String("success"),
-										},
-									},
-								},
-							}),
-						},
-					}),
+							},
+						}),
+						mock.New202Response(mock.NewStringBody("")),
+					),
 				},
 			}},
 		},
diff --git a/pkg/deployment/elasticsearch/restart_test.go b/pkg/deployment/elasticsearch/restart_test.go
index 871f242..89b3815 100644
--- a/pkg/deployment/elasticsearch/restart_test.go
+++ b/pkg/deployment/elasticsearch/restart_test.go
@@ -18,17 +18,13 @@
 package elasticsearch
 
 import (
-	"bytes"
 	"errors"
 	"net/http"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
-	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/util"
@@ -60,12 +56,8 @@ func TestRestartCluster(t *testing.T) {
 		{
 			name: "succeeds with tracking",
 			args: args{params: RestartClusterParams{
-				TrackParams: util.TrackParams{
-					Track:         true,
-					PollFrequency: time.Nanosecond,
-					MaxRetries:    1,
-					Output:        output.NewDevice(new(bytes.Buffer)),
-				},
+				Track:             true,
+				TrackChangeParams: util.NewMockTrackChangeParams(""),
 				ClusterParams: util.ClusterParams{
 					ClusterID: util.ValidClusterID,
 					API: api.NewMock(mock.Response{
@@ -73,20 +65,7 @@ func TestRestartCluster(t *testing.T) {
 							StatusCode: 202,
 							Body:       mock.NewStringBody(`{}`),
 						},
-					}, mock.Response{Response: http.Response{
-						StatusCode: 404,
-						Body:       mock.NewStringBody(`{}`),
-					}}, mock.Response{Response: http.Response{
-						StatusCode: 404,
-						Body:       mock.NewStringBody(`{}`),
-					}}, mock.Response{Response: http.Response{
-						StatusCode: 200,
-						Body: mock.NewStructBody(&models.ElasticsearchClusterPlansInfo{
-							Current: &models.ElasticsearchClusterPlanInfo{
-								PlanAttemptLog: []*models.ClusterPlanStepInfo{},
-							},
-						}),
-					}}),
+					}),
 				},
 			}},
 		},
@@ -116,19 +95,6 @@ func TestRestartCluster(t *testing.T) {
 				util.ErrAPIReq,
 			}},
 		},
-		{
-			name: "fails due to parameter validation (track params)",
-			args: args{params: RestartClusterParams{
-				ClusterParams: util.ClusterParams{
-					ClusterID: util.ValidClusterID,
-					API:       new(api.API),
-				},
-				TrackParams: util.TrackParams{Track: true},
-			}},
-			err: &multierror.Error{Errors: []error{
-				errors.New("track params: output device cannot be empty"),
-			}},
-		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/pkg/deployment/elasticsearch/shutdown_test.go b/pkg/deployment/elasticsearch/shutdown_test.go
index 038f362..7d296fe 100644
--- a/pkg/deployment/elasticsearch/shutdown_test.go
+++ b/pkg/deployment/elasticsearch/shutdown_test.go
@@ -18,16 +18,13 @@
 package elasticsearch
 
 import (
-	"bytes"
 	"errors"
 	"net/http"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/util"
@@ -59,12 +56,7 @@ func TestShutdownCluster(t *testing.T) {
 		{
 			name: "succeeds with tracking",
 			args: args{params: ShutdownClusterParams{
-				TrackParams: util.TrackParams{
-					Track:         true,
-					PollFrequency: time.Nanosecond,
-					MaxRetries:    1,
-					Output:        output.NewDevice(new(bytes.Buffer)),
-				},
+				TrackChangeParams: util.NewMockTrackChangeParams(""),
 				ClusterParams: util.ClusterParams{
 					ClusterID: util.ValidClusterID,
 					API: api.NewMock(util.AppendTrackResponses(mock.Response{
@@ -102,19 +94,6 @@ func TestShutdownCluster(t *testing.T) {
 				util.ErrAPIReq,
 			}},
 		},
-		{
-			name: "fails due to parameter validation (track params)",
-			args: args{params: ShutdownClusterParams{
-				ClusterParams: util.ClusterParams{
-					ClusterID: util.ValidClusterID,
-					API:       new(api.API),
-				},
-				TrackParams: util.TrackParams{Track: true},
-			}},
-			err: &multierror.Error{Errors: []error{
-				errors.New("track params: output device cannot be empty"),
-			}},
-		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/pkg/deployment/elasticsearch/upgrade_test.go b/pkg/deployment/elasticsearch/upgrade_test.go
index 5adc370..b5d74b6 100644
--- a/pkg/deployment/elasticsearch/upgrade_test.go
+++ b/pkg/deployment/elasticsearch/upgrade_test.go
@@ -18,17 +18,14 @@
 package elasticsearch
 
 import (
-	"bytes"
 	"errors"
 	"net/http"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 
@@ -121,34 +118,18 @@ func TestUpgrade(t *testing.T) {
 		{
 			name: "succeeds with tracking",
 			args: args{params: UpgradeParams{
-				TrackParams: util.TrackParams{
-					Track:         true,
-					Output:        output.NewDevice(new(bytes.Buffer)),
-					PollFrequency: time.Millisecond,
-					MaxRetries:    1,
-				},
-				Version: "5.0.0",
+				TrackChangeParams: util.NewMockTrackChangeParams(""),
+				Version:           "5.0.0",
 				ClusterParams: util.ClusterParams{
 					ClusterID: util.ValidClusterID,
-					API: api.NewMock(util.AppendTrackResponses(
-						mock.Response{
-							Response: http.Response{
-								StatusCode: 200,
-								Body: mock.NewStructBody(
-									newElasticSearchClusterInfoFromVersion("2.4.5"),
-								),
-							},
-						},
-						mock.Response{
-							Response: http.Response{
-								StatusCode: 202,
-								Body: mock.NewStructBody(models.ClusterCrudResponse{
-									ElasticsearchClusterID: util.ValidClusterID,
-								}),
-							},
-						},
-						util.PlanNotFound,
-					)...),
+					API: api.NewMock(
+						mock.New200StructResponse(
+							newElasticSearchClusterInfoFromVersion("2.4.5"),
+						),
+						mock.New202Response(mock.NewStructBody(models.ClusterCrudResponse{
+							ElasticsearchClusterID: util.ValidClusterID,
+						})),
+					),
 				},
 			}},
 			want: &models.ClusterCrudResponse{
diff --git a/pkg/deployment/get_resource_test.go b/pkg/deployment/get_resource_test.go
index 50971de..a245346 100644
--- a/pkg/deployment/get_resource_test.go
+++ b/pkg/deployment/get_resource_test.go
@@ -60,18 +60,18 @@ func TestGetResource(t *testing.T) {
 						models.ApmResourceInfo{
 							ElasticsearchClusterRefID: ec.String("elasticsearch"),
 							ID:                        ec.String("3531aaf988594efa87c1aabb7caed337"),
-							RefID:                     ec.String("apm"),
+							RefID:                     ec.String(util.Apm),
 						},
 					))),
 					DeploymentID: "3531aaf988594efa87c1aabb7caed337",
-					RefID:        "apm",
+					RefID:        util.Apm,
 				},
-				Kind: "apm",
+				Kind: util.Apm,
 			}},
 			want: &models.ApmResourceInfo{
 				ElasticsearchClusterRefID: ec.String("elasticsearch"),
 				ID:                        ec.String("3531aaf988594efa87c1aabb7caed337"),
-				RefID:                     ec.String("apm"),
+				RefID:                     ec.String(util.Apm),
 			},
 		},
 		{
@@ -86,24 +86,24 @@ func TestGetResource(t *testing.T) {
 								Apm: []*models.ApmResourceInfo{{
 									ElasticsearchClusterRefID: ec.String("elasticsearch"),
 									ID:                        ec.String("3531aaf988594efa87c1aabb7caed337"),
-									RefID:                     ec.String("apm"),
+									RefID:                     ec.String(util.Apm),
 								}},
 							},
 						})),
 						mock.New200Response(mock.NewStructBody(models.ApmResourceInfo{
 							ElasticsearchClusterRefID: ec.String("elasticsearch"),
 							ID:                        ec.String("3531aaf988594efa87c1aabb7caed337"),
-							RefID:                     ec.String("apm"),
+							RefID:                     ec.String(util.Apm),
 						})),
 					),
 					DeploymentID: "3531aaf988594efa87c1aabb7caed337",
 				},
-				Kind: "apm",
+				Kind: util.Apm,
 			}},
 			want: &models.ApmResourceInfo{
 				ElasticsearchClusterRefID: ec.String("elasticsearch"),
 				ID:                        ec.String("3531aaf988594efa87c1aabb7caed337"),
-				RefID:                     ec.String("apm"),
+				RefID:                     ec.String(util.Apm),
 			},
 		},
 		{
diff --git a/pkg/deployment/kibana/delete_test.go b/pkg/deployment/kibana/delete_test.go
index 4fd97ec..ec6f32b 100644
--- a/pkg/deployment/kibana/delete_test.go
+++ b/pkg/deployment/kibana/delete_test.go
@@ -18,18 +18,15 @@
 package kibana
 
 import (
-	"bytes"
 	"errors"
 	"net/http"
 	"net/url"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 
@@ -89,13 +86,9 @@ func TestDelete(t *testing.T) {
 		{
 			name: "succeeds deleting with tracking",
 			args: args{params: DeploymentParams{
-				ID: "2c221bd86b7f48959a59ee3128d5c5e8",
-				TrackParams: util.TrackParams{
-					Track:         true,
-					Output:        output.NewDevice(new(bytes.Buffer)),
-					PollFrequency: time.Millisecond,
-					MaxRetries:    1,
-				},
+				ID:                "2c221bd86b7f48959a59ee3128d5c5e8",
+				Track:             true,
+				TrackChangeParams: util.NewMockTrackChangeParams(""),
 				API: api.NewMock(
 					util.AppendTrackResponses(
 						mock.Response{Response: http.Response{
diff --git a/pkg/deployment/kibana/reallocate_test.go b/pkg/deployment/kibana/reallocate_test.go
index 4cc4a10..d868e79 100644
--- a/pkg/deployment/kibana/reallocate_test.go
+++ b/pkg/deployment/kibana/reallocate_test.go
@@ -18,17 +18,14 @@
 package kibana
 
 import (
-	"bytes"
 	"errors"
 	"net/http"
 	"net/url"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/util"
@@ -48,7 +45,6 @@ func TestReallocateParams_Validate(t *testing.T) {
 				Errors: []error{
 					errors.New(`api reference is required for command`),
 					errors.New(`id "" is invalid`),
-					errors.New(`track: Output cannot be empty`),
 				},
 			},
 			wantErr: true,
@@ -60,7 +56,6 @@ func TestReallocateParams_Validate(t *testing.T) {
 					ID:  "5c641576747442eba0ebd67944ccbe10",
 					API: &api.API{},
 				},
-				Output: new(output.Device),
 			},
 			wantErr: false,
 		},
@@ -143,7 +138,6 @@ func TestReallocate(t *testing.T) {
 				Errors: []error{
 					errors.New(`api reference is required for command`),
 					errors.New(`id "" is invalid`),
-					errors.New(`track: Output cannot be empty`),
 				},
 			},
 			wantErr: true,
@@ -157,7 +151,6 @@ func TestReallocate(t *testing.T) {
 						Error: errors.New("kibana not found"),
 					}),
 				},
-				Output: new(output.Device),
 			},
 			wantErr: true,
 			err: &url.Error{
@@ -181,7 +174,6 @@ func TestReallocate(t *testing.T) {
 						},
 					),
 				},
-				Output: new(output.Device),
 			},
 			wantErr: true,
 			err: &url.Error{
@@ -194,13 +186,9 @@ func TestReallocate(t *testing.T) {
 			name: "tracking is enabled",
 			params: ReallocateParams{
 				DeploymentParams: DeploymentParams{
-					ID: "853cfe89c4a74fb6a6477574d3c03771",
-					TrackParams: util.TrackParams{
-						Track:         true,
-						Output:        output.NewDevice(new(bytes.Buffer)),
-						PollFrequency: time.Millisecond,
-						MaxRetries:    1,
-					},
+					ID:                "853cfe89c4a74fb6a6477574d3c03771",
+					TrackChangeParams: util.NewMockTrackChangeParams(""),
+					Track:             true,
 					API: api.NewMock(util.AppendTrackResponses(
 						mock.Response{Response: http.Response{
 							StatusCode: 200,
@@ -211,7 +199,6 @@ func TestReallocate(t *testing.T) {
 						},
 					)...),
 				},
-				Output: new(output.Device),
 			},
 			wantErr: true,
 			err: &url.Error{
diff --git a/pkg/deployment/kibana/restart_test.go b/pkg/deployment/kibana/restart_test.go
index 508ce43..33cb8db 100644
--- a/pkg/deployment/kibana/restart_test.go
+++ b/pkg/deployment/kibana/restart_test.go
@@ -18,17 +18,14 @@
 package kibana
 
 import (
-	"bytes"
 	"errors"
 	"net/http"
 	"net/url"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/util"
@@ -78,13 +75,9 @@ func TestRestart(t *testing.T) {
 		{
 			name: "Succeeds restarting with tracking",
 			args: args{params: DeploymentParams{
-				TrackParams: util.TrackParams{
-					Track:         true,
-					Output:        output.NewDevice(new(bytes.Buffer)),
-					PollFrequency: time.Millisecond,
-					MaxRetries:    1,
-				},
-				ID: "d324608c97154bdba2dff97511d40368",
+				TrackChangeParams: util.NewMockTrackChangeParams(""),
+				Track:             true,
+				ID:                "d324608c97154bdba2dff97511d40368",
 				API: api.NewMock(util.AppendTrackResponses(mock.Response{Response: http.Response{
 					StatusCode: 202,
 					Body:       mock.NewStringBody(`{}`),
diff --git a/pkg/deployment/kibana/stop_test.go b/pkg/deployment/kibana/stop_test.go
index cfd2505..9115092 100644
--- a/pkg/deployment/kibana/stop_test.go
+++ b/pkg/deployment/kibana/stop_test.go
@@ -18,17 +18,14 @@
 package kibana
 
 import (
-	"bytes"
 	"errors"
 	"net/http"
 	"net/url"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	multierror "github.com/hashicorp/go-multierror"
 
 	"github.com/elastic/ecctl/pkg/util"
@@ -141,13 +138,9 @@ func TestStop(t *testing.T) {
 			args: args{params: StopParams{
 				Hide: true,
 				DeploymentParams: DeploymentParams{
-					ID: "2c221bd86b7f48959a59ee3128d5c5e8",
-					TrackParams: util.TrackParams{
-						Track:         true,
-						Output:        output.NewDevice(new(bytes.Buffer)),
-						PollFrequency: time.Millisecond,
-						MaxRetries:    1,
-					},
+					ID:                "2c221bd86b7f48959a59ee3128d5c5e8",
+					TrackChangeParams: util.NewMockTrackChangeParams(""),
+					Track:             true,
 					API: api.NewMock(util.AppendTrackResponses(
 						mock.Response{Response: http.Response{
 							StatusCode: 202,
diff --git a/pkg/deployment/kibana/upgrade_test.go b/pkg/deployment/kibana/upgrade_test.go
index 6f56513..3fdf816 100644
--- a/pkg/deployment/kibana/upgrade_test.go
+++ b/pkg/deployment/kibana/upgrade_test.go
@@ -18,18 +18,15 @@
 package kibana
 
 import (
-	"bytes"
 	"errors"
 	"net/http"
 	"net/url"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
-	"github.com/elastic/cloud-sdk-go/pkg/output"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 
@@ -88,13 +85,9 @@ func TestUpgrade(t *testing.T) {
 		{
 			name: "Succeeds upgrading with tracking",
 			args: args{params: DeploymentParams{
-				TrackParams: util.TrackParams{
-					Track:         true,
-					Output:        output.NewDevice(new(bytes.Buffer)),
-					PollFrequency: time.Millisecond,
-					MaxRetries:    1,
-				},
-				ID: "d324608c97154bdba2dff97511d40368",
+				TrackChangeParams: util.NewMockTrackChangeParams(""),
+				Track:             true,
+				ID:                "d324608c97154bdba2dff97511d40368",
 				API: api.NewMock(util.AppendTrackResponses(mock.Response{Response: http.Response{
 					StatusCode: 202,
 					Body: mock.NewStructBody(models.ClusterUpgradeInfo{
diff --git a/pkg/platform/allocator/vacate_full_test.go b/pkg/platform/allocator/vacate_full_test.go
index 0f37ac5..09a385c 100644
--- a/pkg/platform/allocator/vacate_full_test.go
+++ b/pkg/platform/allocator/vacate_full_test.go
@@ -65,6 +65,7 @@ func TestVacate(t *testing.T) {
 									plan: []*models.ClusterPlanStepInfo{
 										newPlanStep("step1", "success"),
 										newPlanStep("step2", "success"),
+										newPlanStep("plan-completed", "success"),
 									},
 								},
 							},
@@ -73,8 +74,8 @@ func TestVacate(t *testing.T) {
 				}),
 			},
 			want: newOutputResponses(
-				"Cluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
 			),
 		},
 		{
@@ -97,6 +98,7 @@ func TestVacate(t *testing.T) {
 									plan: []*models.ClusterPlanStepInfo{
 										newPlanStep("step1", "success"),
 										newPlanStep("step2", "success"),
+										newPlanStep("plan-completed", "success"),
 									},
 								},
 							},
@@ -105,8 +107,8 @@ func TestVacate(t *testing.T) {
 				}),
 			},
 			want: newOutputResponses(
-				"Cluster [3ee11eb40eda22cac0cce259625c6734][Kibana]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [3ee11eb40eda22cac0cce259625c6734][Kibana]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][3ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][3ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
 			),
 		},
 		{
@@ -129,6 +131,7 @@ func TestVacate(t *testing.T) {
 									plan: []*models.ClusterPlanStepInfo{
 										newPlanStep("step1", "success"),
 										newPlanStep("step2", "success"),
+										newPlanStep("plan-completed", "success"),
 									},
 								},
 							},
@@ -137,8 +140,8 @@ func TestVacate(t *testing.T) {
 				}),
 			},
 			want: newOutputResponses(
-				"Cluster [3ee11eb40eda22cac0cce259625c6734][Apm]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [3ee11eb40eda22cac0cce259625c6734][Apm]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Apm][3ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Apm][3ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
 			),
 		},
 		{
@@ -162,6 +165,7 @@ func TestVacate(t *testing.T) {
 									plan: []*models.ClusterPlanStepInfo{
 										newPlanStep("step1", "success"),
 										newPlanStep("step2", "success"),
+										newPlanStep("plan-completed", "success"),
 									},
 								},
 							},
@@ -171,7 +175,7 @@ func TestVacate(t *testing.T) {
 			},
 		},
 		{
-			name: "Succeeds moving a single Appsearch cluster from a single allocator, but returns error due to unsupported tracking type",
+			name: "Succeeds moving a single Appsearch cluster from a single allocator",
 			args: args{
 				buf: sdkSync.NewBuffer(),
 				params: newVacateTestCase(t, vacateCase{
@@ -190,6 +194,7 @@ func TestVacate(t *testing.T) {
 									plan: []*models.ClusterPlanStepInfo{
 										newPlanStep("step1", "success"),
 										newPlanStep("step2", "success"),
+										newPlanStep("plan-completed", "success"),
 									},
 								},
 							},
@@ -197,10 +202,10 @@ func TestVacate(t *testing.T) {
 					},
 				}),
 			},
-			err: `1 error occurred:
-	* allocator allocatorID: cluster [3ee11eb40eda22cac0cce259625c6734][appsearch]: instance is being moved but tracking is not supported, please check the vacate progress from the UI
-
-`,
+			want: newOutputResponses(
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Appsearch][3ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Appsearch][3ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
+			),
 		},
 		{
 			name: "Succeeds moving a multiple clusters from a single allocator",
@@ -221,6 +226,7 @@ func TestVacate(t *testing.T) {
 								plan: []*models.ClusterPlanStepInfo{
 									newPlanStep("step1", "success"),
 									newPlanStep("step2", "success"),
+									newPlanStep("plan-completed", "success"),
 								},
 							},
 						},
@@ -236,6 +242,7 @@ func TestVacate(t *testing.T) {
 								plan: []*models.ClusterPlanStepInfo{
 									newPlanStep("step1", "success"),
 									newPlanStep("step2", "success"),
+									newPlanStep("plan-completed", "success"),
 								},
 							},
 						},
@@ -243,10 +250,10 @@ func TestVacate(t *testing.T) {
 				}}),
 			},
 			want: newOutputResponses(
-				"Cluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: finished running all the plan steps\x1b[0m (Total plan duration )",
-				"Cluster [2ee11eb40eda22cac0cce259625c6734][Kibana]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [2ee11eb40eda22cac0cce259625c6734][Kibana]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][2ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][2ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
 			),
 		},
 		{
@@ -268,6 +275,7 @@ func TestVacate(t *testing.T) {
 								plan: []*models.ClusterPlanStepInfo{
 									newPlanStep("step1", "success"),
 									newPlanStep("step2", "success"),
+									newPlanStep("plan-completed", "success"),
 								},
 							},
 						},
@@ -283,6 +291,7 @@ func TestVacate(t *testing.T) {
 								plan: []*models.ClusterPlanStepInfo{
 									newPlanStep("step1", "success"),
 									newPlanStep("step2", "success"),
+									newPlanStep("plan-completed", "success"),
 								},
 							},
 						},
@@ -301,6 +310,7 @@ func TestVacate(t *testing.T) {
 								plan: []*models.ClusterPlanStepInfo{
 									newPlanStep("step1", "success"),
 									newPlanStep("step2", "success"),
+									newPlanStep("plan-completed", "success"),
 								},
 							},
 						},
@@ -316,6 +326,7 @@ func TestVacate(t *testing.T) {
 								plan: []*models.ClusterPlanStepInfo{
 									newPlanStep("step1", "success"),
 									newPlanStep("step2", "success"),
+									newPlanStep("plan-completed", "success"),
 								},
 							},
 						},
@@ -323,14 +334,14 @@ func TestVacate(t *testing.T) {
 				}}),
 			},
 			want: newOutputResponses(
-				"Cluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: finished running all the plan steps\x1b[0m (Total plan duration )",
-				"Cluster [2ee11eb40eda22cac0cce259625c6734][Kibana]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [2ee11eb40eda22cac0cce259625c6734][Kibana]: finished running all the plan steps\x1b[0m (Total plan duration )",
-				"Cluster [5ee11eb40eda22cac0cce259625c6734][Elasticsearch]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [5ee11eb40eda22cac0cce259625c6734][Elasticsearch]: finished running all the plan steps\x1b[0m (Total plan duration )",
-				"Cluster [4ee11eb40eda22cac0cce259625c6734][Kibana]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [4ee11eb40eda22cac0cce259625c6734][Kibana]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][2ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][2ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][5ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][5ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][4ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][4ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
 			),
 		},
 		{
@@ -352,6 +363,7 @@ func TestVacate(t *testing.T) {
 								plan: []*models.ClusterPlanStepInfo{
 									newPlanStep("step1", "success"),
 									newPlanStep("step2", "success"),
+									newPlanStep("plan-completed", "success"),
 								},
 							},
 						},
@@ -367,6 +379,7 @@ func TestVacate(t *testing.T) {
 								plan: []*models.ClusterPlanStepInfo{
 									newPlanStep("step1", "success"),
 									newPlanStep("step2", "success"),
+									newPlanStep("plan-completed", "success"),
 								},
 							},
 						},
@@ -410,6 +423,7 @@ func TestVacate(t *testing.T) {
 								plan: []*models.ClusterPlanStepInfo{
 									newPlanStep("step1", "success"),
 									newPlanStep("step2", "success"),
+									newPlanStep("plan-completed", "success"),
 								},
 							},
 						},
@@ -417,18 +431,18 @@ func TestVacate(t *testing.T) {
 				}}),
 			},
 			want: newOutputResponses(
-				"Cluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: finished running all the plan steps\x1b[0m (Total plan duration )",
-				"Cluster [2ee11eb40eda22cac0cce259625c6734][Kibana]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [2ee11eb40eda22cac0cce259625c6734][Kibana]: finished running all the plan steps\x1b[0m (Total plan duration )",
-				"Cluster [5ee11eb40eda22cac0cce259625c6734][Elasticsearch]: running step \"step2\" (Plan duration )...",
-				"Cluster [5ee11eb40eda22cac0cce259625c6734][Elasticsearch]: running step \"step3\" caught error: \"Unexpected error during step: [perform-snapshot]: [no.found.constructor.models.TimeoutException: Timeout]\" (Plan duration )...",
-				"\x1b[91;1mCluster [5ee11eb40eda22cac0cce259625c6734][Elasticsearch]: caught error: \"Unexpected error during step: [perform-snapshot]: [no.found.constructor.models.TimeoutException: Timeout]\"\x1b[0m (Total plan duration )",
-				"Cluster [4ee11eb40eda22cac0cce259625c6734][Kibana]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [4ee11eb40eda22cac0cce259625c6734][Kibana]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][2ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][2ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][5ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				`Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][5ee11eb40eda22cac0cce259625c6734]: running step "step3" caught error: "Unexpected error during step: [perform-snapshot]: [no.found.constructor.models.TimeoutException: Timeout]" (Plan duration )...`,
+				`Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][5ee11eb40eda22cac0cce259625c6734]: running step "last step" caught error: "Unexpected error during step: [perform-snapshot]: [no.found.constructor.models.TimeoutException: Timeout]" (Plan duration )...`,
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][4ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][4ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
 			),
 			err: `1 error occurred:
-	* cluster [5ee11eb40eda22cac0cce259625c6734][elasticsearch] Unexpected error during step: [perform-snapshot]: [no.found.constructor.models.TimeoutException: Timeout]
+	* resource id [5ee11eb40eda22cac0cce259625c6734][elasticsearch] Unexpected error during step: [perform-snapshot]: [no.found.constructor.models.TimeoutException: Timeout]
 
 `,
 		},
@@ -457,6 +471,7 @@ func TestVacate(t *testing.T) {
 								plan: []*models.ClusterPlanStepInfo{
 									newPlanStep("step1", "success"),
 									newPlanStep("step2", "success"),
+									newPlanStep("plan-completed", "success"),
 								},
 							},
 						},
@@ -485,6 +500,9 @@ func TestVacate(t *testing.T) {
 									newPlanStepWithDetails("step3", "error", []*models.ClusterPlanStepLogMessageInfo{{
 										Message: ec.String(planStepLogErrorMessage),
 									}}),
+									newPlanStepWithDetails("plan-completed", "error", []*models.ClusterPlanStepLogMessageInfo{{
+										Message: ec.String(planStepLogErrorMessage),
+									}}),
 								},
 							},
 						},
@@ -500,6 +518,7 @@ func TestVacate(t *testing.T) {
 								plan: []*models.ClusterPlanStepInfo{
 									newPlanStep("step1", "success"),
 									newPlanStep("step2", "success"),
+									newPlanStep("plan-completed", "success"),
 								},
 							},
 						},
@@ -507,17 +526,17 @@ func TestVacate(t *testing.T) {
 				}}),
 			},
 			want: newOutputResponses(
-				"Cluster [2ee11eb40eda22cac0cce259625c6734][Kibana]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [2ee11eb40eda22cac0cce259625c6734][Kibana]: finished running all the plan steps\x1b[0m (Total plan duration )",
-				"Cluster [5ee11eb40eda22cac0cce259625c6734][Elasticsearch]: running step \"step2\" (Plan duration )...",
-				"Cluster [5ee11eb40eda22cac0cce259625c6734][Elasticsearch]: running step \"step3\" caught error: \"Unexpected error during step: [perform-snapshot]: [no.found.constructor.models.TimeoutException: Timeout]\" (Plan duration )...",
-				"\x1b[91;1mCluster [5ee11eb40eda22cac0cce259625c6734][Elasticsearch]: caught error: \"Unexpected error during step: [perform-snapshot]: [no.found.constructor.models.TimeoutException: Timeout]\"\x1b[0m (Total plan duration )",
-				"Cluster [4ee11eb40eda22cac0cce259625c6734][Kibana]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [4ee11eb40eda22cac0cce259625c6734][Kibana]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][2ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][2ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][5ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				`Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][5ee11eb40eda22cac0cce259625c6734]: running step "step3" caught error: "Unexpected error during step: [perform-snapshot]: [no.found.constructor.models.TimeoutException: Timeout]" (Plan duration )...`,
+				"\x1b[91;1mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][5ee11eb40eda22cac0cce259625c6734]: caught error: \"Unexpected error during step: [perform-snapshot]: [no.found.constructor.models.TimeoutException: Timeout]\"\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][4ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][4ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
 			),
 			err: `2 errors occurred:
-	* cluster [3ee11eb40eda22cac0cce259625c6734][elasticsearch] failed vacating, reason: code: a code, message: a message
-	* cluster [5ee11eb40eda22cac0cce259625c6734][elasticsearch] Unexpected error during step: [perform-snapshot]: [no.found.constructor.models.TimeoutException: Timeout]
+	* resource id [3ee11eb40eda22cac0cce259625c6734][elasticsearch] failed vacating, reason: code: a code, message: a message
+	* deployment [DISCOVERED_DEPLOYMENT_ID] - [elasticsearch][5ee11eb40eda22cac0cce259625c6734]: caught error: "Unexpected error during step: [perform-snapshot]: [no.found.constructor.models.TimeoutException: Timeout]"
 
 `,
 		},
@@ -537,7 +556,7 @@ func TestVacate(t *testing.T) {
 				},
 			},
 			err: `1 error occurred:
-	* cluster [3ee11eb40eda22cac0cce259625c6734][kibana] failed vacating, reason: code: some code, message: failed for reason
+	* resource id [3ee11eb40eda22cac0cce259625c6734][kibana] failed vacating, reason: code: some code, message: failed for reason
 
 `,
 		},
@@ -641,14 +660,14 @@ func TestVacateInterrupt(t *testing.T) {
 				}}),
 			},
 			want: newOutputResponses(
-				"Cluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: running step \"step2\" (Plan duration )...",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
 				"pool: received interrupt, stopping pool...",
-				"Cluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: running step \"step3\" (Plan duration )...",
-				"Cluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: running step \"step4\" (Plan duration )...",
-				"\x1b[92;mCluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: running step \"step3\" (Plan duration )...",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: running step \"step4\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
 			),
 			err: `1 error occurred:
-	* allocator allocatorID: cluster [2ee11eb40eda22cac0cce259625c6734][kibana]: was either cancelled or not processed, follow up accordingly
+	* allocator allocatorID: resource id [2ee11eb40eda22cac0cce259625c6734][kibana]: was either cancelled or not processed, follow up accordingly
 
 `,
 		},
diff --git a/pkg/platform/allocator/vacate_params_test.go b/pkg/platform/allocator/vacate_params_test.go
index 7c87b0d..d335397 100644
--- a/pkg/platform/allocator/vacate_params_test.go
+++ b/pkg/platform/allocator/vacate_params_test.go
@@ -28,6 +28,8 @@ import (
 	"github.com/elastic/cloud-sdk-go/pkg/sync/pool"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
+
+	"github.com/elastic/ecctl/pkg/util"
 )
 
 func TestVacateParamsValidate(t *testing.T) {
@@ -86,7 +88,7 @@ func TestVacateParamsValidate(t *testing.T) {
 			fields: fields{
 				API:         new(api.API),
 				Allocators:  []string{"an allocator"},
-				KindFilter:  "apm",
+				KindFilter:  util.Apm,
 				Concurrency: 1,
 				Output:      new(output.Device),
 			},
diff --git a/pkg/platform/allocator/vacate_responses_test.go b/pkg/platform/allocator/vacate_responses_test.go
index 0c5907a..ab033c4 100644
--- a/pkg/platform/allocator/vacate_responses_test.go
+++ b/pkg/platform/allocator/vacate_responses_test.go
@@ -37,6 +37,7 @@ import (
 	"github.com/go-openapi/strfmt"
 	multierror "github.com/hashicorp/go-multierror"
 
+	"github.com/elastic/ecctl/pkg/deployment/depresource"
 	"github.com/elastic/ecctl/pkg/util"
 )
 
@@ -79,6 +80,17 @@ func newBody(t *testing.T, v interface{}) string {
 	return string(b)
 }
 
+func newDeploymentDiscovery() mock.Response {
+	return mock.Response{Response: http.Response{
+		StatusCode: 200,
+		Body: mock.NewStructBody(models.DeploymentsSearchResponse{
+			Deployments: []*models.DeploymentSearchResponse{
+				{ID: ec.String("DISCOVERED_DEPLOYMENT_ID")},
+			},
+		}),
+	}}
+}
+
 func newElasticsearchMove(t *testing.T, clusterID, allocator string) io.ReadCloser {
 	return ioutil.NopCloser(strings.NewReader(newBody(t,
 		models.MoveClustersCommandResponse{
@@ -259,23 +271,75 @@ func newMulipleMoves(t *testing.T, allocator string, es, kibana, apm, appsearch
 	return ioutil.NopCloser(strings.NewReader(newBody(t, res)))
 }
 
-func newPollerBody(t *testing.T, pending, current *models.ElasticsearchClusterPlanInfo) io.ReadCloser {
-	payload := &models.ElasticsearchClusterPlansInfo{Pending: pending, Current: current}
+func newPollerBody(t *testing.T, id string, pending, current *models.ElasticsearchClusterPlanInfo) io.ReadCloser {
+	payload := models.DeploymentGetResponse{
+		ID: ec.String("DISCOVERED_DEPLOYMENT_ID"),
+		Resources: &models.DeploymentResources{
+			Elasticsearch: []*models.ElasticsearchResourceInfo{
+				{
+					ID:    &id,
+					RefID: ec.String(depresource.DefaultElasticsearchRefID),
+					Info: &models.ElasticsearchClusterInfo{PlanInfo: &models.ElasticsearchClusterPlansInfo{
+						Current: current, Pending: pending,
+					}},
+				},
+			},
+		},
+	}
 	return newPayloadFromStruct(t, payload)
 }
 
-func newApmPollerBody(t *testing.T, pending, current *models.ApmPlanInfo) io.ReadCloser {
-	payload := &models.ApmPlansInfo{Pending: pending, Current: current}
+func newApmPollerBody(t *testing.T, id string, pending, current *models.ApmPlanInfo) io.ReadCloser {
+	payload := models.DeploymentGetResponse{
+		ID: ec.String("DISCOVERED_DEPLOYMENT_ID"),
+		Resources: &models.DeploymentResources{
+			Apm: []*models.ApmResourceInfo{
+				{
+					ID:    &id,
+					RefID: ec.String(depresource.DefaultApmRefID),
+					Info: &models.ApmInfo{PlanInfo: &models.ApmPlansInfo{
+						Current: current, Pending: pending,
+					}},
+				},
+			},
+		},
+	}
 	return newPayloadFromStruct(t, payload)
 }
 
-func newAppSearchPollerBody(t *testing.T, pending, current *models.AppSearchPlanInfo) io.ReadCloser {
-	payload := &models.AppSearchPlansInfo{Pending: pending, Current: current}
+func newAppSearchPollerBody(t *testing.T, id string, pending, current *models.AppSearchPlanInfo) io.ReadCloser {
+	payload := models.DeploymentGetResponse{
+		ID: ec.String("DISCOVERED_DEPLOYMENT_ID"),
+		Resources: &models.DeploymentResources{
+			Appsearch: []*models.AppSearchResourceInfo{
+				{
+					ID:    &id,
+					RefID: ec.String(depresource.DefaultAppSearchRefID),
+					Info: &models.AppSearchInfo{PlanInfo: &models.AppSearchPlansInfo{
+						Current: current, Pending: pending,
+					}},
+				},
+			},
+		},
+	}
 	return newPayloadFromStruct(t, payload)
 }
 
-func newKibanaPollerBody(t *testing.T, pending, current *models.KibanaClusterPlanInfo) io.ReadCloser {
-	payload := &models.KibanaClusterPlansInfo{Pending: pending, Current: current}
+func newKibanaPollerBody(t *testing.T, id string, pending, current *models.KibanaClusterPlanInfo) io.ReadCloser {
+	payload := models.DeploymentGetResponse{
+		ID: ec.String("DISCOVERED_DEPLOYMENT_ID"),
+		Resources: &models.DeploymentResources{
+			Kibana: []*models.KibanaResourceInfo{
+				{
+					ID:    &id,
+					RefID: ec.String(depresource.DefaultKibanaRefID),
+					Info: &models.KibanaClusterInfo{PlanInfo: &models.KibanaClusterPlansInfo{
+						Current: current, Pending: pending,
+					}},
+				},
+			},
+		},
+	}
 	return newPayloadFromStruct(t, payload)
 }
 
@@ -458,6 +522,7 @@ func newVacateTestCase(t *testing.T, tc vacateCase) *VacateParams {
 		Output:         output.NewDevice(sdkSync.NewBuffer()),
 		Allocators:     allocators,
 		API:            api.NewMock(responses...),
+		OutputFormat:   "text",
 		Concurrency:    1,
 		MaxPollRetries: 1,
 		TrackFrequency: time.Nanosecond,
@@ -467,7 +532,7 @@ func newVacateTestCase(t *testing.T, tc vacateCase) *VacateParams {
 func newAPMVacateMove(t *testing.T, alloc string, move vacateCaseClusterConfig) (*api.API, []mock.Response) {
 	var responses = make([]mock.Response, 0, 4)
 	responses = append(responses, mock.Response{Response: http.Response{
-		Body:       newAllocator(t, alloc, move.ID, "apm"),
+		Body:       newAllocator(t, alloc, move.ID, util.Apm),
 		StatusCode: 200,
 	}}, mock.Response{Response: http.Response{
 		Body:       newApmMove(t, move.ID, alloc),
@@ -505,14 +570,14 @@ func newAPMVacateMove(t *testing.T, alloc string, move vacateCaseClusterConfig)
 	responses = append(responses, mock.Response{Response: http.Response{
 		Body:       newApmMove(t, move.ID, alloc),
 		StatusCode: 202,
-	}})
+	}}, newDeploymentDiscovery())
 
 	// Define steps
 	for iii := range move.steps {
 		var step = move.steps[iii]
 		responses = append(responses, mock.Response{Response: http.Response{
 			StatusCode: 200,
-			Body: newApmPollerBody(t,
+			Body: newApmPollerBody(t, move.ID,
 				&models.ApmPlanInfo{PlanAttemptLog: step},
 				nil,
 			),
@@ -525,7 +590,7 @@ func newAPMVacateMove(t *testing.T, alloc string, move vacateCaseClusterConfig)
 		util.PlanNotFound,
 		mock.Response{Response: http.Response{
 			StatusCode: 200,
-			Body: newApmPollerBody(t,
+			Body: newApmPollerBody(t, move.ID,
 				nil,
 				&models.ApmPlanInfo{PlanAttemptLog: move.plan},
 			),
@@ -577,14 +642,14 @@ func newKibanaVacateMove(t *testing.T, alloc string, move vacateCaseClusterConfi
 	responses = append(responses, mock.Response{Response: http.Response{
 		Body:       newKibanaMove(t, move.ID, alloc),
 		StatusCode: 202,
-	}})
+	}}, newDeploymentDiscovery())
 
 	// Define steps
 	for iii := range move.steps {
 		var step = move.steps[iii]
 		responses = append(responses, mock.Response{Response: http.Response{
 			StatusCode: 200,
-			Body: newKibanaPollerBody(t,
+			Body: newKibanaPollerBody(t, move.ID,
 				&models.KibanaClusterPlanInfo{PlanAttemptLog: step},
 				nil,
 			),
@@ -596,7 +661,7 @@ func newKibanaVacateMove(t *testing.T, alloc string, move vacateCaseClusterConfi
 		util.PlanNotFound,
 		mock.Response{Response: http.Response{
 			StatusCode: 200,
-			Body: newKibanaPollerBody(t,
+			Body: newKibanaPollerBody(t, move.ID,
 				nil,
 				&models.KibanaClusterPlanInfo{PlanAttemptLog: move.plan},
 			),
@@ -646,14 +711,14 @@ func newElasticsearchVacateMove(t *testing.T, alloc string, move vacateCaseClust
 	responses = append(responses, mock.Response{Response: http.Response{
 		Body:       newElasticsearchMove(t, move.ID, alloc),
 		StatusCode: 202,
-	}})
+	}}, newDeploymentDiscovery())
 
 	// Define steps
 	for iii := range move.steps {
 		var step = move.steps[iii]
 		responses = append(responses, mock.Response{Response: http.Response{
 			StatusCode: 200,
-			Body: newPollerBody(t,
+			Body: newPollerBody(t, move.ID,
 				&models.ElasticsearchClusterPlanInfo{PlanAttemptLog: step},
 				nil,
 			),
@@ -666,7 +731,7 @@ func newElasticsearchVacateMove(t *testing.T, alloc string, move vacateCaseClust
 		util.PlanNotFound,
 		mock.Response{Response: http.Response{
 			StatusCode: 200,
-			Body: newPollerBody(t,
+			Body: newPollerBody(t, move.ID,
 				nil,
 				&models.ElasticsearchClusterPlanInfo{PlanAttemptLog: move.plan},
 			),
@@ -717,14 +782,14 @@ func newAppsearchVacateMove(t *testing.T, alloc string, move vacateCaseClusterCo
 	responses = append(responses, mock.Response{Response: http.Response{
 		Body:       newAppsearchMove(t, move.ID, alloc),
 		StatusCode: 202,
-	}})
+	}}, newDeploymentDiscovery())
 
 	// Define steps
 	for iii := range move.steps {
 		var step = move.steps[iii]
 		responses = append(responses, mock.Response{Response: http.Response{
 			StatusCode: 200,
-			Body: newAppSearchPollerBody(t,
+			Body: newAppSearchPollerBody(t, move.ID,
 				&models.AppSearchPlanInfo{PlanAttemptLog: step},
 				nil,
 			),
@@ -737,7 +802,7 @@ func newAppsearchVacateMove(t *testing.T, alloc string, move vacateCaseClusterCo
 		util.PlanNotFound,
 		mock.Response{Response: http.Response{
 			StatusCode: 200,
-			Body: newAppSearchPollerBody(t,
+			Body: newAppSearchPollerBody(t, move.ID,
 				nil,
 				&models.AppSearchPlanInfo{PlanAttemptLog: move.plan},
 			),
diff --git a/pkg/platform/allocator/vacate_test.go b/pkg/platform/allocator/vacate_test.go
index 74de0d0..3cb14e6 100644
--- a/pkg/platform/allocator/vacate_test.go
+++ b/pkg/platform/allocator/vacate_test.go
@@ -580,7 +580,7 @@ func TestCheckVacateFailures(t *testing.T) {
 				},
 			},
 			err: &multierror.Error{Errors: []error{
-				errors.New("cluster [123456789][elasticsearch] failed vacating, reason: code: unknown, message: a message"),
+				errors.New("resource id [123456789][elasticsearch] failed vacating, reason: code: unknown, message: a message"),
 			}},
 		},
 		{
@@ -601,7 +601,7 @@ func TestCheckVacateFailures(t *testing.T) {
 				},
 			},
 			err: &multierror.Error{Errors: []error{
-				errors.New("cluster [123456789][kibana] failed vacating, reason: code: unknown, message: a kibana error message"),
+				errors.New("resource id [123456789][kibana] failed vacating, reason: code: unknown, message: a kibana error message"),
 			}},
 		},
 		{
@@ -622,7 +622,7 @@ func TestCheckVacateFailures(t *testing.T) {
 				},
 			},
 			err: &multierror.Error{Errors: []error{
-				errors.New("cluster [123456789][apm] failed vacating, reason: code: unknown, message: an apm error message"),
+				errors.New("resource id [123456789][apm] failed vacating, reason: code: unknown, message: an apm error message"),
 			}},
 		},
 		{
@@ -643,7 +643,7 @@ func TestCheckVacateFailures(t *testing.T) {
 				},
 			},
 			err: &multierror.Error{Errors: []error{
-				errors.New("cluster [123456789][appsearch] failed vacating, reason: code: unknown, message: an apm error message"),
+				errors.New("resource id [123456789][appsearch] failed vacating, reason: code: unknown, message: an apm error message"),
 			}},
 		},
 		{
@@ -675,8 +675,8 @@ func TestCheckVacateFailures(t *testing.T) {
 				},
 			},
 			err: &multierror.Error{Errors: []error{
-				errors.New("cluster [123456789][elasticsearch] failed vacating, reason: code: unknown, message: a message"),
-				errors.New("cluster [123456789][kibana] failed vacating, reason: code: unknown, message: a kibana error message"),
+				errors.New("resource id [123456789][elasticsearch] failed vacating, reason: code: unknown, message: a message"),
+				errors.New("resource id [123456789][kibana] failed vacating, reason: code: unknown, message: a kibana error message"),
 			}},
 		},
 		{
@@ -704,7 +704,7 @@ func TestCheckVacateFailures(t *testing.T) {
 				},
 			},
 			err: &multierror.Error{Errors: []error{
-				errors.New("cluster [1234567890][elasticsearch] failed vacating, reason: code: unknown, message: a message"),
+				errors.New("resource id [1234567890][elasticsearch] failed vacating, reason: code: unknown, message: a message"),
 			}},
 		},
 	}
@@ -718,7 +718,7 @@ func TestCheckVacateFailures(t *testing.T) {
 }
 
 func TestVacateCluster(t *testing.T) {
-	var errEmptyParams = `allocator : cluster [][]: parameter validation: 5 errors occurred:
+	var errEmptyParams = `allocator : resource id [][]: parameter validation: 5 errors occurred:
 	* api reference is required for command
 	* vacate cluster: invalid allocator ID 
 	* vacate cluster: invalid cluster ID 
@@ -726,7 +726,7 @@ func TestVacateCluster(t *testing.T) {
 	* output device cannot be nil
 
 `
-	var errInvalidParams = `allocator someID: cluster [3ee11eb40eda22cac0cce259625c6734][somethingweird]: parameter validation: 3 errors occurred:
+	var errInvalidParams = `allocator someID: resource id [3ee11eb40eda22cac0cce259625c6734][somethingweird]: parameter validation: 3 errors occurred:
 	* api reference is required for command
 	* vacate cluster: invalid kind somethingweird
 	* output device cannot be nil
@@ -775,6 +775,7 @@ func TestVacateCluster(t *testing.T) {
 					Kind:           "elasticsearch",
 					Output:         new(output.Device),
 					TrackFrequency: time.Nanosecond,
+					OutputFormat:   "text",
 					MaxPollRetries: 1,
 					API: discardResponses(newElasticsearchVacateMove(t, "someID", vacateCaseClusterConfig{
 						ID: "3ee11eb40eda22cac0cce259625c6734",
@@ -787,13 +788,14 @@ func TestVacateCluster(t *testing.T) {
 						plan: []*models.ClusterPlanStepInfo{
 							newPlanStep("step1", "success"),
 							newPlanStep("step2", "success"),
+							newPlanStep("plan-completed", "success"),
 						},
 					})),
 				},
 			},
 			want: newOutputResponses(
-				"Cluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: running step \"step2\" (Plan duration )...",
-				"\x1b[92;mCluster [3ee11eb40eda22cac0cce259625c6734][Elasticsearch]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				`Deployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: running step "step2" (Plan duration )...`,
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Elasticsearch][3ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
 			),
 		},
 		{
@@ -824,6 +826,7 @@ func TestVacateCluster(t *testing.T) {
 					Kind:           "kibana",
 					Output:         new(output.Device),
 					TrackFrequency: time.Nanosecond,
+					OutputFormat:   "text",
 					MaxPollRetries: 1,
 					API: discardResponses(newKibanaVacateMove(t, "someID", vacateCaseClusterConfig{
 						ID: "2ee11eb40eda22cac0cce259625c6734",
@@ -842,14 +845,15 @@ func TestVacateCluster(t *testing.T) {
 							newPlanStep("step1", "success"),
 							newPlanStep("step2", "success"),
 							newPlanStep("step3", "success"),
+							newPlanStep("plan-completed", "success"),
 						},
 					})),
 				},
 			},
 			want: newOutputResponses(
-				"Cluster [2ee11eb40eda22cac0cce259625c6734][Kibana]: running step \"step2\" (Plan duration )...",
-				"Cluster [2ee11eb40eda22cac0cce259625c6734][Kibana]: running step \"step3\" (Plan duration )...",
-				"\x1b[92;mCluster [2ee11eb40eda22cac0cce259625c6734][Kibana]: finished running all the plan steps\x1b[0m (Total plan duration )",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][2ee11eb40eda22cac0cce259625c6734]: running step \"step2\" (Plan duration )...",
+				"Deployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][2ee11eb40eda22cac0cce259625c6734]: running step \"step3\" (Plan duration )...",
+				"\x1b[92;mDeployment [DISCOVERED_DEPLOYMENT_ID] - [Kibana][2ee11eb40eda22cac0cce259625c6734]: finished running all the plan steps\x1b[0m (Total plan duration )",
 			),
 		},
 		{
@@ -906,7 +910,7 @@ func TestVacateCluster(t *testing.T) {
 				},
 			},
 			err: newMultierror(
-				errors.New("cluster [2ee11eb40eda22cac0cce259625c6734][kibana] failed vacating, reason: code: a code, message: a message"),
+				errors.New("resource id [2ee11eb40eda22cac0cce259625c6734][kibana] failed vacating, reason: code: a code, message: a message"),
 			),
 		},
 	}
@@ -953,7 +957,7 @@ func Test_fillVacateClusterParams(t *testing.T) {
 					Output:    output.NewDevice(new(bytes.Buffer)),
 				},
 			},
-			err: errors.New(`allocator allocator-1: cluster [3ee11eb40eda22cac0cce259625c6734][elasticsearch]: allocator health autodiscovery: Get https://mock-host/mock-path/platform/infrastructure/allocators/allocator-1: unauthorized`),
+			err: errors.New(`allocator allocator-1: resource id [3ee11eb40eda22cac0cce259625c6734][elasticsearch]: allocator health autodiscovery: Get https://mock-host/mock-path/platform/infrastructure/allocators/allocator-1: unauthorized`),
 		},
 		{
 			name: "sets defaults on parameters that aren't specified",
@@ -1053,7 +1057,7 @@ func Test_newMoveClusterParams(t *testing.T) {
 				Kind:      "elasticsearch",
 				Output:    output.NewDevice(new(bytes.Buffer)),
 			}},
-			err: errors.New(`allocator allocator-1: cluster [3ee11eb40eda22cac0cce259625c6734][elasticsearch]: validate_only: Post https://mock-host/mock-path/platform/infrastructure/allocators/allocator-1/clusters/_move?force_update=false&validate_only=true: unauthorized`),
+			err: errors.New(`allocator allocator-1: resource id [3ee11eb40eda22cac0cce259625c6734][elasticsearch]: validate_only: Post https://mock-host/mock-path/platform/infrastructure/allocators/allocator-1/clusters/_move?force_update=false&validate_only=true: unauthorized`),
 		},
 		{
 			name: "elasticsearch move succeeds to get parameters",
@@ -1098,13 +1102,13 @@ func Test_newMoveClusterParams(t *testing.T) {
 				}}),
 				ID:            "allocator-1",
 				ClusterID:     "3ee11eb40eda22cac0cce259625c6734",
-				Kind:          "apm",
+				Kind:          util.Apm,
 				Output:        output.NewDevice(new(bytes.Buffer)),
 				AllocatorDown: ec.Bool(false),
 			}},
 			want: platform_infrastructure.NewMoveClustersByTypeParams().
 				WithAllocatorID("allocator-1").
-				WithClusterType(ec.String("apm")).
+				WithClusterType(ec.String(util.Apm)).
 				WithAllocatorDown(ec.Bool(false)).
 				WithBody(&models.MoveClustersRequest{
 					ApmClusters: []*models.MoveApmClusterConfiguration{
@@ -1358,7 +1362,7 @@ func Test_addAllocatorMovesToPool(t *testing.T) {
 					})
 					return p
 				}(),
-				VacateParams: &VacateParams{KindFilter: "apm"},
+				VacateParams: &VacateParams{KindFilter: util.Apm},
 				Moves: &models.MoveClustersDetails{
 					ElasticsearchClusters: []*models.MoveElasticsearchClusterDetails{
 						{
diff --git a/pkg/util/response_parse_test.go b/pkg/util/response_parse_test.go
index c6e8717..e6baf92 100644
--- a/pkg/util/response_parse_test.go
+++ b/pkg/util/response_parse_test.go
@@ -18,27 +18,30 @@
 package util
 
 import (
-	"bytes"
 	"errors"
 	"reflect"
 	"testing"
 	"time"
 
 	"github.com/elastic/cloud-sdk-go/pkg/api"
+	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_elasticsearch"
 	"github.com/elastic/cloud-sdk-go/pkg/client/clusters_kibana"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
 	"github.com/elastic/cloud-sdk-go/pkg/output"
+	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
+	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	multierror "github.com/hashicorp/go-multierror"
 )
 
 func TestParseCUResponseParams_Validate(t *testing.T) {
 	type fields struct {
-		API            *api.API
 		CreateResponse interface{}
 		UpdateResponse interface{}
 		Err            error
-		TrackParams    TrackParams
+		Track          bool
+		planutil.TrackChangeParams
 	}
 	tests := []struct {
 		name   string
@@ -54,29 +57,19 @@ func TestParseCUResponseParams_Validate(t *testing.T) {
 			name:   "Empty fields trigger error",
 			fields: fields{},
 			err: &multierror.Error{Errors: []error{
-				errors.New("parse response: API cannot be empty"),
 				errors.New("parse response: One of Create or Update response must be populated"),
 			}},
 		},
-		{
-			name: "Setting track to true requres an output device",
-			fields: fields{
-				API:            new(api.API),
-				CreateResponse: "something",
-				TrackParams:    TrackParams{Track: true},
-			},
-			err: &multierror.Error{Errors: []error{
-				errors.New("track params: output device cannot be empty"),
-			}},
-		},
 		{
 			name: "Correct parameter validation with CreateResponse returns no error",
 			fields: fields{
-				API:            new(api.API),
 				CreateResponse: "something",
-				TrackParams: TrackParams{
-					Track:  true,
-					Output: new(output.Device),
+				Track:          true,
+				TrackChangeParams: planutil.TrackChangeParams{
+					TrackChangeParams: plan.TrackChangeParams{
+						API: new(api.API),
+					},
+					Writer: new(output.Device),
 				},
 			},
 			err: nil,
@@ -84,11 +77,13 @@ func TestParseCUResponseParams_Validate(t *testing.T) {
 		{
 			name: "Correct parameter validation with UpdateResponse returns no error",
 			fields: fields{
-				API:            new(api.API),
 				UpdateResponse: "something",
-				TrackParams: TrackParams{
-					Track:  true,
-					Output: new(output.Device),
+				Track:          true,
+				TrackChangeParams: planutil.TrackChangeParams{
+					TrackChangeParams: plan.TrackChangeParams{
+						API: new(api.API),
+					},
+					Writer: new(output.Device),
 				},
 			},
 			err: nil,
@@ -97,11 +92,11 @@ func TestParseCUResponseParams_Validate(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			params := ParseCUResponseParams{
-				API:            tt.fields.API,
-				CreateResponse: tt.fields.CreateResponse,
-				UpdateResponse: tt.fields.UpdateResponse,
-				Err:            tt.fields.Err,
-				TrackParams:    tt.fields.TrackParams,
+				CreateResponse:    tt.fields.CreateResponse,
+				UpdateResponse:    tt.fields.UpdateResponse,
+				Err:               tt.fields.Err,
+				Track:             tt.fields.Track,
+				TrackChangeParams: tt.fields.TrackChangeParams,
 			}
 			if err := params.Validate(); !reflect.DeepEqual(err, tt.err) {
 				t.Errorf("ParseCUResponseParams.Validate() error = %v, wantErr %v", err, tt.err)
@@ -111,6 +106,11 @@ func TestParseCUResponseParams_Validate(t *testing.T) {
 }
 
 func TestParseCUResponse(t *testing.T) {
+	var foundDeploymentIDResponse = models.DeploymentsSearchResponse{
+		Deployments: []*models.DeploymentSearchResponse{
+			{ID: ec.String("cbb4bc6c09684c86aa5de54c05ea1d38")},
+		},
+	}
 	type args struct {
 		params ParseCUResponseParams
 	}
@@ -130,44 +130,40 @@ func TestParseCUResponse(t *testing.T) {
 		{
 			name: "Passing an Elasticsearch create response succeeds",
 			args: args{params: ParseCUResponseParams{
-				API: &api.API{},
 				CreateResponse: &clusters_elasticsearch.CreateEsClusterOK{
 					Payload: &models.ClusterCrudResponse{ElasticsearchClusterID: "someID"},
 				},
-				TrackParams: TrackParams{Track: false},
+				Track: false,
 			}},
 			want: &models.ClusterCrudResponse{ElasticsearchClusterID: "someID"},
 		},
 		{
 			name: "Passing an Elasticsearch update response succeeds",
 			args: args{params: ParseCUResponseParams{
-				API: &api.API{},
 				UpdateResponse: &clusters_elasticsearch.UpdateEsClusterPlanAccepted{
 					Payload: &models.ClusterCrudResponse{ElasticsearchClusterID: "someID"},
 				},
-				TrackParams: TrackParams{Track: false},
+				Track: false,
 			}},
 			want: &models.ClusterCrudResponse{ElasticsearchClusterID: "someID"},
 		},
 		{
 			name: "Passing an Kibana create response succeeds",
 			args: args{params: ParseCUResponseParams{
-				API: &api.API{},
 				CreateResponse: &clusters_kibana.CreateKibanaClusterOK{
 					Payload: &models.ClusterCrudResponse{KibanaClusterID: "someID"},
 				},
-				TrackParams: TrackParams{Track: false},
+				Track: false,
 			}},
 			want: &models.ClusterCrudResponse{KibanaClusterID: "someID"},
 		},
 		{
 			name: "Passing an Kibana update response succeeds",
 			args: args{params: ParseCUResponseParams{
-				API: &api.API{},
 				UpdateResponse: &clusters_kibana.UpdateKibanaClusterPlanAccepted{
 					Payload: &models.ClusterCrudResponse{KibanaClusterID: "someID"},
 				},
-				TrackParams: TrackParams{Track: false},
+				Track: false,
 			}},
 			want: &models.ClusterCrudResponse{KibanaClusterID: "someID"},
 		},
@@ -175,44 +171,45 @@ func TestParseCUResponse(t *testing.T) {
 		{
 			name: "Passing a Kibana update response succeeds and tracks the cluster",
 			args: args{params: ParseCUResponseParams{
-				TrackParams: TrackParams{
-					PollFrequency: time.Nanosecond,
-					MaxRetries:    1,
-					Track:         true,
-					Output:        output.NewDevice(new(bytes.Buffer)),
+				Track: true,
+				TrackChangeParams: planutil.TrackChangeParams{
+					TrackChangeParams: plan.TrackChangeParams{
+						API: api.NewMock(AppendTrackResponses(
+							mock.New200StructResponse(foundDeploymentIDResponse),
+						)...),
+						Config: plan.TrackFrequencyConfig{
+							PollFrequency: time.Nanosecond,
+							MaxRetries:    1,
+						},
+					},
+					Writer: new(output.Device),
 				},
-				API: api.NewMock(
-					PlanNotFound,
-					PlanNotFound,
-					NewFailedPlanUnknown(),
-				),
 				UpdateResponse: &clusters_kibana.UpdateKibanaClusterPlanAccepted{
 					Payload: &models.ClusterCrudResponse{KibanaClusterID: "3ee11eb40eda22cac0cce259625c6734"},
 				},
 			}},
 			want: &models.ClusterCrudResponse{KibanaClusterID: "3ee11eb40eda22cac0cce259625c6734"},
-			err:  errors.New("cluster [3ee11eb40eda22cac0cce259625c6734][kibana] plan failed due to unknown error"),
 		},
 		{
 			name: "Passing an Elasticsearch update response succeeds and tracks the cluster",
 			args: args{params: ParseCUResponseParams{
-				TrackParams: TrackParams{
-					PollFrequency: time.Nanosecond,
-					MaxRetries:    1,
-					Track:         true,
-					Output:        output.NewDevice(new(bytes.Buffer)),
+				TrackChangeParams: planutil.TrackChangeParams{
+					TrackChangeParams: plan.TrackChangeParams{
+						API: api.NewMock(AppendTrackResponses(
+							mock.New200StructResponse(foundDeploymentIDResponse),
+						)...),
+						Config: plan.TrackFrequencyConfig{
+							PollFrequency: time.Nanosecond,
+							MaxRetries:    1,
+						},
+					},
+					Writer: new(output.Device),
 				},
-				API: api.NewMock(
-					PlanNotFound,
-					PlanNotFound,
-					NewFailedPlanUnknown(),
-				),
 				CreateResponse: &clusters_elasticsearch.CreateEsClusterOK{
 					Payload: &models.ClusterCrudResponse{ElasticsearchClusterID: "3ee11eb40eda22cac0cce259625c6734"},
 				},
 			}},
 			want: &models.ClusterCrudResponse{ElasticsearchClusterID: "3ee11eb40eda22cac0cce259625c6734"},
-			err:  errors.New("cluster [3ee11eb40eda22cac0cce259625c6734][elasticsearch] plan failed due to unknown error"),
 		},
 	}
 	for _, tt := range tests {
diff --git a/pkg/util/testing.go b/pkg/util/testing.go
index ab2b909..b760884 100644
--- a/pkg/util/testing.go
+++ b/pkg/util/testing.go
@@ -18,13 +18,19 @@
 package util
 
 import (
+	"bytes"
 	"fmt"
 	"net/http"
 	"reflect"
 	"testing"
+	"time"
 
+	"github.com/elastic/cloud-sdk-go/pkg/api"
 	"github.com/elastic/cloud-sdk-go/pkg/api/mock"
 	"github.com/elastic/cloud-sdk-go/pkg/models"
+	"github.com/elastic/cloud-sdk-go/pkg/output"
+	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
 	"github.com/elastic/cloud-sdk-go/pkg/util/ec"
 	"github.com/go-openapi/strfmt"
 )
@@ -47,17 +53,44 @@ var (
 	}}
 )
 
-// NewSuccessfulPlan returns a mocked response from a successful plan.
-func NewSuccessfulPlan() mock.Response {
+// NewSuccessfulCurrentPlan returns a mocked response from a successful plan.
+func NewSuccessfulCurrentPlan(currentTime ...time.Time) mock.Response {
+	var now strfmt.DateTime
+	if len(currentTime) > 0 {
+		now = strfmt.DateTime(currentTime[0])
+	}
 	return mock.Response{Response: http.Response{
 		StatusCode: 200,
-		Body: mock.NewStructBody(&models.ElasticsearchClusterPlansInfo{
-			Current: &models.ElasticsearchClusterPlanInfo{
-				PlanAttemptLog: []*models.ClusterPlanStepInfo{
-					{
-						Status: ec.String("success"),
-						StepID: ec.String("step-1"),
-					},
+		Body: mock.NewStructBody(&models.DeploymentGetResponse{
+			ID: ec.String("DEPLOYMENT_TEST_ID"),
+			Resources: &models.DeploymentResources{
+				Apm: []*models.ApmResourceInfo{
+					{ID: ec.String(ValidClusterID), RefID: ec.String("main-apm"), Info: &models.ApmInfo{PlanInfo: &models.ApmPlansInfo{
+						Current: &models.ApmPlanInfo{PlanAttemptLog: []*models.ClusterPlanStepInfo{
+							{Status: ec.String("success"), StepID: ec.String("plan-completed"), Started: &now},
+						}},
+					}}},
+				},
+				Appsearch: []*models.AppSearchResourceInfo{
+					{ID: ec.String(ValidClusterID), RefID: ec.String("main-appsearch"), Info: &models.AppSearchInfo{PlanInfo: &models.AppSearchPlansInfo{
+						Current: &models.AppSearchPlanInfo{PlanAttemptLog: []*models.ClusterPlanStepInfo{
+							{Status: ec.String("success"), StepID: ec.String("plan-completed"), Started: &now},
+						}},
+					}}},
+				},
+				Elasticsearch: []*models.ElasticsearchResourceInfo{
+					{ID: ec.String(ValidClusterID), RefID: ec.String("main-elasticsearch"), Info: &models.ElasticsearchClusterInfo{PlanInfo: &models.ElasticsearchClusterPlansInfo{
+						Current: &models.ElasticsearchClusterPlanInfo{PlanAttemptLog: []*models.ClusterPlanStepInfo{
+							{Status: ec.String("success"), StepID: ec.String("plan-completed"), Started: &now},
+						}},
+					}}},
+				},
+				Kibana: []*models.KibanaResourceInfo{
+					{ID: ec.String(ValidClusterID), RefID: ec.String("main-kibana"), Info: &models.KibanaClusterInfo{PlanInfo: &models.KibanaClusterPlansInfo{
+						Current: &models.KibanaClusterPlanInfo{PlanAttemptLog: []*models.ClusterPlanStepInfo{
+							{Status: ec.String("success"), StepID: ec.String("plan-completed"), Started: &now},
+						}},
+					}}},
 				},
 			},
 		}),
@@ -86,14 +119,34 @@ func NewFailedPlanUnknown() mock.Response {
 // by the client.
 func AppendTrackResponses(res ...mock.Response) []mock.Response {
 	var response = append(res,
-		PlanNotFound,
-		PlanNotFound,
-		NewSuccessfulPlan(),
+		NewSuccessfulCurrentPlan(),
+		NewSuccessfulCurrentPlan(),
+		NewSuccessfulCurrentPlan(),
 	)
 
 	return response
 }
 
+// NewMockTrackChangeParams creates new tracking params for test purposes.
+func NewMockTrackChangeParams(id string) planutil.TrackChangeParams {
+	var res = AppendTrackResponses()
+	if id == "" {
+		res = AppendTrackResponses(mock.New200Response(mock.NewStructBody(models.DeploymentsSearchResponse{
+			Deployments: []*models.DeploymentSearchResponse{
+				{ID: ec.String("cbb4bc6c09684c86aa5de54c05ea1d38")},
+			},
+		})))
+	}
+	return planutil.TrackChangeParams{
+		TrackChangeParams: plan.TrackChangeParams{
+			API:          api.NewMock(res...),
+			DeploymentID: id,
+		},
+		Writer: output.NewDevice(new(bytes.Buffer)),
+		Format: "text",
+	}
+}
+
 // CheckErrType receives two errors, if either one is not nil, it triggers a
 // comparison between the two, returning an error if the errors are not equal
 // This function is useful for testing to ensure specific error types.
diff --git a/pkg/util/testing_test.go b/pkg/util/testing_test.go
index 30fc7af..e2f3d7b 100644
--- a/pkg/util/testing_test.go
+++ b/pkg/util/testing_test.go
@@ -63,6 +63,7 @@ func TestCheckErrType(t *testing.T) {
 }
 
 func TestAppendTrackResponses(t *testing.T) {
+	var success = NewSuccessfulCurrentPlan()
 	type args struct {
 		res []mock.Response
 	}
@@ -76,12 +77,10 @@ func TestAppendTrackResponses(t *testing.T) {
 			args: args{res: []mock.Response{
 				{Error: errors.New("someerror")},
 			}},
-			want: append(
-				[]mock.Response{{Error: errors.New("someerror")}},
-				PlanNotFound,
-				PlanNotFound,
-				NewSuccessfulPlan(),
-			),
+			want: []mock.Response{
+				{Error: errors.New("someerror")},
+				success, success, success,
+			},
 		},
 		{
 			name: "appends multiple responses",
@@ -89,15 +88,11 @@ func TestAppendTrackResponses(t *testing.T) {
 				{Error: errors.New("someerror")},
 				{Error: errors.New("someothererror")},
 			}},
-			want: append(
-				[]mock.Response{
-					{Error: errors.New("someerror")},
-					{Error: errors.New("someothererror")},
-				},
-				PlanNotFound,
-				PlanNotFound,
-				NewSuccessfulPlan(),
-			),
+			want: []mock.Response{
+				{Error: errors.New("someerror")},
+				{Error: errors.New("someothererror")},
+				success, success, success,
+			},
 		},
 	}
 	for _, tt := range tests {
diff --git a/pkg/util/track_test.go b/pkg/util/track_test.go
new file mode 100644
index 0000000..f3612fb
--- /dev/null
+++ b/pkg/util/track_test.go
@@ -0,0 +1,71 @@
+// Licensed to Elasticsearch B.V. under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Elasticsearch B.V. licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package util
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/elastic/cloud-sdk-go/pkg/plan"
+	"github.com/elastic/cloud-sdk-go/pkg/plan/planutil"
+)
+
+func TestSetClusterTracking(t *testing.T) {
+	type args struct {
+		params planutil.TrackChangeParams
+		id     string
+		kind   string
+	}
+	tests := []struct {
+		name string
+		args args
+		want planutil.TrackChangeParams
+	}{
+		{
+			args: args{id: "123", kind: Elasticsearch},
+			want: planutil.TrackChangeParams{TrackChangeParams: plan.TrackChangeParams{
+				ResourceID:       "123",
+				Kind:             Elasticsearch,
+				IgnoreDownstream: true,
+			}},
+		},
+		{
+			args: args{id: "123", kind: Kibana},
+			want: planutil.TrackChangeParams{TrackChangeParams: plan.TrackChangeParams{
+				ResourceID:       "123",
+				Kind:             Kibana,
+				IgnoreDownstream: true,
+			}},
+		},
+		{
+			args: args{id: "123", kind: Apm},
+			want: planutil.TrackChangeParams{TrackChangeParams: plan.TrackChangeParams{
+				ResourceID:       "123",
+				Kind:             Apm,
+				IgnoreDownstream: true,
+			}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := SetClusterTracking(tt.args.params, tt.args.id, tt.args.kind); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("SetClusterTracking() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}