2021-07-13 13:03:25 +08:00
/ *
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"
2021-08-11 11:49:44 +08:00
"strings"
2021-07-13 13:03:25 +08:00
"time"
2021-12-09 20:31:19 +08:00
"k8s.io/client-go/rest"
2021-07-13 13:03:25 +08:00
"github.com/gosuri/uitable"
2021-12-07 15:11:52 +08:00
2021-07-13 13:03:25 +08:00
"github.com/pkg/errors"
"github.com/spf13/cobra"
types2 "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
2021-11-25 14:08:20 +08:00
common2 "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
2021-07-13 13:03:25 +08:00
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
2021-12-07 15:11:52 +08:00
pkgaddon "github.com/oam-dev/kubevela/pkg/addon"
2021-07-13 13:03:25 +08:00
"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"
)
const (
// DescAnnotation records the description of addon
DescAnnotation = "addons.oam.dev/description"
2021-11-16 13:56:39 +08:00
// DependsOnWorkFlowStepName is workflow step name which is used to check dependsOn app
DependsOnWorkFlowStepName = "depends-on-app"
2021-11-25 10:32:30 +08:00
// AddonTerraformProviderNamespace is the namespace of addon terraform provider
AddonTerraformProviderNamespace = "default"
// AddonTerraformProviderNameArgument is the argument name of addon terraform provider
AddonTerraformProviderNameArgument = "providerName"
2021-07-13 13:03:25 +08:00
)
2021-12-16 11:12:06 +08:00
const (
2021-12-20 14:34:45 +08:00
statusEnabling = "enabling"
2021-12-16 11:12:06 +08:00
)
2021-07-13 13:03:25 +08:00
var clt client . Client
var clientArgs common . Args
2021-12-07 15:11:52 +08:00
// var legacyAddonNamespace map[string]string
2021-10-14 18:21:49 +08:00
2021-07-13 13:03:25 +08:00
func init ( ) {
clientArgs , _ = common . InitBaseRestConfig ( )
clt , _ = clientArgs . GetClient ( )
2021-12-07 15:11:52 +08:00
// assume KubeVela 1.2 needn't consider the compatibility of 1.1
// legacyAddonNamespace = map[string]string{
// "fluxcd": types.DefaultKubeVelaNS,
// "ns-flux-system": types.DefaultKubeVelaNS,
// "kruise": types.DefaultKubeVelaNS,
// "prometheus": types.DefaultKubeVelaNS,
// "observability": "observability",
// "observability-asset": types.DefaultKubeVelaNS,
// "istio": "istio-system",
// "ns-istio-system": types.DefaultKubeVelaNS,
// "keda": types.DefaultKubeVelaNS,
// "ocm-cluster-manager": types.DefaultKubeVelaNS,
// "terraform": types.DefaultKubeVelaNS,
// "terraform-provider/alibaba": "default",
// "terraform-provider/azure": "default",
// }
2021-07-13 13:03:25 +08:00
}
// NewAddonCommand create `addon` command
func NewAddonCommand ( c common . Args , ioStreams cmdutil . IOStreams ) * cobra . Command {
cmd := & cobra . Command {
Use : "addon" ,
Short : "List and get addon in KubeVela" ,
Long : "List and get addon in KubeVela" ,
Annotations : map [ string ] string {
types . TagCommandType : types . TypeSystem ,
} ,
}
cmd . AddCommand (
NewAddonListCommand ( ) ,
2021-11-25 10:32:30 +08:00
NewAddonEnableCommand ( c , ioStreams ) ,
2021-07-13 13:03:25 +08:00
NewAddonDisableCommand ( ioStreams ) ,
2021-12-07 15:11:52 +08:00
NewAddonStatusCommand ( ioStreams ) ,
2021-12-13 19:47:32 +08:00
NewAddonRegistryCommand ( c , ioStreams ) ,
2021-07-13 13:03:25 +08:00
)
return cmd
}
// NewAddonListCommand create addon list command
func NewAddonListCommand ( ) * cobra . Command {
return & cobra . Command {
2021-10-14 18:21:49 +08:00
Use : "list" ,
Aliases : [ ] string { "ls" } ,
Short : "List addons" ,
Long : "List addons in KubeVela" ,
2021-07-13 13:03:25 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2021-12-07 15:11:52 +08:00
err := listAddons ( context . Background ( ) , "" )
2021-07-13 13:03:25 +08:00
if err != nil {
return err
}
return nil
} ,
}
}
// NewAddonEnableCommand create addon enable command
2021-11-25 10:32:30 +08:00
func NewAddonEnableCommand ( c common . Args , ioStream cmdutil . IOStreams ) * cobra . Command {
ctx := context . Background ( )
2021-07-13 13:03:25 +08:00
return & cobra . Command {
Use : "enable" ,
Short : "enable an addon" ,
Long : "enable an addon in cluster" ,
Example : "vela addon enable <addon-name>" ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2021-11-25 10:32:30 +08:00
k8sClient , err := c . GetClient ( )
if err != nil {
return err
}
2021-07-13 13:03:25 +08:00
if len ( args ) < 1 {
2021-08-18 11:37:46 +08:00
return fmt . Errorf ( "must specify addon name" )
2021-07-13 13:03:25 +08:00
}
name := args [ 0 ]
2021-08-11 11:49:44 +08:00
addonArgs , err := parseToMap ( args [ 1 : ] )
if err != nil {
return err
}
2021-12-09 20:31:19 +08:00
err = enableAddon ( ctx , k8sClient , c . Config , name , addonArgs )
2021-07-13 13:03:25 +08:00
if err != nil {
return err
}
2021-10-14 18:21:49 +08:00
fmt . Printf ( "Successfully enable addon:%s\n" , name )
2021-12-07 15:11:52 +08:00
if name == "velaux" {
fmt . Println ( ` Please use command: "vela port-forward -n vela-system addon-velaux 9082:80" and Select "Cluster: local | Namespace: vela-system | Component: velaux | Kind: Service" to check the dashboard ` )
}
2021-07-13 13:03:25 +08:00
return nil
} ,
}
}
2021-12-07 15:11:52 +08:00
func parseToMap ( args [ ] string ) ( map [ string ] interface { } , error ) {
res := map [ string ] interface { } { }
2021-08-11 11:49:44 +08:00
for _ , pair := range args {
line := strings . Split ( pair , "=" )
if len ( line ) != 2 {
return nil , fmt . Errorf ( "parameter format should be foo=bar, %s not match" , pair )
}
k := strings . TrimSpace ( line [ 0 ] )
v := strings . TrimSpace ( line [ 1 ] )
if k != "" && v != "" {
res [ k ] = v
}
}
return res , nil
}
2021-07-13 13:03:25 +08:00
// NewAddonDisableCommand create addon disable command
func NewAddonDisableCommand ( ioStream cmdutil . IOStreams ) * cobra . Command {
return & cobra . Command {
Use : "disable" ,
Short : "disable an addon" ,
Long : "disable an addon in cluster" ,
Example : "vela addon disable <addon-name>" ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) < 1 {
2021-08-18 11:37:46 +08:00
return fmt . Errorf ( "must specify addon name" )
2021-07-13 13:03:25 +08:00
}
name := args [ 0 ]
err := disableAddon ( name )
if err != nil {
return err
}
2021-10-14 18:21:49 +08:00
fmt . Printf ( "Successfully disable addon:%s\n" , name )
2021-07-13 13:03:25 +08:00
return nil
} ,
}
}
2021-12-07 15:11:52 +08:00
// NewAddonStatusCommand create addon status command
func NewAddonStatusCommand ( ioStream cmdutil . IOStreams ) * cobra . Command {
return & cobra . Command {
Use : "status" ,
Short : "get an addon's status" ,
Long : "get an addon's status from cluster" ,
Example : "vela addon status <addon-name>" ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) < 1 {
return fmt . Errorf ( "must specify addon name" )
}
name := args [ 0 ]
err := statusAddon ( name )
if err != nil {
return err
}
return nil
} ,
2021-07-13 13:03:25 +08:00
}
}
2021-12-09 20:31:19 +08:00
func enableAddon ( ctx context . Context , k8sClient client . Client , config * rest . Config , name string , args map [ string ] interface { } ) error {
2021-12-07 15:11:52 +08:00
var err error
registryDS := pkgaddon . NewRegistryDataStore ( k8sClient )
registries , err := registryDS . ListRegistries ( ctx )
2021-07-13 13:03:25 +08:00
if err != nil {
return err
}
2021-11-25 10:32:30 +08:00
2021-12-07 15:11:52 +08:00
for _ , registry := range registries {
2021-12-21 09:31:37 +08:00
err = pkgaddon . EnableAddon ( ctx , name , k8sClient , apply . NewAPIApplicator ( k8sClient ) , config , registry , args , nil )
if errors . Is ( err , pkgaddon . ErrNotExist ) {
2021-12-16 11:12:06 +08:00
continue
2021-12-07 15:11:52 +08:00
}
if err != nil {
return err
}
2021-12-21 09:31:37 +08:00
if err = waitApplicationRunning ( name ) ; err != nil {
2021-12-07 15:11:52 +08:00
return err
}
return nil
2021-11-25 10:32:30 +08:00
}
2021-12-07 15:11:52 +08:00
return fmt . Errorf ( "addon: %s not found in registrys" , name )
2021-07-13 13:03:25 +08:00
}
func disableAddon ( name string ) error {
2021-12-07 15:11:52 +08:00
if err := pkgaddon . DisableAddon ( context . Background ( ) , clt , name ) ; err != nil {
2021-07-13 13:03:25 +08:00
return err
}
2021-12-07 15:11:52 +08:00
return nil
2021-10-14 18:21:49 +08:00
}
2021-12-07 15:11:52 +08:00
func statusAddon ( name string ) error {
2021-12-20 14:34:45 +08:00
status , err := pkgaddon . GetAddonStatus ( context . Background ( ) , clt , name )
2021-12-07 15:11:52 +08:00
if err != nil {
return err
2021-07-13 13:03:25 +08:00
}
2021-12-20 14:34:45 +08:00
fmt . Printf ( "addon %s status is %s \n" , name , status . AddonPhase )
if status . AddonPhase == statusEnabling {
2021-12-16 11:12:06 +08:00
fmt . Printf ( "this addon is still enabling, please run \"vela status %s -n vela-system \" to check the status of the addon related app" , pkgaddon . Convert2AppName ( name ) )
2021-10-14 18:21:49 +08:00
}
2021-12-07 15:11:52 +08:00
return nil
2021-07-13 13:03:25 +08:00
}
2021-12-07 15:11:52 +08:00
func listAddons ( ctx context . Context , registry string ) error {
2021-12-21 09:31:37 +08:00
var addons [ ] * pkgaddon . UIData
2021-12-07 15:11:52 +08:00
var err error
registryDS := pkgaddon . NewRegistryDataStore ( clt )
registries , err := registryDS . ListRegistries ( ctx )
2021-07-13 13:03:25 +08:00
if err != nil {
2021-12-07 15:11:52 +08:00
return err
2021-07-13 13:03:25 +08:00
}
2021-12-07 15:11:52 +08:00
for _ , r := range registries {
if registry != "" && r . Name != registry {
continue
2021-07-13 13:03:25 +08:00
}
2021-12-21 09:31:37 +08:00
meta , err := r . ListAddonMeta ( )
if err != nil {
continue
2021-12-07 15:11:52 +08:00
}
2021-12-21 09:31:37 +08:00
addList , err := r . ListUIData ( meta , pkgaddon . CLIMetaOptions )
2021-12-07 15:11:52 +08:00
if err != nil {
continue
}
addons = mergeAddons ( addons , addList )
2021-08-11 11:49:44 +08:00
}
2021-07-13 13:03:25 +08:00
2021-12-07 15:11:52 +08:00
table := uitable . New ( )
table . AddRow ( "NAME" , "DESCRIPTION" , "STATUS" )
2021-11-25 10:32:30 +08:00
2021-12-07 15:11:52 +08:00
for _ , addon := range addons {
2021-12-20 14:34:45 +08:00
status , err := pkgaddon . GetAddonStatus ( ctx , clt , addon . Name )
2021-12-07 15:11:52 +08:00
if err != nil {
2021-11-25 10:32:30 +08:00
return err
}
2021-12-20 14:34:45 +08:00
table . AddRow ( addon . Name , addon . Description , status . AddonPhase )
2021-07-25 10:16:43 +08:00
}
2021-12-07 15:11:52 +08:00
fmt . Println ( table . String ( ) )
2021-07-13 13:03:25 +08:00
return nil
}
2021-12-07 15:11:52 +08:00
func waitApplicationRunning ( addonName string ) error {
2021-11-25 14:08:20 +08:00
trackInterval := 5 * time . Second
2021-12-06 12:33:52 +08:00
timeout := 600 * time . Second
2021-11-25 14:08:20 +08:00
start := time . Now ( )
ctx := context . Background ( )
2021-10-14 18:21:49 +08:00
var app v1beta1 . Application
2021-11-25 14:08:20 +08:00
spinner := newTrackingSpinnerWithDelay ( "Waiting addon running ..." , 1 * time . Second )
spinner . Start ( )
defer spinner . Stop ( )
for {
2021-12-07 15:11:52 +08:00
err := clt . Get ( ctx , types2 . NamespacedName { Name : pkgaddon . Convert2AppName ( addonName ) , Namespace : types . DefaultKubeVelaNS } , & app )
2021-07-25 10:16:43 +08:00
if err != nil {
2021-11-25 14:08:20 +08:00
return client . IgnoreNotFound ( err )
2021-07-25 10:16:43 +08:00
}
2021-10-14 18:21:49 +08:00
phase := app . Status . Phase
if phase == common2 . ApplicationRunning {
2021-11-25 14:08:20 +08:00
return nil
2021-07-25 10:16:43 +08:00
}
2021-11-25 14:08:20 +08:00
timeConsumed := int ( time . Since ( start ) . Seconds ( ) )
applySpinnerNewSuffix ( spinner , fmt . Sprintf ( "Waiting addon application running. It is now in phase: %s (timeout %d/%d seconds)..." ,
phase , timeConsumed , int ( timeout . Seconds ( ) ) ) )
2021-12-06 12:33:52 +08:00
if timeConsumed > int ( timeout . Seconds ( ) ) {
2021-12-07 15:11:52 +08:00
return errors . Errorf ( "Enabling timeout, please run \"vela status %s -n vela-system\" to check the status of the addon" , addonName )
2021-12-06 12:33:52 +08:00
}
2021-11-25 14:08:20 +08:00
time . Sleep ( trackInterval )
}
2021-08-11 11:49:44 +08:00
2021-11-16 13:56:39 +08:00
}
2021-09-14 19:35:21 +08:00
// TransAddonName will turn addon's name from xxx/yyy to xxx-yyy
func TransAddonName ( name string ) string {
return strings . ReplaceAll ( name , "/" , "-" )
}
2021-11-25 10:32:30 +08:00
2021-12-21 09:31:37 +08:00
func mergeAddons ( a1 , a2 [ ] * pkgaddon . UIData ) [ ] * pkgaddon . UIData {
2021-12-07 15:11:52 +08:00
for _ , item := range a2 {
if hasAddon ( a1 , item . Name ) {
continue
2021-11-25 10:32:30 +08:00
}
2021-12-07 15:11:52 +08:00
a1 = append ( a1 , item )
2021-11-25 10:32:30 +08:00
}
2021-12-07 15:11:52 +08:00
return a1
2021-11-25 10:32:30 +08:00
}
2021-12-21 09:31:37 +08:00
func hasAddon ( addons [ ] * pkgaddon . UIData , name string ) bool {
2021-12-07 15:11:52 +08:00
for _ , addon := range addons {
if addon . Name == name {
return true
2021-11-25 10:32:30 +08:00
}
}
2021-12-07 15:11:52 +08:00
return false
2021-11-25 10:32:30 +08:00
}
2021-12-07 15:11:52 +08:00
// TODO(wangyike) addon can support multi-tenancy, an addon can be enabled multi times and will create many times
// func checkWhetherTerraformProviderExist(ctx context.Context, k8sClient client.Client, addonName string, args map[string]string) (string, bool, error) {
// _, providerName := getTerraformProviderArgumentValue(addonName, args)
//
// providerNames, err := getTerraformProviderNames(ctx, k8sClient)
// if err != nil {
// return "", false, err
// }
// for _, name := range providerNames {
// if providerName == name {
// return providerName, true, nil
// }
// }
// return providerName, false, nil
// }
// func getTerraformProviderNames(ctx context.Context, k8sClient client.Client) ([]string, error) {
// var names []string
// providerList := &terraformv1beta1.ProviderList{}
// err := k8sClient.List(ctx, providerList, client.InNamespace(AddonTerraformProviderNamespace))
// if err != nil {
// if apimeta.IsNoMatchError(err) || kerrors.IsNotFound(err) {
// return nil, nil
// }
// return nil, err
// }
// for _, provider := range providerList.Items {
// names = append(names, provider.Name)
// }
// return names, nil
// }
//
// Get the value of argument AddonTerraformProviderNameArgument
// func getTerraformProviderArgumentValue(addonName string, args map[string]string) (map[string]string, string) {
// providerName, ok := args[AddonTerraformProviderNameArgument]
// if !ok {
// switch addonName {
// case "terraform-alibaba":
// providerName = "default"
// case "terraform-aws":
// providerName = "aws"
// case "terraform-azure":
// providerName = "azure"
// }
// args[AddonTerraformProviderNameArgument] = providerName
// }
// return args, providerName
// }