mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 18:10:21 +00:00
Feat: support install and uninstall vela core (#3181)
* Feat: support install and uninstall vela core Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: support upgrade crd Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: support set reuse args Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: apply CRD before install or upgrade Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
This commit is contained in:
@@ -118,7 +118,7 @@ multicluster:
|
||||
image:
|
||||
repository: oamdev/cluster-gateway
|
||||
tag: v1.1.7
|
||||
pullPolicy: Always
|
||||
pullPolicy: IfNotPresent
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
|
||||
252
pkg/utils/helm/helm_helper.go
Normal file
252
pkg/utils/helm/helm_helper.go
Normal file
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package helm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"helm.sh/helm/v3/pkg/kube"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
"helm.sh/helm/v3/pkg/storage"
|
||||
"helm.sh/helm/v3/pkg/storage/driver"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
kyaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/client-go/rest"
|
||||
k8scmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
)
|
||||
|
||||
// Helper provides helper functions for common Helm operations
|
||||
type Helper struct {
|
||||
}
|
||||
|
||||
// NewHelper creates a Helper
|
||||
func NewHelper() *Helper {
|
||||
return &Helper{}
|
||||
}
|
||||
|
||||
// LoadCharts load helm chart from local or remote
|
||||
func (h *Helper) LoadCharts(chartRepoURL string) (*chart.Chart, error) {
|
||||
var err error
|
||||
var chart *chart.Chart
|
||||
if utils.IsValidURL(chartRepoURL) {
|
||||
chartBytes, err := common.HTTPGet(context.Background(), chartRepoURL)
|
||||
if err != nil {
|
||||
return nil, errors.New("error retrieving Helm Chart at " + chartRepoURL + ": " + err.Error())
|
||||
}
|
||||
ch, err := loader.LoadArchive(bytes.NewReader(chartBytes))
|
||||
if err != nil {
|
||||
return nil, errors.New("error retrieving Helm Chart at " + chartRepoURL + ": " + err.Error())
|
||||
}
|
||||
return ch, err
|
||||
}
|
||||
chart, err = loader.Load(chartRepoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return chart, nil
|
||||
}
|
||||
|
||||
// UpgradeChartOptions options for upgrade chart
|
||||
type UpgradeChartOptions struct {
|
||||
Config *rest.Config
|
||||
Detail bool
|
||||
Logging cmdutil.IOStreams
|
||||
Wait bool
|
||||
ReuseValues bool
|
||||
}
|
||||
|
||||
// UpgradeChart install or upgrade helm chart
|
||||
func (h *Helper) UpgradeChart(ch *chart.Chart, releaseName, namespace string, values map[string]interface{}, config UpgradeChartOptions) (*release.Release, error) {
|
||||
if ch == nil || len(ch.Templates) == 0 {
|
||||
return nil, fmt.Errorf("empty chart provided for %s", releaseName)
|
||||
}
|
||||
config.Logging.Infof("Start upgrading Helm Chart %s in namespace %s\n", releaseName, namespace)
|
||||
|
||||
cfg, err := newActionConfig(config.Config, namespace, config.Detail, config.Logging)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
histClient := action.NewHistory(cfg)
|
||||
var newRelease *release.Release
|
||||
timeoutInMinutes := 18
|
||||
releases, err := histClient.Run(releaseName)
|
||||
if err != nil {
|
||||
if errors.Is(err, driver.ErrReleaseNotFound) {
|
||||
// fresh install
|
||||
install := action.NewInstall(cfg)
|
||||
install.Namespace = namespace
|
||||
install.ReleaseName = releaseName
|
||||
install.Wait = config.Wait
|
||||
install.Timeout = time.Duration(timeoutInMinutes) * time.Minute
|
||||
newRelease, err = install.Run(ch, values)
|
||||
} else {
|
||||
return nil, fmt.Errorf("could not retrieve history of releases associated to %s: %w", releaseName, err)
|
||||
}
|
||||
} else {
|
||||
config.Logging.Infof("Found existing installation, overwriting...")
|
||||
|
||||
// check if the previous installation is still pending (e.g., waiting to complete)
|
||||
for _, r := range releases {
|
||||
if r.Info.Status == release.StatusPendingInstall || r.Info.Status == release.StatusPendingUpgrade ||
|
||||
r.Info.Status == release.StatusPendingRollback {
|
||||
return nil, fmt.Errorf("previous installation (e.g., using vela install or helm upgrade) is still in progress. Please try again in %d minutes", timeoutInMinutes)
|
||||
}
|
||||
}
|
||||
|
||||
// overwrite existing installation
|
||||
install := action.NewUpgrade(cfg)
|
||||
install.Namespace = namespace
|
||||
install.Wait = config.Wait
|
||||
install.Timeout = time.Duration(timeoutInMinutes) * time.Minute
|
||||
install.ReuseValues = config.ReuseValues
|
||||
newRelease, err = install.Run(releaseName, ch, values)
|
||||
}
|
||||
// check if install/upgrade worked
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error when installing/upgrading Helm Chart %s in namespace %s: %w",
|
||||
releaseName, namespace, err)
|
||||
}
|
||||
if newRelease == nil {
|
||||
return nil, fmt.Errorf("failed to install release %s", releaseName)
|
||||
}
|
||||
return newRelease, nil
|
||||
}
|
||||
|
||||
// UninstallRelease uninstalls the provided release
|
||||
func (h *Helper) UninstallRelease(releaseName, namespace string, config *rest.Config, showDetail bool, logging cmdutil.IOStreams) error {
|
||||
cfg, err := newActionConfig(config, namespace, showDetail, logging)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iCli := action.NewUninstall(cfg)
|
||||
_, err = iCli.Run(releaseName)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when uninstalling Helm release %s in namespace %s: %w",
|
||||
releaseName, namespace, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListVersions list available versions from repo
|
||||
func (h *Helper) ListVersions(repoURL string, chartName string) (repo.ChartVersions, error) {
|
||||
var body []byte
|
||||
if utils.IsValidURL(repoURL) {
|
||||
parsedURL, err := url.Parse(repoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsedURL.RawPath = path.Join(parsedURL.RawPath, "index.yaml")
|
||||
parsedURL.Path = path.Join(parsedURL.Path, "index.yaml")
|
||||
indexURL := parsedURL.String()
|
||||
body, err = common.HTTPGet(context.Background(), indexURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("download index file from %s failure %w", repoURL, err)
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
body, err = ioutil.ReadFile(path.Join(filepath.Clean(repoURL), "index.yaml"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read index file from %s failure %w", repoURL, err)
|
||||
}
|
||||
}
|
||||
i := &repo.IndexFile{}
|
||||
if err := yaml.UnmarshalStrict(body, i); err != nil {
|
||||
return nil, fmt.Errorf("parse index file from %s failure %w", repoURL, err)
|
||||
}
|
||||
return i.Entries[chartName], nil
|
||||
}
|
||||
|
||||
// GetDeploymentsFromManifest get deployment from helm manifest
|
||||
func GetDeploymentsFromManifest(helmManifest string) []*appsv1.Deployment {
|
||||
deployments := []*appsv1.Deployment{}
|
||||
dec := kyaml.NewYAMLToJSONDecoder(strings.NewReader(helmManifest))
|
||||
for {
|
||||
var deployment appsv1.Deployment
|
||||
err := dec.Decode(&deployment)
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(deployment.Kind, "deployment") {
|
||||
deployments = append(deployments, &deployment)
|
||||
}
|
||||
}
|
||||
return deployments
|
||||
}
|
||||
|
||||
// GetCRDFromChart get crd from helm chart
|
||||
func GetCRDFromChart(chart *chart.Chart) []*crdv1.CustomResourceDefinition {
|
||||
crds := []*crdv1.CustomResourceDefinition{}
|
||||
for _, crdFile := range chart.CRDs() {
|
||||
var crd crdv1.CustomResourceDefinition
|
||||
err := kyaml.Unmarshal(crdFile.Data, &crd)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
crds = append(crds, &crd)
|
||||
}
|
||||
return crds
|
||||
}
|
||||
|
||||
func newActionConfig(config *rest.Config, namespace string, showDetail bool, logging cmdutil.IOStreams) (*action.Configuration, error) {
|
||||
restClientGetter := cmdutil.NewRestConfigGetterByConfig(config, namespace)
|
||||
log := func(format string, a ...interface{}) {
|
||||
if showDetail {
|
||||
logging.Infof(format+"\n", a...)
|
||||
}
|
||||
}
|
||||
kubeClient := &kube.Client{
|
||||
Factory: k8scmdutil.NewFactory(restClientGetter),
|
||||
Log: log,
|
||||
}
|
||||
client, err := kubeClient.Factory.KubernetesClientSet()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := driver.NewSecrets(client.CoreV1().Secrets(namespace))
|
||||
s.Log = log
|
||||
return &action.Configuration{
|
||||
RESTClientGetter: restClientGetter,
|
||||
Releases: storage.Init(s),
|
||||
KubeClient: kubeClient,
|
||||
Log: log,
|
||||
}, nil
|
||||
}
|
||||
68
pkg/utils/helm/helm_helper_test.go
Normal file
68
pkg/utils/helm/helm_helper_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package helm
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
)
|
||||
|
||||
var _ = Describe("Test helm helper", func() {
|
||||
|
||||
It("Test LoadCharts ", func() {
|
||||
helper := NewHelper()
|
||||
chart, err := helper.LoadCharts("./testdata/autoscalertrait-0.1.0.tgz")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(chart).ShouldNot(BeNil())
|
||||
Expect(chart.Metadata).ShouldNot(BeNil())
|
||||
Expect(cmp.Diff(chart.Metadata.Version, "0.1.0")).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("Test UpgradeChart", func() {
|
||||
helper := NewHelper()
|
||||
chart, err := helper.LoadCharts("./testdata/autoscalertrait-0.1.0.tgz")
|
||||
Expect(err).Should(BeNil())
|
||||
release, err := helper.UpgradeChart(chart, "autoscalertrait", "default", nil, UpgradeChartOptions{
|
||||
Config: cfg,
|
||||
Detail: false,
|
||||
Logging: util.IOStreams{Out: os.Stdout, ErrOut: os.Stderr},
|
||||
Wait: false,
|
||||
})
|
||||
crds := GetCRDFromChart(release.Chart)
|
||||
Expect(cmp.Diff(len(crds), 1)).Should(BeEmpty())
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test UninstallRelease", func() {
|
||||
helper := NewHelper()
|
||||
err := helper.UninstallRelease("autoscalertrait", "default", cfg, false, util.IOStreams{Out: os.Stdout, ErrOut: os.Stderr})
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test ListVersions ", func() {
|
||||
helper := NewHelper()
|
||||
versions, err := helper.ListVersions("./testdata", "autoscalertrait")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(cmp.Diff(len(versions), 2)).Should(BeEmpty())
|
||||
})
|
||||
|
||||
})
|
||||
63
pkg/utils/helm/helm_suite_test.go
Normal file
63
pkg/utils/helm/helm_suite_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package helm
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var testEnv *envtest.Environment
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
By("bootstrapping test environment")
|
||||
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
}
|
||||
|
||||
By("start kube test env")
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
close(done)
|
||||
}, 240)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
if testEnv != nil {
|
||||
err := testEnv.Stop()
|
||||
Expect(err).Should(BeNil())
|
||||
}
|
||||
})
|
||||
|
||||
func TestHelm(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Helm Suite")
|
||||
}
|
||||
BIN
pkg/utils/helm/testdata/autoscalertrait-0.1.0.tgz
vendored
Normal file
BIN
pkg/utils/helm/testdata/autoscalertrait-0.1.0.tgz
vendored
Normal file
Binary file not shown.
24
pkg/utils/helm/testdata/index.yaml
vendored
Normal file
24
pkg/utils/helm/testdata/index.yaml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
apiVersion: v1
|
||||
entries:
|
||||
autoscalertrait:
|
||||
- apiVersion: v2
|
||||
appVersion: 1.16.0
|
||||
created: "2021-03-13T23:42:30.9837659+09:00"
|
||||
description: A Helm chart for kubevela autoscalertrait controller
|
||||
digest: 33c4b9eeabf6c26e6aa8c4edaaf0e4a5d2a2f1fd857ffe32b6d3be97d6d971f0
|
||||
name: autoscalertrait
|
||||
type: application
|
||||
urls:
|
||||
- https://charts.kubevela.net/example/autoscalertrait-0.1.0.tgz
|
||||
version: 0.1.0
|
||||
- apiVersion: v2
|
||||
appVersion: 1.16.0
|
||||
created: "2021-03-13T23:42:30.9837659+09:00"
|
||||
description: A Helm chart for kubevela autoscalertrait controller
|
||||
digest: 33c4b9eeabf6c26e6aa8c4edaaf0e4a5d2a2f1fd857ffe32b6d3be97d6d971f0
|
||||
name: autoscalertrait
|
||||
type: application
|
||||
urls:
|
||||
- https://charts.kubevela.net/example/autoscalertrait-0.1.0.tgz
|
||||
version: 0.2.0
|
||||
generated: "2021-03-13T23:42:30.9832644+09:00"
|
||||
@@ -18,6 +18,7 @@ package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
@@ -56,3 +57,16 @@ func ParseAPIServerEndpoint(server string) (string, error) {
|
||||
}
|
||||
return fmt.Sprintf("%s://%s:%s", scheme, host, port), nil
|
||||
}
|
||||
|
||||
// IsValidURL checks whether the given string is a valid URL or not
|
||||
func IsValidURL(strURL string) bool {
|
||||
_, err := url.ParseRequestURI(strURL)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
u, err := url.Parse(strURL)
|
||||
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -48,6 +48,14 @@ func NewRestConfigGetter(namespace string) genericclioptions.RESTClientGetter {
|
||||
}
|
||||
}
|
||||
|
||||
// NewRestConfigGetterByConfig new rest config getter
|
||||
func NewRestConfigGetterByConfig(config *rest.Config, namespace string) genericclioptions.RESTClientGetter {
|
||||
return &restConfigGetter{
|
||||
config: config,
|
||||
namespace: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
type restConfigGetter struct {
|
||||
config *rest.Config
|
||||
namespace string
|
||||
|
||||
@@ -22,16 +22,20 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
gov "github.com/hashicorp/go-version"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/klog"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/system"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
"github.com/oam-dev/kubevela/version"
|
||||
)
|
||||
|
||||
var assumeYes bool
|
||||
|
||||
// NewCommand will contain all commands
|
||||
func NewCommand() *cobra.Command {
|
||||
ioStream := util.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
|
||||
@@ -45,7 +49,6 @@ func NewCommand() *cobra.Command {
|
||||
PrintHelpByTag(cmd, allCommands, types.TypeStart)
|
||||
PrintHelpByTag(cmd, allCommands, types.TypeApp)
|
||||
PrintHelpByTag(cmd, allCommands, types.TypeCD)
|
||||
|
||||
PrintHelpByTag(cmd, allCommands, types.TypeExtension)
|
||||
PrintHelpByTag(cmd, allCommands, types.TypeSystem)
|
||||
cmd.Println("Flags:")
|
||||
@@ -98,10 +101,12 @@ func NewCommand() *cobra.Command {
|
||||
NewComponentsCommand(commandArgs, ioStream),
|
||||
|
||||
// System
|
||||
NewInstallCommand(commandArgs, "1", ioStream),
|
||||
NewUnInstallCommand(commandArgs, "2", ioStream),
|
||||
NewExportCommand(commandArgs, ioStream),
|
||||
NewCUEPackageCommand(commandArgs, ioStream),
|
||||
SystemCommandGroup(commandArgs, ioStream),
|
||||
NewVersionCommand(),
|
||||
NewVersionCommand(ioStream),
|
||||
NewCompletionCommand(),
|
||||
|
||||
// helper
|
||||
@@ -117,12 +122,14 @@ func NewCommand() *cobra.Command {
|
||||
klog.InitFlags(fset)
|
||||
_ = fset.Set("v", "-1")
|
||||
|
||||
// init global flags
|
||||
cmds.PersistentFlags().BoolVarP(&assumeYes, "yes", "y", false, "Assume yes for all user prompts")
|
||||
return cmds
|
||||
}
|
||||
|
||||
// NewVersionCommand print client version
|
||||
func NewVersionCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
func NewVersionCommand(ioStream util.IOStreams) *cobra.Command {
|
||||
version := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Prints vela build version information",
|
||||
Long: "Prints vela build version information.",
|
||||
@@ -139,4 +146,47 @@ GolangVersion: %v
|
||||
types.TagCommandType: types.TypeSystem,
|
||||
},
|
||||
}
|
||||
version.AddCommand(NewVersionListCommand(ioStream))
|
||||
return version
|
||||
}
|
||||
|
||||
// NewVersionListCommand show all versions command
|
||||
func NewVersionListCommand(ioStream util.IOStreams) *cobra.Command {
|
||||
var showAll bool
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all available versions",
|
||||
Long: "Query all available versions from remote server.",
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
helmHelper := helm.NewHelper()
|
||||
versions, err := helmHelper.ListVersions(kubevelaInstallerHelmRepoURL, kubeVelaChartName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentV, err := gov.NewVersion(version.VelaVersion)
|
||||
if err != nil && !showAll {
|
||||
return fmt.Errorf("can not parse current version %s", version.VelaVersion)
|
||||
}
|
||||
for _, chartV := range versions {
|
||||
if chartV != nil {
|
||||
v, err := gov.NewVersion(chartV.Version)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if v.GreaterThan(currentV) {
|
||||
ioStream.Info("Newer Version:", v.String())
|
||||
} else if showAll {
|
||||
ioStream.Info("Older Version:", v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeSystem,
|
||||
},
|
||||
}
|
||||
cmd.PersistentFlags().BoolVarP(&showAll, "all", "a", false, "List all available versions, if not, only list newer version")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -74,3 +74,10 @@ var _ = BeforeSuite(func(done Done) {
|
||||
Expect(err).Should(BeNil())
|
||||
close(done)
|
||||
}, 240)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
if testEnv != nil {
|
||||
err := testEnv.Stop()
|
||||
Expect(err).Should(BeNil())
|
||||
}
|
||||
})
|
||||
|
||||
323
references/cli/install.go
Normal file
323
references/cli/install.go
Normal file
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"cuelang.org/go/pkg/strings"
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/strvals"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierror "k8s.io/apimachinery/pkg/api/errors"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
innerVersion "github.com/oam-dev/kubevela/version"
|
||||
)
|
||||
|
||||
// defaultConstraint
|
||||
const defaultConstraint = ">= 1.19, <= 1.21"
|
||||
|
||||
const kubevelaInstallerHelmRepoURL = "https://charts.kubevela.net/core/"
|
||||
|
||||
// kubeVelaReleaseName release name
|
||||
const kubeVelaReleaseName = "kubevela"
|
||||
|
||||
// kubeVelaChartName the name of veal core chart
|
||||
const kubeVelaChartName = "vela-core"
|
||||
|
||||
// InstallArgs the args for install command
|
||||
type InstallArgs struct {
|
||||
userInput *UserInput
|
||||
helmHelper *helm.Helper
|
||||
Args common.Args
|
||||
Values []string
|
||||
Namespace string
|
||||
Version string
|
||||
ChartFilePath string
|
||||
Detail bool
|
||||
ReuseValues bool
|
||||
}
|
||||
|
||||
// NewInstallCommand creates `install` command to install vela core
|
||||
func NewInstallCommand(c common.Args, order string, ioStreams util.IOStreams) *cobra.Command {
|
||||
installArgs := &InstallArgs{Args: c, userInput: NewUserInput(), helmHelper: helm.NewHelper()}
|
||||
cmd := &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "Installs or Upgrades Kubevela control plane on a Kubernetes cluster.",
|
||||
Long: "The Kubevela CLI allows installing Kubevela on any Kubernetes derivative to which your kube config is pointing to.",
|
||||
Args: cobra.ExactArgs(0),
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
// CheckRequirements
|
||||
ioStreams.Info("Check Requirements ...")
|
||||
restConfig, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get kube config, You can set KUBECONFIG env or make file ~/.kube/config")
|
||||
}
|
||||
if isNewerVersion, serverVersion, err := checkKubeServerVersion(restConfig); err != nil {
|
||||
ioStreams.Error(err.Error())
|
||||
ioStreams.Error("This is not recommended and could have negative impacts on the stability of KubeVela - use at your own risk.")
|
||||
|
||||
userConfirmation := installArgs.userInput.AskBool("Do you want to continue?", &UserInputOptions{assumeYes})
|
||||
if !userConfirmation {
|
||||
return fmt.Errorf("stopping installation")
|
||||
}
|
||||
} else if isNewerVersion {
|
||||
ioStreams.Errorf("The Kubernetes server version(%s) is higher than the one officially supported(%s).\n", serverVersion, defaultConstraint)
|
||||
ioStreams.Error("This is not recommended and could have negative impacts on the stability of KubeVela - use at your own risk.")
|
||||
userInput := NewUserInput()
|
||||
userConfirmation := userInput.AskBool("Do you want to continue?", &UserInputOptions{assumeYes})
|
||||
if !userConfirmation {
|
||||
return fmt.Errorf("stopping installation")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Step1: Download Helm Chart
|
||||
ioStreams.Info("Installing KubeVela Core ...")
|
||||
if installArgs.ChartFilePath == "" {
|
||||
installArgs.ChartFilePath = getKubeVelaHelmChartRepoURL(installArgs.Version)
|
||||
}
|
||||
chart, err := installArgs.helmHelper.LoadCharts(installArgs.ChartFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loadding the helm chart of kubeVela control plane failure, %w", err)
|
||||
}
|
||||
ioStreams.Infof("Helm Chart used for KubeVela control plane installation: %s \n", installArgs.ChartFilePath)
|
||||
|
||||
// Step2: Prepare namespace
|
||||
restConfig, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get kube config failure: %w", err)
|
||||
}
|
||||
kubeClient, err := c.GetClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("create kube client failure: %w", err)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
var namespace corev1.Namespace
|
||||
var namespaceExists = true
|
||||
if err := kubeClient.Get(ctx, apitypes.NamespacedName{Name: installArgs.Namespace}, &namespace); err != nil {
|
||||
if !apierror.IsNotFound(err) {
|
||||
return fmt.Errorf("failed to check if namespace %s already exists: %w", installArgs.Namespace, err)
|
||||
}
|
||||
namespaceExists = false
|
||||
}
|
||||
if namespaceExists {
|
||||
fmt.Printf("Existing KubeVela installation found in namespace %s\n\n", installArgs.Namespace)
|
||||
userConfirmation := installArgs.userInput.AskBool("Do you want to overwrite this installation?", &UserInputOptions{assumeYes})
|
||||
if !userConfirmation {
|
||||
return fmt.Errorf("stopping installation")
|
||||
}
|
||||
} else {
|
||||
namespace.Name = installArgs.Namespace
|
||||
if err := kubeClient.Create(ctx, &namespace); err != nil {
|
||||
return fmt.Errorf("failed to create kubeVela namespace %s: %w", installArgs.Namespace, err)
|
||||
}
|
||||
}
|
||||
// Step3: Prepare the values for chart
|
||||
imageTag := installArgs.Version
|
||||
if !strings.HasPrefix(imageTag, "v") {
|
||||
imageTag = "v" + imageTag
|
||||
}
|
||||
var values = map[string]interface{}{
|
||||
"image": map[string]interface{}{
|
||||
"tag": imageTag,
|
||||
"pullPolicy": "IfNotPresent",
|
||||
},
|
||||
}
|
||||
if len(installArgs.Values) > 0 {
|
||||
for _, value := range installArgs.Values {
|
||||
if err := strvals.ParseInto(value, values); err != nil {
|
||||
return errors.Wrap(err, "failed parsing --set data")
|
||||
}
|
||||
}
|
||||
}
|
||||
// Step4: apply new CRDs
|
||||
if err := upgradeCRDs(cmd.Context(), kubeClient, chart); err != nil {
|
||||
return errors.New(fmt.Sprintf("upgrade CRD failure %s", err.Error()))
|
||||
}
|
||||
// Step5: Install or upgrade helm release
|
||||
release, err := installArgs.helmHelper.UpgradeChart(chart, kubeVelaReleaseName, installArgs.Namespace, values,
|
||||
helm.UpgradeChartOptions{
|
||||
Config: restConfig,
|
||||
Detail: installArgs.Detail,
|
||||
Logging: ioStreams,
|
||||
Wait: true,
|
||||
ReuseValues: installArgs.ReuseValues,
|
||||
})
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Could not install KubeVela control plane installation: %s", err.Error())
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
err = waitKubeVelaControllerRunning(kubeClient, installArgs.Namespace, release.Manifest)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Could not complete KubeVela control plane installation: %s \nFor troubleshooting, please check the status of the kubevela deployment by executing the following command: \n\nkubectl get pods -n %s\n", err.Error(), installArgs.Namespace)
|
||||
return errors.New(msg)
|
||||
}
|
||||
ioStreams.Info()
|
||||
ioStreams.Info("KubeVela control plane has been successfully set up on your cluster.")
|
||||
ioStreams.Info("If you want to enable dashboard, please run \"vela addon enable velaux\"")
|
||||
return nil
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandOrder: order,
|
||||
types.TagCommandType: types.TypeSystem,
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringArrayVarP(&installArgs.Values, "set", "", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
|
||||
cmd.Flags().StringVarP(&installArgs.Namespace, "namespace", "n", "vela-system", "namespace scope for installing KubeVela Core")
|
||||
cmd.Flags().StringVarP(&installArgs.Version, "version", "v", innerVersion.VelaVersion, "")
|
||||
cmd.Flags().BoolVarP(&installArgs.Detail, "detail", "d", true, "show detail log of installation")
|
||||
cmd.Flags().BoolVarP(&installArgs.ReuseValues, "reuse", "r", true, "will re-use the user's last supplied values.")
|
||||
cmd.Flags().StringVarP(&installArgs.ChartFilePath, "file", "f", "", "custom the chart path of KubeVela control plane")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func checkKubeServerVersion(config *rest.Config) (bool, string, error) {
|
||||
// get kubernetes cluster api version
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
// check version
|
||||
serverVersion, err := client.ServerVersion()
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("get kubernetes api version failure %w", err)
|
||||
}
|
||||
vStr := fmt.Sprintf("%s.%s", serverVersion.Major, strings.Replace(serverVersion.Minor, "+", "", 1))
|
||||
currentVersion, err := version.NewVersion(vStr)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
hConstraints, err := version.NewConstraint(defaultConstraint)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
isNewerVersion, allConstraintsValid := checkIsNewVersion(hConstraints, currentVersion)
|
||||
|
||||
if allConstraintsValid {
|
||||
return false, vStr, nil
|
||||
}
|
||||
if isNewerVersion {
|
||||
return true, vStr, nil
|
||||
}
|
||||
|
||||
return false, vStr, fmt.Errorf("the kubernetes server version '%s' doesn't satisfy constraints '%s'", serverVersion, defaultConstraint)
|
||||
}
|
||||
|
||||
// checkIsNewVersion checks if the provided version is higher than all constraints and if all constraints are valid
|
||||
func checkIsNewVersion(hConstraints version.Constraints, serverVersion *version.Version) (bool, bool) {
|
||||
isNewerVersion := false
|
||||
allConstraintsValid := true
|
||||
for _, constraint := range hConstraints {
|
||||
validConstraint := constraint.Check(serverVersion)
|
||||
if !validConstraint {
|
||||
allConstraintsValid = false
|
||||
constraintVersionString := getConstraintVersion(constraint.String())
|
||||
constraintVersion, err := version.NewVersion(constraintVersionString)
|
||||
if err != nil {
|
||||
return false, false
|
||||
}
|
||||
if serverVersion.GreaterThan(constraintVersion) {
|
||||
isNewerVersion = true
|
||||
} else {
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return isNewerVersion, allConstraintsValid
|
||||
}
|
||||
|
||||
// getConstraintVersion returns the version of a constraint without leading spaces, <, >, =
|
||||
func getConstraintVersion(constraint string) string {
|
||||
for index, character := range constraint {
|
||||
if character != '<' && character != '>' && character != ' ' && character != '=' {
|
||||
return constraint[index:]
|
||||
}
|
||||
}
|
||||
return constraint
|
||||
}
|
||||
|
||||
func getKubeVelaHelmChartRepoURL(version string) string {
|
||||
// Determine installer version
|
||||
if innerVersion.IsOfficialKubeVelaVersion(version) {
|
||||
version, _ := innerVersion.GetOfficialKubeVelaVersion(version)
|
||||
return kubevelaInstallerHelmRepoURL + kubeVelaChartName + "-" + version + ".tgz"
|
||||
}
|
||||
return kubevelaInstallerHelmRepoURL + kubeVelaChartName + "-" + version + ".tgz"
|
||||
}
|
||||
|
||||
func waitKubeVelaControllerRunning(kubeClient client.Client, namespace, manifest string) error {
|
||||
deployments := helm.GetDeploymentsFromManifest(manifest)
|
||||
spinner := newTrackingSpinnerWithDelay("Waiting KubeVela control plane running ...", 1*time.Second)
|
||||
spinner.Start()
|
||||
defer spinner.Stop()
|
||||
trackInterval := 5 * time.Second
|
||||
timeout := 600 * time.Second
|
||||
start := time.Now()
|
||||
ctx := context.Background()
|
||||
for {
|
||||
timeConsumed := int(time.Since(start).Seconds())
|
||||
var readyCount = 0
|
||||
for i, d := range deployments {
|
||||
err := kubeClient.Get(ctx, apitypes.NamespacedName{Name: d.Name, Namespace: namespace}, deployments[i])
|
||||
if err != nil {
|
||||
return client.IgnoreNotFound(err)
|
||||
}
|
||||
if deployments[i].Status.ReadyReplicas != deployments[i].Status.Replicas {
|
||||
applySpinnerNewSuffix(spinner, fmt.Sprintf("Waiting deployment %s ready. (timeout %d/%d seconds)...", deployments[i].Name, timeConsumed, int(timeout.Seconds())))
|
||||
} else {
|
||||
readyCount++
|
||||
}
|
||||
}
|
||||
if readyCount >= len(deployments) {
|
||||
return nil
|
||||
}
|
||||
if timeConsumed > int(timeout.Seconds()) {
|
||||
return errors.Errorf("Enabling timeout, please run \"kubectl get pod -n vela-system\" to check the status")
|
||||
}
|
||||
time.Sleep(trackInterval)
|
||||
}
|
||||
}
|
||||
|
||||
func upgradeCRDs(ctx context.Context, kubeClient client.Client, chart *chart.Chart) error {
|
||||
crds := helm.GetCRDFromChart(chart)
|
||||
apply := apply.NewAPIApplicator(kubeClient)
|
||||
for _, crd := range crds {
|
||||
if err := apply.Apply(ctx, crd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
38
references/cli/install_test.go
Normal file
38
references/cli/install_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetKubeVelaHelmChartRepoURL(t *testing.T) {
|
||||
assert.Equal(t, getKubeVelaHelmChartRepoURL("v1.2.2"), "https://charts.kubevela.net/core/vela-core-1.2.2.tgz")
|
||||
assert.Equal(t, getKubeVelaHelmChartRepoURL("1.1.11"), "https://charts.kubevela.net/core/vela-core-1.1.11.tgz")
|
||||
}
|
||||
|
||||
var _ = Describe("Test Install Command", func() {
|
||||
It("Test checkKubeServerVersion", func() {
|
||||
new, _, err := checkKubeServerVersion(cfg)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(new).Should(BeFalse())
|
||||
})
|
||||
})
|
||||
@@ -30,6 +30,9 @@ import (
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
)
|
||||
|
||||
// AllNamespace list app in all namespaces
|
||||
var AllNamespace bool
|
||||
|
||||
// NewListCommand creates `ls` command and its nested children command
|
||||
func NewListCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
ctx := context.Background()
|
||||
@@ -49,6 +52,9 @@ func NewListCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *c
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if AllNamespace {
|
||||
namespace = ""
|
||||
}
|
||||
return printApplicationList(ctx, newClient, namespace, ioStreams)
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
@@ -57,12 +63,17 @@ func NewListCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *c
|
||||
},
|
||||
}
|
||||
addNamespaceAndEnvArg(cmd)
|
||||
cmd.Flags().BoolVarP(&AllNamespace, "all-namespaces", "A", false, "If true, check the specified action in all namespaces.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func printApplicationList(ctx context.Context, c client.Reader, namespace string, ioStreams cmdutil.IOStreams) error {
|
||||
table := newUITable()
|
||||
table.AddRow("APP", "COMPONENT", "TYPE", "TRAITS", "PHASE", "HEALTHY", "STATUS", "CREATED-TIME")
|
||||
header := []interface{}{"APP", "COMPONENT", "TYPE", "TRAITS", "PHASE", "HEALTHY", "STATUS", "CREATED-TIME"}
|
||||
if AllNamespace {
|
||||
header = append([]interface{}{"NAMESPACE"}, header...)
|
||||
}
|
||||
table.AddRow(header...)
|
||||
applist := v1beta1.ApplicationList{}
|
||||
if err := c.List(ctx, &applist, client.InNamespace(namespace)); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
@@ -94,7 +105,11 @@ func printApplicationList(ctx context.Context, c client.Reader, namespace string
|
||||
for _, tr := range cmp.Traits {
|
||||
traits = append(traits, tr.Type)
|
||||
}
|
||||
table.AddRow(appName, cmp.Name, cmp.Type, strings.Join(traits, ","), a.Status.Phase, healthy, status, a.CreationTimestamp)
|
||||
if AllNamespace {
|
||||
table.AddRow(a.Namespace, appName, cmp.Name, cmp.Type, strings.Join(traits, ","), a.Status.Phase, healthy, status, a.CreationTimestamp)
|
||||
} else {
|
||||
table.AddRow(appName, cmp.Name, cmp.Type, strings.Join(traits, ","), a.Status.Phase, healthy, status, a.CreationTimestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
ioStreams.Info(table.String())
|
||||
|
||||
129
references/cli/uninstall.go
Normal file
129
references/cli/uninstall.go
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierror "k8s.io/apimachinery/pkg/api/errors"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
)
|
||||
|
||||
// UnInstallArgs the args for uninstall command
|
||||
type UnInstallArgs struct {
|
||||
userInput *UserInput
|
||||
helmHelper *helm.Helper
|
||||
Args common.Args
|
||||
Namespace string
|
||||
Detail bool
|
||||
}
|
||||
|
||||
// NewUnInstallCommand creates `uninstall` command to uninstall vela core
|
||||
func NewUnInstallCommand(c common.Args, order string, ioStreams util.IOStreams) *cobra.Command {
|
||||
unInstallArgs := &UnInstallArgs{Args: c, userInput: NewUserInput(), helmHelper: helm.NewHelper()}
|
||||
cmd := &cobra.Command{
|
||||
Use: "uninstall",
|
||||
Short: "Uninstalls KubeVela from a Kubernetes cluster",
|
||||
Example: `vela uninstall`,
|
||||
Long: "Uninstalls KubeVela from a Kubernetes cluster.",
|
||||
Args: cobra.ExactArgs(0),
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
userConfirmation := unInstallArgs.userInput.AskBool("Would you like to uninstall KubeVela from this cluster?", &UserInputOptions{AssumeYes: assumeYes})
|
||||
if !userConfirmation {
|
||||
return nil
|
||||
}
|
||||
kubeClient, err := c.GetClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get kube client")
|
||||
}
|
||||
var apps v1beta1.ApplicationList
|
||||
err = kubeClient.List(context.Background(), &apps, &client.ListOptions{
|
||||
Namespace: "",
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to check app in cluster")
|
||||
}
|
||||
if len(apps.Items) > 0 {
|
||||
return fmt.Errorf("please delete all applications before uninstall. using \"vela ls -A\" view all applications")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ioStreams.Info("Starting to uninstall KubeVela")
|
||||
restConfig, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get kube config, You can set KUBECONFIG env or make file ~/.kube/config")
|
||||
}
|
||||
if err := unInstallArgs.helmHelper.UninstallRelease(kubeVelaReleaseName, unInstallArgs.Namespace, restConfig, unInstallArgs.Detail, ioStreams); err != nil {
|
||||
return err
|
||||
}
|
||||
// Clean up vela-system namespace
|
||||
kubeClient, err := c.GetClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get kube client")
|
||||
}
|
||||
if err := deleteNamespace(kubeClient, unInstallArgs.Namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
var namespace corev1.Namespace
|
||||
var namespaceExists = true
|
||||
if err := kubeClient.Get(cmd.Context(), apitypes.NamespacedName{Name: "kubevela"}, &namespace); err != nil {
|
||||
if !apierror.IsNotFound(err) {
|
||||
return fmt.Errorf("failed to check if namespace kubevela already exists: %w", err)
|
||||
}
|
||||
namespaceExists = false
|
||||
}
|
||||
if namespaceExists {
|
||||
fmt.Printf("The namespace kubevela is exist, it is the default database of the velaux\n\n")
|
||||
userConfirmation := unInstallArgs.userInput.AskBool("Do you want to delete it?", &UserInputOptions{assumeYes})
|
||||
if userConfirmation {
|
||||
if err := deleteNamespace(kubeClient, "kubevela"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
ioStreams.Info("Successfully uninstalled KubeVela")
|
||||
ioStreams.Info("Please delete all CRD from cluster using \"kubectl get crd |grep oam | awk '{print $1}' | xargs kubectl delete crd\"")
|
||||
return nil
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandOrder: order,
|
||||
types.TagCommandType: types.TypeSystem,
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&unInstallArgs.Namespace, "namespace", "n", "vela-system", "namespace scope for installing KubeVela Core")
|
||||
cmd.Flags().BoolVarP(&unInstallArgs.Detail, "detail", "d", true, "show detail log of installation")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func deleteNamespace(kubeClient client.Client, namespace string) error {
|
||||
var ns corev1.Namespace
|
||||
ns.Name = namespace
|
||||
return kubeClient.Delete(context.Background(), &ns)
|
||||
}
|
||||
@@ -17,10 +17,15 @@ limitations under the License.
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
@@ -66,3 +71,47 @@ func getCompNameFromClusterObjectReference(ctx context.Context, k8sClient client
|
||||
}
|
||||
return labels[oam.LabelAppComponent], nil
|
||||
}
|
||||
|
||||
// UserInput user input in command
|
||||
type UserInput struct {
|
||||
Writer io.Writer
|
||||
Reader *bufio.Reader
|
||||
}
|
||||
|
||||
// UserInputOptions user input options
|
||||
type UserInputOptions struct {
|
||||
AssumeYes bool
|
||||
}
|
||||
|
||||
// NewUserInput new user input util
|
||||
func NewUserInput() *UserInput {
|
||||
return &UserInput{
|
||||
Writer: os.Stdout,
|
||||
Reader: bufio.NewReader(os.Stdin),
|
||||
}
|
||||
}
|
||||
|
||||
// AskBool format the answer to bool type
|
||||
func (ui *UserInput) AskBool(question string, opts *UserInputOptions) bool {
|
||||
fmt.Fprintf(ui.Writer, "%s (y/n)", question)
|
||||
if opts.AssumeYes {
|
||||
return true
|
||||
}
|
||||
line, err := ui.read()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
if input := strings.TrimSpace(strings.ToLower(line)); input == "y" || input == "yes" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ui *UserInput) read() (string, error) {
|
||||
line, err := ui.Reader.ReadString('\n')
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return "", err
|
||||
}
|
||||
resultStr := strings.TrimSuffix(line, "\n")
|
||||
return resultStr, err
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/meta"
|
||||
@@ -43,6 +42,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcekeeper"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
@@ -256,7 +256,7 @@ func ReadRemoteOrLocalPath(pathOrURL string) ([]byte, error) {
|
||||
}
|
||||
var body []byte
|
||||
var err error
|
||||
if strings.HasPrefix(pathOrURL, "https://") || strings.HasPrefix(pathOrURL, "http://") {
|
||||
if utils.IsValidURL(pathOrURL) {
|
||||
body, err = common.HTTPGet(context.Background(), pathOrURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -16,8 +16,31 @@ limitations under the License.
|
||||
|
||||
package version
|
||||
|
||||
import "github.com/hashicorp/go-version"
|
||||
|
||||
// GitRevision is the commit of repo
|
||||
var GitRevision = "UNKNOWN"
|
||||
|
||||
// VelaVersion is the version of cli.
|
||||
var VelaVersion = "UNKNOWN"
|
||||
|
||||
// IsOfficialKubeVelaVersion checks whether the provided version string follows a KubeVela version pattern
|
||||
func IsOfficialKubeVelaVersion(versionStr string) bool {
|
||||
_, err := version.NewSemver(versionStr)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// GetOfficialKubeVelaVersion extracts the KubeVela version from the provided string
|
||||
// More precisely, this method returns the segments and prerelease info w/o metadata
|
||||
func GetOfficialKubeVelaVersion(versionStr string) (string, error) {
|
||||
s, err := version.NewSemver(versionStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
v := s.String()
|
||||
metadata := s.Metadata()
|
||||
if metadata != "" {
|
||||
metadata = "+" + metadata
|
||||
}
|
||||
return v[:len(v)-len(metadata)], nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user