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"
2022-01-07 15:49:27 +08:00
"os"
2022-01-11 11:29:10 +08:00
"path/filepath"
2022-04-13 22:20:07 +08:00
"sort"
2021-08-11 11:49:44 +08:00
"strings"
2021-07-13 13:03:25 +08:00
"time"
2022-03-28 21:25:38 +08:00
"github.com/fatih/color"
2022-02-23 12:17:31 +08:00
"k8s.io/client-go/discovery"
2022-02-14 14:02:16 +08:00
"helm.sh/helm/v3/pkg/strvals"
2022-06-02 10:01:04 +08:00
"github.com/oam-dev/kubevela/pkg/apiserver/domain/service"
2022-01-11 11:29:10 +08:00
"github.com/oam-dev/kubevela/pkg/oam"
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"
"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-25 11:01:59 +08:00
statusEnabled = "enabled"
statusDisabled = "disabled"
2022-03-31 10:24:20 +08:00
statusSuspend = "suspend"
2021-12-16 11:12:06 +08:00
)
2022-03-21 10:38:56 +08:00
var forceDisable bool
2022-03-28 21:25:38 +08:00
var addonVersion string
2022-03-21 10:38:56 +08:00
2022-03-29 23:03:57 +08:00
var addonClusters string
2022-05-19 16:21:36 +08:00
var verboseSatatus bool
2021-07-13 13:03:25 +08:00
// NewAddonCommand create `addon` command
2022-01-06 13:29:02 +08:00
func NewAddonCommand ( c common . Args , order string , ioStreams cmdutil . IOStreams ) * cobra . Command {
2021-07-13 13:03:25 +08:00
cmd := & cobra . Command {
Use : "addon" ,
2022-01-10 17:45:20 +08:00
Short : "Manage addons for extension." ,
Long : "Manage addons for extension." ,
2021-07-13 13:03:25 +08:00
Annotations : map [ string ] string {
2022-01-06 13:29:02 +08:00
types . TagCommandOrder : order ,
types . TagCommandType : types . TypeExtension ,
2021-07-13 13:03:25 +08:00
} ,
}
cmd . AddCommand (
2022-01-07 15:49:27 +08:00
NewAddonListCommand ( c ) ,
2021-11-25 10:32:30 +08:00
NewAddonEnableCommand ( c , ioStreams ) ,
2022-01-07 15:49:27 +08:00
NewAddonDisableCommand ( c , ioStreams ) ,
2021-12-25 11:01:59 +08:00
NewAddonStatusCommand ( c , ioStreams ) ,
2021-12-13 19:47:32 +08:00
NewAddonRegistryCommand ( c , ioStreams ) ,
2021-12-22 19:31:20 +08:00
NewAddonUpgradeCommand ( c , ioStreams ) ,
2022-05-07 23:39:34 +08:00
NewAddonPackageCommand ( c ) ,
2021-07-13 13:03:25 +08:00
)
return cmd
}
// NewAddonListCommand create addon list command
2022-01-07 15:49:27 +08:00
func NewAddonListCommand ( c common . Args ) * cobra . Command {
2021-07-13 13:03:25 +08:00
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 {
2022-01-11 11:29:10 +08:00
k8sClient , err := c . GetClient ( )
2022-01-07 15:49:27 +08:00
if err != nil {
return err
}
2022-05-10 13:35:12 +08:00
table , err := listAddons ( context . Background ( ) , k8sClient , "" )
2021-07-13 13:03:25 +08:00
if err != nil {
return err
}
2022-05-10 13:35:12 +08:00
fmt . Println ( table . String ( ) )
2021-07-13 13:03:25 +08:00
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 ( )
2022-01-11 11:29:10 +08:00
cmd := & cobra . Command {
2022-03-23 14:33:15 +08:00
Use : "enable" ,
Short : "enable an addon" ,
Long : "enable an addon in cluster." ,
Example : ` \
Enable addon by :
vela addon enable < addon - name >
2022-03-28 21:25:38 +08:00
Enable addon with specify version :
2022-05-10 13:35:12 +08:00
vela addon enable < addon - name > -- version < addon - version >
2022-03-23 14:33:15 +08:00
Enable addon for specific clusters , ( local means control plane ) :
vela addon enable < addon - name > -- clusters = { local , cluster1 , cluster2 }
` ,
2021-07-13 13:03:25 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2021-11-25 10:32:30 +08:00
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
}
2022-03-23 14:33:15 +08:00
addonArgs , err := parseAddonArgsToMap ( args [ 1 : ] )
2021-08-11 11:49:44 +08:00
if err != nil {
return err
}
2022-03-29 23:03:57 +08:00
addonArgs [ types . ClustersArg ] = transClusters ( addonClusters )
2022-01-07 15:49:27 +08:00
config , err := c . GetConfig ( )
if err != nil {
return err
}
k8sClient , err := c . GetClient ( )
if err != nil {
return err
}
2022-02-23 12:17:31 +08:00
dc , err := c . GetDiscoveryClient ( )
if err != nil {
return err
}
2022-01-11 11:29:10 +08:00
addonOrDir := args [ 0 ]
2022-01-12 17:43:08 +08:00
var name = addonOrDir
2022-02-11 19:56:29 +08:00
if file , err := os . Stat ( addonOrDir ) ; err == nil {
if ! file . IsDir ( ) {
return fmt . Errorf ( "%s is not addon dir" , addonOrDir )
}
2022-02-14 18:04:34 +08:00
ioStream . Infof ( "enable addon by local dir: %s \n" , addonOrDir )
2022-01-11 11:29:10 +08:00
// args[0] is a local path install with local dir, use base dir name as addonName
2022-06-09 10:12:19 +08:00
abs , err := filepath . Abs ( addonOrDir )
if err != nil {
return errors . Wrapf ( err , "directory %s is invalid" , addonOrDir )
}
name = filepath . Base ( abs )
2022-02-23 12:17:31 +08:00
err = enableAddonByLocal ( ctx , name , addonOrDir , k8sClient , dc , config , addonArgs )
2022-01-11 11:29:10 +08:00
if err != nil {
return err
}
} else {
2022-02-16 14:20:37 +08:00
if filepath . IsAbs ( addonOrDir ) || strings . HasPrefix ( addonOrDir , "." ) || strings . HasSuffix ( addonOrDir , "/" ) {
return fmt . Errorf ( "addon directory %s not found in local" , addonOrDir )
}
2022-03-28 21:25:38 +08:00
err = enableAddon ( ctx , k8sClient , dc , config , name , addonVersion , addonArgs )
2022-01-11 11:29:10 +08:00
if err != nil {
return err
}
2021-07-13 13:03:25 +08:00
}
2022-06-09 10:12:19 +08:00
fmt . Printf ( "Addon %s enabled successfully.\n" , name )
2022-04-26 15:27:43 +08:00
AdditionalEndpointPrinter ( ctx , c , k8sClient , name , false )
2021-12-22 19:31:20 +08:00
return nil
} ,
}
2022-03-28 21:25:38 +08:00
cmd . Flags ( ) . StringVarP ( & addonVersion , "version" , "v" , "" , "specify the addon version to enable" )
2022-03-29 23:03:57 +08:00
cmd . Flags ( ) . StringVarP ( & addonClusters , types . ClustersArg , "c" , "" , "specify the runtime-clusters to enable" )
2022-01-11 11:29:10 +08:00
return cmd
2021-12-22 19:31:20 +08:00
}
2022-01-12 17:43:08 +08:00
// AdditionalEndpointPrinter will print endpoints
2022-04-26 15:27:43 +08:00
func AdditionalEndpointPrinter ( ctx context . Context , c common . Args , k8sClient client . Client , name string , isUpgrade bool ) {
2022-06-09 10:12:19 +08:00
fmt . Printf ( "Please access %s from the following endpoints:\n" , name )
2022-06-20 10:37:40 +08:00
err := printAppEndpoints ( ctx , pkgaddon . Convert2AppName ( name ) , types . DefaultKubeVelaNS , Filter { } , c , true )
2022-04-24 09:16:34 +08:00
if err != nil {
fmt . Println ( "Get application endpoints error:" , err )
2022-01-12 17:43:08 +08:00
return
}
if name == "velaux" {
2022-04-26 15:27:43 +08:00
if ! isUpgrade {
2022-06-02 10:01:04 +08:00
fmt . Printf ( "Initialized admin username and password: admin / %s \n" , service . InitAdminPassword )
2022-04-26 15:27:43 +08:00
}
2022-03-31 17:33:06 +08:00
fmt . Println ( ` To open the dashboard directly by port-forward: ` )
fmt . Println ( ` vela port-forward -n vela-system addon-velaux 9082:80 ` )
2022-04-26 15:27:43 +08:00
fmt . Println ( ` Select "Cluster: local | Namespace: vela-system | Kind: Service | Name: velaux" from the prompt. ` )
2022-03-31 17:33:06 +08:00
fmt . Println ( ` Please refer to https://kubevela.io/docs/reference/addons/velaux for more VelaUX addon installation and visiting method. ` )
2022-01-12 17:43:08 +08:00
}
}
2021-12-22 19:31:20 +08:00
// NewAddonUpgradeCommand create addon upgrade command
func NewAddonUpgradeCommand ( c common . Args , ioStream cmdutil . IOStreams ) * cobra . Command {
ctx := context . Background ( )
2022-01-11 11:29:10 +08:00
cmd := & cobra . Command {
2022-03-23 14:33:15 +08:00
Use : "upgrade" ,
Short : "upgrade an addon" ,
Long : "upgrade an addon in cluster." ,
Example : ` \
Upgrade addon by :
vela addon upgrade < addon - name >
2022-03-28 21:25:38 +08:00
Upgrade addon with specify version :
2022-05-10 13:35:12 +08:00
vela addon upgrade < addon - name > -- version < addon - version >
2022-03-23 14:33:15 +08:00
Upgrade addon for specific clusters , ( local means control plane ) :
vela addon upgrade < addon - name > -- clusters = { local , cluster1 , cluster2 }
` ,
2021-12-22 19:31:20 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) < 1 {
return fmt . Errorf ( "must specify addon name" )
}
2022-01-07 15:49:27 +08:00
config , err := c . GetConfig ( )
if err != nil {
return err
}
k8sClient , err := c . GetClient ( )
if err != nil {
return err
}
2022-02-23 12:17:31 +08:00
dc , err := c . GetDiscoveryClient ( )
if err != nil {
return err
}
2022-03-23 14:33:15 +08:00
addonArgs , err := parseAddonArgsToMap ( args [ 1 : ] )
2021-12-22 19:31:20 +08:00
if err != nil {
return err
}
2022-03-29 23:03:57 +08:00
addonArgs [ types . ClustersArg ] = transClusters ( addonClusters )
2022-01-11 11:29:10 +08:00
addonOrDir := args [ 0 ]
var name string
2022-02-11 19:56:29 +08:00
if file , err := os . Stat ( addonOrDir ) ; err == nil {
if ! file . IsDir ( ) {
return fmt . Errorf ( "%s is not addon dir" , addonOrDir )
}
2022-02-14 18:04:34 +08:00
ioStream . Infof ( "enable addon by local dir: %s \n" , addonOrDir )
2022-01-11 11:29:10 +08:00
// args[0] is a local path install with local dir
2022-06-09 10:12:19 +08:00
abs , err := filepath . Abs ( addonOrDir )
if err != nil {
return errors . Wrapf ( err , "cannot open directory %s" , addonOrDir )
}
name = filepath . Base ( abs )
2022-01-11 11:29:10 +08:00
_ , err = pkgaddon . FetchAddonRelatedApp ( context . Background ( ) , k8sClient , name )
if err != nil {
return errors . Wrapf ( err , "cannot fetch addon related addon %s" , name )
}
2022-02-23 12:17:31 +08:00
err = enableAddonByLocal ( ctx , name , addonOrDir , k8sClient , dc , config , addonArgs )
2022-01-11 11:29:10 +08:00
if err != nil {
return err
}
} else {
2022-02-16 14:20:37 +08:00
if filepath . IsAbs ( addonOrDir ) || strings . HasPrefix ( addonOrDir , "." ) || strings . HasSuffix ( addonOrDir , "/" ) {
return fmt . Errorf ( "addon directory %s not found in local" , addonOrDir )
}
2022-04-01 01:04:29 +08:00
name = addonOrDir
2022-01-11 11:29:10 +08:00
_ , err = pkgaddon . FetchAddonRelatedApp ( context . Background ( ) , k8sClient , addonOrDir )
if err != nil {
return errors . Wrapf ( err , "cannot fetch addon related addon %s" , addonOrDir )
}
2022-03-28 21:25:38 +08:00
err = enableAddon ( ctx , k8sClient , dc , config , addonOrDir , addonVersion , addonArgs )
2022-01-11 11:29:10 +08:00
if err != nil {
return err
}
2021-12-22 19:31:20 +08:00
}
2022-01-11 11:29:10 +08:00
2022-06-09 10:12:19 +08:00
fmt . Printf ( "Addon %s enabled successfully." , name )
2022-04-26 15:27:43 +08:00
AdditionalEndpointPrinter ( ctx , c , k8sClient , name , true )
2021-07-13 13:03:25 +08:00
return nil
} ,
}
2022-03-28 21:25:38 +08:00
cmd . Flags ( ) . StringVarP ( & addonVersion , "version" , "v" , "" , "specify the addon version to upgrade" )
2022-01-11 11:29:10 +08:00
return cmd
2021-07-13 13:03:25 +08:00
}
2022-03-23 14:33:15 +08:00
func parseAddonArgsToMap ( args [ ] string ) ( map [ string ] interface { } , error ) {
2021-12-07 15:11:52 +08:00
res := map [ string ] interface { } { }
2022-02-14 14:02:16 +08:00
for _ , arg := range args {
2022-04-28 15:58:23 +08:00
if err := strvals . ParseInto ( arg , res ) ; err != nil {
2022-02-14 14:02:16 +08:00
return nil , err
2021-08-11 11:49:44 +08:00
}
}
return res , nil
}
2021-07-13 13:03:25 +08:00
// NewAddonDisableCommand create addon disable command
2022-01-07 15:49:27 +08:00
func NewAddonDisableCommand ( c common . Args , ioStream cmdutil . IOStreams ) * cobra . Command {
2022-03-21 10:38:56 +08:00
cmd := & cobra . Command {
2021-07-13 13:03:25 +08:00
Use : "disable" ,
Short : "disable an addon" ,
2022-01-12 17:43:08 +08:00
Long : "disable an addon in cluster." ,
2021-07-13 13:03:25 +08:00
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 ]
2022-01-07 15:49:27 +08:00
k8sClient , err := c . GetClient ( )
if err != nil {
return err
}
2022-03-21 10:38:56 +08:00
config , err := c . GetConfig ( )
if err != nil {
return err
}
err = disableAddon ( k8sClient , name , config , forceDisable )
2021-07-13 13:03:25 +08:00
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
} ,
}
2022-03-21 10:38:56 +08:00
cmd . Flags ( ) . BoolVarP ( & forceDisable , "force" , "f" , false , "skip checking if applications are still using this addon" )
return cmd
2021-07-13 13:03:25 +08:00
}
2021-12-07 15:11:52 +08:00
// NewAddonStatusCommand create addon status command
2021-12-25 11:01:59 +08:00
func NewAddonStatusCommand ( c common . Args , ioStream cmdutil . IOStreams ) * cobra . Command {
2022-05-19 16:21:36 +08:00
cmd := & cobra . Command {
2021-12-07 15:11:52 +08:00
Use : "status" ,
2022-01-12 17:43:08 +08:00
Short : "get an addon's status." ,
Long : "get an addon's status from cluster." ,
2021-12-07 15:11:52 +08:00
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 ]
2021-12-25 11:01:59 +08:00
err := statusAddon ( name , ioStream , cmd , c )
2021-12-07 15:11:52 +08:00
if err != nil {
return err
}
return nil
} ,
2021-07-13 13:03:25 +08:00
}
2022-05-19 16:21:36 +08:00
cmd . Flags ( ) . BoolVarP ( & verboseSatatus , "verbose" , "v" , false , "show addon descriptions and parameters in addition to status" )
return cmd
2021-07-13 13:03:25 +08:00
}
2022-03-28 21:25:38 +08:00
func enableAddon ( ctx context . Context , k8sClient client . Client , dc * discovery . DiscoveryClient , config * rest . Config , name string , version 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 {
2022-03-28 21:25:38 +08:00
err = pkgaddon . EnableAddon ( ctx , name , version , k8sClient , dc , apply . NewAPIApplicator ( k8sClient ) , config , registry , args , nil )
2021-12-21 09:31:37 +08:00
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 {
2022-05-31 18:56:04 +08:00
if errors . As ( err , & pkgaddon . VersionUnMatchError { } ) {
return fmt . Errorf ( "%w\nyou can try another version by command: \"vela addon enable %s --version <version> \" " , err , name )
}
2021-12-07 15:11:52 +08:00
return err
}
2022-01-07 15:49:27 +08:00
if err = waitApplicationRunning ( k8sClient , name ) ; err != nil {
2021-12-07 15:11:52 +08:00
return err
}
return nil
2021-11-25 10:32:30 +08:00
}
2022-01-12 17:43:08 +08:00
return fmt . Errorf ( "addon: %s not found in registries" , name )
2021-07-13 13:03:25 +08:00
}
2022-01-11 11:29:10 +08:00
// enableAddonByLocal enable addon in local dir and return the addon name
2022-02-23 12:17:31 +08:00
func enableAddonByLocal ( ctx context . Context , name string , dir string , k8sClient client . Client , dc * discovery . DiscoveryClient , config * rest . Config , args map [ string ] interface { } ) error {
if err := pkgaddon . EnableAddonByLocalDir ( ctx , name , dir , k8sClient , dc , apply . NewAPIApplicator ( k8sClient ) , config , args ) ; err != nil {
2022-01-11 11:29:10 +08:00
return err
}
if err := waitApplicationRunning ( k8sClient , name ) ; err != nil {
return err
}
return nil
}
2022-03-21 10:38:56 +08:00
func disableAddon ( client client . Client , name string , config * rest . Config , force bool ) error {
if err := pkgaddon . DisableAddon ( context . Background ( ) , client , name , config , force ) ; 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-25 11:01:59 +08:00
func statusAddon ( name string , ioStreams cmdutil . IOStreams , cmd * cobra . Command , c common . Args ) error {
2022-01-11 11:29:10 +08:00
k8sClient , err := c . GetClient ( )
2022-01-07 15:49:27 +08:00
if err != nil {
return err
}
2022-05-19 16:21:36 +08:00
statusString , status , err := generateAddonInfo ( k8sClient , name )
2021-12-07 15:11:52 +08:00
if err != nil {
return err
2021-07-13 13:03:25 +08:00
}
2022-03-31 10:24:20 +08:00
2022-05-19 16:21:36 +08:00
fmt . Print ( statusString )
2022-03-31 10:24:20 +08:00
2021-12-25 11:01:59 +08:00
if status . AddonPhase != statusEnabled && status . AddonPhase != statusDisabled {
2021-12-25 12:37:23 +08:00
fmt . Printf ( "diagnose addon info from application %s" , pkgaddon . Convert2AppName ( name ) )
2022-01-11 11:29:10 +08:00
err := printAppStatus ( context . Background ( ) , k8sClient , ioStreams , pkgaddon . Convert2AppName ( name ) , types . DefaultKubeVelaNS , cmd , c )
2021-12-25 11:01:59 +08:00
if err != nil {
return err
}
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
}
2022-05-19 16:21:36 +08:00
// generateAddonInfo will get addon status, description, version, dependencies (and whether they are installed),
// and parameters (and their current values).
// The first return value is the formatted string for printing.
// The second return value is just for diagnostic purposes, as it is needed in statusAddon to print diagnostic info.
func generateAddonInfo ( c client . Client , name string ) ( string , pkgaddon . Status , error ) {
2022-03-31 10:24:20 +08:00
var res string
var phase string
2022-05-19 16:21:36 +08:00
var installed bool
var addonPackage * pkgaddon . WholeAddonPackage
// Get addon install package
if verboseSatatus {
// We need the metadata to get descriptions about parameters
addonPackages , err := pkgaddon . FindWholeAddonPackagesFromRegistry ( context . Background ( ) , c , [ ] string { name } , nil )
// Not found error can be ignored, because the user can define their own addon. Others can't.
if err != nil && ! errors . Is ( err , pkgaddon . ErrNotExist ) && ! errors . Is ( err , pkgaddon . ErrRegistryNotExist ) {
return "" , pkgaddon . Status { } , err
}
if len ( addonPackages ) != 0 {
addonPackage = addonPackages [ 0 ]
}
}
// Check current addon status
status , err := pkgaddon . GetAddonStatus ( context . Background ( ) , c , name )
if err != nil {
return res , status , err
}
2022-03-31 10:24:20 +08:00
switch status . AddonPhase {
case statusEnabled :
2022-05-19 16:21:36 +08:00
installed = true
2022-03-31 10:24:20 +08:00
c := color . New ( color . FgGreen )
phase = c . Sprintf ( "%s" , status . AddonPhase )
case statusSuspend :
2022-05-19 16:21:36 +08:00
installed = true
2022-03-31 10:24:20 +08:00
c := color . New ( color . FgRed )
phase = c . Sprintf ( "%s" , status . AddonPhase )
2022-05-19 16:21:36 +08:00
case statusDisabled :
c := color . New ( color . Faint )
phase = c . Sprintf ( "%s" , status . AddonPhase )
// If the addon is
// 1. disabled,
// 2. does not exist in the registry,
// 3. verbose is on (when off, it is not possible to know whether the addon is in registry or not),
// means the addon does not exist at all.
// So, no need to go further, we return error message saying that we can't find it.
if addonPackage == nil && verboseSatatus {
return res , pkgaddon . Status { } , fmt . Errorf ( "addon %s is not found in registries nor locally installed" , name )
}
2022-03-31 10:24:20 +08:00
default :
2022-05-19 16:21:36 +08:00
c := color . New ( color . Faint )
phase = c . Sprintf ( "%s" , status . AddonPhase )
2022-03-31 10:24:20 +08:00
}
2022-05-19 16:21:36 +08:00
// Addon name
res += color . New ( color . Bold ) . Sprintf ( "%s" , name )
res += fmt . Sprintf ( ": %s " , phase )
if installed {
res += fmt . Sprintf ( "(%s)" , status . InstalledVersion )
2022-03-31 10:24:20 +08:00
}
2022-05-19 16:21:36 +08:00
res += "\n"
2022-03-31 10:24:20 +08:00
2022-05-19 16:21:36 +08:00
// Description
// Skip this if addon is installed from local sources.
// Description is fetched from the Internet, which is not useful for local sources.
if status . InstalledRegistry != pkgaddon . LocalAddonRegistryName && addonPackage != nil {
res += fmt . Sprintln ( addonPackage . Description )
}
// Installed Clusters
2022-03-31 10:24:20 +08:00
if len ( status . Clusters ) != 0 {
2022-05-19 16:21:36 +08:00
res += color . New ( color . FgHiBlue ) . Sprint ( "==> " ) + color . New ( color . Bold ) . Sprintln ( "Installed Clusters" )
2022-03-31 10:24:20 +08:00
var ic [ ] string
for c := range status . Clusters {
ic = append ( ic , c )
}
2022-04-13 22:20:07 +08:00
sort . Strings ( ic )
2022-05-19 16:21:36 +08:00
res += fmt . Sprintln ( ic )
2022-03-31 10:24:20 +08:00
}
2022-05-19 16:21:36 +08:00
// Registry name
registryName := status . InstalledRegistry
// Disabled addons will have empty InstalledRegistry, so if the addon exists in the registry, we use the registry name.
if registryName == "" && addonPackage != nil {
registryName = addonPackage . RegistryName
}
if registryName != "" {
res += color . New ( color . FgHiBlue ) . Sprint ( "==> " ) + color . New ( color . Bold ) . Sprintln ( "Registry Name" )
res += fmt . Sprintln ( registryName )
}
// If the addon is installed from local sources, or does not exist at all, stop here!
// The following information is fetched from the Internet, which is not useful for local sources.
if registryName == pkgaddon . LocalAddonRegistryName || registryName == "" || addonPackage == nil {
return res , status , nil
}
// Available Versions
res += color . New ( color . FgHiBlue ) . Sprint ( "==> " ) + color . New ( color . Bold ) . Sprintln ( "Available Versions" )
res += genAvailableVersionInfo ( addonPackage . AvailableVersions , status . InstalledVersion , 8 )
res += "\n"
// Dependencies
dependenciesString , allInstalled := generateDependencyString ( c , addonPackage . Dependencies )
res += color . New ( color . FgHiBlue ) . Sprint ( "==> " ) + color . New ( color . Bold ) . Sprint ( "Dependencies " )
if allInstalled {
res += color . GreenString ( "✔" )
} else {
res += color . RedString ( "✘" )
}
res += "\n"
res += dependenciesString
res += "\n"
// Parameters
parameterString := generateParameterString ( status , addonPackage )
if len ( parameterString ) != 0 {
res += color . New ( color . FgHiBlue ) . Sprint ( "==> " ) + color . New ( color . Bold ) . Sprintln ( "Parameters" )
res += parameterString
}
return res , status , nil
}
func generateParameterString ( status pkgaddon . Status , addonPackage * pkgaddon . WholeAddonPackage ) string {
ret := ""
if addonPackage . APISchema == nil {
return ret
}
// Required parameters
required := make ( map [ string ] bool )
for _ , k := range addonPackage . APISchema . Required {
required [ k ] = true
}
for propKey , propValue := range addonPackage . APISchema . Properties {
desc := propValue . Value . Description
defaultValue := propValue . Value . Default
if defaultValue == nil {
defaultValue = ""
}
required := required [ propKey ]
currentValue := status . Parameters [ propKey ]
if currentValue == nil {
currentValue = ""
}
// Header: addon: description
ret += color . New ( color . FgCyan ) . Sprintf ( "-> " )
ret += color . New ( color . Bold ) . Sprint ( propKey ) + ": "
ret += fmt . Sprintf ( "%s\n" , desc )
// Current value
if currentValue != "" {
ret += "\tcurrent: " + color . New ( color . FgGreen ) . Sprintf ( "%#v\n" , currentValue )
}
// Default value
if defaultValue != "" {
ret += "\tdefault: " + fmt . Sprintf ( "%#v\n" , defaultValue )
}
// Required or not
if required {
ret += "\trequired: "
ret += color . GreenString ( "✔\n" )
}
}
return ret
}
func generateDependencyString ( c client . Client , dependencies [ ] * pkgaddon . Dependency ) ( string , bool ) {
if len ( dependencies ) == 0 {
return "[]" , true
}
ret := "["
allDependenciesInstalled := true
for idx , d := range dependencies {
name := d . Name
// Checks if the dependency is enabled, and mark it
status , err := pkgaddon . GetAddonStatus ( context . Background ( ) , c , name )
if err != nil {
continue
}
var enabledString string
switch status . AddonPhase {
case statusEnabled :
enabledString = color . GreenString ( "✔" )
case statusSuspend :
enabledString = color . RedString ( "✔" )
default :
enabledString = color . RedString ( "✘" )
allDependenciesInstalled = false
}
ret += fmt . Sprintf ( "%s %s" , name , enabledString )
if idx != len ( dependencies ) - 1 {
ret += ", "
}
}
ret += "]"
return ret , allDependenciesInstalled
2022-03-31 10:24:20 +08:00
}
2022-05-10 13:35:12 +08:00
func listAddons ( ctx context . Context , clt client . Client , registry string ) ( * uitable . Table , 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 {
2022-05-10 13:35:12 +08:00
return nil , err
2021-07-13 13:03:25 +08:00
}
2022-05-10 13:35:12 +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
}
2022-03-28 21:25:38 +08:00
var addonList [ ] * pkgaddon . UIData
var err error
if ! pkgaddon . IsVersionRegistry ( r ) {
meta , err := r . ListAddonMeta ( )
if err != nil {
continue
}
addonList , err = r . ListUIData ( meta , pkgaddon . CLIMetaOptions )
if err != nil {
continue
}
} else {
2022-05-07 23:39:34 +08:00
versionedRegistry := pkgaddon . BuildVersionedRegistry ( r . Name , r . Helm . URL , & common . HTTPOption { Username : r . Helm . Username , Password : r . Helm . Password } )
2022-03-28 21:25:38 +08:00
addonList , err = versionedRegistry . ListAddon ( )
if err != nil {
continue
}
2021-12-07 15:11:52 +08:00
}
2022-03-28 21:25:38 +08:00
addons = mergeAddons ( addons , addonList )
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 ( )
2022-03-28 21:25:38 +08:00
table . AddRow ( "NAME" , "REGISTRY" , "DESCRIPTION" , "AVAILABLE-VERSIONS" , "STATUS" )
2021-11-25 10:32:30 +08:00
2022-05-10 13:35:12 +08:00
// get locally installed addons first
locallyInstalledAddons := map [ string ] bool { }
appList := v1beta1 . ApplicationList { }
if err := clt . List ( ctx , & appList , client . MatchingLabels { oam . LabelAddonRegistry : pkgaddon . LocalAddonRegistryName } ) ; err != nil {
return table , err
}
for _ , app := range appList . Items {
labels := app . GetLabels ( )
addonName := labels [ oam . LabelAddonName ]
addonVersion := labels [ oam . LabelAddonVersion ]
2022-05-19 16:21:36 +08:00
table . AddRow ( addonName , app . GetLabels ( ) [ oam . LabelAddonRegistry ] , "" , genAvailableVersionInfo ( [ ] string { addonVersion } , addonVersion , 3 ) , statusEnabled )
2022-05-10 13:35:12 +08:00
locallyInstalledAddons [ addonName ] = true
}
2021-12-07 15:11:52 +08:00
for _ , addon := range addons {
2022-05-10 13:35:12 +08:00
// if the addon with same name has already installed locally, display the registry one as not installed
if locallyInstalledAddons [ addon . Name ] {
2022-05-19 16:21:36 +08:00
table . AddRow ( addon . Name , addon . RegistryName , limitStringLength ( addon . Description , 60 ) , genAvailableVersionInfo ( addon . AvailableVersions , "" , 3 ) , "disabled" )
2022-05-10 13:35:12 +08:00
continue
}
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 {
2022-05-10 13:35:12 +08:00
return table , err
2021-11-25 10:32:30 +08:00
}
2022-03-28 21:25:38 +08:00
statusRow := status . AddonPhase
if len ( status . InstalledVersion ) != 0 {
statusRow += fmt . Sprintf ( " (%s)" , status . InstalledVersion )
}
2022-05-19 16:21:36 +08:00
table . AddRow ( addon . Name , addon . RegistryName , limitStringLength ( addon . Description , 60 ) , genAvailableVersionInfo ( addon . AvailableVersions , status . InstalledVersion , 3 ) , statusRow )
2022-01-11 11:29:10 +08:00
}
2022-05-10 13:35:12 +08:00
return table , nil
2021-07-13 13:03:25 +08:00
}
2022-01-07 15:49:27 +08:00
func waitApplicationRunning ( k8sClient client . Client , 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 {
2022-01-07 15:49:27 +08:00
err := k8sClient . 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 ( ) ) {
2022-01-07 15:49:27 +08:00
return errors . Errorf ( "Enabling timeout, please run \"vela status %s -n vela-system\" to check the status of the addon" , pkgaddon . Convert2AppName ( 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
}
2022-03-31 10:24:20 +08:00
// generate the available version
// this func put the installed version as the first version and keep the origin order
// print ... if available version too much
2022-05-19 16:21:36 +08:00
func genAvailableVersionInfo ( versions [ ] string , installedVersion string , limit int ) string {
2022-03-31 10:24:20 +08:00
var v [ ] string
// put installed-version as the first version and keep the origin order
2022-05-10 13:35:12 +08:00
if len ( installedVersion ) != 0 {
2022-03-31 10:24:20 +08:00
for i , version := range versions {
2022-05-10 13:35:12 +08:00
if version == installedVersion {
2022-03-31 10:24:20 +08:00
v = append ( v , version )
versions = append ( versions [ : i ] , versions [ i + 1 : ] ... )
}
}
}
v = append ( v , versions ... )
2022-03-28 21:25:38 +08:00
res := "["
2022-03-31 10:24:20 +08:00
var count int
for _ , version := range v {
2022-05-19 16:21:36 +08:00
if count == limit {
2022-03-31 10:24:20 +08:00
// just show newest 3 versions
res += "..."
break
}
2022-05-10 13:35:12 +08:00
if version == installedVersion {
2022-03-28 21:25:38 +08:00
col := color . New ( color . Bold , color . FgGreen )
res += col . Sprintf ( "%s" , version )
} else {
res += version
}
res += ", "
2022-03-31 10:24:20 +08:00
count ++
2022-03-28 21:25:38 +08:00
}
res = strings . TrimSuffix ( res , ", " )
res += "]"
return res
}
2022-05-19 16:21:36 +08:00
// limitStringLength limits the length of the string, and add ... if it is too long
func limitStringLength ( str string , length int ) string {
if length <= 0 {
return str
}
if len ( str ) > length {
return str [ : length ] + "..."
}
return str
}
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
2022-03-29 23:03:57 +08:00
func transClusters ( cstr string ) [ ] string {
cstr = strings . TrimPrefix ( strings . TrimSuffix ( cstr , "}" ) , "{" )
var clusterL [ ] string
clusterList := strings . Split ( cstr , "," )
for _ , v := range clusterList {
clusterL = append ( clusterL , strings . TrimSpace ( v ) )
}
return clusterL
}
2022-05-07 23:39:34 +08:00
// NewAddonPackageCommand create addon package command
func NewAddonPackageCommand ( c common . Args ) * cobra . Command {
cmd := & cobra . Command {
Use : "package" ,
Short : "package an addon directory" ,
Long : "package an addon directory into a helm chart archive." ,
Example : "vela addon package <addon directory>" ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) < 1 {
return fmt . Errorf ( "must specify addon directory path" )
}
addonDict , err := filepath . Abs ( args [ 0 ] )
if err != nil {
return err
}
archive , err := pkgaddon . PackageAddon ( addonDict )
if err != nil {
return errors . Wrapf ( err , "fail to package %s into helm chart archive" , addonDict )
}
fmt . Printf ( "Successfully package addon to: %s\n" , archive )
return nil
} ,
}
return cmd
}
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
// }