Skip to content

Commit

Permalink
Add support to exec a component (#63)
Browse files Browse the repository at this point in the history
* Add support to exec a componenet

* Use github.com/stretchr/testify/assert
  • Loading branch information
LinuxSuRen committed Mar 5, 2021
1 parent 623dfeb commit 3d7b2f3
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 97 deletions.
15 changes: 15 additions & 0 deletions kubectl-plugin/common/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package common

import "github.com/spf13/cobra"

// NoFileCompletion avoid completion with files
func NoFileCompletion(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveNoFileComp
}

// ArrayCompletion return a completion which base on an array
func ArrayCompletion(array ...string) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return array, cobra.ShellCompDirectiveNoFileComp
}
}
86 changes: 2 additions & 84 deletions kubectl-plugin/component/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"strconv"
)

// NewComponentCmd returns a command to manage components of KubeSphere
Expand All @@ -26,7 +25,8 @@ func NewComponentCmd(client dynamic.Interface, clientset *kubernetes.Clientset)
NewComponentEditCmd(client),
NewComponentResetCmd(client),
NewComponentWatchCmd(client),
NewComponentLogCmd(client, clientset))
NewComponentLogCmd(client, clientset),
newComponentsExecCmd(client))
return
}

Expand Down Expand Up @@ -67,88 +67,6 @@ type WatchOption struct {
PrivateLocal string
}

// EnableOption is the option for component enable command
type EnableOption struct {
Option

Edit bool
Toggle bool
}

// NewComponentEnableCmd returns a command to enable (or disable) a component by name
func NewComponentEnableCmd(client dynamic.Interface) (cmd *cobra.Command) {
opt := &EnableOption{
Option: Option{
Client: client,
},
}
cmd = &cobra.Command{
Use: "enable",
Short: "Enable or disable the specific KubeSphere component",
PreRunE: opt.enablePreRunE,
RunE: opt.enableRunE,
}

flags := cmd.Flags()
flags.BoolVarP(&opt.Edit, "edit", "e", false,
"Indicate if you want to edit it instead of enable/disable a specified one. This flag will make others not work.")
flags.BoolVarP(&opt.Toggle, "toggle", "t", false,
"Indicate if you want to disable a component")
flags.StringVarP(&opt.Name, "name", "n", "",
"The name of target component which you want to enable/disable. Please provide option --sonarqube if you want to enable SonarQube.")
flags.StringVarP(&opt.SonarQube, "sonarqube", "", "",
"The SonarQube URL")
flags.StringVarP(&opt.SonarQube, "sonar", "", "",
"The SonarQube URL")
flags.StringVarP(&opt.SonarQubeToken, "sonarqube-token", "", "",
"The token of SonarQube")

// these are aliased options
_ = flags.MarkHidden("sonar")
return
}

func (o *EnableOption) enablePreRunE(cmd *cobra.Command, args []string) (err error) {
if o.Edit {
return
}

return o.componentNameCheck(cmd, args)
}

func (o *EnableOption) enableRunE(cmd *cobra.Command, args []string) (err error) {
if o.Edit {
err = common.UpdateWithEditor(kstypes.GetClusterConfiguration(), "kubesphere-system", "ks-installer", o.Client)
} else {
enabled := strconv.FormatBool(!o.Toggle)
ns, name := "kubesphere-system", "ks-installer"
var patchTarget string
switch o.Name {
case "devops", "alerting", "auditing", "events", "logging", "metrics_server", "networkpolicy", "notification", "openpitrix", "servicemesh":
patchTarget = o.Name
case "sonarqube", "sonar":
if o.SonarQube == "" || o.SonarQubeToken == "" {
err = fmt.Errorf("SonarQube or token is empty, please provide --sonarqube")
} else {
name = "ks-console-config"
err = integrateSonarQube(o.Client, ns, name, o.SonarQube, o.SonarQubeToken)
}
return
default:
err = fmt.Errorf("not support [%s] yet", o.Name)
return
}

patch := fmt.Sprintf(`[{"op": "replace", "path": "/spec/%s/enabled", "value": %s}]`, patchTarget, enabled)
ctx := context.TODO()
_, err = o.Client.Resource(kstypes.GetClusterConfiguration()).Namespace(ns).Patch(ctx,
name, types.JSONPatchType,
[]byte(patch),
metav1.PatchOptions{})
}
return
}

func (o *Option) getNsAndName(component string) (ns, name string) {
ns = "kubesphere-system"
switch o.Name {
Expand Down
105 changes: 105 additions & 0 deletions kubectl-plugin/component/enable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package component

import (
"context"
"fmt"
"github.com/linuxsuren/ks/kubectl-plugin/common"
kstypes "github.com/linuxsuren/ks/kubectl-plugin/types"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
"strconv"
)

// EnableOption is the option for component enable command
type EnableOption struct {
Option

Edit bool
Toggle bool
}

func getAvailableComponents() func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return common.ArrayCompletion("devops", "alerting", "auditing", "events", "logging", "metrics_server", "networkpolicy", "notification", "openpitrix", "servicemesh")
}

// NewComponentEnableCmd returns a command to enable (or disable) a component by name
func NewComponentEnableCmd(client dynamic.Interface) (cmd *cobra.Command) {
opt := &EnableOption{
Option: Option{
Client: client,
},
}

availableComs := getAvailableComponents()

cmd = &cobra.Command{
Use: "enable",
Short: "Enable or disable the specific KubeSphere component",
PreRunE: opt.enablePreRunE,
ValidArgsFunction: availableComs,
RunE: opt.enableRunE,
}

flags := cmd.Flags()
flags.BoolVarP(&opt.Edit, "edit", "e", false,
"Indicate if you want to edit it instead of enable/disable a specified one. This flag will make others not work.")
flags.BoolVarP(&opt.Toggle, "toggle", "t", false,
"Indicate if you want to disable a component")
flags.StringVarP(&opt.Name, "name", "n", "",
"The name of target component which you want to enable/disable. Please provide option --sonarqube if you want to enable SonarQube.")
flags.StringVarP(&opt.SonarQube, "sonarqube", "", "",
"The SonarQube URL")
flags.StringVarP(&opt.SonarQube, "sonar", "", "",
"The SonarQube URL")
flags.StringVarP(&opt.SonarQubeToken, "sonarqube-token", "", "",
"The token of SonarQube")

_ = cmd.RegisterFlagCompletionFunc("name", availableComs)

// these are aliased options
_ = flags.MarkHidden("sonar")
return
}

func (o *EnableOption) enablePreRunE(cmd *cobra.Command, args []string) (err error) {
if o.Edit {
return
}

return o.componentNameCheck(cmd, args)
}

func (o *EnableOption) enableRunE(cmd *cobra.Command, args []string) (err error) {
if o.Edit {
err = common.UpdateWithEditor(kstypes.GetClusterConfiguration(), "kubesphere-system", "ks-installer", o.Client)
} else {
enabled := strconv.FormatBool(!o.Toggle)
ns, name := "kubesphere-system", "ks-installer"
var patchTarget string
switch o.Name {
case "devops", "alerting", "auditing", "events", "logging", "metrics_server", "networkpolicy", "notification", "openpitrix", "servicemesh":
patchTarget = o.Name
case "sonarqube", "sonar":
if o.SonarQube == "" || o.SonarQubeToken == "" {
err = fmt.Errorf("SonarQube or token is empty, please provide --sonarqube")
} else {
name = "ks-console-config"
err = integrateSonarQube(o.Client, ns, name, o.SonarQube, o.SonarQubeToken)
}
return
default:
err = fmt.Errorf("not support [%s] yet", o.Name)
return
}

patch := fmt.Sprintf(`[{"op": "replace", "path": "/spec/%s/enabled", "value": %s}]`, patchTarget, enabled)
ctx := context.TODO()
_, err = o.Client.Resource(kstypes.GetClusterConfiguration()).Namespace(ns).Patch(ctx,
name, types.JSONPatchType,
[]byte(patch),
metav1.PatchOptions{})
}
return
}
81 changes: 81 additions & 0 deletions kubectl-plugin/component/exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package component

import (
"context"
"fmt"
"github.com/linuxsuren/ks/kubectl-plugin/common"
kstypes "github.com/linuxsuren/ks/kubectl-plugin/types"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
"os"
"os/exec"
"strings"
"syscall"
)

func newComponentsExecCmd(client dynamic.Interface) (cmd *cobra.Command) {
availableComs := common.ArrayCompletion("jenkins", "apiserver")

cmd = &cobra.Command{
Use: "exec",
Short: "Execute a command in a container.",
Long: `Execute a command in a container.
This command is similar with kubectl exec, the only difference is that you don't need to type the fullname'`,
ValidArgsFunction: availableComs,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
var kubectl string
if kubectl, err = exec.LookPath("kubectl"); err != nil {
return
}

switch args[0] {
case "jenkins":
var jenkinsPodName string
var list *unstructured.UnstructuredList
if list, err = client.Resource(kstypes.GetPodSchema()).Namespace("kubesphere-devops-system").List(
context.TODO(), metav1.ListOptions{}); err == nil {
for _, item := range list.Items {
if strings.HasPrefix(item.GetName(), "ks-jenkins") {
jenkinsPodName = item.GetName()
}
}
} else {
fmt.Println(err)
return
}

if jenkinsPodName == "" {
err = fmt.Errorf("cannot found ks-jenkins pod")
} else {
err = syscall.Exec(kubectl, []string{"kubectl", "-n", "kubesphere-devops-system", "exec", "-it", jenkinsPodName, "bash"}, os.Environ())
}
case "apiserver":
var apiserverPodName string
var list *unstructured.UnstructuredList
if list, err = client.Resource(kstypes.GetPodSchema()).Namespace("kubesphere-system").List(
context.TODO(), metav1.ListOptions{}); err == nil {
for _, item := range list.Items {
if strings.HasPrefix(item.GetName(), "ks-apiserver") {
apiserverPodName = item.GetName()
}
}
} else {
fmt.Println(err)
return
}

if apiserverPodName == "" {
err = fmt.Errorf("cannot found ks-jenkins pod")
} else {
err = syscall.Exec(kubectl, []string{"kubectl", "-n", "kubesphere-system", "exec", "-it", apiserverPodName, "sh"}, os.Environ())
}
}
return
},
}

return
}
4 changes: 4 additions & 0 deletions kubectl-plugin/component/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package component
import (
"context"
"fmt"
"github.com/linuxsuren/ks/kubectl-plugin/common"
kstypes "github.com/linuxsuren/ks/kubectl-plugin/types"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -54,6 +55,9 @@ take value from environment 'KS_REPO' if you don't set it`)
flags.StringVarP(&opt.PrivateLocal, "private-local", "", "127.0.0.1",
`The local address of registry
take value from environment 'KS_PRIVATE_LOCAL' if you don't set it`)

_ = cmd.RegisterFlagCompletionFunc("watch-deploy", common.ArrayCompletion("apiserver", "controller", "console"))
_ = cmd.RegisterFlagCompletionFunc("registry", common.ArrayCompletion("docker", " aliyun", "qingcloud", "private"))
return
}

Expand Down
Loading

0 comments on commit 3d7b2f3

Please sign in to comment.