diff --git a/cmd/ecloud/ecloud_instance.go b/cmd/ecloud/ecloud_instance.go index b360159..fd16b07 100644 --- a/cmd/ecloud/ecloud_instance.go +++ b/cmd/ecloud/ecloud_instance.go @@ -356,7 +356,7 @@ func ecloudInstanceUnlock(service ecloud.ECloudService, cmd *cobra.Command, args } func ecloudInstanceStartCmd(f factory.ClientFactory) *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "start ...", Short: "Starts an instance", Long: "This command powers on one or more instances", @@ -370,13 +370,27 @@ func ecloudInstanceStartCmd(f factory.ClientFactory) *cobra.Command { }, RunE: ecloudCobraRunEFunc(f, ecloudInstanceStart), } + + cmd.Flags().Bool("wait", false, "Specifies that the command should wait until the instance power on task has been completed") + + return cmd } func ecloudInstanceStart(service ecloud.ECloudService, cmd *cobra.Command, args []string) error { for _, arg := range args { - err := service.PowerOnInstance(arg) + taskID, err := service.PowerOnInstance(arg) if err != nil { output.OutputWithErrorLevelf("Error starting instance [%s]: %s", arg, err) + continue + } + + waitFlag, _ := cmd.Flags().GetBool("wait") + if waitFlag { + err := helper.WaitForCommand(TaskStatusWaitFunc(service, taskID, ecloud.TaskStatusComplete)) + if err != nil { + output.OutputWithErrorLevelf("Error waiting for task to complete for instance [%s]: %s", arg, err) + continue + } } } return nil @@ -399,6 +413,7 @@ func ecloudInstanceStopCmd(f factory.ClientFactory) *cobra.Command { } cmd.Flags().Bool("force", false, "Specifies that instance should be forcefully powered off") + cmd.Flags().Bool("wait", false, "Specifies that the command should wait until the instance power off task has been completed") return cmd } @@ -407,15 +422,28 @@ func ecloudInstanceStop(service ecloud.ECloudService, cmd *cobra.Command, args [ force, _ := cmd.Flags().GetBool("force") for _, arg := range args { + var taskID string + var err error if force { - err := service.PowerOffInstance(arg) + taskID, err = service.PowerOffInstance(arg) if err != nil { output.OutputWithErrorLevelf("Error stopping instance [%s] (forced): %s", arg, err) + continue } } else { - err := service.PowerShutdownInstance(arg) + taskID, err = service.PowerShutdownInstance(arg) if err != nil { output.OutputWithErrorLevelf("Error stopping instance [%s]: %s", arg, err) + continue + } + } + + waitFlag, _ := cmd.Flags().GetBool("wait") + if waitFlag { + err := helper.WaitForCommand(TaskStatusWaitFunc(service, taskID, ecloud.TaskStatusComplete)) + if err != nil { + output.OutputWithErrorLevelf("Error waiting for task to complete for instance [%s]: %s", arg, err) + continue } } } @@ -439,6 +467,7 @@ func ecloudInstanceRestartCmd(f factory.ClientFactory) *cobra.Command { } cmd.Flags().Bool("force", false, "Specifies that instance should be forcefully reset") + cmd.Flags().Bool("wait", false, "Specifies that the command should wait until the instance restart task has been completed") return cmd } @@ -447,15 +476,28 @@ func ecloudInstanceRestart(service ecloud.ECloudService, cmd *cobra.Command, arg force, _ := cmd.Flags().GetBool("force") for _, arg := range args { + var taskID string + var err error if force { - err := service.PowerResetInstance(arg) + taskID, err = service.PowerResetInstance(arg) if err != nil { output.OutputWithErrorLevelf("Error restarting instance [%s] (forced): %s", arg, err) + continue } } else { - err := service.PowerRestartInstance(arg) + taskID, err = service.PowerRestartInstance(arg) if err != nil { output.OutputWithErrorLevelf("Error restarting instance [%s]: %s", arg, err) + continue + } + } + + waitFlag, _ := cmd.Flags().GetBool("wait") + if waitFlag { + err := helper.WaitForCommand(TaskStatusWaitFunc(service, taskID, ecloud.TaskStatusComplete)) + if err != nil { + output.OutputWithErrorLevelf("Error waiting for task to complete for instance [%s]: %s", arg, err) + continue } } } diff --git a/cmd/ecloud/ecloud_instance_test.go b/cmd/ecloud/ecloud_instance_test.go index 533b855..4e904f7 100644 --- a/cmd/ecloud/ecloud_instance_test.go +++ b/cmd/ecloud/ecloud_instance_test.go @@ -468,7 +468,7 @@ func Test_ecloudInstanceStart(t *testing.T) { defer mockCtrl.Finish() service := mocks.NewMockECloudService(mockCtrl) - service.EXPECT().PowerOnInstance("i-abcdef12").Return(nil) + service.EXPECT().PowerOnInstance("i-abcdef12").Return("task-abcdef12", nil) ecloudInstanceStart(service, &cobra.Command{}, []string{"i-abcdef12"}) }) @@ -479,19 +479,49 @@ func Test_ecloudInstanceStart(t *testing.T) { service := mocks.NewMockECloudService(mockCtrl) gomock.InOrder( - service.EXPECT().PowerOnInstance("i-abcdef12").Return(nil), - service.EXPECT().PowerOnInstance("i-12abcdef").Return(nil), + service.EXPECT().PowerOnInstance("i-abcdef12").Return("task-abcdef12", nil), + service.EXPECT().PowerOnInstance("i-12abcdef").Return("task-abcdef23", nil), ) ecloudInstanceStart(service, &cobra.Command{}, []string{"i-abcdef12", "i-12abcdef"}) }) + t.Run("WithWaitFlag_NoError_Succeeds", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + cmd := ecloudInstanceStartCmd(nil) + cmd.ParseFlags([]string{"--wait"}) + + service := mocks.NewMockECloudService(mockCtrl) + service.EXPECT().PowerOnInstance("i-abcdef12").Return("task-abcdef12", nil) + service.EXPECT().GetTask("task-abcdef12").Return(ecloud.Task{Status: ecloud.TaskStatusComplete}, nil) + + ecloudInstanceStart(service, cmd, []string{"i-abcdef12"}) + }) + + t.Run("WithWaitFlag_GetTaskError_ReturnsError", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + cmd := ecloudInstanceStartCmd(nil) + cmd.ParseFlags([]string{"--wait"}) + + service := mocks.NewMockECloudService(mockCtrl) + service.EXPECT().PowerOnInstance("i-abcdef12").Return("task-abcdef12", nil) + service.EXPECT().GetTask("task-abcdef12").Return(ecloud.Task{}, errors.New("test error")) + + test_output.AssertErrorOutput(t, "Error waiting for task to complete for instance [i-abcdef12]: Error waiting for command: Failed to retrieve task status: test error\n", func() { + ecloudInstanceStart(service, cmd, []string{"i-abcdef12"}) + }) + }) + t.Run("PowerOnInstanceError_OutputsError", func(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() service := mocks.NewMockECloudService(mockCtrl) - service.EXPECT().PowerOnInstance("i-abcdef12").Return(errors.New("test error")) + service.EXPECT().PowerOnInstance("i-abcdef12").Return("", errors.New("test error")) test_output.AssertErrorOutput(t, "Error starting instance [i-abcdef12]: test error\n", func() { ecloudInstanceStart(service, &cobra.Command{}, []string{"i-abcdef12"}) @@ -520,7 +550,7 @@ func Test_ecloudInstanceStop(t *testing.T) { defer mockCtrl.Finish() service := mocks.NewMockECloudService(mockCtrl) - service.EXPECT().PowerShutdownInstance("i-abcdef12").Return(nil) + service.EXPECT().PowerShutdownInstance("i-abcdef12").Return("task-abcdef12", nil) ecloudInstanceStop(service, &cobra.Command{}, []string{"i-abcdef12"}) }) @@ -530,7 +560,7 @@ func Test_ecloudInstanceStop(t *testing.T) { defer mockCtrl.Finish() service := mocks.NewMockECloudService(mockCtrl) - service.EXPECT().PowerOffInstance("i-abcdef12").Return(nil) + service.EXPECT().PowerOffInstance("i-abcdef12").Return("task-abcdef12", nil) cmd := ecloudInstanceStopCmd(nil) cmd.ParseFlags([]string{"--force"}) @@ -538,12 +568,42 @@ func Test_ecloudInstanceStop(t *testing.T) { ecloudInstanceStop(service, cmd, []string{"i-abcdef12"}) }) + t.Run("WithWaitFlag_NoError_Succeeds", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + cmd := ecloudInstanceStopCmd(nil) + cmd.ParseFlags([]string{"--wait"}) + + service := mocks.NewMockECloudService(mockCtrl) + service.EXPECT().PowerShutdownInstance("i-abcdef12").Return("task-abcdef12", nil) + service.EXPECT().GetTask("task-abcdef12").Return(ecloud.Task{Status: ecloud.TaskStatusComplete}, nil) + + ecloudInstanceStop(service, cmd, []string{"i-abcdef12"}) + }) + + t.Run("WithWaitFlag_GetTaskError_ReturnsError", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + cmd := ecloudInstanceStopCmd(nil) + cmd.ParseFlags([]string{"--wait"}) + + service := mocks.NewMockECloudService(mockCtrl) + service.EXPECT().PowerShutdownInstance("i-abcdef12").Return("task-abcdef12", nil) + service.EXPECT().GetTask("task-abcdef12").Return(ecloud.Task{}, errors.New("test error")) + + test_output.AssertErrorOutput(t, "Error waiting for task to complete for instance [i-abcdef12]: Error waiting for command: Failed to retrieve task status: test error\n", func() { + ecloudInstanceStop(service, cmd, []string{"i-abcdef12"}) + }) + }) + t.Run("PowerShutdownInstanceError_OutputsError", func(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() service := mocks.NewMockECloudService(mockCtrl) - service.EXPECT().PowerShutdownInstance("i-abcdef12").Return(errors.New("test error")).Times(1) + service.EXPECT().PowerShutdownInstance("i-abcdef12").Return("", errors.New("test error")).Times(1) test_output.AssertErrorOutput(t, "Error stopping instance [i-abcdef12]: test error\n", func() { ecloudInstanceStop(service, &cobra.Command{}, []string{"i-abcdef12"}) @@ -555,7 +615,7 @@ func Test_ecloudInstanceStop(t *testing.T) { defer mockCtrl.Finish() service := mocks.NewMockECloudService(mockCtrl) - service.EXPECT().PowerOffInstance("i-abcdef12").Return(errors.New("test error")) + service.EXPECT().PowerOffInstance("i-abcdef12").Return("", errors.New("test error")) cmd := ecloudInstanceStopCmd(nil) cmd.ParseFlags([]string{"--force"}) @@ -587,7 +647,7 @@ func Test_ecloudInstanceRestart(t *testing.T) { defer mockCtrl.Finish() service := mocks.NewMockECloudService(mockCtrl) - service.EXPECT().PowerRestartInstance("i-abcdef12").Return(nil) + service.EXPECT().PowerRestartInstance("i-abcdef12").Return("task-abcdef12", nil) ecloudInstanceRestart(service, &cobra.Command{}, []string{"i-abcdef12"}) }) @@ -597,7 +657,7 @@ func Test_ecloudInstanceRestart(t *testing.T) { defer mockCtrl.Finish() service := mocks.NewMockECloudService(mockCtrl) - service.EXPECT().PowerResetInstance("i-abcdef12").Return(nil) + service.EXPECT().PowerResetInstance("i-abcdef12").Return("task-abcdef12", nil) cmd := ecloudInstanceRestartCmd(nil) cmd.ParseFlags([]string{"--force"}) @@ -605,12 +665,42 @@ func Test_ecloudInstanceRestart(t *testing.T) { ecloudInstanceRestart(service, cmd, []string{"i-abcdef12"}) }) + t.Run("WithWaitFlag_NoError_Succeeds", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + cmd := ecloudInstanceRestartCmd(nil) + cmd.ParseFlags([]string{"--wait"}) + + service := mocks.NewMockECloudService(mockCtrl) + service.EXPECT().PowerRestartInstance("i-abcdef12").Return("task-abcdef12", nil) + service.EXPECT().GetTask("task-abcdef12").Return(ecloud.Task{Status: ecloud.TaskStatusComplete}, nil) + + ecloudInstanceRestart(service, cmd, []string{"i-abcdef12"}) + }) + + t.Run("WithWaitFlag_GetTaskError_ReturnsError", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + cmd := ecloudInstanceRestartCmd(nil) + cmd.ParseFlags([]string{"--wait"}) + + service := mocks.NewMockECloudService(mockCtrl) + service.EXPECT().PowerRestartInstance("i-abcdef12").Return("task-abcdef12", nil) + service.EXPECT().GetTask("task-abcdef12").Return(ecloud.Task{}, errors.New("test error")) + + test_output.AssertErrorOutput(t, "Error waiting for task to complete for instance [i-abcdef12]: Error waiting for command: Failed to retrieve task status: test error\n", func() { + ecloudInstanceRestart(service, cmd, []string{"i-abcdef12"}) + }) + }) + t.Run("PowerRestartInstanceError_OutputsError", func(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() service := mocks.NewMockECloudService(mockCtrl) - service.EXPECT().PowerRestartInstance("i-abcdef12").Return(errors.New("test error")).Times(1) + service.EXPECT().PowerRestartInstance("i-abcdef12").Return("", errors.New("test error")).Times(1) test_output.AssertErrorOutput(t, "Error restarting instance [i-abcdef12]: test error\n", func() { ecloudInstanceRestart(service, &cobra.Command{}, []string{"i-abcdef12"}) @@ -622,7 +712,7 @@ func Test_ecloudInstanceRestart(t *testing.T) { defer mockCtrl.Finish() service := mocks.NewMockECloudService(mockCtrl) - service.EXPECT().PowerResetInstance("i-abcdef12").Return(errors.New("test error")) + service.EXPECT().PowerResetInstance("i-abcdef12").Return("", errors.New("test error")) cmd := ecloudInstanceRestartCmd(nil) cmd.ParseFlags([]string{"--force"}) diff --git a/go.mod b/go.mod index 45abfa9..5acbc33 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/spf13/cobra v0.0.5 github.com/spf13/viper v1.3.2 github.com/stretchr/testify v1.6.1 - github.com/ukfast/sdk-go v1.4.8 + github.com/ukfast/sdk-go v1.4.9 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b // indirect gopkg.in/go-playground/assert.v1 v1.2.1 k8s.io/client-go v11.0.0+incompatible diff --git a/go.sum b/go.sum index ce23d61..ecc2901 100644 --- a/go.sum +++ b/go.sum @@ -101,8 +101,8 @@ github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4U github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ukfast/go-durationstring v1.0.0 h1:kgPuA7XjLjgLDfkG8j0MpolxcZh/eMdiVoOIFD/uc5I= github.com/ukfast/go-durationstring v1.0.0/go.mod h1:Ci81n51kfxlKUIaLY9cINIKRO94VTqV+iCGbOMTb0V8= -github.com/ukfast/sdk-go v1.4.8 h1:ROAh2r3G5XKb9uoBHvoHOaOpTr8xspAOt68TAz0b7ms= -github.com/ukfast/sdk-go v1.4.8/go.mod h1:tspweEP77MHhVEYgEEieKAKGITFgwkYl1q5fLh4HZAo= +github.com/ukfast/sdk-go v1.4.9 h1:MwxQE76YkBVWeAaM5TSKpyAQgJfm0dGYHky9HgTVzh0= +github.com/ukfast/sdk-go v1.4.9/go.mod h1:tspweEP77MHhVEYgEEieKAKGITFgwkYl1q5fLh4HZAo= github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= diff --git a/test/mocks/mock_ecloudservice.go b/test/mocks/mock_ecloudservice.go index 3233eaf..c9b9cb5 100644 --- a/test/mocks/mock_ecloudservice.go +++ b/test/mocks/mock_ecloudservice.go @@ -3878,11 +3878,12 @@ func (mr *MockECloudServiceMockRecorder) PodConsoleAvailable(arg0 interface{}) * } // PowerOffInstance mocks base method -func (m *MockECloudService) PowerOffInstance(arg0 string) error { +func (m *MockECloudService) PowerOffInstance(arg0 string) (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PowerOffInstance", arg0) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 } // PowerOffInstance indicates an expected call of PowerOffInstance @@ -3906,11 +3907,12 @@ func (mr *MockECloudServiceMockRecorder) PowerOffVirtualMachine(arg0 interface{} } // PowerOnInstance mocks base method -func (m *MockECloudService) PowerOnInstance(arg0 string) error { +func (m *MockECloudService) PowerOnInstance(arg0 string) (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PowerOnInstance", arg0) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 } // PowerOnInstance indicates an expected call of PowerOnInstance @@ -3934,11 +3936,12 @@ func (mr *MockECloudServiceMockRecorder) PowerOnVirtualMachine(arg0 interface{}) } // PowerResetInstance mocks base method -func (m *MockECloudService) PowerResetInstance(arg0 string) error { +func (m *MockECloudService) PowerResetInstance(arg0 string) (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PowerResetInstance", arg0) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 } // PowerResetInstance indicates an expected call of PowerResetInstance @@ -3962,11 +3965,12 @@ func (mr *MockECloudServiceMockRecorder) PowerResetVirtualMachine(arg0 interface } // PowerRestartInstance mocks base method -func (m *MockECloudService) PowerRestartInstance(arg0 string) error { +func (m *MockECloudService) PowerRestartInstance(arg0 string) (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PowerRestartInstance", arg0) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 } // PowerRestartInstance indicates an expected call of PowerRestartInstance @@ -3990,11 +3994,12 @@ func (mr *MockECloudServiceMockRecorder) PowerRestartVirtualMachine(arg0 interfa } // PowerShutdownInstance mocks base method -func (m *MockECloudService) PowerShutdownInstance(arg0 string) error { +func (m *MockECloudService) PowerShutdownInstance(arg0 string) (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PowerShutdownInstance", arg0) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 } // PowerShutdownInstance indicates an expected call of PowerShutdownInstance