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"
2022-07-06 15:39:46 +08:00
"encoding/json"
2021-07-13 13:03:25 +08:00
"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-06-30 23:38:38 +08:00
"github.com/getkin/kin-openapi/openapi3"
2021-07-13 13:03:25 +08:00
"github.com/gosuri/uitable"
"github.com/pkg/errors"
"github.com/spf13/cobra"
2022-06-29 17:46:56 +08:00
"helm.sh/helm/v3/pkg/strvals"
2021-07-13 13:03:25 +08:00
types2 "k8s.io/apimachinery/pkg/types"
2022-06-29 17:46:56 +08:00
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
2021-07-13 13:03:25 +08:00
"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"
2022-06-29 17:46:56 +08:00
"github.com/oam-dev/kubevela/pkg/apiserver/domain/service"
"github.com/oam-dev/kubevela/pkg/oam"
2022-06-29 10:55:50 +08:00
addonutil "github.com/oam-dev/kubevela/pkg/utils/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-09-30 13:23:27 +08:00
var enabledAddonColor = color . New ( color . Bold , color . FgGreen )
2022-12-12 13:40:48 +08:00
var (
forceDisable bool
addonVersion string
addonClusters string
verboseStatus bool
skipValidate bool
overrideDefs bool
dryRun bool
yes2all bool
)
2022-09-19 17:24:07 +08:00
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 ,
2022-08-01 19:44:27 +08:00
types . TagCommandType : types . TypeApp ,
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 ) ,
2022-07-18 10:40:42 +08:00
NewAddonInitCommand ( ) ,
2022-06-28 20:16:45 +08:00
NewAddonPushCommand ( 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-09-19 17:24:07 +08:00
Use : "enable" ,
Aliases : [ ] string { "install" } ,
Short : "enable an addon" ,
Long : "enable an addon in cluster." ,
2022-07-11 16:59:54 +08:00
Example : ` Enable addon by :
2022-03-23 14:33:15 +08:00
vela addon enable < addon - name >
2022-07-11 16:59:54 +08:00
Enable addon with specify version :
2022-05-10 13:35:12 +08:00
vela addon enable < addon - name > -- version < addon - version >
2022-07-11 16:59:54 +08:00
Enable addon for specific clusters , ( local means control plane ) :
2022-03-23 14:33:15 +08:00
vela addon enable < addon - name > -- clusters = { local , cluster1 , cluster2 }
2022-07-11 16:59:54 +08:00
Enable addon locally :
vela addon enable < your - local - addon - path >
Enable addon with specified args ( the args should be defined in addon ' s parameters ) :
vela addon enable < addon - name > < my - parameter - of - addon >= < my - value >
2022-11-24 09:42:28 +08:00
Enable addon with specified registry :
vela addon enable < registryName > / < addonName >
2022-03-23 14:33:15 +08:00
` ,
2021-07-13 13:03:25 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2022-12-16 18:07:27 +08:00
var additionalInfo string
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-07-22 17:58:19 +08:00
clusterArgs := transClusters ( addonClusters )
if len ( clusterArgs ) != 0 {
addonArgs [ types . ClustersArg ] = clusterArgs
}
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-12-16 18:07:27 +08:00
// inject runtime info
addonArgs [ pkgaddon . InstallerRuntimeOption ] = map [ string ] interface { } {
"upgrade" : false ,
}
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-12-12 13:40:48 +08:00
if ! yes2all {
if err := checkUninstallFromClusters ( ctx , k8sClient , name , addonArgs ) ; err != nil {
return err
}
}
2022-12-16 18:07:27 +08:00
additionalInfo , 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 , "/" ) {
2022-12-12 13:40:48 +08:00
return fmt . Errorf ( "addon directory %s not found in local file system" , addonOrDir )
}
if ! yes2all {
if err := checkUninstallFromClusters ( ctx , k8sClient , name , addonArgs ) ; err != nil {
return err
}
2022-02-16 14:20:37 +08:00
}
2022-12-16 18:07:27 +08:00
additionalInfo , 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-09-19 17:24:07 +08:00
if dryRun {
return nil
}
2022-06-09 10:12:19 +08:00
fmt . Printf ( "Addon %s enabled successfully.\n" , name )
2022-12-16 18:07:27 +08:00
AdditionalEndpointPrinter ( ctx , c , k8sClient , name , additionalInfo , 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-06-24 14:11:57 +08:00
cmd . Flags ( ) . BoolVarP ( & skipValidate , "skip-version-validating" , "s" , false , "skip validating system version requirement" )
2022-07-29 16:42:16 +08:00
cmd . Flags ( ) . BoolVarP ( & overrideDefs , "override-definitions" , "" , false , "override existing definitions if conflict with those contained in this addon" )
2022-09-19 17:24:07 +08:00
cmd . Flags ( ) . BoolVarP ( & dryRun , FlagDryRun , "" , false , "render all yaml files out without real execute it" )
2022-12-12 13:40:48 +08:00
cmd . Flags ( ) . BoolVarP ( & yes2all , "yes" , "y" , false , "all checks will be skipped and the default answer is yes for all validation check." )
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-12-16 18:07:27 +08:00
func AdditionalEndpointPrinter ( ctx context . Context , c common . Args , k8sClient client . Client , name , info string , isUpgrade bool ) {
2022-06-29 10:55:50 +08:00
err := printAppEndpoints ( ctx , addonutil . Addon2AppName ( 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-07-06 17:42:20 +08:00
fmt . Printf ( "\nInitialized admin username and password: admin / %s \n\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: ` )
2022-07-06 17:42:20 +08:00
fmt . Println ( )
2022-03-31 17:33:06 +08:00
fmt . Println ( ` vela port-forward -n vela-system addon-velaux 9082:80 ` )
2022-07-06 17:42:20 +08:00
fmt . Println ( )
2022-08-12 14:37:39 +08:00
fmt . Println ( ` Select "local | velaux | velaux" from the prompt. ` )
2022-07-06 17:42:20 +08:00
fmt . Println ( )
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
}
2022-12-16 18:07:27 +08:00
if len ( info ) > 0 {
fmt . Println ( info )
}
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." ,
2022-06-28 20:16:45 +08:00
Example : `
2022-07-11 16:59:54 +08:00
Upgrade addon by :
2022-03-23 14:33:15 +08:00
vela addon upgrade < addon - name >
2022-07-11 16:59:54 +08:00
Upgrade addon with specify version :
2022-05-10 13:35:12 +08:00
vela addon upgrade < addon - name > -- version < addon - version >
2022-07-11 16:59:54 +08:00
Upgrade addon for specific clusters , ( local means control plane ) :
2022-03-23 14:33:15 +08:00
vela addon upgrade < addon - name > -- clusters = { local , cluster1 , cluster2 }
2022-07-11 16:59:54 +08:00
Upgrade addon locally :
2022-07-22 14:39:09 +08:00
vela addon upgrade < your - local - addon - path >
2022-07-11 16:59:54 +08:00
Upgrade addon with specified args ( the args should be defined in addon ' s parameters ) :
2022-07-22 14:39:09 +08:00
vela addon upgrade < addon - name > < my - parameter - of - addon >= < my - value >
The specified args will be merged with legacy args , what user specified in ' vela addon enable ' , and non - empty legacy arg will be overridden by
non - empty new arg
2022-03-23 14:33:15 +08:00
` ,
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-07-22 14:39:09 +08:00
addonInputArgs , err := parseAddonArgsToMap ( args [ 1 : ] )
2021-12-22 19:31:20 +08:00
if err != nil {
return err
}
2022-07-22 17:58:19 +08:00
clusterArgs := transClusters ( addonClusters )
if len ( clusterArgs ) != 0 {
addonInputArgs [ types . ClustersArg ] = clusterArgs
}
2022-01-11 11:29:10 +08:00
addonOrDir := args [ 0 ]
2022-12-16 18:07:27 +08:00
// inject runtime info
addonInputArgs [ pkgaddon . InstallerRuntimeOption ] = map [ string ] interface { } {
"upgrade" : true ,
}
var name , additionalInfo 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-07-22 14:39:09 +08:00
addonArgs , err := pkgaddon . MergeAddonInstallArgs ( ctx , k8sClient , name , addonInputArgs )
if err != nil {
return err
}
2022-12-16 18:07:27 +08:00
additionalInfo , 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-07-22 14:39:09 +08:00
addonArgs , err := pkgaddon . MergeAddonInstallArgs ( ctx , k8sClient , name , addonInputArgs )
if err != nil {
return err
}
2022-12-16 18:07:27 +08:00
additionalInfo , 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-12-16 18:07:27 +08:00
fmt . Printf ( "Addon %s enabled successfully.\n" , name )
AdditionalEndpointPrinter ( ctx , c , k8sClient , name , additionalInfo , 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-12-03 12:42:24 +08:00
cmd . Flags ( ) . StringVarP ( & addonClusters , types . ClustersArg , "c" , "" , "specify the runtime-clusters to upgrade" )
2022-06-24 14:11:57 +08:00
cmd . Flags ( ) . BoolVarP ( & skipValidate , "skip-version-validating" , "s" , false , "skip validating system version requirement" )
2022-07-29 16:42:16 +08:00
cmd . Flags ( ) . BoolVarP ( & overrideDefs , "override-definitions" , "" , false , "override existing definitions if conflict with those contained in this addon" )
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" ,
2022-09-19 17:24:07 +08:00
Aliases : [ ] string { "uninstall" } ,
2021-07-13 13:03:25 +08:00
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-06-24 14:25:36 +08:00
cmd . Flags ( ) . BoolVarP ( & verboseStatus , "verbose" , "v" , false , "show addon descriptions and parameters in addition to status" )
return cmd
}
2022-07-18 10:40:42 +08:00
// NewAddonInitCommand creates an addon scaffold
func NewAddonInitCommand ( ) * cobra . Command {
2022-07-25 21:32:01 +08:00
var path string
initCmd := pkgaddon . InitCmd { }
2022-06-24 14:25:36 +08:00
cmd := & cobra . Command {
Use : "init" ,
Short : "create an addon scaffold" ,
2022-07-18 10:40:42 +08:00
Long : "Create an addon scaffold for quick starting." ,
Example : ` Store the scaffold in a different directory :
vela addon init mongodb - p path / to / addon
Add a Helm component :
vela addon init mongodb -- helm - repo https : //marketplace.azurecr.io/helm/v1/repo --chart mongodb --chart-version 12.1.16
Add resources from URL using ref - objects component
vela addon init my - addon -- url https : //domain.com/resource.yaml
Use -- no - samples options to skip creating sample files
vela addon init my - addon -- no - sample
You can combine all the options together . ` ,
2022-06-24 14:25:36 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) < 1 {
return fmt . Errorf ( "an addon name is required" )
}
addonName := args [ 0 ]
// Scaffold will be created in ./addonName, unless the user specifies a path
// validity of addon names will be checked later
addonPath := addonName
if len ( path ) > 0 {
addonPath = path
}
if addonName == "" || addonPath == "" {
return fmt . Errorf ( "addon name or path should not be empty" )
}
2022-07-25 21:32:01 +08:00
initCmd . AddonName = addonName
initCmd . Path = addonPath
2022-06-24 14:25:36 +08:00
2022-07-18 10:40:42 +08:00
return initCmd . CreateScaffold ( )
2022-06-24 14:25:36 +08:00
} ,
}
2022-07-18 10:40:42 +08:00
f := cmd . Flags ( )
2022-07-25 21:32:01 +08:00
f . StringVar ( & initCmd . HelmRepoURL , "helm-repo" , "" , "URL that points to a Helm repo" )
f . StringVar ( & initCmd . HelmChartName , "chart" , "" , "Helm Chart name" )
f . StringVar ( & initCmd . HelmChartVersion , "chart-version" , "" , "version of the Chart" )
2022-07-18 10:40:42 +08:00
f . StringVarP ( & path , "path" , "p" , "" , "path to the addon directory (default is ./<addon-name>)" )
2022-07-25 21:32:01 +08:00
f . StringArrayVarP ( & initCmd . RefObjURLs , "url" , "u" , [ ] string { } , "add URL resources using ref-object component" )
f . BoolVarP ( & initCmd . NoSamples , "no-samples" , "" , false , "do not generate sample files" )
f . BoolVarP ( & initCmd . Overwrite , "force" , "f" , false , "overwrite existing addon files" )
2022-06-24 14:25:36 +08:00
2022-05-19 16:21:36 +08:00
return cmd
2021-07-13 13:03:25 +08:00
}
2022-06-28 20:16:45 +08:00
// NewAddonPushCommand pushes an addon dir/package to a ChartMuseum
func NewAddonPushCommand ( c common . Args ) * cobra . Command {
p := & pkgaddon . PushCmd { }
cmd := & cobra . Command {
Use : "push" ,
Short : "uploads an addon package to ChartMuseum" ,
Long : ` Uploads an addon package to ChartMuseum .
Two arguments are needed < addon directory / package > and < name / URL of ChartMuseum > .
The first argument < addon directory / package > can be :
- your conventional addon directory ( containing metadata . yaml ) . We will package it for you .
- packaged addon ( . tgz ) generated by ' vela addon package ' command
The second argument < name / URL of ChartMuseum > can be :
- registry name ( helm type ) . You can add your ChartMuseum registry using ' vela addon registry add ' .
- ChartMuseum URL , e . g . http : //localhost:8080`,
Example : ` # Push the addon in directory < your - addon > to a ChartMuseum registry named < localcm >
$ vela addon push your - addon localcm
# Push packaged addon mongo - 1.0 .0 . tgz to a ChartMuseum registry at http : //localhost:8080
$ vela addon push mongo - 1.0 .0 . tgz http : //localhost:8080
# Force push , overwriting existing ones
$ vela addon push your - addon localcm - f
# If you already written your own Chart . yaml and don ' t want us to generate it for you :
$ vela addon push your - addon localcm -- keep - chartmeta
# Note : when using . tgz packages , we will always keep the original Chart . yaml
# In addition to cli flags , you can also use environment variables
$ HELM_REPO_USERNAME = name HELM_REPO_PASSWORD = pswd vela addon push mongo - 1.0 .0 . tgz http : //localhost:8080`,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) != 2 {
return fmt . Errorf ( "two arguments are needed: addon directory/package, name/URL of Chart repository" )
}
c , err := c . GetClient ( )
if err != nil {
return err
}
p . Client = c
p . Out = cmd . OutOrStdout ( )
p . ChartName = args [ 0 ]
p . RepoName = args [ 1 ]
p . SetFieldsFromEnv ( )
return p . Push ( context . Background ( ) )
} ,
}
f := cmd . Flags ( )
f . StringVarP ( & p . ChartVersion , "version" , "v" , "" , "override chart version pre-push" )
f . StringVarP ( & p . AppVersion , "app-version" , "a" , "" , "override app version pre-push" )
f . StringVarP ( & p . Username , "username" , "u" , "" , "override HTTP basic auth username [$HELM_REPO_USERNAME]" )
f . StringVarP ( & p . Password , "password" , "p" , "" , "override HTTP basic auth password [$HELM_REPO_PASSWORD]" )
f . StringVarP ( & p . AccessToken , "access-token" , "" , "" , "send token in Authorization header [$HELM_REPO_ACCESS_TOKEN]" )
f . StringVarP ( & p . AuthHeader , "auth-header" , "" , "" , "alternative header to use for token auth [$HELM_REPO_AUTH_HEADER]" )
f . StringVarP ( & p . ContextPath , "context-path" , "" , "" , "ChartMuseum context path [$HELM_REPO_CONTEXT_PATH]" )
f . StringVarP ( & p . CaFile , "ca-file" , "" , "" , "verify certificates of HTTPS-enabled servers using this CA bundle [$HELM_REPO_CA_FILE]" )
f . StringVarP ( & p . CertFile , "cert-file" , "" , "" , "identify HTTPS client using this SSL certificate file [$HELM_REPO_CERT_FILE]" )
f . StringVarP ( & p . KeyFile , "key-file" , "" , "" , "identify HTTPS client using this SSL key file [$HELM_REPO_KEY_FILE]" )
f . BoolVarP ( & p . InsecureSkipVerify , "insecure" , "" , false , "connect to server with an insecure way by skipping certificate verification [$HELM_REPO_INSECURE]" )
f . BoolVarP ( & p . ForceUpload , "force" , "f" , false , "force upload even if chart version exists" )
f . BoolVarP ( & p . UseHTTP , "use-http" , "" , false , "use HTTP" )
f . BoolVarP ( & p . KeepChartMetadata , "keep-chartmeta" , "" , false , "do not update Chart.yaml automatically according to addon metadata (only when addon dir provided)" )
f . Int64VarP ( & p . Timeout , "timeout" , "t" , 30 , "The duration (in seconds) vela cli will wait to get response from ChartMuseum" )
return cmd
}
2022-12-16 18:07:27 +08:00
func enableAddon ( ctx context . Context , k8sClient client . Client , dc * discovery . DiscoveryClient , config * rest . Config , name string , version string , args map [ string ] interface { } ) ( string , error ) {
2021-12-07 15:11:52 +08:00
var err error
2022-12-16 18:07:27 +08:00
var additionalInfo string
2021-12-07 15:11:52 +08:00
registryDS := pkgaddon . NewRegistryDataStore ( k8sClient )
registries , err := registryDS . ListRegistries ( ctx )
2021-07-13 13:03:25 +08:00
if err != nil {
2022-12-16 18:07:27 +08:00
return "" , err
2021-07-13 13:03:25 +08:00
}
2022-11-24 09:42:28 +08:00
registryName , addonName , err := splitSpecifyRegistry ( name )
if err != nil {
2022-12-16 18:07:27 +08:00
return "" , err
2022-11-24 09:42:28 +08:00
}
if len ( registryName ) != 0 {
foundRegistry := false
for _ , registry := range registries {
if registry . Name == registryName {
foundRegistry = true
}
}
if ! foundRegistry {
2022-12-16 18:07:27 +08:00
return "" , fmt . Errorf ( "specified registry %s not exist" , registryName )
2022-11-24 09:42:28 +08:00
}
}
for i , registry := range registries {
2022-09-19 17:24:07 +08:00
opts := addonOptions ( )
2022-11-24 09:42:28 +08:00
if len ( registryName ) != 0 && registryName != registry . Name {
continue
}
2022-12-16 18:07:27 +08:00
additionalInfo , err = pkgaddon . EnableAddon ( ctx , addonName , version , k8sClient , dc , apply . NewAPIApplicator ( k8sClient ) , config , registry , args , nil , pkgaddon . FilterDependencyRegistries ( i , registries ) , opts ... )
2022-12-14 13:35:12 +08:00
if errors . Is ( err , pkgaddon . ErrNotExist ) || errors . Is ( err , pkgaddon . ErrFetch ) {
2021-12-16 11:12:06 +08:00
continue
2021-12-07 15:11:52 +08:00
}
2022-06-29 17:46:56 +08:00
if unMatchErr := new ( pkgaddon . VersionUnMatchError ) ; errors . As ( err , unMatchErr ) {
// Get available version of the addon
availableVersion , err := unMatchErr . GetAvailableVersion ( )
if err != nil {
2022-12-16 18:07:27 +08:00
return "" , err
2022-06-29 17:46:56 +08:00
}
input := NewUserInput ( )
if input . AskBool ( unMatchErr . Error ( ) , & UserInputOptions { AssumeYes : false } ) {
2022-12-16 18:07:27 +08:00
return pkgaddon . EnableAddon ( ctx , addonName , availableVersion , k8sClient , dc , apply . NewAPIApplicator ( k8sClient ) , config , registry , args , nil , pkgaddon . FilterDependencyRegistries ( i , registries ) )
2022-05-31 18:56:04 +08:00
}
2022-06-29 17:46:56 +08:00
// The user does not agree to use the version provided by us
2022-12-16 18:07:27 +08:00
return "" , fmt . Errorf ( "you can try another version by command: \"vela addon enable %s --version <version> \" " , addonName )
2022-06-29 17:46:56 +08:00
}
if err != nil {
2022-12-16 18:07:27 +08:00
return "" , err
2021-12-07 15:11:52 +08:00
}
2022-11-24 09:42:28 +08:00
if err = waitApplicationRunning ( k8sClient , addonName ) ; err != nil {
2022-12-16 18:07:27 +08:00
return "" , err
2021-12-07 15:11:52 +08:00
}
2022-12-16 18:07:27 +08:00
return additionalInfo , nil
2021-11-25 10:32:30 +08:00
}
2022-11-24 09:42:28 +08:00
if len ( registryName ) != 0 {
2022-12-16 18:07:27 +08:00
return "" , fmt . Errorf ( "addon: %s not found in registry %s" , addonName , registryName )
2022-11-24 09:42:28 +08:00
}
2022-12-16 18:07:27 +08:00
return "" , fmt . Errorf ( "addon: %s not found in all candidate registries" , addonName )
2021-07-13 13:03:25 +08:00
}
2022-09-19 17:24:07 +08:00
func addonOptions ( ) [ ] pkgaddon . InstallOption {
2022-06-24 14:11:57 +08:00
var opts [ ] pkgaddon . InstallOption
2022-12-12 13:40:48 +08:00
if skipValidate || yes2all {
2022-06-24 14:11:57 +08:00
opts = append ( opts , pkgaddon . SkipValidateVersion )
}
2022-12-12 13:40:48 +08:00
if overrideDefs || yes2all {
2022-07-29 16:42:16 +08:00
opts = append ( opts , pkgaddon . OverrideDefinitions )
}
2022-09-19 17:24:07 +08:00
if dryRun {
opts = append ( opts , pkgaddon . DryRunAddon )
}
return opts
}
// enableAddonByLocal enable addon in local dir and return the addon name
2022-12-16 18:07:27 +08:00
func enableAddonByLocal ( ctx context . Context , name string , dir string , k8sClient client . Client , dc * discovery . DiscoveryClient , config * rest . Config , args map [ string ] interface { } ) ( string , error ) {
2022-09-19 17:24:07 +08:00
opts := addonOptions ( )
2022-12-16 18:07:27 +08:00
info , err := pkgaddon . EnableAddonByLocalDir ( ctx , name , dir , k8sClient , dc , apply . NewAPIApplicator ( k8sClient ) , config , args , opts ... )
if err != nil {
return "" , err
2022-01-11 11:29:10 +08:00
}
2022-12-16 18:07:27 +08:00
if err = waitApplicationRunning ( k8sClient , name ) ; err != nil {
return "" , err
2022-01-11 11:29:10 +08:00
}
2022-12-16 18:07:27 +08:00
return info , nil
2022-01-11 11:29:10 +08:00
}
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 {
2022-06-29 10:55:50 +08:00
fmt . Printf ( "diagnose addon info from application %s" , addonutil . Addon2AppName ( name ) )
2022-09-28 17:18:08 +08:00
err := printAppStatus ( context . Background ( ) , k8sClient , ioStreams , addonutil . Addon2AppName ( name ) , types . DefaultKubeVelaNS , cmd , c , false )
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-12-07 17:22:38 +08:00
func addonNotExist ( err error ) bool {
if errors . Is ( err , pkgaddon . ErrNotExist ) || errors . Is ( err , pkgaddon . ErrRegistryNotExist ) {
return true
}
if strings . Contains ( err . Error ( ) , "not found" ) {
return true
}
return false
}
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
2022-12-07 17:22:38 +08:00
// Check current addon status
status , err := pkgaddon . GetAddonStatus ( context . Background ( ) , c , name )
if err != nil {
return res , status , err
}
2022-05-19 16:21:36 +08:00
// Get addon install package
2022-12-07 17:22:38 +08:00
if verboseStatus || status . AddonPhase == statusDisabled {
2022-05-19 16:21:36 +08:00
// We need the metadata to get descriptions about parameters
2022-12-07 17:22:38 +08:00
addonPackages , err := pkgaddon . FindAddonPackagesDetailFromRegistry ( context . Background ( ) , c , [ ] string { name } , nil )
// If the state of addon is not disabled, we don't check the error, because it could be installed from local.
if status . AddonPhase == statusDisabled && err != nil {
if addonNotExist ( err ) {
return "" , pkgaddon . Status { } , fmt . Errorf ( "addon '%s' not found in cluster or any registry" , name )
}
2022-05-19 16:21:36 +08:00
return "" , pkgaddon . Status { } , err
}
if len ( addonPackages ) != 0 {
addonPackage = addonPackages [ 0 ]
}
}
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.
2022-06-24 14:25:36 +08:00
if addonPackage == nil && verboseStatus {
2022-05-19 16:21:36 +08:00
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
}
2022-06-30 23:38:38 +08:00
ret = printSchema ( addonPackage . APISchema , status . Parameters , 0 )
return ret
}
2022-12-07 17:22:38 +08:00
func convertInterface2StringList ( l [ ] interface { } ) [ ] string {
var strl [ ] string
for _ , s := range l {
str , ok := s . ( string )
if ! ok {
continue
}
strl = append ( strl , str )
}
return strl
}
2022-06-30 23:38:38 +08:00
// printSchema prints the parameters in an addon recursively to a string
// Deeper the parameter is nested, more the indentations.
func printSchema ( ref * openapi3 . Schema , currentParams map [ string ] interface { } , indent int ) string {
ret := ""
if ref == nil {
return ret
}
addIndent := func ( n int ) string {
r := ""
for i := 0 ; i < n ; i ++ {
r += "\t"
}
return r
}
2022-05-19 16:21:36 +08:00
// Required parameters
required := make ( map [ string ] bool )
2022-06-30 23:38:38 +08:00
for _ , k := range ref . Required {
2022-05-19 16:21:36 +08:00
required [ k ] = true
}
2022-06-30 23:38:38 +08:00
for propKey , propValue := range ref . Properties {
2022-05-19 16:21:36 +08:00
desc := propValue . Value . Description
defaultValue := propValue . Value . Default
if defaultValue == nil {
defaultValue = ""
}
required := required [ propKey ]
2022-06-30 23:38:38 +08:00
2022-07-06 15:39:46 +08:00
// Extra indentation on nested objects
addedIndent := addIndent ( indent )
2022-06-30 23:38:38 +08:00
var currentValue string
thisParam , hasParam := currentParams [ propKey ]
if hasParam {
2022-07-06 15:39:46 +08:00
currentValue = fmt . Sprintf ( "%#v" , thisParam )
2022-06-30 23:38:38 +08:00
switch thisParam . ( type ) {
case int :
case int64 :
case int32 :
case float32 :
case float64 :
case string :
case bool :
default :
2022-07-06 15:39:46 +08:00
if js , err := json . MarshalIndent ( thisParam , "" , " " ) ; err == nil {
currentValue = strings . ReplaceAll ( string ( js ) , "\n" , "\n\t " + addedIndent )
}
2022-06-30 23:38:38 +08:00
}
2022-05-19 16:21:36 +08:00
}
// Header: addon: description
2022-06-30 23:38:38 +08:00
ret += addedIndent
2022-05-19 16:21:36 +08:00
ret += color . New ( color . FgCyan ) . Sprintf ( "-> " )
ret += color . New ( color . Bold ) . Sprint ( propKey ) + ": "
ret += fmt . Sprintf ( "%s\n" , desc )
2022-06-30 23:38:38 +08:00
// Show current value
2022-05-19 16:21:36 +08:00
if currentValue != "" {
2022-07-06 15:39:46 +08:00
ret += addedIndent
2022-12-07 17:22:38 +08:00
ret += "\tcurrent value: " + color . New ( color . FgGreen ) . Sprintf ( "%s\n" , currentValue )
2022-05-19 16:21:36 +08:00
}
2022-12-07 17:22:38 +08:00
2022-06-30 23:38:38 +08:00
// Show required or not
2022-05-19 16:21:36 +08:00
if required {
2022-06-30 23:38:38 +08:00
ret += addedIndent
2022-05-19 16:21:36 +08:00
ret += "\trequired: "
ret += color . GreenString ( "✔\n" )
}
2022-12-07 17:22:38 +08:00
// Show Enum options
if len ( propValue . Value . Enum ) > 0 {
ret += addedIndent
ret += "\toptions: \"" + strings . Join ( convertInterface2StringList ( propValue . Value . Enum ) , "\", \"" ) + "\"\n"
}
// Show default value
if defaultValue != "" && currentValue == "" {
ret += addedIndent
ret += "\tdefault: " + fmt . Sprintf ( "%#v\n" , defaultValue )
}
2022-06-30 23:38:38 +08:00
// Object type param, we will get inside the object.
// To show what's inside nested objects.
if propValue . Value . Type == "object" {
nestedParam := make ( map [ string ] interface { } )
if hasParam {
nestedParam = currentParams [ propKey ] . ( map [ string ] interface { } )
}
ret += printSchema ( propValue . Value , nestedParam , indent + 1 )
}
2022-05-19 16:21:36 +08:00
}
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-07-19 18:00:28 +08:00
versionedRegistry := pkgaddon . BuildVersionedRegistry ( r . Name , r . Helm . URL , & common . HTTPOption {
Username : r . Helm . Username ,
Password : r . Helm . Password ,
InsecureSkipTLS : r . Helm . InsecureSkipTLS ,
} )
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-09-30 13:23:27 +08:00
table . AddRow ( enabledAddonColor . Sprintf ( "%s" , addonName ) , app . GetLabels ( ) [ oam . LabelAddonRegistry ] , "" , genAvailableVersionInfo ( [ ] string { addonVersion } , addonVersion , 3 ) , enabledAddonColor . Sprintf ( "%s" , 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-09-30 13:23:27 +08:00
table . AddRow ( addon . Name , addon . RegistryName , limitStringLength ( addon . Description , 60 ) , genAvailableVersionInfo ( addon . AvailableVersions , "" , 3 ) , "-" )
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
2022-09-30 13:23:27 +08:00
name := addon . Name
2022-03-28 21:25:38 +08:00
if len ( status . InstalledVersion ) != 0 {
2022-09-30 13:23:27 +08:00
statusRow = enabledAddonColor . Sprintf ( "%s (%s)" , statusRow , status . InstalledVersion )
name = enabledAddonColor . Sprintf ( "%s" , name )
}
if statusRow == statusDisabled {
statusRow = "-"
2022-03-28 21:25:38 +08:00
}
2022-09-30 13:23:27 +08:00
table . AddRow ( 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 {
2022-09-19 17:24:07 +08:00
if dryRun {
return nil
}
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-06-29 10:55:50 +08:00
err := k8sClient . Get ( ctx , types2 . NamespacedName { Name : addonutil . Addon2AppName ( 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
}
2022-10-24 19:32:45 +08:00
2021-10-14 18:21:49 +08:00
phase := app . Status . Phase
2022-10-24 19:32:45 +08:00
if app . Generation > app . Status . ObservedGeneration {
phase = common2 . ApplicationStarting
} else {
switch app . Status . Phase {
case common2 . ApplicationRunning :
return nil
case common2 . ApplicationWorkflowSuspending :
fmt . Printf ( "Enabling suspend, please run \"vela workflow resume %s -n vela-system\" to continue" , addonutil . Addon2AppName ( addonName ) )
return nil
case common2 . ApplicationWorkflowTerminated , common2 . ApplicationWorkflowFailed :
return errors . Errorf ( "Enabling failed, please run \"vela status %s -n vela-system\" to check the status of the addon" , addonutil . Addon2AppName ( addonName ) )
default :
}
2021-07-25 10:16:43 +08:00
}
2022-10-24 19:32:45 +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-06-29 10:55:50 +08:00
return errors . Errorf ( "Enabling timeout, please run \"vela status %s -n vela-system\" to check the status of the addon" , addonutil . Addon2AppName ( 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-09-30 13:23:27 +08:00
res += enabledAddonColor . Sprintf ( "%s" , version )
2022-03-28 21:25:38 +08:00
} 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 {
2022-07-22 17:58:19 +08:00
if len ( cstr ) == 0 {
return nil
}
2022-03-29 23:03:57 +08:00
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
}
2022-11-24 09:42:28 +08:00
func splitSpecifyRegistry ( name string ) ( string , string , error ) {
res := strings . Split ( name , "/" )
switch len ( res ) {
case 2 :
return res [ 0 ] , res [ 1 ] , nil
case 1 :
return "" , res [ 0 ] , nil
default :
return "" , "" , fmt . Errorf ( "invalid addon name, you should specify name only <addonName> or with registry as prefix <registryName>/<addonName>" )
}
}
2022-12-12 13:40:48 +08:00
func checkUninstallFromClusters ( ctx context . Context , k8sClient client . Client , addonName string , args map [ string ] interface { } ) error {
status , err := pkgaddon . GetAddonStatus ( ctx , k8sClient , addonName )
if err != nil {
return fmt . Errorf ( "failed to check addon status: %w" , err )
}
if status . AddonPhase == statusDisabled {
return nil
}
if _ , ok := args [ "clusters" ] ; ! ok {
return nil
}
cList , ok := args [ "clusters" ] . ( [ ] interface { } )
if ! ok {
return fmt . Errorf ( "clusters parameter must be a list of string" )
}
clusters := map [ string ] struct { } { }
for _ , c := range cList {
clusterName := c . ( string )
clusters [ clusterName ] = struct { } { }
}
var disableClusters , installedClusters [ ] string
for c := range status . Clusters {
if _ , ok := clusters [ c ] ; ! ok {
disableClusters = append ( disableClusters , c )
}
installedClusters = append ( installedClusters , c )
}
fmt . Println ( color . New ( color . FgRed ) . Sprintf ( "'%s' addon was currently installed on clusters %s, but this operation will uninstall from these clusters: %s \n" , addonName , generateClustersInfo ( installedClusters ) , generateClustersInfo ( disableClusters ) ) )
input := NewUserInput ( )
if ! input . AskBool ( "Do you want to continue?" , & UserInputOptions { AssumeYes : false } ) {
return fmt . Errorf ( "operation abort" )
}
return nil
}
func generateClustersInfo ( clusters [ ] string ) string {
ret := "["
for i , cluster := range clusters {
ret += cluster
if i < len ( clusters ) - 1 {
ret += ","
}
}
ret += "]"
return ret
}