diff --git a/flytectl/cmd/config/subcommand/docker/config_flags.go b/flytectl/cmd/config/subcommand/docker/config_flags.go new file mode 100644 index 0000000000..6c2a9bcea5 --- /dev/null +++ b/flytectl/cmd/config/subcommand/docker/config_flags.go @@ -0,0 +1,55 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots. + +package docker + +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 (Config) 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 (Config) mustJsonMarshal(v interface{}) string { + raw, err := json.Marshal(v) + if err != nil { + panic(err) + } + + return string(raw) +} + +func (Config) 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 Config and its nested types. The format of the +// flags is json-name.json-sub-name... etc. +func (cfg Config) GetPFlagSet(prefix string) *pflag.FlagSet { + cmdFlags := pflag.NewFlagSet("Config", pflag.ExitOnError) + cmdFlags.BoolVar(&DefaultConfig.Force, fmt.Sprintf("%v%v", prefix, "force"), DefaultConfig.Force, "Optional. Forcefully delete existing sandbox cluster if it exists.") + return cmdFlags +} diff --git a/flytectl/cmd/config/subcommand/docker/config_flags_test.go b/flytectl/cmd/config/subcommand/docker/config_flags_test.go new file mode 100644 index 0000000000..e1efe4a644 --- /dev/null +++ b/flytectl/cmd/config/subcommand/docker/config_flags_test.go @@ -0,0 +1,116 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots. + +package docker + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + "testing" + + "github.com/mitchellh/mapstructure" + "github.com/stretchr/testify/assert" +) + +var dereferencableKindsConfig = 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 canGetElementConfig(t reflect.Kind) bool { + _, exists := dereferencableKindsConfig[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 jsonUnmarshalerHookConfig(_, to reflect.Type, data interface{}) (interface{}, error) { + unmarshalerType := reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() + if to.Implements(unmarshalerType) || reflect.PtrTo(to).Implements(unmarshalerType) || + (canGetElementConfig(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_Config(input, result interface{}) error { + config := &mapstructure.DecoderConfig{ + TagName: "json", + WeaklyTypedInput: true, + Result: result, + DecodeHook: mapstructure.ComposeDecodeHookFunc( + mapstructure.StringToTimeDurationHookFunc(), + mapstructure.StringToSliceHookFunc(","), + jsonUnmarshalerHookConfig, + ), + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +func join_Config(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_Config(t *testing.T, val, result interface{}) { + assert.NoError(t, decode_Config(val, result)) +} + +func testDecodeRaw_Config(t *testing.T, vStringSlice, result interface{}) { + assert.NoError(t, decode_Config(vStringSlice, result)) +} + +func TestConfig_GetPFlagSet(t *testing.T) { + val := Config{} + cmdFlags := val.GetPFlagSet("") + assert.True(t, cmdFlags.HasFlags()) +} + +func TestConfig_SetFlags(t *testing.T) { + actual := Config{} + cmdFlags := actual.GetPFlagSet("") + assert.True(t, cmdFlags.HasFlags()) + + t.Run("Test_force", func(t *testing.T) { + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("force", testValue) + if vBool, err := cmdFlags.GetBool("force"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vBool), &actual.Force) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) +} diff --git a/flytectl/cmd/config/subcommand/docker/docker_config.go b/flytectl/cmd/config/subcommand/docker/docker_config.go new file mode 100644 index 0000000000..17d8c78157 --- /dev/null +++ b/flytectl/cmd/config/subcommand/docker/docker_config.go @@ -0,0 +1,13 @@ +package docker + +//go:generate pflags Config --default-var DefaultConfig --bind-default-var +var ( + DefaultConfig = &Config{ + Force: false, + } +) + +// Configs +type Config struct { + Force bool `json:"force" pflag:",Optional. Forcefully delete existing sandbox cluster if it exists."` +} diff --git a/flytectl/cmd/config/subcommand/sandbox/config_flags.go b/flytectl/cmd/config/subcommand/sandbox/config_flags.go index 39b9ba1ba1..32e1423057 100755 --- a/flytectl/cmd/config/subcommand/sandbox/config_flags.go +++ b/flytectl/cmd/config/subcommand/sandbox/config_flags.go @@ -61,5 +61,6 @@ func (cfg Config) GetPFlagSet(prefix string) *pflag.FlagSet { cmdFlags.StringVar(&DefaultConfig.ImagePullOptions.Platform, fmt.Sprintf("%v%v", prefix, "imagePullOptions.platform"), DefaultConfig.ImagePullOptions.Platform, "Forces a specific platform's image to be pulled.'") cmdFlags.BoolVar(&DefaultConfig.Dev, fmt.Sprintf("%v%v", prefix, "dev"), DefaultConfig.Dev, "Optional. Only start minio and postgres in the sandbox.") cmdFlags.BoolVar(&DefaultConfig.DryRun, fmt.Sprintf("%v%v", prefix, "dryRun"), DefaultConfig.DryRun, "Optional. Only print the docker commands to bring up flyte sandbox/demo container.This will still call github api's to get the latest flyte release to use'") + cmdFlags.BoolVar(&DefaultConfig.Force, fmt.Sprintf("%v%v", prefix, "force"), DefaultConfig.Force, "Optional. Forcefully delete existing sandbox cluster if it exists.") return cmdFlags } diff --git a/flytectl/cmd/config/subcommand/sandbox/config_flags_test.go b/flytectl/cmd/config/subcommand/sandbox/config_flags_test.go index 37e2fc2ab7..8519a75583 100755 --- a/flytectl/cmd/config/subcommand/sandbox/config_flags_test.go +++ b/flytectl/cmd/config/subcommand/sandbox/config_flags_test.go @@ -251,4 +251,18 @@ func TestConfig_SetFlags(t *testing.T) { } }) }) + t.Run("Test_force", func(t *testing.T) { + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("force", testValue) + if vBool, err := cmdFlags.GetBool("force"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vBool), &actual.Force) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) } diff --git a/flytectl/cmd/config/subcommand/sandbox/sandbox_config.go b/flytectl/cmd/config/subcommand/sandbox/sandbox_config.go index b1fa698541..f566de0118 100644 --- a/flytectl/cmd/config/subcommand/sandbox/sandbox_config.go +++ b/flytectl/cmd/config/subcommand/sandbox/sandbox_config.go @@ -34,6 +34,8 @@ type Config struct { // It's used for development. Users are able to start flyte locally via single binary and save the data to the minio or postgres in the sandbox. Dev bool `json:"dev" pflag:",Optional. Only start minio and postgres in the sandbox."` DryRun bool `json:"dryRun" pflag:",Optional. Only print the docker commands to bring up flyte sandbox/demo container.This will still call github api's to get the latest flyte release to use'"` + + Force bool `json:"force" pflag:",Optional. Forcefully delete existing sandbox cluster if it exists."` } //go:generate pflags Config --default-var DefaultConfig --bind-default-var diff --git a/flytectl/pkg/docker/docker_config.go b/flytectl/pkg/docker/docker_config.go new file mode 100644 index 0000000000..a3453d8012 --- /dev/null +++ b/flytectl/pkg/docker/docker_config.go @@ -0,0 +1,12 @@ +package docker + +// Config holds configuration flags for docker command. +var ( + DefaultConfig = &Config{ + Force: false, + } +) + +type Config struct { + Force bool `json:"force" pflag:",Optional. Forcefully delete existing sandbox cluster if it exists."` +} diff --git a/flytectl/pkg/docker/docker_util.go b/flytectl/pkg/docker/docker_util.go index a4e768bb9b..77475f8667 100644 --- a/flytectl/pkg/docker/docker_util.go +++ b/flytectl/pkg/docker/docker_util.go @@ -12,8 +12,6 @@ import ( "github.com/docker/docker/client" "github.com/enescakir/emoji" - "github.com/flyteorg/flytectl/clierrors" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" @@ -21,6 +19,8 @@ import ( "github.com/docker/docker/api/types/volume" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/go-connections/nat" + "github.com/flyteorg/flytectl/clierrors" + "github.com/flyteorg/flytectl/cmd/config/subcommand/docker" cmdUtil "github.com/flyteorg/flytectl/pkg/commandutils" f "github.com/flyteorg/flytectl/pkg/filesystemutils" ) @@ -93,8 +93,9 @@ func RemoveSandbox(ctx context.Context, cli Docker, reader io.Reader) error { if err != nil { return err } + if c != nil { - if cmdUtil.AskForConfirmation("delete existing sandbox cluster", reader) { + if docker.DefaultConfig.Force || cmdUtil.AskForConfirmation("delete existing sandbox cluster", reader) { err := cli.ContainerRemove(context.Background(), c.ID, types.ContainerRemoveOptions{ Force: true, }) diff --git a/flytectl/pkg/docker/docker_util_test.go b/flytectl/pkg/docker/docker_util_test.go index 83e5bc9703..32ad1cda12 100644 --- a/flytectl/pkg/docker/docker_util_test.go +++ b/flytectl/pkg/docker/docker_util_test.go @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/docker/docker/api/types" + "github.com/flyteorg/flytectl/cmd/config/subcommand/docker" "github.com/stretchr/testify/assert" ) @@ -88,6 +89,10 @@ func TestRemoveSandboxWithNoReply(t *testing.T) { mockDocker.OnContainerRemove(ctx, mock.Anything, types.ContainerRemoveOptions{Force: true}).Return(nil) err := RemoveSandbox(ctx, mockDocker, strings.NewReader("n")) assert.NotNil(t, err) + + docker.DefaultConfig.Force = true + err = RemoveSandbox(ctx, mockDocker, strings.NewReader("")) + assert.Nil(t, err) }) t.Run("Successfully remove sandbox container with zero sandbox containers are running", func(t *testing.T) { diff --git a/flytectl/pkg/sandbox/start.go b/flytectl/pkg/sandbox/start.go index 7a9551804b..6fb649ab7b 100644 --- a/flytectl/pkg/sandbox/start.go +++ b/flytectl/pkg/sandbox/start.go @@ -14,6 +14,7 @@ import ( "github.com/docker/go-connections/nat" "github.com/enescakir/emoji" "github.com/flyteorg/flytectl/clierrors" + dockerCmdConfig "github.com/flyteorg/flytectl/cmd/config/subcommand/docker" sandboxCmdConfig "github.com/flyteorg/flytectl/cmd/config/subcommand/sandbox" "github.com/flyteorg/flytectl/pkg/configutil" "github.com/flyteorg/flytectl/pkg/docker" @@ -157,6 +158,7 @@ func startSandbox(ctx context.Context, cli docker.Docker, g github.GHRepoService if sandboxConfig.DryRun { docker.PrintRemoveContainer(docker.FlyteSandboxClusterName) } else { + dockerCmdConfig.DefaultConfig.Force = sandboxConfig.Force if err := docker.RemoveSandbox(ctx, cli, reader); err != nil { if err.Error() != clierrors.ErrSandboxExists { return nil, err