diff --git a/operator/internal/cli/cli.go b/operator/cmd/cli/app/cli.go similarity index 95% rename from operator/internal/cli/cli.go rename to operator/cmd/cli/app/cli.go index 7ae7ebe9..9c471a61 100644 --- a/operator/internal/cli/cli.go +++ b/operator/cmd/cli/app/cli.go @@ -16,16 +16,16 @@ * limitations under the License. */ -package cli +package app import ( "fmt" "github.com/spf13/cobra" + "github.com/NVIDIA/skyhook/operator/cmd/cli/app/node" + pkg "github.com/NVIDIA/skyhook/operator/cmd/cli/app/package" "github.com/NVIDIA/skyhook/operator/internal/cli/context" - "github.com/NVIDIA/skyhook/operator/internal/cli/node" - pkg "github.com/NVIDIA/skyhook/operator/internal/cli/package" internalVersion "github.com/NVIDIA/skyhook/operator/internal/version" ) diff --git a/operator/internal/cli/cli_test.go b/operator/cmd/cli/app/cli_test.go similarity index 99% rename from operator/internal/cli/cli_test.go rename to operator/cmd/cli/app/cli_test.go index a30c0c38..321750d9 100644 --- a/operator/internal/cli/cli_test.go +++ b/operator/cmd/cli/app/cli_test.go @@ -16,7 +16,7 @@ * limitations under the License. */ -package cli +package app import ( "bytes" diff --git a/operator/cmd/cli/app/lifecycle.go b/operator/cmd/cli/app/lifecycle.go new file mode 100644 index 00000000..5ac3986c --- /dev/null +++ b/operator/cmd/cli/app/lifecycle.go @@ -0,0 +1,175 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * + * Licensed 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 app + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/NVIDIA/skyhook/operator/internal/cli/client" + cliContext "github.com/NVIDIA/skyhook/operator/internal/cli/context" + "github.com/NVIDIA/skyhook/operator/internal/cli/utils" +) + +// lifecycleConfig defines the configuration for a lifecycle command +type lifecycleConfig struct { + use string + short string + long string + example string + annotation string + action string // "set" or "remove" + verb string // past tense for output message (e.g., "paused", "resumed") + confirmVerb string // verb for confirmation prompt (e.g., "pause", "disable") + needsConfirm bool +} + +// lifecycleOptions holds the options for lifecycle commands that need confirmation +type lifecycleOptions struct { + confirm bool +} + +// newLifecycleCmd creates a lifecycle command based on the provided configuration +func newLifecycleCmd(ctx *cliContext.CLIContext, cfg lifecycleConfig) *cobra.Command { + opts := &lifecycleOptions{} + + cmd := &cobra.Command{ + Use: cfg.use, + Short: cfg.short, + Long: cfg.long, + Example: cfg.example, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + skyhookName := args[0] + + if cfg.needsConfirm && !opts.confirm { + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "This will %s Skyhook %q. Continue? [y/N]: ", + cfg.confirmVerb, skyhookName) + var response string + if _, err := fmt.Scanln(&response); err != nil || (response != "y" && response != "Y") { + _, _ = fmt.Fprintln(cmd.OutOrStdout(), "Aborted.") + return nil + } + } + + clientFactory := client.NewFactory(ctx.GlobalFlags.ConfigFlags) + kubeClient, err := clientFactory.Client() + if err != nil { + return fmt.Errorf("initializing kubernetes client: %w", err) + } + + if cfg.action == "set" { + err = utils.SetSkyhookAnnotation(cmd.Context(), kubeClient.Dynamic(), skyhookName, cfg.annotation, "true") + } else { + err = utils.RemoveSkyhookAnnotation(cmd.Context(), kubeClient.Dynamic(), skyhookName, cfg.annotation) + } + if err != nil { + return err + } + + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Skyhook %q %s\n", skyhookName, cfg.verb) + return nil + }, + } + + if cfg.needsConfirm { + cmd.Flags().BoolVarP(&opts.confirm, "confirm", "y", false, "Skip confirmation prompt") + } + + return cmd +} + +// NewPauseCmd creates the pause command +func NewPauseCmd(ctx *cliContext.CLIContext) *cobra.Command { + return newLifecycleCmd(ctx, lifecycleConfig{ + use: "pause ", + short: "Pause a Skyhook from processing", + long: `Pause a Skyhook by setting the pause annotation. + +When a Skyhook is paused, the operator will stop processing new nodes +but will not interrupt any currently running operations.`, + example: ` # Pause a Skyhook + kubectl skyhook pause gpu-init + + # Pause without confirmation + kubectl skyhook pause gpu-init --confirm`, + annotation: utils.PauseAnnotation, + action: "set", + verb: "paused", + confirmVerb: "pause", + needsConfirm: true, + }) +} + +// NewResumeCmd creates the resume command +func NewResumeCmd(ctx *cliContext.CLIContext) *cobra.Command { + return newLifecycleCmd(ctx, lifecycleConfig{ + use: "resume ", + short: "Resume a paused Skyhook", + long: `Resume a paused Skyhook by removing the pause annotation. + +The operator will resume processing nodes after this command.`, + example: ` # Resume a paused Skyhook + kubectl skyhook resume gpu-init`, + annotation: utils.PauseAnnotation, + action: "remove", + verb: "resumed", + needsConfirm: false, + }) +} + +// NewDisableCmd creates the disable command +func NewDisableCmd(ctx *cliContext.CLIContext) *cobra.Command { + return newLifecycleCmd(ctx, lifecycleConfig{ + use: "disable ", + short: "Disable a Skyhook completely", + long: `Disable a Skyhook by setting the disable annotation. + +When a Skyhook is disabled, the operator will completely stop processing +and the Skyhook will be effectively inactive.`, + example: ` # Disable a Skyhook + kubectl skyhook disable gpu-init + + # Disable without confirmation + kubectl skyhook disable gpu-init --confirm`, + annotation: utils.DisableAnnotation, + action: "set", + verb: "disabled", + confirmVerb: "disable", + needsConfirm: true, + }) +} + +// NewEnableCmd creates the enable command +func NewEnableCmd(ctx *cliContext.CLIContext) *cobra.Command { + return newLifecycleCmd(ctx, lifecycleConfig{ + use: "enable ", + short: "Enable a disabled Skyhook", + long: `Enable a disabled Skyhook by removing the disable annotation. + +The operator will resume normal processing after this command.`, + example: ` # Enable a disabled Skyhook + kubectl skyhook enable gpu-init`, + annotation: utils.DisableAnnotation, + action: "remove", + verb: "enabled", + needsConfirm: false, + }) +} diff --git a/operator/cmd/cli/app/lifecycle_test.go b/operator/cmd/cli/app/lifecycle_test.go new file mode 100644 index 00000000..a5c543ab --- /dev/null +++ b/operator/cmd/cli/app/lifecycle_test.go @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * + * Licensed 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 app + +import ( + "github.com/spf13/cobra" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/NVIDIA/skyhook/operator/internal/cli/context" +) + +var _ = Describe("Lifecycle Commands", func() { + type lifecycleTestCase struct { + name string + cmdFactory func(*context.CLIContext) *cobra.Command + expectedUse string + expectedVerb string + hasConfirmFlag bool + } + + testCases := []lifecycleTestCase{ + { + name: "Pause", + cmdFactory: NewPauseCmd, + expectedUse: "pause ", + expectedVerb: "Pause", + hasConfirmFlag: true, + }, + { + name: "Resume", + cmdFactory: NewResumeCmd, + expectedUse: "resume ", + expectedVerb: "Resume", + hasConfirmFlag: false, + }, + { + name: "Disable", + cmdFactory: NewDisableCmd, + expectedUse: "disable ", + expectedVerb: "Disable", + hasConfirmFlag: true, + }, + { + name: "Enable", + cmdFactory: NewEnableCmd, + expectedUse: "enable ", + expectedVerb: "Enable", + hasConfirmFlag: false, + }, + } + + for _, tc := range testCases { + Describe(tc.name+" Command", func() { + It("should create command with correct properties", func() { + ctx := context.NewCLIContext(nil) + cmd := tc.cmdFactory(ctx) + + Expect(cmd.Use).To(Equal(tc.expectedUse)) + Expect(cmd.Short).To(ContainSubstring(tc.expectedVerb)) + }) + + It("should handle confirm flag correctly", func() { + ctx := context.NewCLIContext(nil) + cmd := tc.cmdFactory(ctx) + + confirmFlag := cmd.Flags().Lookup("confirm") + if tc.hasConfirmFlag { + Expect(confirmFlag).NotTo(BeNil()) + Expect(confirmFlag.Shorthand).To(Equal("y")) + Expect(confirmFlag.DefValue).To(Equal("false")) + } else { + Expect(confirmFlag).To(BeNil()) + } + }) + + It("should require exactly one argument", func() { + ctx := context.NewCLIContext(nil) + cmd := tc.cmdFactory(ctx) + + err := cmd.Args(cmd, []string{}) + Expect(err).To(HaveOccurred()) + + err = cmd.Args(cmd, []string{"skyhook1"}) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should have examples in help", func() { + ctx := context.NewCLIContext(nil) + cmd := tc.cmdFactory(ctx) + + Expect(cmd.Example).To(ContainSubstring("kubectl skyhook")) + }) + }) + } +}) diff --git a/operator/internal/cli/node/node.go b/operator/cmd/cli/app/node/node.go similarity index 100% rename from operator/internal/cli/node/node.go rename to operator/cmd/cli/app/node/node.go diff --git a/operator/internal/cli/node/node_ignore.go b/operator/cmd/cli/app/node/node_ignore.go similarity index 100% rename from operator/internal/cli/node/node_ignore.go rename to operator/cmd/cli/app/node/node_ignore.go diff --git a/operator/internal/cli/node/node_ignore_test.go b/operator/cmd/cli/app/node/node_ignore_test.go similarity index 100% rename from operator/internal/cli/node/node_ignore_test.go rename to operator/cmd/cli/app/node/node_ignore_test.go diff --git a/operator/internal/cli/node/node_list.go b/operator/cmd/cli/app/node/node_list.go similarity index 100% rename from operator/internal/cli/node/node_list.go rename to operator/cmd/cli/app/node/node_list.go diff --git a/operator/internal/cli/node/node_list_test.go b/operator/cmd/cli/app/node/node_list_test.go similarity index 100% rename from operator/internal/cli/node/node_list_test.go rename to operator/cmd/cli/app/node/node_list_test.go diff --git a/operator/internal/cli/node/node_reset.go b/operator/cmd/cli/app/node/node_reset.go similarity index 100% rename from operator/internal/cli/node/node_reset.go rename to operator/cmd/cli/app/node/node_reset.go diff --git a/operator/internal/cli/node/node_reset_test.go b/operator/cmd/cli/app/node/node_reset_test.go similarity index 100% rename from operator/internal/cli/node/node_reset_test.go rename to operator/cmd/cli/app/node/node_reset_test.go diff --git a/operator/internal/cli/node/node_status.go b/operator/cmd/cli/app/node/node_status.go similarity index 100% rename from operator/internal/cli/node/node_status.go rename to operator/cmd/cli/app/node/node_status.go diff --git a/operator/internal/cli/node/node_status_test.go b/operator/cmd/cli/app/node/node_status_test.go similarity index 100% rename from operator/internal/cli/node/node_status_test.go rename to operator/cmd/cli/app/node/node_status_test.go diff --git a/operator/internal/cli/node/node_test.go b/operator/cmd/cli/app/node/node_test.go similarity index 100% rename from operator/internal/cli/node/node_test.go rename to operator/cmd/cli/app/node/node_test.go diff --git a/operator/internal/cli/package/package.go b/operator/cmd/cli/app/package/package.go similarity index 100% rename from operator/internal/cli/package/package.go rename to operator/cmd/cli/app/package/package.go diff --git a/operator/internal/cli/package/package_logs.go b/operator/cmd/cli/app/package/package_logs.go similarity index 100% rename from operator/internal/cli/package/package_logs.go rename to operator/cmd/cli/app/package/package_logs.go diff --git a/operator/internal/cli/package/package_logs_test.go b/operator/cmd/cli/app/package/package_logs_test.go similarity index 100% rename from operator/internal/cli/package/package_logs_test.go rename to operator/cmd/cli/app/package/package_logs_test.go diff --git a/operator/internal/cli/package/package_rerun.go b/operator/cmd/cli/app/package/package_rerun.go similarity index 98% rename from operator/internal/cli/package/package_rerun.go rename to operator/cmd/cli/app/package/package_rerun.go index 9418f42a..00f24b58 100644 --- a/operator/internal/cli/package/package_rerun.go +++ b/operator/cmd/cli/app/package/package_rerun.go @@ -280,7 +280,14 @@ func promptConfirmation(cmd *cobra.Command, opts *rerunOptions) (bool, error) { } // updateNodeAnnotations updates each node's annotation to trigger re-run -func updateNodeAnnotations(ctx context.Context, kubeClient *client.Client, nodesToUpdate []string, nodeStates map[string]v1alpha1.NodeState, packageKey, annotationKey string, opts *rerunOptions) (int, []string) { +func updateNodeAnnotations( + ctx context.Context, + kubeClient *client.Client, + nodesToUpdate []string, + nodeStates map[string]v1alpha1.NodeState, + packageKey, annotationKey string, + opts *rerunOptions, +) (int, []string) { var updateErrors []string successCount := 0 diff --git a/operator/internal/cli/package/package_rerun_test.go b/operator/cmd/cli/app/package/package_rerun_test.go similarity index 99% rename from operator/internal/cli/package/package_rerun_test.go rename to operator/cmd/cli/app/package/package_rerun_test.go index 3a78c52c..b7204e81 100644 --- a/operator/internal/cli/package/package_rerun_test.go +++ b/operator/cmd/cli/app/package/package_rerun_test.go @@ -316,6 +316,7 @@ var _ = Describe("Package Rerun Command", func() { Expect(output.String()).To(ContainSubstring("No nodes matched")) }) + //nolint:dupl // Test setup intentionally similar to other test cases for clarity It("should show message when package not found on nodes", func() { mockDynamic.On("Resource", skyhookGVR).Return(mockNSRes) mockNSRes.On("Get", mock.Anything, testSkyhookNameRerun, mock.Anything).Return(createSkyhookUnstructured(), nil) @@ -424,6 +425,7 @@ var _ = Describe("Package Rerun Command", func() { Expect(output.String()).To(ContainSubstring("Aborted")) }) + //nolint:dupl // Test setup intentionally similar to other test cases for clarity It("should update node annotations when confirmed", func() { mockDynamic.On("Resource", skyhookGVR).Return(mockNSRes) mockNSRes.On("Get", mock.Anything, testSkyhookNameRerun, mock.Anything).Return(createSkyhookUnstructured(), nil) diff --git a/operator/internal/cli/package/package_status.go b/operator/cmd/cli/app/package/package_status.go similarity index 100% rename from operator/internal/cli/package/package_status.go rename to operator/cmd/cli/app/package/package_status.go diff --git a/operator/internal/cli/package/package_status_test.go b/operator/cmd/cli/app/package/package_status_test.go similarity index 100% rename from operator/internal/cli/package/package_status_test.go rename to operator/cmd/cli/app/package/package_status_test.go diff --git a/operator/internal/cli/package/package_test.go b/operator/cmd/cli/app/package/package_test.go similarity index 100% rename from operator/internal/cli/package/package_test.go rename to operator/cmd/cli/app/package/package_test.go diff --git a/operator/internal/cli/version.go b/operator/cmd/cli/app/version.go similarity index 99% rename from operator/internal/cli/version.go rename to operator/cmd/cli/app/version.go index 1aeab4df..f507ff3f 100644 --- a/operator/internal/cli/version.go +++ b/operator/cmd/cli/app/version.go @@ -16,7 +16,7 @@ * limitations under the License. */ -package cli +package app import ( "context" diff --git a/operator/internal/cli/version_test.go b/operator/cmd/cli/app/version_test.go similarity index 99% rename from operator/internal/cli/version_test.go rename to operator/cmd/cli/app/version_test.go index 1464241e..738d2597 100644 --- a/operator/internal/cli/version_test.go +++ b/operator/cmd/cli/app/version_test.go @@ -16,7 +16,7 @@ * limitations under the License. */ -package cli +package app import ( "bytes" diff --git a/operator/cmd/cli/main.go b/operator/cmd/cli/main.go index e64cbf8e..128b3d29 100644 --- a/operator/cmd/cli/main.go +++ b/operator/cmd/cli/main.go @@ -21,7 +21,7 @@ package main import ( "os" - "github.com/NVIDIA/skyhook/operator/internal/cli" + cli "github.com/NVIDIA/skyhook/operator/cmd/cli/app" "github.com/NVIDIA/skyhook/operator/internal/cli/context" ) diff --git a/operator/internal/cli/disable.go b/operator/internal/cli/disable.go deleted file mode 100644 index 6604d679..00000000 --- a/operator/internal/cli/disable.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - * - * - * Licensed 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 cli - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/NVIDIA/skyhook/operator/internal/cli/client" - cliContext "github.com/NVIDIA/skyhook/operator/internal/cli/context" - "github.com/NVIDIA/skyhook/operator/internal/cli/utils" -) - -// disableOptions holds the options for the disable command -type disableOptions struct { - confirm bool -} - -// BindToCmd binds the options to the command flags -func (o *disableOptions) BindToCmd(cmd *cobra.Command) { - cmd.Flags().BoolVarP(&o.confirm, "confirm", "y", false, "Skip confirmation prompt") -} - -// NewDisableCmd creates the disable command -func NewDisableCmd(ctx *cliContext.CLIContext) *cobra.Command { - opts := &disableOptions{} - - cmd := &cobra.Command{ - Use: "disable ", - Short: "Disable a Skyhook completely", - Long: `Disable a Skyhook by setting the disable annotation. - -When a Skyhook is disabled, the operator will completely stop processing -and the Skyhook will be effectively inactive.`, - Example: ` # Disable a Skyhook - kubectl skyhook disable gpu-init - - # Disable without confirmation - kubectl skyhook disable gpu-init --confirm`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - skyhookName := args[0] - - if !opts.confirm { - _, _ = fmt.Fprintf(cmd.OutOrStdout(), "This will disable Skyhook %q. Continue? [y/N]: ", skyhookName) - var response string - if _, err := fmt.Scanln(&response); err != nil || (response != "y" && response != "Y") { - _, _ = fmt.Fprintln(cmd.OutOrStdout(), "Aborted.") - return nil - } - } - - clientFactory := client.NewFactory(ctx.GlobalFlags.ConfigFlags) - kubeClient, err := clientFactory.Client() - if err != nil { - return fmt.Errorf("initializing kubernetes client: %w", err) - } - - if err := utils.SetSkyhookAnnotation(cmd.Context(), kubeClient.Dynamic(), skyhookName, utils.DisableAnnotation, "true"); err != nil { - return err - } - _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Skyhook %q disabled\n", skyhookName) - return nil - }, - } - - opts.BindToCmd(cmd) - - return cmd -} diff --git a/operator/internal/cli/disable_test.go b/operator/internal/cli/disable_test.go deleted file mode 100644 index 6e6443db..00000000 --- a/operator/internal/cli/disable_test.go +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - * - * - * Licensed 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 cli - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/NVIDIA/skyhook/operator/internal/cli/context" -) - -var _ = Describe("Disable Command", func() { - Describe("NewDisableCmd", func() { - It("should create command with correct properties", func() { - ctx := context.NewCLIContext(nil) - cmd := NewDisableCmd(ctx) - - Expect(cmd.Use).To(Equal("disable ")) - Expect(cmd.Short).To(ContainSubstring("Disable")) - }) - - It("should have confirm flag with shorthand", func() { - ctx := context.NewCLIContext(nil) - cmd := NewDisableCmd(ctx) - - confirmFlag := cmd.Flags().Lookup("confirm") - Expect(confirmFlag).NotTo(BeNil()) - Expect(confirmFlag.Shorthand).To(Equal("y")) - Expect(confirmFlag.DefValue).To(Equal("false")) - }) - - It("should require exactly one argument", func() { - ctx := context.NewCLIContext(nil) - cmd := NewDisableCmd(ctx) - - err := cmd.Args(cmd, []string{}) - Expect(err).To(HaveOccurred()) - - err = cmd.Args(cmd, []string{"skyhook1"}) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should have examples in help", func() { - ctx := context.NewCLIContext(nil) - cmd := NewDisableCmd(ctx) - - Expect(cmd.Example).To(ContainSubstring("kubectl skyhook disable")) - }) - }) -}) diff --git a/operator/internal/cli/enable.go b/operator/internal/cli/enable.go deleted file mode 100644 index 5efa1b32..00000000 --- a/operator/internal/cli/enable.go +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - * - * - * Licensed 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 cli - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/NVIDIA/skyhook/operator/internal/cli/client" - cliContext "github.com/NVIDIA/skyhook/operator/internal/cli/context" - "github.com/NVIDIA/skyhook/operator/internal/cli/utils" -) - -// NewEnableCmd creates the enable command -func NewEnableCmd(ctx *cliContext.CLIContext) *cobra.Command { - cmd := &cobra.Command{ - Use: "enable ", - Short: "Enable a disabled Skyhook", - Long: `Enable a disabled Skyhook by removing the disable annotation. - -The operator will resume normal processing after this command.`, - Example: ` # Enable a disabled Skyhook - kubectl skyhook enable gpu-init`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - skyhookName := args[0] - - clientFactory := client.NewFactory(ctx.GlobalFlags.ConfigFlags) - kubeClient, err := clientFactory.Client() - if err != nil { - return fmt.Errorf("initializing kubernetes client: %w", err) - } - - if err := utils.RemoveSkyhookAnnotation(cmd.Context(), kubeClient.Dynamic(), skyhookName, utils.DisableAnnotation); err != nil { - return err - } - _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Skyhook %q enabled\n", skyhookName) - return nil - }, - } - - return cmd -} diff --git a/operator/internal/cli/enable_test.go b/operator/internal/cli/enable_test.go deleted file mode 100644 index 9fa71aa7..00000000 --- a/operator/internal/cli/enable_test.go +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - * - * - * Licensed 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 cli - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/NVIDIA/skyhook/operator/internal/cli/context" -) - -var _ = Describe("Enable Command", func() { - Describe("NewEnableCmd", func() { - It("should create command with correct properties", func() { - ctx := context.NewCLIContext(nil) - cmd := NewEnableCmd(ctx) - - Expect(cmd.Use).To(Equal("enable ")) - Expect(cmd.Short).To(ContainSubstring("Enable")) - }) - - It("should not have confirm flag (enable is safe)", func() { - ctx := context.NewCLIContext(nil) - cmd := NewEnableCmd(ctx) - - confirmFlag := cmd.Flags().Lookup("confirm") - Expect(confirmFlag).To(BeNil()) - }) - - It("should require exactly one argument", func() { - ctx := context.NewCLIContext(nil) - cmd := NewEnableCmd(ctx) - - err := cmd.Args(cmd, []string{}) - Expect(err).To(HaveOccurred()) - - err = cmd.Args(cmd, []string{"skyhook1"}) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should have examples in help", func() { - ctx := context.NewCLIContext(nil) - cmd := NewEnableCmd(ctx) - - Expect(cmd.Example).To(ContainSubstring("kubectl skyhook enable")) - }) - }) -}) diff --git a/operator/internal/cli/pause.go b/operator/internal/cli/pause.go deleted file mode 100644 index 195a6503..00000000 --- a/operator/internal/cli/pause.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - * - * - * Licensed 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 cli - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/NVIDIA/skyhook/operator/internal/cli/client" - cliContext "github.com/NVIDIA/skyhook/operator/internal/cli/context" - "github.com/NVIDIA/skyhook/operator/internal/cli/utils" -) - -// pauseOptions holds the options for the pause command -type pauseOptions struct { - confirm bool -} - -// BindToCmd binds the options to the command flags -func (o *pauseOptions) BindToCmd(cmd *cobra.Command) { - cmd.Flags().BoolVarP(&o.confirm, "confirm", "y", false, "Skip confirmation prompt") -} - -// NewPauseCmd creates the pause command -func NewPauseCmd(ctx *cliContext.CLIContext) *cobra.Command { - opts := &pauseOptions{} - - cmd := &cobra.Command{ - Use: "pause ", - Short: "Pause a Skyhook from processing", - Long: `Pause a Skyhook by setting the pause annotation. - -When a Skyhook is paused, the operator will stop processing new nodes -but will not interrupt any currently running operations.`, - Example: ` # Pause a Skyhook - kubectl skyhook pause gpu-init - - # Pause without confirmation - kubectl skyhook pause gpu-init --confirm`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - skyhookName := args[0] - - if !opts.confirm { - _, _ = fmt.Fprintf(cmd.OutOrStdout(), "This will pause Skyhook %q. Continue? [y/N]: ", skyhookName) - var response string - if _, err := fmt.Scanln(&response); err != nil || (response != "y" && response != "Y") { - _, _ = fmt.Fprintln(cmd.OutOrStdout(), "Aborted.") - return nil - } - } - - clientFactory := client.NewFactory(ctx.GlobalFlags.ConfigFlags) - kubeClient, err := clientFactory.Client() - if err != nil { - return fmt.Errorf("initializing kubernetes client: %w", err) - } - - if err := utils.SetSkyhookAnnotation(cmd.Context(), kubeClient.Dynamic(), skyhookName, utils.PauseAnnotation, "true"); err != nil { - return err - } - _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Skyhook %q paused\n", skyhookName) - return nil - }, - } - - opts.BindToCmd(cmd) - - return cmd -} diff --git a/operator/internal/cli/pause_test.go b/operator/internal/cli/pause_test.go deleted file mode 100644 index d64c5044..00000000 --- a/operator/internal/cli/pause_test.go +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - * - * - * Licensed 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 cli - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/NVIDIA/skyhook/operator/internal/cli/context" -) - -var _ = Describe("Pause Command", func() { - Describe("NewPauseCmd", func() { - It("should create command with correct properties", func() { - ctx := context.NewCLIContext(nil) - cmd := NewPauseCmd(ctx) - - Expect(cmd.Use).To(Equal("pause ")) - Expect(cmd.Short).To(ContainSubstring("Pause")) - }) - - It("should have confirm flag with shorthand", func() { - ctx := context.NewCLIContext(nil) - cmd := NewPauseCmd(ctx) - - confirmFlag := cmd.Flags().Lookup("confirm") - Expect(confirmFlag).NotTo(BeNil()) - Expect(confirmFlag.Shorthand).To(Equal("y")) - Expect(confirmFlag.DefValue).To(Equal("false")) - }) - - It("should require exactly one argument", func() { - ctx := context.NewCLIContext(nil) - cmd := NewPauseCmd(ctx) - - err := cmd.Args(cmd, []string{}) - Expect(err).To(HaveOccurred()) - - err = cmd.Args(cmd, []string{"skyhook1", "skyhook2"}) - Expect(err).To(HaveOccurred()) - - err = cmd.Args(cmd, []string{"skyhook1"}) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should have examples in help", func() { - ctx := context.NewCLIContext(nil) - cmd := NewPauseCmd(ctx) - - Expect(cmd.Example).To(ContainSubstring("kubectl skyhook pause")) - }) - }) -}) diff --git a/operator/internal/cli/resume.go b/operator/internal/cli/resume.go deleted file mode 100644 index a14e735f..00000000 --- a/operator/internal/cli/resume.go +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - * - * - * Licensed 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 cli - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/NVIDIA/skyhook/operator/internal/cli/client" - cliContext "github.com/NVIDIA/skyhook/operator/internal/cli/context" - "github.com/NVIDIA/skyhook/operator/internal/cli/utils" -) - -// NewResumeCmd creates the resume command -func NewResumeCmd(ctx *cliContext.CLIContext) *cobra.Command { - cmd := &cobra.Command{ - Use: "resume ", - Short: "Resume a paused Skyhook", - Long: `Resume a paused Skyhook by removing the pause annotation. - -The operator will resume processing nodes after this command.`, - Example: ` # Resume a paused Skyhook - kubectl skyhook resume gpu-init`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - skyhookName := args[0] - - clientFactory := client.NewFactory(ctx.GlobalFlags.ConfigFlags) - kubeClient, err := clientFactory.Client() - if err != nil { - return fmt.Errorf("initializing kubernetes client: %w", err) - } - - if err := utils.RemoveSkyhookAnnotation(cmd.Context(), kubeClient.Dynamic(), skyhookName, utils.PauseAnnotation); err != nil { - return err - } - _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Skyhook %q resumed\n", skyhookName) - return nil - }, - } - - return cmd -} diff --git a/operator/internal/cli/resume_test.go b/operator/internal/cli/resume_test.go deleted file mode 100644 index dcf4cbf1..00000000 --- a/operator/internal/cli/resume_test.go +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - * - * - * Licensed 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 cli - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/NVIDIA/skyhook/operator/internal/cli/context" -) - -var _ = Describe("Resume Command", func() { - Describe("NewResumeCmd", func() { - It("should create command with correct properties", func() { - ctx := context.NewCLIContext(nil) - cmd := NewResumeCmd(ctx) - - Expect(cmd.Use).To(Equal("resume ")) - Expect(cmd.Short).To(ContainSubstring("Resume")) - }) - - It("should not have confirm flag (resume is safe)", func() { - ctx := context.NewCLIContext(nil) - cmd := NewResumeCmd(ctx) - - confirmFlag := cmd.Flags().Lookup("confirm") - Expect(confirmFlag).To(BeNil()) - }) - - It("should require exactly one argument", func() { - ctx := context.NewCLIContext(nil) - cmd := NewResumeCmd(ctx) - - err := cmd.Args(cmd, []string{}) - Expect(err).To(HaveOccurred()) - - err = cmd.Args(cmd, []string{"skyhook1"}) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should have examples in help", func() { - ctx := context.NewCLIContext(nil) - cmd := NewResumeCmd(ctx) - - Expect(cmd.Example).To(ContainSubstring("kubectl skyhook resume")) - }) - }) -})