diff --git a/cmd/non-admin/namespace.go b/cmd/non-admin/namespace.go new file mode 100644 index 00000000..0757aa22 --- /dev/null +++ b/cmd/non-admin/namespace.go @@ -0,0 +1,77 @@ +/* +Copyright 2025 The OADP CLI Contributors. + +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 nonadmin + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// ConfigureNamespaceBehavior hides the inherited --namespace flag from nonadmin +// help output and rejects runtime use of -n/--namespace. Nonadmin operations are +// scoped to the user's current context namespace for security. +func ConfigureNamespaceBehavior(cmd *cobra.Command) { + hideNamespaceFlagFromCommand(cmd) + + existingPersistentPreRunE := cmd.PersistentPreRunE + existingPersistentPreRun := cmd.PersistentPreRun + + cmd.PersistentPreRunE = func(c *cobra.Command, args []string) error { + if c.Flags().Changed("namespace") { + return fmt.Errorf("-n/--namespace is not supported for nonadmin commands; namespace is determined by your current context") + } + if existingPersistentPreRunE != nil { + return existingPersistentPreRunE(c, args) + } + if existingPersistentPreRun != nil { + existingPersistentPreRun(c, args) + } + return nil + } +} + +func hideNamespaceFlagFromCommand(cmd *cobra.Command) { + originalHelpFunc := cmd.HelpFunc() + cmd.SetHelpFunc(func(c *cobra.Command, args []string) { + withHiddenNamespaceFlag(c, func() { + originalHelpFunc(c, args) + }) + }) + + originalUsageFunc := cmd.UsageFunc() + cmd.SetUsageFunc(func(c *cobra.Command) error { + var err error + withHiddenNamespaceFlag(c, func() { + err = originalUsageFunc(c) + }) + return err + }) + + for _, subCmd := range cmd.Commands() { + hideNamespaceFlagFromCommand(subCmd) + } +} + +func withHiddenNamespaceFlag(cmd *cobra.Command, fn func()) { + if flag := cmd.InheritedFlags().Lookup("namespace"); flag != nil { + originalHidden := flag.Hidden + flag.Hidden = true + defer func() { flag.Hidden = originalHidden }() + } + fn() +} diff --git a/cmd/non-admin/namespace_test.go b/cmd/non-admin/namespace_test.go new file mode 100644 index 00000000..59410afd --- /dev/null +++ b/cmd/non-admin/namespace_test.go @@ -0,0 +1,51 @@ +/* +Copyright 2025 The OADP CLI Contributors. + +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 nonadmin + +import ( + "context" + "os/exec" + "strings" + "testing" + + "github.com/migtools/oadp-cli/internal/testutil" +) + +func TestNonAdminNamespaceFlagBehavior(t *testing.T) { + binaryPath := testutil.BuildCLIBinary(t) + + t.Run("help hides namespace flag", func(t *testing.T) { + output, _ := testutil.RunCommand(t, binaryPath, "nonadmin", "backup", "get", "--help") + if strings.Contains(output, "-n, --namespace") || strings.Contains(output, "--namespace string") { + t.Errorf("Expected help output not to contain the namespace flag\nFull output:\n%s", output) + } + }) + + t.Run("rejects namespace flag at runtime", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), testutil.TestTimeout) + defer cancel() + + output, err := exec.CommandContext(ctx, binaryPath, "nonadmin", "backup", "get", "-n", "other-namespace").CombinedOutput() + if err == nil { + t.Fatalf("Expected error when -n/--namespace is provided, got output:\n%s", string(output)) + } + expected := "-n/--namespace is not supported for nonadmin commands; namespace is determined by your current context" + if !strings.Contains(string(output), expected) { + t.Errorf("Expected output to contain %q, got:\n%s", expected, string(output)) + } + }) +} diff --git a/cmd/non-admin/nonadmin.go b/cmd/non-admin/nonadmin.go index dd6c08e5..ec15c979 100644 --- a/cmd/non-admin/nonadmin.go +++ b/cmd/non-admin/nonadmin.go @@ -50,5 +50,7 @@ func NewNonAdminCommand(f client.Factory) *cobra.Command { c.AddCommand(verbs.NewDescribeCommand(f)) c.AddCommand(verbs.NewLogsCommand(f)) + ConfigureNamespaceBehavior(c) + return c }