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:
barnettZQG
2022-02-09 11:48:38 +08:00
committed by GitHub
parent 24f147a72c
commit f6eea78ec8
17 changed files with 1072 additions and 9 deletions

View File

@@ -118,7 +118,7 @@ multicluster:
image:
repository: oamdev/cluster-gateway
tag: v1.1.7
pullPolicy: Always
pullPolicy: IfNotPresent
resources:
limits:
cpu: 100m

View 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
}

View 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())
})
})

View 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")
}

Binary file not shown.

24
pkg/utils/helm/testdata/index.yaml vendored Normal file
View 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"

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
View 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
}

View 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())
})
})

View File

@@ -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
View 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)
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}