Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions kubectl-plugin/pkg/cmd/get/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func NewGetCommand(cmdFactory cmdutil.Factory, streams genericclioptions.IOStrea
cmd.AddCommand(NewGetClusterCommand(cmdFactory, streams))
cmd.AddCommand(NewGetWorkerGroupCommand(cmdFactory, streams))
cmd.AddCommand(NewGetNodesCommand(cmdFactory, streams))
cmd.AddCommand(NewGetTokenCommand(cmdFactory, streams))
return cmd
}

Expand Down
89 changes: 89 additions & 0 deletions kubectl-plugin/pkg/cmd/get/get_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package get

import (
"context"
"fmt"

"github.com/spf13/cobra"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
cmdutil "k8s.io/kubectl/pkg/cmd/util"

"github.com/ray-project/kuberay/kubectl-plugin/pkg/util/client"
"github.com/ray-project/kuberay/kubectl-plugin/pkg/util/completion"
rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1"
)

type GetTokenOptions struct {
cmdFactory cmdutil.Factory
ioStreams *genericclioptions.IOStreams
namespace string
cluster string
}

func NewGetTokenOptions(cmdFactory cmdutil.Factory, streams genericclioptions.IOStreams) *GetTokenOptions {
return &GetTokenOptions{
cmdFactory: cmdFactory,
ioStreams: &streams,
}
}

func NewGetTokenCommand(cmdFactory cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
options := NewGetTokenOptions(cmdFactory, streams)

cmd := &cobra.Command{
Use: "token [CLUSTER NAME]",
Aliases: []string{"token"},
Short: "Get the auth token from the ray cluster.",
SilenceUsage: true,
ValidArgsFunction: completion.RayClusterCompletionFunc(cmdFactory),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if err := options.Complete(args, cmd); err != nil {
return err
}
// running cmd.Execute or cmd.ExecuteE sets the context, which will be done by root
k8sClient, err := client.NewClient(cmdFactory)
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}
return options.Run(cmd.Context(), k8sClient)
},
}
return cmd
}

func (options *GetTokenOptions) Complete(args []string, cmd *cobra.Command) error {
namespace, err := cmd.Flags().GetString("namespace")
if err != nil {
return fmt.Errorf("failed to get namespace: %w", err)
}
options.namespace = namespace
if options.namespace == "" {
options.namespace = "default"
}
// guarded by cobra.ExactArgs(1)
options.cluster = args[0]
return nil
}

func (options *GetTokenOptions) Run(ctx context.Context, k8sClient client.Client) error {
cluster, err := k8sClient.RayClient().RayV1().RayClusters(options.namespace).Get(ctx, options.cluster, v1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get RayCluster %s/%s: %w", options.namespace, options.cluster, err)
}
if cluster.Spec.AuthOptions == nil || cluster.Spec.AuthOptions.Mode != rayv1.AuthModeToken {
return fmt.Errorf("RayCluster %s/%s was not configured to use authentication tokens", options.namespace, options.cluster)
}
// TODO: support custom token secret?
secret, err := k8sClient.KubernetesClient().CoreV1().Secrets(options.namespace).Get(ctx, options.cluster, v1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get secret %s/%s: %w", options.namespace, options.cluster, err)
}
if token, ok := secret.Data["auth_token"]; ok {
_, err = fmt.Fprint(options.ioStreams.Out, string(token))
} else {
err = fmt.Errorf("secret %s/%s does not have an auth_token", options.namespace, options.cluster)
}
return err
}
61 changes: 61 additions & 0 deletions kubectl-plugin/pkg/cmd/get/get_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package get

import (
"testing"

"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
kubefake "k8s.io/client-go/kubernetes/fake"
cmdutil "k8s.io/kubectl/pkg/cmd/util"

"github.com/ray-project/kuberay/kubectl-plugin/pkg/util/client"
rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1"
rayClientFake "github.com/ray-project/kuberay/ray-operator/pkg/client/clientset/versioned/fake"
)

// Tests the Run() step of the command and ensure that the output is as expected.
func TestTokenGetRun(t *testing.T) {
cmdFactory := cmdutil.NewFactory(genericclioptions.NewConfigFlags(true))

testStreams, _, resBuf, _ := genericclioptions.NewTestIOStreams()
fakeTokenGetOptions := NewGetTokenOptions(cmdFactory, testStreams)

rayCluster := &rayv1.RayCluster{
ObjectMeta: v1.ObjectMeta{
Name: "raycluster-kuberay",
Namespace: "test",
},
Spec: rayv1.RayClusterSpec{
AuthOptions: &rayv1.AuthOptions{
Mode: rayv1.AuthModeToken,
},
},
}

secret := &corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Name: "raycluster-kuberay",
Namespace: "test",
},
Data: map[string][]byte{
"auth_token": []byte("token"),
},
}

kubeClientSet := kubefake.NewClientset(secret)
rayClient := rayClientFake.NewSimpleClientset(rayCluster)
k8sClients := client.NewClientForTesting(kubeClientSet, rayClient)

cmd := &cobra.Command{}
cmd.Flags().StringVarP(&fakeTokenGetOptions.namespace, "namespace", "n", secret.Namespace, "")
err := fakeTokenGetOptions.Complete([]string{rayCluster.Name}, cmd)
require.NoError(t, err)
err = fakeTokenGetOptions.Run(t.Context(), k8sClients)
require.NoError(t, err)

assert.Equal(t, secret.Data["auth_token"], resBuf.Bytes())
}
Loading