-
Notifications
You must be signed in to change notification settings - Fork 698
[kubectl-plugin][Refactor] Standardize namespace resolution to respect kubeconfig context. #4367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,8 @@ import ( | |
| "k8s.io/apimachinery/pkg/runtime" | ||
| "k8s.io/cli-runtime/pkg/genericclioptions" | ||
| kubefake "k8s.io/client-go/kubernetes/fake" | ||
| "k8s.io/client-go/tools/clientcmd" | ||
| "k8s.io/client-go/tools/clientcmd/api" | ||
| cmdutil "k8s.io/kubectl/pkg/cmd/util" | ||
| "k8s.io/utils/ptr" | ||
|
|
||
|
|
@@ -23,48 +25,94 @@ import ( | |
| rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1" | ||
| ) | ||
|
|
||
| func createTempKubeConfigFile(t *testing.T, currentNamespace string) (string, error) { | ||
| tmpDir := t.TempDir() | ||
|
|
||
| // Set up fake config for kubeconfig | ||
| config := &api.Config{ | ||
| Clusters: map[string]*api.Cluster{ | ||
| "test-cluster": { | ||
| Server: "https://fake-kubernetes-cluster.example.com", | ||
| InsecureSkipTLSVerify: true, // For testing purposes | ||
| }, | ||
| }, | ||
| Contexts: map[string]*api.Context{ | ||
| "test-context": { | ||
| Cluster: "test-cluster", | ||
| AuthInfo: "my-fake-user", | ||
| Namespace: currentNamespace, | ||
| }, | ||
| }, | ||
| CurrentContext: "test-context", | ||
| AuthInfos: map[string]*api.AuthInfo{ | ||
| "my-fake-user": { | ||
| Token: "", // Empty for testing without authentication | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| fakeFile := filepath.Join(tmpDir, ".kubeconfig") | ||
|
|
||
| return fakeFile, clientcmd.WriteToFile(*config, fakeFile) | ||
| } | ||
|
|
||
| func TestRayCreateClusterComplete(t *testing.T) { | ||
| kubeConfigWithCurrentContext, err := createTempKubeConfigFile(t, "test-namespace") | ||
| require.NoError(t, err) | ||
| testStreams, _, _, _ := genericclioptions.NewTestIOStreams() | ||
|
|
||
| tests := map[string]struct { | ||
| image string | ||
| rayVersion string | ||
| expectedError string | ||
| expectedImage string | ||
| args []string | ||
| image string | ||
| namespace string | ||
| rayVersion string | ||
| expectedError string | ||
| expectedImage string | ||
| expectedNamespace string | ||
| args []string | ||
| }{ | ||
| "should error when there are no args": { | ||
| args: []string{}, | ||
| expectedError: "See 'cluster -h' for help and examples", | ||
| args: []string{}, | ||
| expectedError: "See 'cluster -h' for help and examples", | ||
| expectedNamespace: "test-namespace", | ||
| }, | ||
| "should error when too many args": { | ||
| args: []string{"testRayClusterName", "extra-arg"}, | ||
| expectedError: "See 'cluster -h' for help and examples", | ||
| args: []string{"testRayClusterName", "extra-arg"}, | ||
| expectedError: "See 'cluster -h' for help and examples", | ||
| expectedNamespace: "test-namespace", | ||
| }, | ||
| "should succeed with default image when no image is specified": { | ||
| args: []string{"testRayClusterName"}, | ||
| rayVersion: util.RayVersion, | ||
| expectedImage: defaultImageWithTag, | ||
| args: []string{"testRayClusterName"}, | ||
| rayVersion: util.RayVersion, | ||
| expectedImage: defaultImageWithTag, | ||
| expectedNamespace: "test-namespace", | ||
| }, | ||
| "should succeed with provided image when provided": { | ||
| args: []string{"testRayClusterName"}, | ||
| image: "DEADBEEF", | ||
| expectedImage: "DEADBEEF", | ||
| args: []string{"testRayClusterName"}, | ||
| image: "DEADBEEF", | ||
| expectedImage: "DEADBEEF", | ||
| expectedNamespace: "test-namespace", | ||
| }, | ||
| "should set the image to the same version as the ray version when the image is the default and the ray version is not the default": { | ||
| args: []string{"testRayClusterName"}, | ||
| image: defaultImageWithTag, | ||
| rayVersion: "2.52.0", | ||
| expectedImage: fmt.Sprintf("%s:2.52.0", defaultImage), | ||
| args: []string{"testRayClusterName"}, | ||
| image: defaultImageWithTag, | ||
| namespace: "foo", | ||
| rayVersion: "2.52.0", | ||
| expectedImage: fmt.Sprintf("%s:2.52.0", defaultImage), | ||
| expectedNamespace: "foo", | ||
| }, | ||
| } | ||
|
|
||
| for name, tc := range tests { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be possible to add a test case that respects the namespace in kubeconfig? |
||
| t.Run(name, func(t *testing.T) { | ||
| cmdFactory := cmdutil.NewFactory(genericclioptions.NewConfigFlags(true)) | ||
| configFlags := &genericclioptions.ConfigFlags{KubeConfig: &kubeConfigWithCurrentContext} | ||
| if tc.namespace != "" { | ||
| configFlags.Namespace = &tc.namespace | ||
| } | ||
|
|
||
| cmdFactory := cmdutil.NewFactory(configFlags) | ||
| fakeCreateClusterOptions := NewCreateClusterOptions(cmdFactory, testStreams) | ||
| cmd := &cobra.Command{Use: "cluster"} | ||
| cmd.Flags().StringVarP(&fakeCreateClusterOptions.namespace, "namespace", "n", "", "") | ||
| configFlags.AddFlags(cmd.Flags()) | ||
| fakeCreateClusterOptions.rayVersion = tc.rayVersion | ||
|
|
||
| if tc.image != "" { | ||
|
|
@@ -78,6 +126,7 @@ func TestRayCreateClusterComplete(t *testing.T) { | |
| } else { | ||
| require.NoError(t, err) | ||
| require.Equal(t, tc.expectedImage, fakeCreateClusterOptions.image) | ||
| require.Equal(t, tc.expectedNamespace, fakeCreateClusterOptions.namespace) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,8 @@ import ( | |
| "github.com/stretchr/testify/require" | ||
| corev1 "k8s.io/api/core/v1" | ||
| "k8s.io/apimachinery/pkg/api/resource" | ||
| "k8s.io/cli-runtime/pkg/genericclioptions" | ||
| cmdutil "k8s.io/kubectl/pkg/cmd/util" | ||
| "k8s.io/utils/ptr" | ||
|
|
||
| "github.com/ray-project/kuberay/kubectl-plugin/pkg/util" | ||
|
|
@@ -104,10 +106,17 @@ func TestCreateWorkerGroupCommandComplete(t *testing.T) { | |
| }, | ||
| }, | ||
| { | ||
| name: "Valid input without namespace flag", | ||
| args: []string{"example-group"}, | ||
| flags: map[string]string{}, | ||
| expectedError: "failed to get namespace: flag accessed but not defined: namespace", | ||
| name: "Valid input without namespace flag", | ||
| args: []string{"example-group"}, | ||
| flags: map[string]string{ | ||
| "namespace": "", | ||
| }, | ||
|
Comment on lines
+111
to
+113
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why should it add an empty namespace flag? It seems a bit of different from the description above. |
||
| expected: &CreateWorkerGroupOptions{ | ||
| namespace: "default", | ||
| groupName: "example-group", | ||
| image: "rayproject/ray:latest", | ||
| rayVersion: "latest", | ||
| }, | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, | ||
| { | ||
| name: "mMissing group name", | ||
|
|
@@ -181,12 +190,24 @@ func TestCreateWorkerGroupCommandComplete(t *testing.T) { | |
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| configFlags := genericclioptions.NewConfigFlags(true) | ||
| cmd := &cobra.Command{} | ||
| configFlags.AddFlags(cmd.Flags()) | ||
| cmd.Flags().String( | ||
| "worker-ray-start-params", | ||
| "", | ||
| "ray start parameters for worker", | ||
| ) | ||
| for key, value := range tt.flags { | ||
| cmd.Flags().String(key, value, "") | ||
| err := cmd.Flags().Set(key, value) | ||
| if err != nil { | ||
| require.NoError(t, err) | ||
| } | ||
| } | ||
|
|
||
| factory := cmdutil.NewFactory(configFlags) | ||
| options := &CreateWorkerGroupOptions{ | ||
| cmdFactory: factory, | ||
| rayVersion: "latest", | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Config file namespace ignored due to always-set options.namespace
High Severity
The
resolveNamespace()function can no longer use the namespace from a config file. Previously,cmd.Flags().GetString("namespace")returned an empty string when the user didn't pass--namespace, allowing the config file namespace to be used. Now,ToRawKubeConfigLoader().Namespace()always returns a value (at minimum "default"), sooptions.namespaceis never empty. This causes the conditionif options.namespace != ""to always be true, making theelse ifbranch that usesrayClusterConfig.Namespaceunreachable. Additionally, the error check at line 249 will now incorrectly fail when a config file specifies a namespace different from the kubeconfig default.Additional Locations (2)
kubectl-plugin/pkg/cmd/create/create_cluster.go#L152-L157kubectl-plugin/pkg/cmd/create/create_cluster.go#L248-L251Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, and I think you should add test to check this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some different behavior if the namespace is specified in config file,
--file ray-cluster.yaml.before applying this pr:
after applying this pr: