Skip to content

Commit

Permalink
Revert "feat: playground only support k3d (#471)"
Browse files Browse the repository at this point in the history
This reverts commit ae1093d.
  • Loading branch information
yipeng1030 authored Oct 21, 2024
1 parent ae1093d commit 0a8c01b
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 175 deletions.
25 changes: 19 additions & 6 deletions docs/user_docs/cli/kbcli_playground_init.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ kbcli playground init [flags]
# create a k3d cluster on local host and install KubeBlocks
kbcli playground init
# create an AWS EKS cluster and install KubeBlocks, the region is required
kbcli playground init --cloud-provider aws --region us-west-1
# create an Alibaba cloud ACK cluster and install KubeBlocks, the region is required
kbcli playground init --cloud-provider alicloud --region cn-hangzhou
# create a Tencent cloud TKE cluster and install KubeBlocks, the region is required
kbcli playground init --cloud-provider tencentcloud --region ap-chengdu
# create a Google cloud GKE cluster and install KubeBlocks, the region is required
kbcli playground init --cloud-provider gcp --region us-east1
# after init, run the following commands to experience KubeBlocks quickly
# list database cluster and check its status
kbcli cluster list
Expand All @@ -40,12 +52,13 @@ kbcli playground init [flags]
### Options

```
--auto-approve Skip interactive approval during the initialization of playground
--cluster-type string Specify the cluster type to create, use 'kbcli cluster create --help' to get the available cluster type. (default "apecloud-mysql")
-h, --help help for init
--region string The region to create kubernetes cluster
--timeout duration Time to wait for init playground, such as --timeout=10m (default 10m0s)
--version string KubeBlocks version
--auto-approve Skip interactive approval during the initialization of playground
--cloud-provider string Cloud provider type, one of [local aws gcp alicloud tencentcloud] (default "local")
--cluster-definition string Specify the cluster definition, run "kbcli cd list" to get the available cluster definitions (default "apecloud-mysql")
-h, --help help for init
--region string The region to create kubernetes cluster
--timeout duration Time to wait for init playground, such as --timeout=10m (default 10m0s)
--version string KubeBlocks version
```

### Options inherited from parent commands
Expand Down
129 changes: 15 additions & 114 deletions pkg/cmd/cluster/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package cluster

import (
"github.com/apecloud/kbcli/pkg/cluster"
"github.com/apecloud/kbcli/pkg/printer"
kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/version"
fakediscovery "k8s.io/client-go/discovery/fake"
"k8s.io/client-go/kubernetes/scheme"

"fmt"
Expand All @@ -46,126 +40,33 @@ import (
)

var _ = Describe("Cluster", func() {

const (
clusterType = "apecloud-mysql"
clusterName = "test"
namespace = "default"
)
var (
tf *cmdtesting.TestFactory
streams genericiooptions.IOStreams
createOptions *action.CreateOptions
mockClient = func(data runtime.Object) *cmdtesting.TestFactory {
tf = testing.NewTestFactory(testing.Namespace)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &clientfake.RESTClient{
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
GroupVersion: schema.GroupVersion{Group: types.AppsAPIGroup, Version: types.AppsAPIVersion},
Resp: &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, data)},
}
tf.Client = tf.UnstructuredClient
tf.FakeDynamicClient = testing.FakeDynamicClient(data)
tf.WithDiscoveryClient(cmdtesting.NewFakeCachedDiscoveryClient())
return tf
}
)

var streams genericiooptions.IOStreams
var tf *cmdtesting.TestFactory
fakeConfigData := map[string]string{
"config.yaml": `# the default storage class name.
DEFAULT_STORAGE_CLASS: ""`,
}
BeforeEach(func() {
_ = kbappsv1.AddToScheme(scheme.Scheme)
_ = metav1.AddMetaToScheme(scheme.Scheme)
streams, _, _, _ = genericiooptions.NewTestIOStreams()
tf = mockClient(testing.FakeCompDef())
createOptions = &action.CreateOptions{
IOStreams: streams,
Factory: tf,
}
tf = cmdtesting.NewTestFactory().WithNamespace(namespace)
cd := testing.FakeClusterDef()
fakeDefaultStorageClass := testing.FakeStorageClass(testing.StorageClassName, testing.IsDefault)
// TODO: remove unused codes?
tf.FakeDynamicClient = testing.FakeDynamicClient(cd, fakeDefaultStorageClass, testing.FakeConfigMap("kubeblocks-manager-config", types.DefaultNamespace, fakeConfigData), testing.FakeSecret(types.DefaultNamespace, clusterName))
tf.Client = &clientfake.RESTClient{}
})

AfterEach(func() {
tf.Cleanup()
})

Context("create", func() {
It("without name", func() {
o, err := NewSubCmdsOptions(createOptions, clusterType)
Expect(err).Should(Succeed())
Expect(o).ShouldNot(BeNil())
Expect(o.ChartInfo).ShouldNot(BeNil())
o.Format = printer.YAML

Expect(o.CreateOptions.Complete()).To(Succeed())
o.Client = testing.FakeClientSet()
fakeDiscovery1, _ := o.Client.Discovery().(*fakediscovery.FakeDiscovery)
fakeDiscovery1.FakedServerVersion = &version.Info{Major: "1", Minor: "27", GitVersion: "v1.27.0"}
Expect(o.Complete(nil)).To(Succeed())
Expect(o.Validate()).To(Succeed())
Expect(o.Name).ShouldNot(BeEmpty())
Expect(o.Run()).Should(Succeed())
})
})

Context("create validate", func() {
var o *CreateSubCmdsOptions
BeforeEach(func() {
o = &CreateSubCmdsOptions{
CreateOptions: &action.CreateOptions{
Factory: tf,
Namespace: namespace,
Dynamic: tf.FakeDynamicClient,
IOStreams: streams,
},
}
o.Name = "mycluster"
o.ChartInfo, _ = cluster.BuildChartInfo(clusterType)
})

It("can validate the cluster name must begin with a letter and can only contain lowercase letters, numbers, and '-'.", func() {
type fn func()
var succeed = func(name string) fn {
return func() {
o.Name = name
Expect(o.Validate()).Should(Succeed())
}
}
var failed = func(name string) fn {
return func() {
o.Name = name
Expect(o.Validate()).Should(HaveOccurred())
}
}
// more case to add
invalidCase := []string{
"1abcd", "abcd-", "-abcd", "abc#d", "ABCD", "*&(&%",
}

validCase := []string{
"abcd", "abcd1", "a1-2b-3d",
}

for i := range invalidCase {
failed(invalidCase[i])
}

for i := range validCase {
succeed(validCase[i])
}

})

It("can validate whether the name is not longer than 16 characters when create a new cluster", func() {
Expect(len(o.Name)).Should(BeNumerically("<=", 16))
Expect(o.Validate()).Should(Succeed())
moreThan16 := 17
bytes := make([]byte, 0)
var clusterNameMoreThan16 string
for i := 0; i < moreThan16; i++ {
bytes = append(bytes, byte(i%26+'a'))
}
clusterNameMoreThan16 = string(bytes)
Expect(len(clusterNameMoreThan16)).Should(BeNumerically(">", 16))
o.Name = clusterNameMoreThan16
Expect(o.Validate()).Should(HaveOccurred())
})
// TODO: add create cluster case
Context("delete cluster", func() {

})

Expand Down
20 changes: 5 additions & 15 deletions pkg/cmd/cluster/create_subcmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"context"
"fmt"
"os"
"regexp"

"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
Expand Down Expand Up @@ -98,8 +97,8 @@ func buildCreateSubCmds(createOptions *action.CreateOptions) []*cobra.Command {
Run: func(cmd *cobra.Command, args []string) {
o.Args = args
cmdutil.CheckErr(o.CreateOptions.Complete())
cmdutil.CheckErr(o.Complete(cmd))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.complete(cmd))
cmdutil.CheckErr(o.validate())
cmdutil.CheckErr(o.Run())
},
}
Expand Down Expand Up @@ -151,7 +150,7 @@ func generateClusterName(dynamic dynamic.Interface, namespace string) (string, e
return "", fmt.Errorf("failed to generate cluster name")
}

func (o *CreateSubCmdsOptions) Complete(cmd *cobra.Command) error {
func (o *CreateSubCmdsOptions) complete(cmd *cobra.Command) error {
var err error

// if name is not specified, generate a random cluster name
Expand All @@ -163,9 +162,7 @@ func (o *CreateSubCmdsOptions) Complete(cmd *cobra.Command) error {
}

// get values from flags
if cmd != nil {
o.Values = getValuesFromFlags(cmd.LocalNonPersistentFlags())
}
o.Values = getValuesFromFlags(cmd.LocalNonPersistentFlags())

// get all the rendered objects
objs, err := o.getObjectsInfo()
Expand Down Expand Up @@ -214,14 +211,7 @@ func (o *CreateSubCmdsOptions) Complete(cmd *cobra.Command) error {
return nil
}

func (o *CreateSubCmdsOptions) Validate() error {
matched, _ := regexp.MatchString(`^[a-z]([-a-z0-9]*[a-z0-9])?$`, o.Name)
if !matched {
return fmt.Errorf("cluster name must begin with a letter and can only contain lowercase letters, numbers, and '-'")
}
if len(o.Name) > 16 {
return fmt.Errorf("cluster name should be less than 16 characters")
}
func (o *CreateSubCmdsOptions) validate() error {
return cluster.ValidateValues(o.ChartInfo, o.Values)
}

Expand Down
8 changes: 4 additions & 4 deletions pkg/cmd/cluster/create_subcmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,14 @@ var _ = Describe("create cluster by cluster type", func() {
o.Client = testing.FakeClientSet()
fakeDiscovery1, _ := o.Client.Discovery().(*fakediscovery.FakeDiscovery)
fakeDiscovery1.FakedServerVersion = &version.Info{Major: "1", Minor: "27", GitVersion: "v1.27.0"}
Expect(o.Complete(mysqlCmd)).Should(Succeed())
Expect(o.complete(mysqlCmd)).Should(Succeed())
Expect(o.Name).ShouldNot(BeEmpty())
Expect(o.Values).ShouldNot(BeNil())
Expect(o.ChartInfo.ClusterDef).Should(Equal(apeCloudMysql))

By("validate")
o.Dynamic = testing.FakeDynamicClient()
Expect(o.Validate()).Should(Succeed())
Expect(o.validate()).Should(Succeed())

By("run")
o.DryRun = "client"
Expand Down Expand Up @@ -157,14 +157,14 @@ var _ = Describe("create cluster by cluster type", func() {
fakeDiscovery1.FakedServerVersion = &version.Info{Major: "1", Minor: "27", GitVersion: "v1.27.0"}

Expect(shardCmd.Flags().Set("mode", "cluster")).Should(Succeed())
Expect(o.Complete(shardCmd)).Should(Succeed())
Expect(o.complete(shardCmd)).Should(Succeed())
Expect(o.Name).ShouldNot(BeEmpty())
Expect(o.Values).ShouldNot(BeNil())
Expect(o.ChartInfo.ComponentDef[0]).Should(Equal(redisComponent))

By("validate")
o.Dynamic = testing.FakeDynamicClient()
Expect(o.Validate()).Should(Succeed())
Expect(o.validate()).Should(Succeed())

By("run")
o.DryRun = "client"
Expand Down
76 changes: 75 additions & 1 deletion pkg/cmd/playground/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/util/templates"

appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1"
Expand All @@ -47,6 +48,7 @@ import (
"github.com/apecloud/kbcli/pkg/types"
"github.com/apecloud/kbcli/pkg/util"
"github.com/apecloud/kbcli/pkg/util/helm"
"github.com/apecloud/kbcli/pkg/util/prompt"
)

var (
Expand Down Expand Up @@ -93,7 +95,10 @@ func (o *destroyOptions) destroy() error {
return fmt.Errorf("no playground cluster found")
}

return o.destroyLocal()
if o.prevCluster.CloudProvider == cp.Local {
return o.destroyLocal()
}
return o.destroyCloud()
}

// destroyLocal destroy local k3d cluster that will destroy all resources
Expand All @@ -115,6 +120,75 @@ func (o *destroyOptions) destroyLocal() error {
return o.removeStateFile()
}

// destroyCloud destroys cloud kubernetes cluster, before destroying, we should delete
// all clusters created by KubeBlocks, uninstall KubeBlocks and remove the KubeBlocks
// namespace that will destroy all resources created by KubeBlocks, avoid to leave resources behind
func (o *destroyOptions) destroyCloud() error {
var err error

printer.Warning(o.Out, `This action will destroy the kubernetes cluster, there may be residual resources,
please confirm and manually clean up related resources after this action.
`)

fmt.Fprintf(o.Out, "Do you really want to destroy the kubernetes cluster %s?\n%s\n\n The operation cannot be rollbacked. Only 'yes' will be accepted to confirm.\n\n",
o.prevCluster.ClusterName, o.prevCluster.String())

// confirm to destroy
if !o.autoApprove {
entered, _ := prompt.NewPrompt("Enter a value:", nil, o.In).Run()
if entered != yesStr {
fmt.Fprintf(o.Out, "\nPlayground destroy cancelled.\n")
return cmdutil.ErrExit
}
}

o.startTime = time.Now()

// for cloud provider, we should delete all clusters created by KubeBlocks first,
// uninstall KubeBlocks and remove the KubeBlocks namespace, then destroy the
// playground cluster, avoid to leave resources behind.
// delete all clusters created by KubeBlocks, MUST BE VERY CAUTIOUS, use the right
// kubeconfig and context, otherwise, it will delete the wrong cluster.
if err = o.deleteClustersAndUninstallKB(); err != nil {
if strings.Contains(err.Error(), kubeClusterUnreachableErr.Error()) {
printer.Warning(o.Out, err.Error())
} else {
return err
}
}

// destroy playground kubernetes cluster
cpPath, err := cloudProviderRepoDir(o.prevCluster.KbcliVersion)
if err != nil {
return err
}

provider, err := cp.New(o.prevCluster.CloudProvider, cpPath, o.Out, o.ErrOut)
if err != nil {
return err
}

fmt.Fprintf(o.Out, "Destroy %s %s cluster %s...\n",
o.prevCluster.CloudProvider, cp.K8sService(o.prevCluster.CloudProvider), o.prevCluster.ClusterName)
if err = provider.DeleteK8sCluster(o.prevCluster); err != nil {
return err
}

// remove the cluster kubeconfig from the use default kubeconfig
if err = o.removeKubeConfig(); err != nil {
return err
}

// at last, remove the state file
if err = o.removeStateFile(); err != nil {
return err
}

fmt.Fprintf(o.Out, "Playground destroy completed in %s.\n", time.Since(o.startTime).Truncate(time.Second))
return nil
}

func (o *destroyOptions) deleteClustersAndUninstallKB() error {
var err error

Expand Down
Loading

0 comments on commit 0a8c01b

Please sign in to comment.