diff --git a/clierrors/errors.go b/clierrors/errors.go index 0085d1711c..44e7368c05 100644 --- a/clierrors/errors.go +++ b/clierrors/errors.go @@ -10,6 +10,9 @@ var ( ErrLPVersionNotPassed = "Launch plan version not passed\n" //nolint ErrFailedLPUpdate = "Launch plan %v failed to get updated due to %v\n" + ErrExecutionNotPassed = "Execution name not passed\n" + ErrFailedExecutionUpdate = "Execution %v failed to get updated due to %v\n" + ErrWorkflowNotPassed = "Workflow name not passed\n" ErrFailedWorkflowUpdate = "Workflow %v failed to get updated to due to %v\n" diff --git a/cmd/config/subcommand/execution/update_config.go b/cmd/config/subcommand/execution/update_config.go new file mode 100644 index 0000000000..8623435784 --- /dev/null +++ b/cmd/config/subcommand/execution/update_config.go @@ -0,0 +1,13 @@ +package execution + +//go:generate pflags UpdateConfig --default-var UConfig --bind-default-var +var ( + UConfig = &UpdateConfig{} +) + +// UpdateConfig +type UpdateConfig struct { + Archive bool `json:"archive" pflag:",archive execution."` + Activate bool `json:"activate" pflag:",activate execution."` + DryRun bool `json:"dryRun" pflag:",execute command without making any modifications."` +} diff --git a/cmd/config/subcommand/execution/updateconfig_flags.go b/cmd/config/subcommand/execution/updateconfig_flags.go new file mode 100755 index 0000000000..03c9d0f90b --- /dev/null +++ b/cmd/config/subcommand/execution/updateconfig_flags.go @@ -0,0 +1,57 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots. + +package execution + +import ( + "encoding/json" + "reflect" + + "fmt" + + "github.com/spf13/pflag" +) + +// If v is a pointer, it will get its element value or the zero value of the element type. +// If v is not a pointer, it will return it as is. +func (UpdateConfig) elemValueOrNil(v interface{}) interface{} { + if t := reflect.TypeOf(v); t.Kind() == reflect.Ptr { + if reflect.ValueOf(v).IsNil() { + return reflect.Zero(t.Elem()).Interface() + } else { + return reflect.ValueOf(v).Interface() + } + } else if v == nil { + return reflect.Zero(t).Interface() + } + + return v +} + +func (UpdateConfig) mustJsonMarshal(v interface{}) string { + raw, err := json.Marshal(v) + if err != nil { + panic(err) + } + + return string(raw) +} + +func (UpdateConfig) mustMarshalJSON(v json.Marshaler) string { + raw, err := v.MarshalJSON() + if err != nil { + panic(err) + } + + return string(raw) +} + +// GetPFlagSet will return strongly types pflags for all fields in UpdateConfig and its nested types. The format of the +// flags is json-name.json-sub-name... etc. +func (cfg UpdateConfig) GetPFlagSet(prefix string) *pflag.FlagSet { + cmdFlags := pflag.NewFlagSet("UpdateConfig", pflag.ExitOnError) + cmdFlags.BoolVar(&UConfig.Archive, fmt.Sprintf("%v%v", prefix, "archive"), UConfig.Archive, "archive execution.") + cmdFlags.BoolVar(&UConfig.Activate, fmt.Sprintf("%v%v", prefix, "activate"), UConfig.Activate, "activate execution.") + cmdFlags.BoolVar(&UConfig.DryRun, fmt.Sprintf("%v%v", prefix, "dryRun"), UConfig.DryRun, "execute command without making any modifications.") + return cmdFlags +} diff --git a/cmd/config/subcommand/execution/updateconfig_flags_test.go b/cmd/config/subcommand/execution/updateconfig_flags_test.go new file mode 100755 index 0000000000..6c65c2d14b --- /dev/null +++ b/cmd/config/subcommand/execution/updateconfig_flags_test.go @@ -0,0 +1,144 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots. + +package execution + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + "testing" + + "github.com/mitchellh/mapstructure" + "github.com/stretchr/testify/assert" +) + +var dereferencableKindsUpdateConfig = map[reflect.Kind]struct{}{ + reflect.Array: {}, reflect.Chan: {}, reflect.Map: {}, reflect.Ptr: {}, reflect.Slice: {}, +} + +// Checks if t is a kind that can be dereferenced to get its underlying type. +func canGetElementUpdateConfig(t reflect.Kind) bool { + _, exists := dereferencableKindsUpdateConfig[t] + return exists +} + +// This decoder hook tests types for json unmarshaling capability. If implemented, it uses json unmarshal to build the +// object. Otherwise, it'll just pass on the original data. +func jsonUnmarshalerHookUpdateConfig(_, to reflect.Type, data interface{}) (interface{}, error) { + unmarshalerType := reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() + if to.Implements(unmarshalerType) || reflect.PtrTo(to).Implements(unmarshalerType) || + (canGetElementUpdateConfig(to.Kind()) && to.Elem().Implements(unmarshalerType)) { + + raw, err := json.Marshal(data) + if err != nil { + fmt.Printf("Failed to marshal Data: %v. Error: %v. Skipping jsonUnmarshalHook", data, err) + return data, nil + } + + res := reflect.New(to).Interface() + err = json.Unmarshal(raw, &res) + if err != nil { + fmt.Printf("Failed to umarshal Data: %v. Error: %v. Skipping jsonUnmarshalHook", data, err) + return data, nil + } + + return res, nil + } + + return data, nil +} + +func decode_UpdateConfig(input, result interface{}) error { + config := &mapstructure.DecoderConfig{ + TagName: "json", + WeaklyTypedInput: true, + Result: result, + DecodeHook: mapstructure.ComposeDecodeHookFunc( + mapstructure.StringToTimeDurationHookFunc(), + mapstructure.StringToSliceHookFunc(","), + jsonUnmarshalerHookUpdateConfig, + ), + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +func join_UpdateConfig(arr interface{}, sep string) string { + listValue := reflect.ValueOf(arr) + strs := make([]string, 0, listValue.Len()) + for i := 0; i < listValue.Len(); i++ { + strs = append(strs, fmt.Sprintf("%v", listValue.Index(i))) + } + + return strings.Join(strs, sep) +} + +func testDecodeJson_UpdateConfig(t *testing.T, val, result interface{}) { + assert.NoError(t, decode_UpdateConfig(val, result)) +} + +func testDecodeRaw_UpdateConfig(t *testing.T, vStringSlice, result interface{}) { + assert.NoError(t, decode_UpdateConfig(vStringSlice, result)) +} + +func TestUpdateConfig_GetPFlagSet(t *testing.T) { + val := UpdateConfig{} + cmdFlags := val.GetPFlagSet("") + assert.True(t, cmdFlags.HasFlags()) +} + +func TestUpdateConfig_SetFlags(t *testing.T) { + actual := UpdateConfig{} + cmdFlags := actual.GetPFlagSet("") + assert.True(t, cmdFlags.HasFlags()) + + t.Run("Test_archive", func(t *testing.T) { + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("archive", testValue) + if vBool, err := cmdFlags.GetBool("archive"); err == nil { + testDecodeJson_UpdateConfig(t, fmt.Sprintf("%v", vBool), &actual.Archive) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_activate", func(t *testing.T) { + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("activate", testValue) + if vBool, err := cmdFlags.GetBool("activate"); err == nil { + testDecodeJson_UpdateConfig(t, fmt.Sprintf("%v", vBool), &actual.Activate) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_dryRun", func(t *testing.T) { + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("dryRun", testValue) + if vBool, err := cmdFlags.GetBool("dryRun"); err == nil { + testDecodeJson_UpdateConfig(t, fmt.Sprintf("%v", vBool), &actual.DryRun) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) +} diff --git a/cmd/update/execution.go b/cmd/update/execution.go new file mode 100644 index 0000000000..a91653afb6 --- /dev/null +++ b/cmd/update/execution.go @@ -0,0 +1,73 @@ +package update + +import ( + "context" + "fmt" + + "github.com/flyteorg/flytectl/clierrors" + "github.com/flyteorg/flytectl/cmd/config" + "github.com/flyteorg/flytectl/cmd/config/subcommand/execution" + cmdCore "github.com/flyteorg/flytectl/cmd/core" + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/admin" + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/core" + "github.com/flyteorg/flytestdlib/logger" +) + +const ( + updateExecutionShort = "Update execution status" + updateExecutionLong = ` +Activating an execution shows it in the cli and UI: +:: + + flytectl update execution -p flytectldemo -d development oeh94k9r2r --activate + +Archiving execution hides it from cli and UI: +:: + + flytectl update execution -p flytectldemo -d development oeh94k9r2r --archive + + +Usage +` +) + +func updateExecutionFunc(ctx context.Context, args []string, cmdCtx cmdCore.CommandContext) error { + project := config.GetConfig().Project + domain := config.GetConfig().Domain + if len(args) != 1 { + return fmt.Errorf(clierrors.ErrExecutionNotPassed) + } + executionName := args[0] + activateExec := execution.UConfig.Activate + archiveExec := execution.UConfig.Archive + if activateExec && archiveExec { + return fmt.Errorf(clierrors.ErrInvalidStateUpdate) + } + + var executionState admin.ExecutionStatus_ExecutionState + if activateExec { + executionState = admin.ExecutionStatus_EXECUTION_ACTIVE + } else if archiveExec { + executionState = admin.ExecutionStatus_EXECUTION_ARCHIVED + } + + if execution.UConfig.DryRun { + logger.Debugf(ctx, "skipping UpdateExecution request (DryRun)") + } else { + _, err := cmdCtx.AdminClient().UpdateExecution(ctx, &admin.ExecutionUpdateRequest{ + Id: &core.WorkflowExecutionIdentifier{ + Project: project, + Domain: domain, + Name: executionName, + }, + Status: &admin.ExecutionStatus{State: executionState}, + }) + if err != nil { + fmt.Printf(clierrors.ErrFailedExecutionUpdate, executionName, err) + return err + } + } + fmt.Printf("updated execution %s successfully to state %s\n", executionName, executionState.String()) + + return nil +} diff --git a/cmd/update/execution_test.go b/cmd/update/execution_test.go new file mode 100644 index 0000000000..2f7e5423ba --- /dev/null +++ b/cmd/update/execution_test.go @@ -0,0 +1,71 @@ +package update + +import ( + "fmt" + "testing" + + "github.com/flyteorg/flytectl/cmd/config/subcommand/execution" + "github.com/flyteorg/flytectl/cmd/testutils" + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/admin" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func UpdateExecutionSetup() { + ctx = testutils.Ctx + cmdCtx = testutils.CmdCtx + mockClient = testutils.MockClient +} + +func TestExecutionUpdate(t *testing.T) { + testutils.Setup() + UpdateExecutionSetup() + args = []string{"execution1"} + // Activate + execution.UConfig.Activate = true + mockClient.OnUpdateExecutionMatch(mock.Anything, mock.Anything).Return(&admin.ExecutionUpdateResponse{}, nil) + assert.Nil(t, updateExecutionFunc(ctx, args, cmdCtx)) + // Archive + execution.UConfig.Activate = false + execution.UConfig.Archive = true + assert.Nil(t, updateExecutionFunc(ctx, args, cmdCtx)) + // Reset + execution.UConfig.Activate = false + execution.UConfig.Archive = false + + // Dry run + execution.UConfig.DryRun = true + assert.Nil(t, updateExecutionFunc(ctx, args, cmdCtx)) + mockClient.AssertNotCalled(t, "UpdateExecution", mock.Anything) + + // Reset + execution.UConfig.DryRun = false +} + +func TestExecutionUpdateValidationFailure(t *testing.T) { + testutils.Setup() + UpdateExecutionSetup() + args = []string{"execution1"} + execution.UConfig.Activate = true + execution.UConfig.Archive = true + assert.NotNil(t, updateExecutionFunc(ctx, args, cmdCtx)) + // Reset + execution.UConfig.Activate = false + execution.UConfig.Archive = false +} + +func TestExecutionUpdateFail(t *testing.T) { + testutils.Setup() + UpdateExecutionSetup() + args = []string{"execution1"} + mockClient.OnUpdateExecutionMatch(mock.Anything, mock.Anything).Return(nil, fmt.Errorf("failed to update")) + assert.NotNil(t, updateExecutionFunc(ctx, args, cmdCtx)) +} + +func TestExecutionUpdateInvalidArgs(t *testing.T) { + testutils.Setup() + UpdateExecutionSetup() + args = []string{} + assert.NotNil(t, updateExecutionFunc(ctx, args, cmdCtx)) +} diff --git a/cmd/update/update.go b/cmd/update/update.go index 60603a7dbe..57a391b2a6 100644 --- a/cmd/update/update.go +++ b/cmd/update/update.go @@ -2,6 +2,7 @@ package update import ( "github.com/flyteorg/flytectl/cmd/config/subcommand/clusterresourceattribute" + "github.com/flyteorg/flytectl/cmd/config/subcommand/execution" "github.com/flyteorg/flytectl/cmd/config/subcommand/executionclusterlabel" "github.com/flyteorg/flytectl/cmd/config/subcommand/executionqueueattribute" "github.com/flyteorg/flytectl/cmd/config/subcommand/launchplan" @@ -41,6 +42,8 @@ func CreateUpdateCommand() *cobra.Command { Short: updateLPMetaShort, Long: updateLPMetaLong}, "project": {CmdFunc: updateProjectsFunc, Aliases: []string{}, ProjectDomainNotRequired: true, PFlagProvider: DefaultProjectConfig, Short: projectShort, Long: projectLong}, + "execution": {CmdFunc: updateExecutionFunc, Aliases: []string{}, ProjectDomainNotRequired: false, PFlagProvider: execution.UConfig, + Short: updateExecutionShort, Long: updateExecutionLong}, "task-meta": {CmdFunc: updateTaskFunc, Aliases: []string{}, ProjectDomainNotRequired: false, PFlagProvider: namedEntityConfig, Short: updateTaskShort, Long: updateTaskLong}, "workflow-meta": {CmdFunc: updateWorkflowFunc, Aliases: []string{}, ProjectDomainNotRequired: false, PFlagProvider: namedEntityConfig, diff --git a/cmd/update/update_test.go b/cmd/update/update_test.go index 8cf80f3f73..b289b0a4cd 100644 --- a/cmd/update/update_test.go +++ b/cmd/update/update_test.go @@ -32,18 +32,18 @@ func TestUpdateCommand(t *testing.T) { assert.Equal(t, updateCommand.Use, updateUse) assert.Equal(t, updateCommand.Short, updateShort) assert.Equal(t, updateCommand.Long, updatecmdLong) - assert.Equal(t, len(updateCommand.Commands()), 11) + assert.Equal(t, len(updateCommand.Commands()), 12) cmdNouns := updateCommand.Commands() // Sort by Use value. sort.Slice(cmdNouns, func(i, j int) bool { return cmdNouns[i].Use < cmdNouns[j].Use }) - useArray := []string{"cluster-resource-attribute", "execution-cluster-label", "execution-queue-attribute", "launchplan", + useArray := []string{"cluster-resource-attribute", "execution", "execution-cluster-label", "execution-queue-attribute", "launchplan", "launchplan-meta", "plugin-override", "project", "task-meta", "task-resource-attribute", "workflow-execution-config", "workflow-meta"} - aliases := [][]string{{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}} - shortArray := []string{clusterResourceAttributesShort, executionClusterLabelShort, executionQueueAttributesShort, updateLPShort, updateLPMetaShort, + aliases := [][]string{{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}} + shortArray := []string{clusterResourceAttributesShort, updateExecutionShort, executionClusterLabelShort, executionQueueAttributesShort, updateLPShort, updateLPMetaShort, pluginOverrideShort, projectShort, updateTaskShort, taskResourceAttributesShort, workflowExecutionConfigShort, updateWorkflowShort} - longArray := []string{clusterResourceAttributesLong, executionClusterLabelLong, executionQueueAttributesLong, updateLPLong, updateLPMetaLong, + longArray := []string{clusterResourceAttributesLong, updateExecutionLong, executionClusterLabelLong, executionQueueAttributesLong, updateLPLong, updateLPMetaLong, pluginOverrideLong, projectLong, updateTaskLong, taskResourceAttributesLong, workflowExecutionConfigLong, updateWorkflowLong} for i := range cmdNouns { assert.Equal(t, cmdNouns[i].Use, useArray[i]) diff --git a/go.mod b/go.mod index 19376fa17a..16f5ab8964 100644 --- a/go.mod +++ b/go.mod @@ -55,3 +55,5 @@ require ( k8s.io/client-go v0.21.3 sigs.k8s.io/yaml v1.2.0 ) + +replace github.com/flyteorg/flyteidl => github.com/flyteorg/flyteidl v0.21.20-0.20220111070000-bdd241a81330 diff --git a/go.sum b/go.sum index 9445a185c3..a32df294d5 100644 --- a/go.sum +++ b/go.sum @@ -354,8 +354,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/flyteorg/flyteidl v0.21.14 h1:uOc3u0DFkFPag5fOQ8LMmMBBZqHy19ikxShnRlRAzZ0= -github.com/flyteorg/flyteidl v0.21.14/go.mod h1:576W2ViEyjTpT+kEVHAGbrTP3HARNUZ/eCwrNPmdx9U= +github.com/flyteorg/flyteidl v0.21.20-0.20220111070000-bdd241a81330 h1:pHiSSq3bVs9bKTc2SVnd7oLLuEn4uyXmx54bZqGZJ5M= +github.com/flyteorg/flyteidl v0.21.20-0.20220111070000-bdd241a81330/go.mod h1:576W2ViEyjTpT+kEVHAGbrTP3HARNUZ/eCwrNPmdx9U= github.com/flyteorg/flytestdlib v0.3.13/go.mod h1:Tz8JCECAbX6VWGwFT6cmEQ+RJpZ/6L9pswu3fzWs220= github.com/flyteorg/flytestdlib v0.4.0 h1:cEMkNfjocCuBSLzM9tKjsODhkr5gXTZAGl6k62FrT60= github.com/flyteorg/flytestdlib v0.4.0/go.mod h1:7cDWkY3v7xsoesFcDdu6DSW5Q2U2W5KlHUbUHSwBG1Q=