Skip to content

Commit

Permalink
Merge pull request kosmos-io#101 from GreatLazyMan/importexport
Browse files Browse the repository at this point in the history
title: Add import and export subcommand for kosmosctl
  • Loading branch information
kosmos-robot authored Oct 27, 2023
2 parents 79ec19f + 3125aad commit ad7c407
Show file tree
Hide file tree
Showing 5 changed files with 374 additions and 0 deletions.
7 changes: 7 additions & 0 deletions pkg/kosmosctl/kosmosctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/kosmos.io/kosmos/pkg/kosmosctl/get"
"github.com/kosmos.io/kosmos/pkg/kosmosctl/install"
"github.com/kosmos.io/kosmos/pkg/kosmosctl/join"
"github.com/kosmos.io/kosmos/pkg/kosmosctl/rsmigrate"
"github.com/kosmos.io/kosmos/pkg/kosmosctl/uninstall"
"github.com/kosmos.io/kosmos/pkg/kosmosctl/unjoin"
)
Expand Down Expand Up @@ -72,6 +73,12 @@ func NewKosmosCtlCommand() *cobra.Command {
Commands: []*cobra.Command{
floater.NewCmdDoctor(),
},
}, {
Message: "Cluster Resource Import/Export Commands:",
Commands: []*cobra.Command{
rsmigrate.NewCmdImport(f),
rsmigrate.NewCmdExport(f),
},
},
}
groups.Add(cmds)
Expand Down
52 changes: 52 additions & 0 deletions pkg/kosmosctl/rsmigrate/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package rsmigrate

import (
"context"
"fmt"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"

"github.com/kosmos.io/kosmos/pkg/apis/kosmos/v1alpha1"
kosmosversioned "github.com/kosmos.io/kosmos/pkg/generated/clientset/versioned"
"github.com/kosmos.io/kosmos/pkg/utils"
)

func getClientFromLeafCluster(leafCluster *v1alpha1.Knode) (kubernetes.Interface, kosmosversioned.Interface, error) {
//generate clientset by leafCluster kubeconfig
leafClusterKubeconfig := leafCluster.Spec.Kubeconfig
if len(leafClusterKubeconfig) == 0 {
return nil, nil, fmt.Errorf("leafcluster's kubeconfig is nil, it's unable to work normally")
}
k8sClient, err := utils.NewClientFromBytes(leafClusterKubeconfig)
if err != nil {
return nil, nil, fmt.Errorf("create kubernetes clientset error: %s ", err)
}

kosmosClient, err := utils.NewKosmosClientFromBytes(leafClusterKubeconfig)
if err != nil {
return nil, nil, fmt.Errorf("create kubernetes clientset for leafcluster crd error: %s ", err)
}

return k8sClient, kosmosClient, nil
}

func completeLeafClusterOptions(leafClusterOptions *LeafClusterOptions, masterClient kosmosversioned.Interface) error {
//complete leafClusterOptions by leafCluster name
if leafClusterOptions.LeafClusterName == "" {
return fmt.Errorf("get leafcluster error: %s ", "leafcluster value can't be empty")
}
leafCluster, err := masterClient.KosmosV1alpha1().Knodes().Get(context.TODO(), leafClusterOptions.LeafClusterName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("get leafcluster error: %s", err)
}
leafClusterOptions.LeafCluster = leafCluster
k8sClient, kosmosClient, err := getClientFromLeafCluster(leafClusterOptions.LeafCluster)
if err != nil {
return fmt.Errorf("get leafcluster clientset error: %s", err)
}
leafClusterOptions.LeafClusterNativeClient = k8sClient
leafClusterOptions.LeafClusterKosmosClient = kosmosClient

return nil
}
74 changes: 74 additions & 0 deletions pkg/kosmosctl/rsmigrate/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package rsmigrate

import (
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/util/homedir"
ctlutil "k8s.io/kubectl/pkg/cmd/util"

"github.com/kosmos.io/kosmos/pkg/apis/kosmos/v1alpha1"
kosmosversioned "github.com/kosmos.io/kosmos/pkg/generated/clientset/versioned"
"github.com/kosmos.io/kosmos/pkg/utils"
)

type LeafClusterOptions struct {
LeafClusterName string
LeafCluster *v1alpha1.Knode
LeafClusterNativeClient kubernetes.Interface
//clientset operate leafCluster releted resource
LeafClusterKosmosClient kosmosversioned.Interface
}

type CommandOptions struct {
MasterKubeConfig string
MasterClient kubernetes.Interface
//clientset operate leafCluster releted resource
MasterKosmosClient kosmosversioned.Interface

SrcLeafClusterOptions *LeafClusterOptions
Namespace string
}

func (o *CommandOptions) Validate(cmd *cobra.Command) error {
return nil
}

func (o *CommandOptions) Complete(f ctlutil.Factory, cmd *cobra.Command) error {
var err error
var kubeConfigStream []byte
// get master kubernetes clientset
if len(o.MasterKubeConfig) > 0 {
kubeConfigStream, err = os.ReadFile(o.MasterKubeConfig)
} else {
kubeConfigStream, err = os.ReadFile(filepath.Join(homedir.HomeDir(), ".kube", "config"))
}
if err != nil {
return fmt.Errorf("get master kubeconfig failed: %s", err)
}

masterClient, err := utils.NewClientFromBytes(kubeConfigStream)
if err != nil {
return fmt.Errorf("create master clientset error: %s ", err)
}
o.MasterClient = masterClient

kosmosClient, err := utils.NewKosmosClientFromBytes(kubeConfigStream)
if err != nil {
return fmt.Errorf("get master rest client config error:%s", err)
}

o.MasterKosmosClient = kosmosClient

// get src leafCluster options
if cmd.Flags().Changed("leafcluster") {
err := completeLeafClusterOptions(o.SrcLeafClusterOptions, o.MasterKosmosClient)
if err != nil {
return err
}
}
return nil
}
97 changes: 97 additions & 0 deletions pkg/kosmosctl/rsmigrate/serviceexport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package rsmigrate

import (
"context"
"fmt"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctlutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
mcsv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
)

var exportExample = templates.Examples(i18n.T(`
# Export service in control plane
kosmosctl export service foo -n namespacefoo --kubeconfig=[control plane kubeconfig]
`))

var exportErr string = "kosmosctl export error"

type CommandExportOptions struct {
*CommandOptions
}

// NewCmdExport export resource to control plane
func NewCmdExport(f ctlutil.Factory) *cobra.Command {
o := &CommandExportOptions{CommandOptions: &CommandOptions{SrcLeafClusterOptions: &LeafClusterOptions{}}}

cmd := &cobra.Command{
Use: "export",
Short: i18n.T("Export resource to control plane data storage center"),
Long: "",
Example: exportExample,
SilenceUsage: true,
DisableFlagsInUseLine: true,
RunE: func(cmd *cobra.Command, args []string) error {
ctlutil.CheckErr(o.Complete(f, cmd))
ctlutil.CheckErr(o.Validate(cmd))
ctlutil.CheckErr(o.Run(cmd, args))
return nil
},
}

cmd.Flags().StringVarP(&o.MasterKubeConfig, "kubeconfig", "", "", "Absolute path to the master kubeconfig file.")
cmd.Flags().StringVarP(&o.Namespace, "namespace", "n", "default", "The namespace scope for this CLI request")

return cmd
}

func (o *CommandExportOptions) Complete(f ctlutil.Factory, cmd *cobra.Command) error {
err := o.CommandOptions.Complete(f, cmd)
if err != nil {
return err
}
return nil
}

func (o *CommandExportOptions) Validate(cmd *cobra.Command) error {
err := o.CommandOptions.Validate(cmd)
if err != nil {
return fmt.Errorf("%s, valid args error: %s", exportErr, err)
}

return nil
}

func (o *CommandExportOptions) Run(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("args is null, resource type should be specified")
}

switch args[0] {
case "svc", "services", "service":
if len(args[1:]) != 1 {
return fmt.Errorf("%s, exactly one NAME is required, got %d", exportErr, len(args[1:]))
}

var err error
serviceExport := &mcsv1alpha1.ServiceExport{}
serviceExport.Namespace = o.Namespace
serviceExport.Kind = "ServiceExport"
serviceExport.Name = args[1]

// Create serviceExport, if exists,return error instead of updating it
_, err = o.MasterKosmosClient.MulticlusterV1alpha1().ServiceExports(o.Namespace).
Create(context.TODO(), serviceExport, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("%s, create %s %s/%s %s: %s", exportErr, serviceExport.Kind, o.Namespace, args[1], args[0], err)
}

fmt.Printf("Create %s %s/%s successfully!\n", serviceExport.Kind, o.Namespace, args[1])
default:
return fmt.Errorf("%s, not support export resouece %s", exportErr, args[0])
}
return nil
}
144 changes: 144 additions & 0 deletions pkg/kosmosctl/rsmigrate/serviceimport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package rsmigrate

import (
"context"
"fmt"

"github.com/spf13/cobra"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctlutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
mcsv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
)

var importExample = templates.Examples(i18n.T(`
# Import service from control plane to leafcluster
kosmosctl import service foo -n namespacefoo --kubecnfig=[control plane kubeconfig] --to-leafcluster leafclusterfoo
`))

var importErr string = "kosmosctl import error"

type CommandImportOptions struct {
*CommandOptions
DstLeafClusterOptions *LeafClusterOptions
}

// NewCmdImport import resource
func NewCmdImport(f ctlutil.Factory) *cobra.Command {
o := &CommandImportOptions{
CommandOptions: &CommandOptions{SrcLeafClusterOptions: &LeafClusterOptions{}},
DstLeafClusterOptions: &LeafClusterOptions{},
}

cmd := &cobra.Command{
Use: "import",
Short: i18n.T("Import resource to leafCluster"),
Long: "",
Example: importExample,
SilenceUsage: true,
DisableFlagsInUseLine: true,
RunE: func(cmd *cobra.Command, args []string) error {
ctlutil.CheckErr(o.Complete(f, cmd))
ctlutil.CheckErr(o.Validate(cmd))
ctlutil.CheckErr(o.Run(f, cmd, args))
return nil
},
}
cmd.Flags().StringVarP(&o.MasterKubeConfig, "kubeconfig", "", "", "Absolute path to the master kubeconfig file.")
cmd.Flags().StringVarP(&o.Namespace, "namespace", "n", "default", "The namespace scope for this CLI request")
cmd.Flags().StringVar(&o.DstLeafClusterOptions.LeafClusterName, "to-leafcluster", "", "Import resource to this destination leafcluster")

return cmd
}

func (o *CommandImportOptions) Complete(f ctlutil.Factory, cmd *cobra.Command) error {
err := o.CommandOptions.Complete(f, cmd)
if err != nil {
return err
}

// get dst leafCluster options
if cmd.Flags().Changed("to-leafcluster") {
err := completeLeafClusterOptions(o.DstLeafClusterOptions, o.MasterKosmosClient)
if err != nil {
return err
}
}

return nil
}

func (o *CommandImportOptions) Validate(cmd *cobra.Command) error {
err := o.CommandOptions.Validate(cmd)
if err != nil {
return fmt.Errorf("%s, valid args error: %s", importErr, err)
}

if !cmd.Flags().Changed("to-leafcluster") {
return fmt.Errorf("%s, required flag(s) 'to-leafcluster' not set", importErr)
}
return nil
}

func (o *CommandImportOptions) Run(f ctlutil.Factory, cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("args is null, resource should be specified")
}

switch args[0] {
case "svc", "services", "service":
if len(args[1:]) != 1 {
return fmt.Errorf("%s, exactly one NAME is required, got %d", importErr, len(args[1:]))
}

var srcService *v1.Service
var err error
if o.SrcLeafClusterOptions.LeafClusterName != "" {
srcService, err = o.SrcLeafClusterOptions.LeafClusterNativeClient.CoreV1().Services(o.Namespace).Get(context.TODO(), args[1], metav1.GetOptions{})
} else {
srcService, err = o.MasterClient.CoreV1().Services(o.Namespace).Get(context.TODO(), args[1], metav1.GetOptions{})
}
if err != nil {
return fmt.Errorf("%s, get source service %s/%s error: %s", importErr, o.Namespace, args[1], err)
}

serviceImport := &mcsv1alpha1.ServiceImport{}
serviceImport.Kind = "ServiceImport"
serviceImport.Namespace = o.Namespace
serviceImport.Name = args[1]
serviceImport.Spec.Type = "ClusterSetIP"
if srcService.Spec.ClusterIP == "None" || len(srcService.Spec.ClusterIP) == 0 {
serviceImport.Spec.Type = "Headless"
}

serviceImport.Spec.Ports = make([]mcsv1alpha1.ServicePort, len(srcService.Spec.Ports))
for portIndex, svcPort := range srcService.Spec.Ports {
serviceImport.Spec.Ports[portIndex] = mcsv1alpha1.ServicePort{
Name: svcPort.Name,
Protocol: svcPort.Protocol,
AppProtocol: svcPort.AppProtocol,
Port: svcPort.Port,
}
}

// Create serviceImport, if exists,return error instead of updating it
if len(o.DstLeafClusterOptions.LeafClusterName) != 0 {
_, err = o.DstLeafClusterOptions.LeafClusterKosmosClient.MulticlusterV1alpha1().ServiceImports(o.Namespace).
Create(context.TODO(), serviceImport, metav1.CreateOptions{})
} else {
_, err = o.MasterKosmosClient.MulticlusterV1alpha1().ServiceImports(o.Namespace).
Create(context.TODO(), serviceImport, metav1.CreateOptions{})
}
if err != nil {
return fmt.Errorf("%s, create %s %s/%s %s: %s", importErr, serviceImport.Kind, o.Namespace, args[1], args[0], err)
}

fmt.Printf("Create %s %s/%s successfully!\n", serviceImport.Kind, o.Namespace, args[1])
default:
return fmt.Errorf("%s, not support import resouece %s", importErr, args[0])
}

return nil
}

0 comments on commit ad7c407

Please sign in to comment.