github.com/oam-dev/kubevela@v1.9.11/pkg/addon/addon.go (about)

     1  /*
     2  Copyright 2021 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package addon
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io"
    25  	"net/http"
    26  	"net/url"
    27  	"os"
    28  	"path"
    29  	"path/filepath"
    30  	"sort"
    31  	"strings"
    32  	"sync"
    33  	"time"
    34  
    35  	"github.com/Masterminds/semver/v3"
    36  	"github.com/google/go-github/v32/github"
    37  	"github.com/imdario/mergo"
    38  	"github.com/kubevela/workflow/pkg/cue/model/value"
    39  	"github.com/pkg/errors"
    40  	"github.com/xanzy/go-gitlab"
    41  	"go.uber.org/multierr"
    42  	"golang.org/x/oauth2"
    43  	"helm.sh/helm/v3/pkg/chart/loader"
    44  	"helm.sh/helm/v3/pkg/chartutil"
    45  	appsv1 "k8s.io/api/apps/v1"
    46  	v1 "k8s.io/api/core/v1"
    47  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    48  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    49  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    50  	"k8s.io/apimachinery/pkg/runtime"
    51  	k8syaml "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
    52  	types2 "k8s.io/apimachinery/pkg/types"
    53  	"k8s.io/apimachinery/pkg/util/sets"
    54  	"k8s.io/client-go/discovery"
    55  	"k8s.io/client-go/rest"
    56  	"k8s.io/client-go/util/retry"
    57  	"k8s.io/klog/v2"
    58  	stringslices "k8s.io/utils/strings/slices"
    59  	"sigs.k8s.io/controller-runtime/pkg/client"
    60  	"sigs.k8s.io/yaml"
    61  
    62  	common2 "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    63  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    64  	"github.com/oam-dev/kubevela/apis/types"
    65  	"github.com/oam-dev/kubevela/pkg/config"
    66  	"github.com/oam-dev/kubevela/pkg/cue/script"
    67  	"github.com/oam-dev/kubevela/pkg/definition"
    68  	"github.com/oam-dev/kubevela/pkg/multicluster"
    69  	"github.com/oam-dev/kubevela/pkg/oam"
    70  	"github.com/oam-dev/kubevela/pkg/oam/util"
    71  	"github.com/oam-dev/kubevela/pkg/utils"
    72  	addonutil "github.com/oam-dev/kubevela/pkg/utils/addon"
    73  	"github.com/oam-dev/kubevela/pkg/utils/apply"
    74  	"github.com/oam-dev/kubevela/pkg/utils/common"
    75  	"github.com/oam-dev/kubevela/pkg/velaql"
    76  	version2 "github.com/oam-dev/kubevela/version"
    77  )
    78  
    79  const (
    80  	// ReadmeFileName is the addon readme file name
    81  	ReadmeFileName string = "README.md"
    82  
    83  	// LegacyReadmeFileName is the addon readme lower case file name
    84  	LegacyReadmeFileName string = "readme.md"
    85  
    86  	// MetadataFileName is the addon meatadata.yaml file name
    87  	MetadataFileName string = "metadata.yaml"
    88  
    89  	// TemplateFileName is the addon template.yaml file name
    90  	TemplateFileName string = "template.yaml"
    91  
    92  	// AppTemplateCueFileName is the addon application template.cue file name
    93  	AppTemplateCueFileName string = "template.cue"
    94  
    95  	// NotesCUEFileName is the addon notes print to end users when installed
    96  	NotesCUEFileName string = "NOTES.cue"
    97  
    98  	// KeyWordNotes is the keyword in NOTES.cue which will render the notes message out.
    99  	KeyWordNotes string = "notes"
   100  
   101  	// GlobalParameterFileName is the addon global parameter.cue file name
   102  	GlobalParameterFileName string = "parameter.cue"
   103  
   104  	// ResourcesDirName is the addon resources/ dir name
   105  	ResourcesDirName string = "resources"
   106  
   107  	// DefinitionsDirName is the addon definitions/ dir name
   108  	DefinitionsDirName string = "definitions"
   109  
   110  	// ConfigTemplateDirName is the addon config-templates/ dir name
   111  	ConfigTemplateDirName string = "config-templates"
   112  
   113  	// DefSchemaName is the addon definition schemas dir name
   114  	DefSchemaName string = "schemas"
   115  
   116  	// ViewDirName is the addon views dir name
   117  	ViewDirName string = "views"
   118  
   119  	// AddonParameterDataKey is the key of parameter in addon args secrets
   120  	AddonParameterDataKey string = "addonParameterDataKey"
   121  
   122  	// DefaultGiteeURL is the addon repository of gitee api
   123  	DefaultGiteeURL string = "https://gitee.com/api/v5/"
   124  
   125  	// InstallerRuntimeOption inject install runtime info into addon options
   126  	InstallerRuntimeOption string = "installerRuntimeOption"
   127  
   128  	// CUEExtension with the expected extension for CUE files
   129  	CUEExtension = ".cue"
   130  )
   131  
   132  // ParameterFileName is the addon resources/parameter.cue file name
   133  var ParameterFileName = strings.Join([]string{"resources", "parameter.cue"}, "/")
   134  
   135  // ListOptions contains flags mark what files should be read in an addon directory
   136  type ListOptions struct {
   137  	GetDetail         bool
   138  	GetDefinition     bool
   139  	GetConfigTemplate bool
   140  	GetResource       bool
   141  	GetParameter      bool
   142  	GetTemplate       bool
   143  	GetDefSchema      bool
   144  }
   145  
   146  var (
   147  	// UIMetaOptions get Addon metadata for UI display
   148  	UIMetaOptions = ListOptions{GetDetail: true, GetDefinition: true, GetParameter: true, GetConfigTemplate: true}
   149  
   150  	// CLIMetaOptions get Addon metadata for CLI display
   151  	CLIMetaOptions = ListOptions{}
   152  
   153  	// UnInstallOptions used for addon uninstalling
   154  	UnInstallOptions = ListOptions{GetDefinition: true}
   155  )
   156  
   157  const (
   158  	// LocalAddonRegistryName is the addon-registry name for those installed by local dir
   159  	LocalAddonRegistryName = "local"
   160  	// ClusterLabelSelector define the key of topology cluster label selector
   161  	ClusterLabelSelector = "clusterLabelSelector"
   162  )
   163  
   164  // Pattern indicates the addon framework file pattern, all files should match at least one of the pattern.
   165  type Pattern struct {
   166  	IsDir bool
   167  	Value string
   168  }
   169  
   170  // Patterns is the file pattern that the addon should be in
   171  var Patterns = []Pattern{
   172  	// config-templates pattern
   173  	{IsDir: true, Value: ConfigTemplateDirName},
   174  	// single file reader pattern
   175  	{Value: ReadmeFileName}, {Value: MetadataFileName}, {Value: TemplateFileName},
   176  	// parameter in resource directory
   177  	{Value: ParameterFileName},
   178  	// directory files
   179  	{IsDir: true, Value: ResourcesDirName}, {IsDir: true, Value: DefinitionsDirName}, {IsDir: true, Value: DefSchemaName}, {IsDir: true, Value: ViewDirName},
   180  	// CUE app template, parameter and notes
   181  	{Value: AppTemplateCueFileName}, {Value: GlobalParameterFileName}, {Value: NotesCUEFileName},
   182  	{Value: LegacyReadmeFileName}}
   183  
   184  // GetPatternFromItem will check if the file path has a valid pattern, return empty string if it's invalid.
   185  // AsyncReader is needed to calculate relative path
   186  func GetPatternFromItem(it Item, r AsyncReader, rootPath string) string {
   187  	relativePath := r.RelativePath(it)
   188  	for _, p := range Patterns {
   189  		if strings.HasPrefix(relativePath, strings.Join([]string{rootPath, p.Value}, "/")) {
   190  			return p.Value
   191  		}
   192  		if strings.HasPrefix(relativePath, filepath.Join(rootPath, p.Value)) {
   193  			// for enable addon by load dir, compatible with linux or windows os
   194  			return p.Value
   195  		}
   196  	}
   197  	return ""
   198  }
   199  
   200  // ListAddonUIDataFromReader list addons from AsyncReader
   201  func ListAddonUIDataFromReader(r AsyncReader, registryMeta map[string]SourceMeta, registryName string, opt ListOptions) ([]*UIData, error) {
   202  	var addons []*UIData
   203  	var err error
   204  	var wg sync.WaitGroup
   205  	var errs []error
   206  	errCh := make(chan error)
   207  	waitCh := make(chan struct{})
   208  
   209  	var l sync.Mutex
   210  	for _, subItem := range registryMeta {
   211  		wg.Add(1)
   212  		go func(addonMeta SourceMeta) {
   213  			defer wg.Done()
   214  			addonRes, err := GetUIDataFromReader(r, &addonMeta, opt)
   215  			if err != nil {
   216  				errCh <- err
   217  				return
   218  			}
   219  			addonRes.RegistryName = registryName
   220  			l.Lock()
   221  			addons = append(addons, addonRes)
   222  			l.Unlock()
   223  		}(subItem)
   224  	}
   225  	// in another goroutine for wait group to finish
   226  	go func() {
   227  		wg.Wait()
   228  		close(waitCh)
   229  	}()
   230  forLoop:
   231  	for {
   232  		select {
   233  		case <-waitCh:
   234  			break forLoop
   235  		case err = <-errCh:
   236  			errs = append(errs, err)
   237  		}
   238  	}
   239  	if len(errs) != 0 {
   240  		return addons, compactErrors("error(s) happen when reading from registry: ", errs)
   241  	}
   242  	return addons, nil
   243  }
   244  
   245  func compactErrors(message string, errs []error) error {
   246  	errForPrint := make([]string, 0)
   247  	for _, e := range errs {
   248  		errForPrint = append(errForPrint, e.Error())
   249  	}
   250  
   251  	return errors.New(message + strings.Join(errForPrint, ","))
   252  
   253  }
   254  
   255  // GetUIDataFromReader read ui metadata of addon from Reader, used to be displayed in UI
   256  func GetUIDataFromReader(r AsyncReader, meta *SourceMeta, opt ListOptions) (*UIData, error) {
   257  	addonContentsReader := map[string]struct {
   258  		skip bool
   259  		read func(a *UIData, reader AsyncReader, readPath string) error
   260  	}{
   261  		ReadmeFileName:          {!opt.GetDetail, readReadme},
   262  		LegacyReadmeFileName:    {!opt.GetDetail, readReadme},
   263  		MetadataFileName:        {false, readMetadata},
   264  		DefinitionsDirName:      {!opt.GetDefinition, readDefFile},
   265  		ConfigTemplateDirName:   {!opt.GetConfigTemplate, readConfigTemplateFile},
   266  		ParameterFileName:       {!opt.GetParameter, readParamFile},
   267  		GlobalParameterFileName: {!opt.GetParameter, readGlobalParamFile},
   268  	}
   269  	ptItems := ClassifyItemByPattern(meta, r)
   270  	var addon = &UIData{}
   271  	for contentType, method := range addonContentsReader {
   272  		if method.skip {
   273  			continue
   274  		}
   275  		items := ptItems[contentType]
   276  		for _, it := range items {
   277  			err := method.read(addon, r, r.RelativePath(it))
   278  			if err != nil {
   279  				return nil, fmt.Errorf("fail to read addon %s file %s: %w", meta.Name, r.RelativePath(it), err)
   280  			}
   281  		}
   282  	}
   283  
   284  	if opt.GetParameter && (len(addon.Parameters) != 0 || len(addon.GlobalParameters) != 0) {
   285  		if addon.GlobalParameters != "" {
   286  			if addon.Parameters != "" {
   287  				klog.Warning("both legacy parameter and global parameter are provided, but only global parameter will be used. Consider removing the legacy parameters.")
   288  			}
   289  			addon.Parameters = addon.GlobalParameters
   290  		}
   291  		err := genAddonAPISchema(addon)
   292  		if err != nil {
   293  			return nil, fmt.Errorf("fail to generate openAPIschema for addon %s : %w (parameter: %s)", meta.Name, err, addon.Parameters)
   294  		}
   295  	}
   296  	addon.AvailableVersions = []string{addon.Version}
   297  	return addon, nil
   298  }
   299  
   300  // GetInstallPackageFromReader get install package of addon from Reader, this is used to enable an addon
   301  func GetInstallPackageFromReader(r AsyncReader, meta *SourceMeta, uiData *UIData) (*InstallPackage, error) {
   302  	addonContentsReader := map[string]func(a *InstallPackage, reader AsyncReader, readPath string) error{
   303  		TemplateFileName:       readTemplate,
   304  		ResourcesDirName:       readResFile,
   305  		DefSchemaName:          readDefSchemaFile,
   306  		ViewDirName:            readViewFile,
   307  		AppTemplateCueFileName: readAppCueTemplate,
   308  		NotesCUEFileName:       readNotesFile,
   309  	}
   310  	ptItems := ClassifyItemByPattern(meta, r)
   311  
   312  	// Read the installed data from UI metadata object to reduce network payload
   313  	var addon = &InstallPackage{
   314  		Meta:            uiData.Meta,
   315  		Definitions:     uiData.Definitions,
   316  		CUEDefinitions:  uiData.CUEDefinitions,
   317  		Parameters:      uiData.Parameters,
   318  		ConfigTemplates: uiData.ConfigTemplates,
   319  	}
   320  
   321  	for contentType, method := range addonContentsReader {
   322  		items := ptItems[contentType]
   323  		for _, it := range items {
   324  			err := method(addon, r, r.RelativePath(it))
   325  			if err != nil {
   326  				return nil, fmt.Errorf("fail to read addon %s file %s: %w", meta.Name, r.RelativePath(it), err)
   327  			}
   328  		}
   329  	}
   330  
   331  	return addon, nil
   332  }
   333  
   334  func readTemplate(a *InstallPackage, reader AsyncReader, readPath string) error {
   335  	data, err := reader.ReadFile(readPath)
   336  	if err != nil {
   337  		return err
   338  	}
   339  	dec := k8syaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
   340  	a.AppTemplate = &v1beta1.Application{}
   341  
   342  	// try to check it's a valid app template
   343  	_, _, err = dec.Decode([]byte(data), nil, a.AppTemplate)
   344  	if err != nil {
   345  		return err
   346  	}
   347  	return nil
   348  }
   349  
   350  func readAppCueTemplate(a *InstallPackage, reader AsyncReader, readPath string) error {
   351  	data, err := reader.ReadFile(readPath)
   352  	if err != nil {
   353  		return err
   354  	}
   355  	a.AppCueTemplate = ElementFile{Data: data, Name: filepath.Base(readPath)}
   356  	return nil
   357  }
   358  
   359  // readParamFile read single resource/parameter.cue file
   360  func readParamFile(a *UIData, reader AsyncReader, readPath string) error {
   361  	b, err := reader.ReadFile(readPath)
   362  	if err != nil {
   363  		return err
   364  	}
   365  	a.Parameters = b
   366  	return nil
   367  }
   368  
   369  // readNotesFile read single NOTES.cue file
   370  func readNotesFile(a *InstallPackage, reader AsyncReader, readPath string) error {
   371  	data, err := reader.ReadFile(readPath)
   372  	if err != nil {
   373  		return err
   374  	}
   375  	a.Notes = ElementFile{Data: data, Name: filepath.Base(readPath)}
   376  	return nil
   377  }
   378  
   379  // readGlobalParamFile read global parameter file.
   380  func readGlobalParamFile(a *UIData, reader AsyncReader, readPath string) error {
   381  	b, err := reader.ReadFile(readPath)
   382  	if err != nil {
   383  		return err
   384  	}
   385  	a.GlobalParameters = b
   386  	return nil
   387  }
   388  
   389  // readResFile read single resource file
   390  func readResFile(a *InstallPackage, reader AsyncReader, readPath string) error {
   391  	filename := path.Base(readPath)
   392  	b, err := reader.ReadFile(readPath)
   393  	if err != nil {
   394  		return err
   395  	}
   396  
   397  	if filename == "parameter.cue" {
   398  		return nil
   399  	}
   400  	file := ElementFile{Data: b, Name: filepath.Base(readPath)}
   401  	switch filepath.Ext(filename) {
   402  	case CUEExtension:
   403  		a.CUETemplates = append(a.CUETemplates, file)
   404  	case ".yaml", ".yml":
   405  		a.YAMLTemplates = append(a.YAMLTemplates, file)
   406  	default:
   407  		// skip other file formats
   408  	}
   409  	return nil
   410  }
   411  
   412  // readDefSchemaFile read single file of definition schema
   413  func readDefSchemaFile(a *InstallPackage, reader AsyncReader, readPath string) error {
   414  	b, err := reader.ReadFile(readPath)
   415  	if err != nil {
   416  		return err
   417  	}
   418  	a.DefSchemas = append(a.DefSchemas, ElementFile{Data: b, Name: filepath.Base(readPath)})
   419  	return nil
   420  }
   421  
   422  // readDefFile read single definition file
   423  func readDefFile(a *UIData, reader AsyncReader, readPath string) error {
   424  	b, err := reader.ReadFile(readPath)
   425  	if err != nil {
   426  		return err
   427  	}
   428  	filename := path.Base(readPath)
   429  	file := ElementFile{Data: b, Name: filepath.Base(readPath)}
   430  	switch filepath.Ext(filename) {
   431  	case CUEExtension:
   432  		a.CUEDefinitions = append(a.CUEDefinitions, file)
   433  	case ".yaml", ".yml":
   434  		a.Definitions = append(a.Definitions, file)
   435  	default:
   436  		// skip other file formats
   437  	}
   438  	return nil
   439  }
   440  
   441  // readConfigTemplateFile read single template file of the config
   442  func readConfigTemplateFile(a *UIData, reader AsyncReader, readPath string) error {
   443  	b, err := reader.ReadFile(readPath)
   444  	if err != nil {
   445  		return err
   446  	}
   447  	filename := path.Base(readPath)
   448  	if filepath.Ext(filename) != CUEExtension {
   449  		return nil
   450  	}
   451  	file := ElementFile{Data: b, Name: filepath.Base(readPath)}
   452  	a.ConfigTemplates = append(a.ConfigTemplates, file)
   453  	return nil
   454  }
   455  
   456  // readViewFile read single view file
   457  func readViewFile(a *InstallPackage, reader AsyncReader, readPath string) error {
   458  	b, err := reader.ReadFile(readPath)
   459  	if err != nil {
   460  		return err
   461  	}
   462  	filename := path.Base(readPath)
   463  	switch filepath.Ext(filename) {
   464  	case CUEExtension:
   465  		a.CUEViews = append(a.CUEViews, ElementFile{Data: b, Name: filepath.Base(readPath)})
   466  	case ".yaml", ".yml":
   467  		a.YAMLViews = append(a.YAMLViews, ElementFile{Data: b, Name: filepath.Base(readPath)})
   468  	default:
   469  		// skip other file formats
   470  	}
   471  	return nil
   472  }
   473  
   474  func readMetadata(a *UIData, reader AsyncReader, readPath string) error {
   475  	b, err := reader.ReadFile(readPath)
   476  	if err != nil {
   477  		return err
   478  	}
   479  	err = yaml.Unmarshal([]byte(b), &a.Meta)
   480  	if err != nil {
   481  		return err
   482  	}
   483  	return nil
   484  }
   485  
   486  func readReadme(a *UIData, reader AsyncReader, readPath string) error {
   487  	// the detail will contain readme.md or README.md, if the content already is filled, don't read another.
   488  	if len(a.Detail) != 0 {
   489  		return nil
   490  	}
   491  	content, err := reader.ReadFile(readPath)
   492  	if err != nil {
   493  		return err
   494  	}
   495  	a.Detail = content
   496  	return nil
   497  }
   498  
   499  func createGitHelper(content *utils.Content, token string) *gitHelper {
   500  	var ts oauth2.TokenSource
   501  	if token != "" {
   502  		ts = oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
   503  	}
   504  	tc := oauth2.NewClient(context.Background(), ts)
   505  	tc.Timeout = time.Second * 20
   506  	cli := github.NewClient(tc)
   507  	return &gitHelper{
   508  		Client: cli,
   509  		Meta:   content,
   510  	}
   511  }
   512  
   513  func createGiteeHelper(content *utils.Content, token string) *giteeHelper {
   514  	var ts oauth2.TokenSource
   515  	if token != "" {
   516  		ts = oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
   517  	}
   518  	tc := oauth2.NewClient(context.Background(), ts)
   519  	tc.Timeout = time.Second * 20
   520  	cli := NewGiteeClient(tc, nil)
   521  	return &giteeHelper{
   522  		Client: cli,
   523  		Meta:   content,
   524  	}
   525  }
   526  
   527  func createGitlabHelper(content *utils.Content, token string) (*gitlabHelper, error) {
   528  	newClient, err := gitlab.NewClient(token, gitlab.WithBaseURL(content.GitlabContent.Host))
   529  
   530  	return &gitlabHelper{
   531  		Client: newClient,
   532  		Meta:   content,
   533  	}, err
   534  }
   535  
   536  // readRepo will read relative path (relative to Meta.Path)
   537  func (h *gitHelper) readRepo(relativePath string) (*github.RepositoryContent, []*github.RepositoryContent, error) {
   538  	file, items, _, err := h.Client.Repositories.GetContents(context.Background(), h.Meta.GithubContent.Owner, h.Meta.GithubContent.Repo, path.Join(h.Meta.GithubContent.Path, relativePath), nil)
   539  	if err != nil {
   540  		return nil, nil, WrapErrRateLimit(err)
   541  	}
   542  	return file, items, nil
   543  }
   544  
   545  // readRepo will read relative path (relative to Meta.Path)
   546  func (h *giteeHelper) readRepo(relativePath string) (*github.RepositoryContent, []*github.RepositoryContent, error) {
   547  	file, items, err := h.Client.GetGiteeContents(context.Background(), h.Meta.GiteeContent.Owner, h.Meta.GiteeContent.Repo, path.Join(h.Meta.GiteeContent.Path, relativePath), h.Meta.GiteeContent.Ref)
   548  	if err != nil {
   549  		return nil, nil, WrapErrRateLimit(err)
   550  	}
   551  	return file, items, nil
   552  }
   553  
   554  // GetGiteeContents can return either the metadata and content of a single file
   555  func (c *Client) GetGiteeContents(ctx context.Context, owner, repo, path, ref string) (fileContent *github.RepositoryContent, directoryContent []*github.RepositoryContent, err error) {
   556  	escapedPath := (&url.URL{Path: path}).String()
   557  	u := fmt.Sprintf(c.BaseURL.String()+"repos/%s/%s/contents/%s", owner, repo, escapedPath)
   558  	if ref != "" {
   559  		u = fmt.Sprintf(u+"?ref=%s", ref)
   560  	}
   561  
   562  	req, err := http.NewRequest("GET", u, nil)
   563  	if err != nil {
   564  		return nil, nil, err
   565  	}
   566  	response, err := c.Client.Do(req.WithContext(ctx))
   567  	if err != nil {
   568  		return nil, nil, err
   569  	}
   570  	//nolint:errcheck
   571  	defer response.Body.Close()
   572  	body, err := io.ReadAll(response.Body)
   573  	if err != nil {
   574  		return nil, nil, err
   575  	}
   576  	return unmarshalToContent(body)
   577  }
   578  
   579  func unmarshalToContent(content []byte) (fileContent *github.RepositoryContent, directoryContent []*github.RepositoryContent, err error) {
   580  	fileUnmarshalError := json.Unmarshal(content, &fileContent)
   581  	if fileUnmarshalError == nil {
   582  		return fileContent, nil, nil
   583  	}
   584  	directoryUnmarshalError := json.Unmarshal(content, &directoryContent)
   585  	if directoryUnmarshalError == nil {
   586  		return nil, directoryContent, nil
   587  	}
   588  	return nil, nil, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %w", fileUnmarshalError.Error(), directoryUnmarshalError)
   589  }
   590  
   591  func genAddonAPISchema(addonRes *UIData) error {
   592  	cueScript := script.CUE(addonRes.Parameters)
   593  	schema, err := cueScript.ParsePropertiesToSchema()
   594  	if err != nil {
   595  		return err
   596  	}
   597  	addonRes.APISchema = schema
   598  	return nil
   599  }
   600  
   601  func getClusters(args map[string]interface{}) []string {
   602  	ccr, ok := args[types.ClustersArg]
   603  	if !ok {
   604  		return nil
   605  	}
   606  	cc, ok := ccr.([]string)
   607  	if ok {
   608  		return cc
   609  	}
   610  	ccrslice, ok := ccr.([]interface{})
   611  	if !ok {
   612  		return nil
   613  	}
   614  	var ccstring []string
   615  	for _, c := range ccrslice {
   616  		if cstring, ok := c.(string); ok {
   617  			ccstring = append(ccstring, cstring)
   618  		}
   619  	}
   620  	return ccstring
   621  }
   622  
   623  // renderNeededNamespaceAsComps will convert namespace as app components to create namespace for managed clusters
   624  func renderNeededNamespaceAsComps(addon *InstallPackage) []common2.ApplicationComponent {
   625  	var nscomps []common2.ApplicationComponent
   626  	// create namespace for managed clusters
   627  	for _, namespace := range addon.NeedNamespace {
   628  		// vela-system must exist before rendering vela addon
   629  		if namespace == types.DefaultKubeVelaNS {
   630  			continue
   631  		}
   632  		comp := common2.ApplicationComponent{
   633  			Type:       "raw",
   634  			Name:       fmt.Sprintf("%s-namespace", namespace),
   635  			Properties: util.Object2RawExtension(renderNamespace(namespace)),
   636  		}
   637  		nscomps = append(nscomps, comp)
   638  	}
   639  	return nscomps
   640  }
   641  
   642  func checkDeployClusters(ctx context.Context, k8sClient client.Client, args map[string]interface{}) ([]string, error) {
   643  	deployClusters := getClusters(args)
   644  	if len(deployClusters) == 0 || k8sClient == nil {
   645  		return nil, nil
   646  	}
   647  
   648  	clusters, err := multicluster.NewClusterClient(k8sClient).List(ctx)
   649  	if err != nil {
   650  		return nil, errors.Wrap(err, "fail to get registered cluster")
   651  	}
   652  
   653  	clusterNames := sets.Set[string]{}
   654  	if len(clusters.Items) != 0 {
   655  		for _, cluster := range clusters.Items {
   656  			clusterNames.Insert(cluster.Name)
   657  		}
   658  	}
   659  
   660  	var res []string
   661  	for _, c := range deployClusters {
   662  		c = strings.TrimSpace(c)
   663  		if c == "" {
   664  			continue
   665  		}
   666  		if !clusterNames.Has(c) {
   667  			return nil, errors.Errorf("cluster %s not exist", c)
   668  		}
   669  		res = append(res, c)
   670  	}
   671  	return res, nil
   672  }
   673  
   674  // RenderDefinitions render definition objects if needed
   675  func RenderDefinitions(addon *InstallPackage, config *rest.Config) ([]*unstructured.Unstructured, error) {
   676  	defObjs := make([]*unstructured.Unstructured, 0)
   677  
   678  	// No matter runtime mode or control mode, definition only needs to control plane k8s.
   679  	for _, def := range addon.Definitions {
   680  		obj, err := renderObject(def)
   681  		if err != nil {
   682  			return nil, errors.Wrapf(err, "render definition file %s", def.Name)
   683  		}
   684  		// we should ignore the namespace defined in definition yaml, override the filed by DefaultKubeVelaNS
   685  		obj.SetNamespace(types.DefaultKubeVelaNS)
   686  		defObjs = append(defObjs, obj)
   687  	}
   688  	for _, cueDef := range addon.CUEDefinitions {
   689  		def := definition.Definition{Unstructured: unstructured.Unstructured{}}
   690  		err := def.FromCUEString(cueDef.Data, config)
   691  		if err != nil {
   692  			return nil, errors.Wrapf(err, "fail to render definition: %s in cue's format", cueDef.Name)
   693  		}
   694  		// we should ignore the namespace defined in definition yaml, override the filed by DefaultKubeVelaNS
   695  		def.SetNamespace(types.DefaultKubeVelaNS)
   696  		defObjs = append(defObjs, &def.Unstructured)
   697  	}
   698  
   699  	return defObjs, nil
   700  }
   701  
   702  // RenderConfigTemplates render the config template
   703  func RenderConfigTemplates(addon *InstallPackage, cli client.Client) ([]*unstructured.Unstructured, error) {
   704  	templates := make([]*unstructured.Unstructured, 0)
   705  
   706  	factory := config.NewConfigFactory(cli)
   707  	for _, templateFile := range addon.ConfigTemplates {
   708  		t, err := factory.ParseTemplate("", []byte(templateFile.Data))
   709  		if err != nil {
   710  			return nil, err
   711  		}
   712  		t.ConfigMap.Namespace = types.DefaultKubeVelaNS
   713  		obj, err := util.Object2Unstructured(t.ConfigMap)
   714  		if err != nil {
   715  			return nil, err
   716  		}
   717  		obj.SetKind("ConfigMap")
   718  		obj.SetAPIVersion("v1")
   719  		templates = append(templates, obj)
   720  	}
   721  
   722  	return templates, nil
   723  }
   724  
   725  // RenderDefinitionSchema will render definitions' schema in addons.
   726  func RenderDefinitionSchema(addon *InstallPackage) ([]*unstructured.Unstructured, error) {
   727  	schemaConfigmaps := make([]*unstructured.Unstructured, 0)
   728  
   729  	// No matter runtime mode or control mode , definition schemas only needs to control plane k8s.
   730  	for _, teml := range addon.DefSchemas {
   731  		u, err := renderSchemaConfigmap(teml)
   732  		if err != nil {
   733  			return nil, errors.Wrapf(err, "render uiSchema file %s", teml.Name)
   734  		}
   735  		schemaConfigmaps = append(schemaConfigmaps, u)
   736  	}
   737  	return schemaConfigmaps, nil
   738  }
   739  
   740  // RenderViews will render views in addons.
   741  func RenderViews(addon *InstallPackage) ([]*unstructured.Unstructured, error) {
   742  	views := make([]*unstructured.Unstructured, 0)
   743  	for _, view := range addon.YAMLViews {
   744  		obj, err := renderObject(view)
   745  		if err != nil {
   746  			return nil, errors.Wrapf(err, "render velaQL view file %s", view.Name)
   747  		}
   748  		views = append(views, obj)
   749  	}
   750  	for _, view := range addon.CUEViews {
   751  		obj, err := renderCUEView(view)
   752  		if err != nil {
   753  			return nil, errors.Wrapf(err, "render velaQL view file %s", view.Name)
   754  		}
   755  		views = append(views, obj)
   756  	}
   757  	return views, nil
   758  }
   759  
   760  func renderObject(elem ElementFile) (*unstructured.Unstructured, error) {
   761  	obj := &unstructured.Unstructured{}
   762  	dec := k8syaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
   763  	_, _, err := dec.Decode([]byte(elem.Data), nil, obj)
   764  	if err != nil {
   765  		return nil, err
   766  	}
   767  	return obj, nil
   768  }
   769  
   770  func renderNamespace(namespace string) *unstructured.Unstructured {
   771  	u := &unstructured.Unstructured{}
   772  	u.SetAPIVersion("v1")
   773  	u.SetKind("Namespace")
   774  	u.SetName(namespace)
   775  	return u
   776  }
   777  
   778  func renderK8sObjectsComponent(elems []ElementFile, addonName string) (*common2.ApplicationComponent, error) {
   779  	var objects []*unstructured.Unstructured
   780  	for _, elem := range elems {
   781  		obj, err := renderObject(elem)
   782  		if err != nil {
   783  			return nil, errors.Wrapf(err, "render resource file %s", elem.Name)
   784  		}
   785  		objects = append(objects, obj)
   786  	}
   787  	properties := map[string]interface{}{"objects": objects}
   788  	propJSON, err := json.Marshal(properties)
   789  	if err != nil {
   790  		return nil, err
   791  	}
   792  	baseRawComponent := common2.ApplicationComponent{
   793  		Type:       "k8s-objects",
   794  		Name:       addonName + "-resources",
   795  		Properties: &runtime.RawExtension{Raw: propJSON},
   796  	}
   797  	return &baseRawComponent, nil
   798  }
   799  
   800  func renderSchemaConfigmap(elem ElementFile) (*unstructured.Unstructured, error) {
   801  	jsonData, err := yaml.YAMLToJSON([]byte(elem.Data))
   802  	if err != nil {
   803  		return nil, err
   804  	}
   805  	cm := v1.ConfigMap{
   806  		TypeMeta:   metav1.TypeMeta{APIVersion: "v1", Kind: "ConfigMap"},
   807  		ObjectMeta: metav1.ObjectMeta{Namespace: types.DefaultKubeVelaNS, Name: strings.Split(elem.Name, ".")[0]},
   808  		Data: map[string]string{
   809  			types.UISchema: string(jsonData),
   810  		}}
   811  	return util.Object2Unstructured(cm)
   812  }
   813  
   814  func renderCUEView(elem ElementFile) (*unstructured.Unstructured, error) {
   815  	name, err := utils.GetFilenameFromLocalOrRemote(elem.Name)
   816  	if err != nil {
   817  		return nil, err
   818  	}
   819  
   820  	cm, err := velaql.ParseViewIntoConfigMap(elem.Data, name)
   821  	if err != nil {
   822  		return nil, err
   823  	}
   824  
   825  	return util.Object2Unstructured(*cm)
   826  }
   827  
   828  // RenderArgsSecret render addon enable argument to secret to remember when restart or upgrade
   829  func RenderArgsSecret(addon *InstallPackage, args map[string]interface{}) *unstructured.Unstructured {
   830  	argsByte, err := json.Marshal(args)
   831  	if err != nil {
   832  		return nil
   833  	}
   834  	sec := v1.Secret{
   835  		TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Secret"},
   836  		ObjectMeta: metav1.ObjectMeta{
   837  			Name:      addonutil.Addon2SecName(addon.Name),
   838  			Namespace: types.DefaultKubeVelaNS,
   839  		},
   840  		Data: map[string][]byte{
   841  			AddonParameterDataKey: argsByte,
   842  		},
   843  		Type: v1.SecretTypeOpaque,
   844  	}
   845  	u, err := util.Object2Unstructured(sec)
   846  	if err != nil {
   847  		return nil
   848  	}
   849  	return u
   850  }
   851  
   852  // deleteArgsSecret delete the addon's args secret file
   853  func deleteArgsSecret(ctx context.Context, k8sClient client.Client, addonName string) error {
   854  	var sec v1.Secret
   855  	if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: addonutil.Addon2SecName(addonName)}, &sec); err == nil {
   856  		// Handle successful get operation
   857  		if deleteErr := k8sClient.Delete(ctx, &sec); deleteErr != nil {
   858  			return deleteErr
   859  		}
   860  		return nil
   861  	} else if !apierrors.IsNotFound(err) {
   862  		return err
   863  	}
   864  	return nil
   865  }
   866  
   867  // FetchArgsFromSecret fetch addon args from secrets
   868  func FetchArgsFromSecret(sec *v1.Secret) (map[string]interface{}, error) {
   869  	res := map[string]interface{}{}
   870  	if args, ok := sec.Data[AddonParameterDataKey]; ok {
   871  		err := json.Unmarshal(args, &res)
   872  		if err != nil {
   873  			return nil, err
   874  		}
   875  		return res, nil
   876  	}
   877  
   878  	// this is backward compatibility code for old way to storage parameter
   879  	res = make(map[string]interface{}, len(sec.Data))
   880  	for k, v := range sec.Data {
   881  		res[k] = string(v)
   882  	}
   883  	return res, nil
   884  }
   885  
   886  // Installer helps addon enable, dependency-check, dispatch resources
   887  type Installer struct {
   888  	ctx                 context.Context
   889  	config              *rest.Config
   890  	addon               *InstallPackage
   891  	cli                 client.Client
   892  	apply               apply.Applicator
   893  	r                   *Registry
   894  	registryMeta        map[string]SourceMeta
   895  	args                map[string]interface{}
   896  	cache               *Cache
   897  	dc                  *discovery.DiscoveryClient
   898  	skipVersionValidate bool
   899  	overrideDefs        bool
   900  
   901  	dryRun     bool
   902  	dryRunBuff *bytes.Buffer
   903  
   904  	installerRuntime map[string]interface{}
   905  
   906  	registries []Registry
   907  }
   908  
   909  // NewAddonInstaller will create an installer for addon
   910  func NewAddonInstaller(ctx context.Context, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r *Registry, args map[string]interface{}, cache *Cache, registries []Registry, opts ...InstallOption) Installer {
   911  	if args == nil {
   912  		args = map[string]interface{}{}
   913  	}
   914  	i := Installer{
   915  		ctx:        ctx,
   916  		config:     config,
   917  		cli:        cli,
   918  		apply:      apply,
   919  		r:          r,
   920  		args:       args,
   921  		cache:      cache,
   922  		dc:         discoveryClient,
   923  		dryRunBuff: &bytes.Buffer{},
   924  		registries: registries,
   925  	}
   926  	ir := args[InstallerRuntimeOption]
   927  	if irr, ok := ir.(map[string]interface{}); ok {
   928  		i.installerRuntime = irr
   929  	} else {
   930  		i.installerRuntime = map[string]interface{}{}
   931  	}
   932  	// clean injected data from runtime option
   933  	delete(args, InstallerRuntimeOption)
   934  
   935  	for _, opt := range opts {
   936  		opt(&i)
   937  	}
   938  	return i
   939  }
   940  
   941  func (h *Installer) enableAddon(addon *InstallPackage) (string, error) {
   942  	var err error
   943  	h.addon = addon
   944  	if !h.skipVersionValidate {
   945  		err = checkAddonVersionMeetRequired(h.ctx, addon.SystemRequirements, h.cli, h.dc)
   946  		if err != nil {
   947  			version := h.getAddonVersionMeetSystemRequirement(addon.Name)
   948  			return "", VersionUnMatchError{addonName: addon.Name, err: err, userSelectedAddonVersion: addon.Version, availableVersion: version}
   949  		}
   950  	}
   951  
   952  	if err = h.installDependency(addon); err != nil {
   953  		return "", err
   954  	}
   955  	if err = h.dispatchAddonResource(addon); err != nil {
   956  		return "", err
   957  	}
   958  	// we shouldn't put continue func into dispatchAddonResource, because the re-apply app maybe already update app and
   959  	// the suspend will set with false automatically
   960  	if err := h.continueOrRestartWorkflow(); err != nil {
   961  		return "", err
   962  	}
   963  	additionalInfo, err := h.renderNotes(addon)
   964  	if err != nil {
   965  		klog.Warningf("fail to render notes for addon %s: %v\n", addon.Name, err)
   966  		// notes don't affect the installation, so just print warn logs instead of abort with errors
   967  		return "", nil
   968  	}
   969  	return additionalInfo, nil
   970  }
   971  
   972  func (h *Installer) loadInstallPackage(name, version string) (*InstallPackage, error) {
   973  	var installPackage *InstallPackage
   974  	var err error
   975  	if !IsVersionRegistry(*h.r) {
   976  		metas, err := h.getAddonMeta()
   977  		if err != nil {
   978  			return nil, errors.Wrap(err, "fail to get addon meta")
   979  		}
   980  
   981  		meta, ok := metas[name]
   982  		if !ok {
   983  			return nil, ErrNotExist
   984  		}
   985  		var uiData *UIData
   986  		uiData, err = h.cache.GetUIData(*h.r, name, version)
   987  		if err != nil {
   988  			return nil, err
   989  		}
   990  		// enable this addon if it's invisible
   991  		installPackage, err = h.r.GetInstallPackage(&meta, uiData)
   992  		if err != nil {
   993  			return nil, errors.Wrap(err, "fail to find dependent addon in source repository")
   994  		}
   995  	} else {
   996  		versionedRegistry := BuildVersionedRegistry(h.r.Name, h.r.Helm.URL, &common.HTTPOption{
   997  			Username:        h.r.Helm.Username,
   998  			Password:        h.r.Helm.Password,
   999  			InsecureSkipTLS: h.r.Helm.InsecureSkipTLS,
  1000  		})
  1001  		installPackage, err = versionedRegistry.GetAddonInstallPackage(context.Background(), name, version)
  1002  		if err != nil {
  1003  			return nil, err
  1004  		}
  1005  	}
  1006  
  1007  	return installPackage, nil
  1008  }
  1009  
  1010  func (h *Installer) getAddonMeta() (map[string]SourceMeta, error) {
  1011  	var err error
  1012  	if h.registryMeta == nil {
  1013  		if h.registryMeta, err = h.cache.ListAddonMeta(*h.r); err != nil {
  1014  			return nil, err
  1015  		}
  1016  	}
  1017  	return h.registryMeta, nil
  1018  }
  1019  
  1020  // installDependency checks if addon's dependency and install it
  1021  func (h *Installer) installDependency(addon *InstallPackage) error {
  1022  	installedAddons, err := listInstalledAddons(h.ctx, h.cli)
  1023  	if err != nil {
  1024  		return err
  1025  	}
  1026  
  1027  	var registries []ItemInfoLister
  1028  	registries = append(registries, h.r)
  1029  	for _, registry := range h.registries {
  1030  		r := registry
  1031  		registries = append(registries, &r)
  1032  	}
  1033  	availableAddons, err := listAvailableAddons(registries)
  1034  	if err != nil {
  1035  		return err
  1036  	}
  1037  
  1038  	err = validateAddonDependencies(addon, installedAddons, availableAddons)
  1039  	if err != nil {
  1040  		return err
  1041  	}
  1042  
  1043  	var dependencies []string
  1044  	var addonClusters = getClusters(h.args)
  1045  	for _, dep := range addon.Dependencies {
  1046  		needInstallAddonDep, err := checkDependencyNeedInstall(h.ctx, h.cli, dep.Name, addonClusters)
  1047  		if err != nil {
  1048  			return err
  1049  		}
  1050  		if !needInstallAddonDep {
  1051  			continue
  1052  		}
  1053  
  1054  		dependencies = append(dependencies, dep.Name)
  1055  		if h.dryRun {
  1056  			continue
  1057  		}
  1058  		depHandler := *h
  1059  		// reset dependency addon clusters parameter
  1060  		depArgs, depArgsErr := getDependencyArgs(h.ctx, h.cli, dep.Name, addonClusters)
  1061  		if depArgsErr != nil {
  1062  			return depArgsErr
  1063  		}
  1064  
  1065  		depHandler.args = depArgs
  1066  
  1067  		var depAddon *InstallPackage
  1068  		depVersion, err := calculateDependencyVersionToInstall(*dep, installedAddons, availableAddons)
  1069  		if err != nil {
  1070  			return err
  1071  		}
  1072  		// try to install the dependent addon from the same registry with the current addon
  1073  		depAddon, err = h.loadInstallPackage(dep.Name, depVersion)
  1074  		if err == nil {
  1075  			additionalInfo, err := depHandler.enableAddon(depAddon)
  1076  			if err != nil {
  1077  				return errors.Wrap(err, "fail to dispatch dependent addon resource")
  1078  			}
  1079  			if len(additionalInfo) > 0 {
  1080  				klog.Infof("addon %s installed with additional info: %s\n", addon.Name, additionalInfo)
  1081  			}
  1082  			return nil
  1083  		}
  1084  		if !errors.Is(err, ErrNotExist) {
  1085  			return err
  1086  		}
  1087  		for _, registry := range h.registries {
  1088  			// try to install dependent addon from other registries
  1089  			depHandler.r = &Registry{
  1090  				Name: registry.Name, Helm: registry.Helm, OSS: registry.OSS, Git: registry.Git, Gitee: registry.Gitee, Gitlab: registry.Gitlab,
  1091  			}
  1092  			depAddon, err = depHandler.loadInstallPackage(dep.Name, depVersion)
  1093  			if err == nil {
  1094  				break
  1095  			}
  1096  			if errors.Is(err, ErrNotExist) {
  1097  				continue
  1098  			}
  1099  			return err
  1100  		}
  1101  		if err == nil {
  1102  			additionalInfo, err := depHandler.enableAddon(depAddon)
  1103  			if err != nil {
  1104  				return errors.Wrap(err, "fail to dispatch dependent addon resource")
  1105  			}
  1106  			if len(additionalInfo) > 0 {
  1107  				klog.Infof("addon %s installed with additional info: %s\n", addon.Name, additionalInfo)
  1108  			}
  1109  			return nil
  1110  		}
  1111  		return fmt.Errorf("dependency addon: %s with version: %s cannot be found from all registries", dep.Name, depVersion)
  1112  	}
  1113  	if h.dryRun && len(dependencies) > 0 {
  1114  		klog.Warningf("dry run addon won't install dependencies, please make sure your system has already installed these addons: %v", strings.Join(dependencies, ", "))
  1115  		return nil
  1116  	}
  1117  	return nil
  1118  }
  1119  
  1120  // validateAddonDependencies checks if addon's dependencies can be satisfied.
  1121  // If dependency is installed, check if the version matches required version.
  1122  // If dependency is not installed, check available addons for dependency
  1123  // matching the required version.
  1124  // Return error if any dependency cannot be satisfied.
  1125  func validateAddonDependencies(addon *InstallPackage, installedAddons itemInfoMap, availableAddons itemInfoMap) error {
  1126  	var merr error
  1127  	for _, dep := range addon.Dependencies {
  1128  		_, err := calculateDependencyVersionToInstall(*dep, installedAddons, availableAddons)
  1129  		if err != nil {
  1130  			merr = multierr.Append(merr, fmt.Errorf("addon %s has unresolvable dependency %s: %w", addon.Name, dep.Name, err))
  1131  		}
  1132  	}
  1133  	return merr
  1134  }
  1135  
  1136  // calculateDependencyVersionToInstall compares an addon's dependency to a list
  1137  // of installed and available addons and returns a version to install.
  1138  // If dependency is installed, return the installed version if it matches the
  1139  // required version.
  1140  // If dependency is not installed, return the latest available version that
  1141  // satisfies the dependency version.
  1142  // Return error if dependency version cannot be satisfied.
  1143  func calculateDependencyVersionToInstall(dependency Dependency, installedAddons itemInfoMap, availableAddons itemInfoMap) (string, error) {
  1144  	if dependency.Name == "" {
  1145  		return "", fmt.Errorf("dependency name cannot be empty")
  1146  	}
  1147  
  1148  	// if dependency is installed, return the installed version if it matches
  1149  	// the required version
  1150  	if installedAddons != nil {
  1151  		installedAddon, ok := installedAddons[dependency.Name]
  1152  		if ok {
  1153  			// versions length must be 1
  1154  			if len(installedAddon.AvailableVersions) != 1 {
  1155  				return "", errors.New("installedAddon.Versions length must be 1")
  1156  			}
  1157  			installedVersion := installedAddon.AvailableVersions[0]
  1158  
  1159  			if dependency.Version == "" {
  1160  				return installedVersion, nil
  1161  			}
  1162  
  1163  			match, _ := checkSemVer(installedVersion, dependency.Version)
  1164  			if match {
  1165  				return installedVersion, nil
  1166  			}
  1167  
  1168  			return "", fmt.Errorf("addon %s version '%s' does not match installed version '%s'",
  1169  				dependency.Name, dependency.Version, installedVersion)
  1170  		}
  1171  	}
  1172  
  1173  	availableAddon, ok := availableAddons[dependency.Name]
  1174  	if !ok {
  1175  		return "", fmt.Errorf("no available addon with name %s", dependency.Name)
  1176  	}
  1177  
  1178  	sortedVersions := sortVersionsDescending(availableAddon.AvailableVersions)
  1179  
  1180  	// if no version is specified, return the latest version
  1181  	if dependency.Version == "" {
  1182  		return sortedVersions[0], nil
  1183  	}
  1184  
  1185  	// check if the dependency version is satisfied
  1186  	var match bool
  1187  	for _, version := range sortedVersions {
  1188  		match, _ = checkSemVer(version, dependency.Version)
  1189  		if match {
  1190  			return version, nil
  1191  		}
  1192  	}
  1193  
  1194  	// no available version satisfies the dependency version
  1195  	return "", fmt.Errorf("no available addon with name %s and version '%s', available versions %s",
  1196  		dependency.Name, dependency.Version, availableAddon.AvailableVersions)
  1197  }
  1198  
  1199  func sortVersionsDescending(versions []string) []string {
  1200  	var sortedVersions []*semver.Version
  1201  	var sortedVersionStrings []string
  1202  	for _, v := range versions {
  1203  		var err error
  1204  		// Note: NewVersion attempts to convert SemVer-ish formats into SemVer
  1205  		parsedVersion, err := semver.NewVersion(v)
  1206  		if err == nil {
  1207  			sortedVersions = append(sortedVersions, parsedVersion)
  1208  		}
  1209  	}
  1210  	// sort versions in descending order
  1211  	sort.Sort(sort.Reverse(semver.Collection(sortedVersions)))
  1212  	for _, v := range sortedVersions {
  1213  		sortedVersionStrings = append(sortedVersionStrings, v.String())
  1214  	}
  1215  	return sortedVersionStrings
  1216  }
  1217  
  1218  // ItemInfoLister is an interface for Registry.ListAddonInfo() to enable easier
  1219  // testing with mocks.
  1220  type ItemInfoLister interface {
  1221  	ListAddonInfo() (map[string]ItemInfo, error)
  1222  }
  1223  
  1224  // listAvailableAddons fetches a collection of addons available in a list of
  1225  // registries. Returns a map of ItemInfo grouped by addon name.
  1226  func listAvailableAddons(registries []ItemInfoLister) (itemInfoMap, error) {
  1227  	availableAddons := make(itemInfoMap)
  1228  
  1229  	for _, registry := range registries {
  1230  		addons, err := registry.ListAddonInfo()
  1231  		if err != nil {
  1232  			return nil, err
  1233  		}
  1234  		availableAddons = mergeAddonInfoMaps(availableAddons, addons)
  1235  	}
  1236  	return availableAddons, nil
  1237  }
  1238  
  1239  func mergeAddonInfoMaps(existingAddons itemInfoMap, newAddons itemInfoMap) itemInfoMap {
  1240  	mergedAddons := existingAddons
  1241  	for _, newAddon := range newAddons {
  1242  		if existingAddon, ok := existingAddons[newAddon.Name]; ok {
  1243  			// merge addon versions
  1244  			existingVersions := existingAddon.AvailableVersions
  1245  			newVersions := newAddon.AvailableVersions
  1246  
  1247  			mergedVersionsSet := make(map[string]bool)
  1248  
  1249  			for _, item := range existingVersions {
  1250  				mergedVersionsSet[item] = true
  1251  			}
  1252  			for _, item := range newVersions {
  1253  				mergedVersionsSet[item] = true
  1254  			}
  1255  
  1256  			mergedVersions := make([]string, 0, len(mergedVersionsSet))
  1257  			for item := range mergedVersionsSet {
  1258  				mergedVersions = append(mergedVersions, item)
  1259  			}
  1260  
  1261  			mergedVersions = sortVersionsDescending(mergedVersions)
  1262  
  1263  			existingAddon.AvailableVersions = mergedVersions
  1264  			mergedAddons[existingAddon.Name] = existingAddon
  1265  		} else {
  1266  			mergedAddons[newAddon.Name] = newAddon
  1267  		}
  1268  	}
  1269  	return mergedAddons
  1270  }
  1271  
  1272  // listInstalledAddons fetches a collection of addons installed in the cluster.
  1273  // Returns a map of ItemInfo grouped by addon name.
  1274  func listInstalledAddons(ctx context.Context, k8sClient client.Client) (itemInfoMap, error) {
  1275  	installedAddons := make(itemInfoMap)
  1276  	// get all addons from cluster
  1277  	// for each addon, get the version and add it to addonVersions
  1278  	appList := &v1beta1.ApplicationList{}
  1279  	err := k8sClient.List(ctx, appList, client.InNamespace(types.DefaultKubeVelaNS), client.HasLabels{oam.LabelAddonName, oam.LabelAddonVersion})
  1280  	if err != nil {
  1281  		return nil, err
  1282  	}
  1283  	for _, app := range appList.Items {
  1284  		addonName := app.Labels[oam.LabelAddonName]
  1285  		addonVersion := app.Labels[oam.LabelAddonVersion]
  1286  		if addonName == "" || addonVersion == "" {
  1287  			continue
  1288  		}
  1289  		installedAddons[addonName] = ItemInfo{
  1290  			Name:              addonName,
  1291  			AvailableVersions: []string{addonVersion},
  1292  		}
  1293  	}
  1294  	return installedAddons, nil
  1295  }
  1296  
  1297  // checkDependencyNeedInstall checks whether dependency addon needs to be reinstalled
  1298  // If the dep addon is not installed, need to install
  1299  // If the dep addon is installed locally, don't need to install
  1300  // If the dep addon is installed from registry and not defined clusters, don't need to install
  1301  // If the dep addon is installed from registry and is defined clusters, and clusters value is nil, don't need to install
  1302  // If the dep addon is installed from registry and is defined clusters, and clusters value is not nil,
  1303  // and the upstream addon's clusters is nil, need to install
  1304  // If the dep addon is installed from registry and is defined clusters, and clusters value is not nil,
  1305  // and the upstream addon's clusters is not nil, the re-installation is based on whether the dep clusters value can contain the upstream clusters value
  1306  func checkDependencyNeedInstall(ctx context.Context, k8sClient client.Client, depName string, addonClusters []string) (bool, error) {
  1307  	depApp, err := FetchAddonRelatedApp(ctx, k8sClient, depName)
  1308  	if err != nil {
  1309  		if !apierrors.IsNotFound(err) {
  1310  			return false, err
  1311  		}
  1312  		// dependent addon is not exist, need to install it
  1313  		return true, nil
  1314  	}
  1315  
  1316  	// We should not automatically override addons installed locally by the user, so skip to reinstall it
  1317  	labels := depApp.GetLabels()
  1318  	installedRegistry := labels[oam.LabelAddonRegistry]
  1319  	isLocalRegistry := installedRegistry == LocalAddonRegistryName
  1320  	if isLocalRegistry {
  1321  		klog.Warningf("%v is installed locally. Please ensure that it has been installed to the required clusters, if not, please manually install it.", depName)
  1322  		return false, nil
  1323  	}
  1324  
  1325  	// Addons without the clusters parameter can only be installed on the local cluster. So we don't need to reinstall it
  1326  	hasClustersArg, hasClustersArgsErr := hasClustersParameters(ctx, k8sClient, depName)
  1327  	if hasClustersArgsErr != nil {
  1328  		return false, hasClustersArgsErr
  1329  	}
  1330  	if !hasClustersArg {
  1331  		return false, nil
  1332  	}
  1333  
  1334  	// get addon current parameter value
  1335  	depArgs, depArgsErr := GetAddonLegacyParameters(ctx, k8sClient, depName)
  1336  	if depArgsErr != nil && !apierrors.IsNotFound(depArgsErr) {
  1337  		return false, depArgsErr
  1338  	}
  1339  	clusterArgValue := depArgs[types.ClustersArg]
  1340  
  1341  	// nil clusters indicates that the dependent addon is installed on all clusters
  1342  	if clusterArgValue == nil {
  1343  		return false, nil
  1344  	}
  1345  
  1346  	// nil addonClusters indicates the addon will be installed all clusters, thus the dependent addon should also to be installed on all clusters.
  1347  	if addonClusters == nil {
  1348  		return true, nil
  1349  	}
  1350  
  1351  	// Determine whether the dependent addon's existing clusters can cover the new addon's clusters
  1352  	needInstallAddonDep, _ := hasNotCoveredClusters(clusterArgValue, addonClusters)
  1353  	return needInstallAddonDep, nil
  1354  }
  1355  
  1356  // getDependencyArgs get the dependent addon's install args according to the upstream addon's clusters parameter's value
  1357  // If dep addon has not defined clusters parameter, don't need to set clusters parameter value,
  1358  // If dep addon has not installed, set the clusters value same to addon's clusters
  1359  // If dep addon clusters parameter's value is nil, the dependent addon is installed on all clusters,
  1360  // don't need to reset clusters parameter value
  1361  // If dep addon has defined clusters parameter, and clusters is not nil, and addon clusters is nil,
  1362  // set clusters value as nil
  1363  // If dep addon has defined clusters parameter, and clusters is not nil, and addon clusters is not nil,
  1364  // set clusters value as the union of the dependent addon's and the upstream addon's clusters
  1365  func getDependencyArgs(ctx context.Context, k8sClient client.Client, depName string, addonClusters []string) (map[string]interface{}, error) {
  1366  	hasClustersArg, err := hasClustersParameters(ctx, k8sClient, depName)
  1367  	if err != nil {
  1368  		return nil, err
  1369  	}
  1370  	// dep addon is not install, installed it by assigning clusters value
  1371  	_, depErr := FetchAddonRelatedApp(ctx, k8sClient, depName)
  1372  	if depErr != nil {
  1373  		if !apierrors.IsNotFound(depErr) {
  1374  			return nil, err
  1375  		}
  1376  		depArgs := map[string]interface{}{}
  1377  		if addonClusters != nil {
  1378  			depArgs = map[string]interface{}{
  1379  				types.ClustersArg: addonClusters,
  1380  			}
  1381  		}
  1382  		return depArgs, nil
  1383  	}
  1384  
  1385  	// dep addon is installed
  1386  	depArgs, depArgsErr := GetAddonLegacyParameters(ctx, k8sClient, depName)
  1387  	if depArgsErr != nil && !apierrors.IsNotFound(depArgsErr) {
  1388  		return nil, depArgsErr
  1389  	}
  1390  	if !hasClustersArg || depArgs[types.ClustersArg] == nil {
  1391  		return depArgs, nil
  1392  	}
  1393  	if addonClusters == nil {
  1394  		delete(depArgs, types.ClustersArg)
  1395  	} else {
  1396  		clusterArgValue := depArgs[types.ClustersArg]
  1397  		notCovered, depClusters := hasNotCoveredClusters(clusterArgValue, addonClusters)
  1398  		if notCovered {
  1399  			depArgs[types.ClustersArg] = depClusters
  1400  		}
  1401  	}
  1402  	return depArgs, nil
  1403  }
  1404  
  1405  // hasClustersParameters checks whether the addon defines the clusters parameter.
  1406  // If the addon has been installed, get addon package from the installed registry,
  1407  // Otherwise, get addon package from one of registries where this addon exists.
  1408  func hasClustersParameters(ctx context.Context, k8sClient client.Client, addonName string) (bool, error) {
  1409  	var installedRegistry []string
  1410  	depApp, err := FetchAddonRelatedApp(ctx, k8sClient, addonName)
  1411  	if err == nil {
  1412  		labels := depApp.GetLabels()
  1413  		registryName, ok := labels[oam.LabelAddonRegistry]
  1414  		if ok {
  1415  			installedRegistry = []string{registryName}
  1416  		}
  1417  	}
  1418  	addonPackages, err := FindAddonPackagesDetailFromRegistry(context.Background(), k8sClient, []string{addonName}, installedRegistry)
  1419  	// If the state of addon is not disabled, we don't check the error, because it could be installed from local.
  1420  	if err != nil {
  1421  		return false, err
  1422  	}
  1423  	var addonPackage *WholeAddonPackage
  1424  	if len(addonPackages) != 0 {
  1425  		addonPackage = addonPackages[0]
  1426  	}
  1427  	if addonPackage.APISchema == nil {
  1428  		return false, nil
  1429  	}
  1430  	schemas := addonPackage.APISchema.Properties
  1431  	_, hasClusters := schemas[types.ClustersArg]
  1432  	return hasClusters, nil
  1433  }
  1434  
  1435  // hasNotCoveredClusters check if the clusterArgsValue can cover the values of addonClusters,
  1436  // and if not covered, also return the merged clusters array
  1437  func hasNotCoveredClusters(clusterArgValue interface{}, addonClusters []string) (bool, []string) {
  1438  	var needInstallAddonDep = false
  1439  	var depClusters []string
  1440  	originClusters := clusterArgValue.([]interface{})
  1441  	for _, r := range originClusters {
  1442  		depClusters = append(depClusters, r.(string))
  1443  	}
  1444  	for _, addonCluster := range addonClusters {
  1445  		if !stringslices.Contains(depClusters, addonCluster) {
  1446  			depClusters = append(depClusters, addonCluster)
  1447  			needInstallAddonDep = true
  1448  		}
  1449  	}
  1450  	return needInstallAddonDep, depClusters
  1451  }
  1452  
  1453  // checkDependency checks if addon's dependency
  1454  func (h *Installer) checkDependency(addon *InstallPackage) ([]string, error) {
  1455  	var app v1beta1.Application
  1456  	var needEnable []string
  1457  	for _, dep := range addon.Dependencies {
  1458  		err := h.cli.Get(h.ctx, client.ObjectKey{
  1459  			Namespace: types.DefaultKubeVelaNS,
  1460  			Name:      addonutil.Addon2AppName(dep.Name),
  1461  		}, &app)
  1462  		if err == nil {
  1463  			continue
  1464  		}
  1465  		if !apierrors.IsNotFound(err) {
  1466  			return nil, err
  1467  		}
  1468  		needEnable = append(needEnable, dep.Name)
  1469  	}
  1470  	return needEnable, nil
  1471  }
  1472  
  1473  // createOrUpdate will return true if updated
  1474  func (h *Installer) createOrUpdate(app *v1beta1.Application) (bool, error) {
  1475  	// Set the publish version for the addon application
  1476  	oam.SetPublishVersion(app, util.GenerateVersion("addon"))
  1477  	var existApp v1beta1.Application
  1478  	err := h.cli.Get(h.ctx, client.ObjectKey{Name: app.Name, Namespace: app.Namespace}, &existApp)
  1479  	if apierrors.IsNotFound(err) {
  1480  		return false, h.cli.Create(h.ctx, app)
  1481  	}
  1482  	if err != nil {
  1483  		return false, err
  1484  	}
  1485  	existApp.Spec = app.Spec
  1486  	existApp.Labels = app.Labels
  1487  	existApp.Annotations = app.Annotations
  1488  	err = h.cli.Update(h.ctx, &existApp)
  1489  	if err != nil {
  1490  		klog.Errorf("fail to create application: %v", err)
  1491  		return false, errors.Wrap(err, "fail to create application")
  1492  	}
  1493  	existApp.DeepCopyInto(app)
  1494  	return true, nil
  1495  }
  1496  
  1497  func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
  1498  	app, auxiliaryOutputs, err := RenderApp(h.ctx, addon, h.cli, h.args)
  1499  	if err != nil {
  1500  		return errors.Wrap(err, "render addon application fail")
  1501  	}
  1502  	appName, err := determineAddonAppName(h.ctx, h.cli, h.addon.Name)
  1503  	if err != nil {
  1504  		return err
  1505  	}
  1506  	app.Name = appName
  1507  
  1508  	app.SetLabels(util.MergeMapOverrideWithDst(app.GetLabels(), map[string]string{oam.LabelAddonRegistry: h.r.Name}))
  1509  
  1510  	// Step1: Render the definitions
  1511  	defs, err := RenderDefinitions(addon, h.config)
  1512  	if err != nil {
  1513  		return errors.Wrap(err, "render addon definitions fail")
  1514  	}
  1515  
  1516  	if !h.overrideDefs {
  1517  		existDefs, err := checkConflictDefs(h.ctx, h.cli, defs, app.Name)
  1518  		if err != nil {
  1519  			return err
  1520  		}
  1521  		if len(existDefs) != 0 {
  1522  			return produceDefConflictError(existDefs)
  1523  		}
  1524  	}
  1525  
  1526  	// Step2: Render the config templates
  1527  	templates, err := RenderConfigTemplates(addon, h.cli)
  1528  	if err != nil {
  1529  		return errors.Wrap(err, "render the config template fail")
  1530  	}
  1531  
  1532  	// Step3: Render the definition schemas
  1533  	schemas, err := RenderDefinitionSchema(addon)
  1534  	if err != nil {
  1535  		return errors.Wrap(err, "render addon definitions' schema fail")
  1536  	}
  1537  
  1538  	// Step4: Render the velaQL views
  1539  	views, err := RenderViews(addon)
  1540  	if err != nil {
  1541  		return errors.Wrap(err, "render addon views fail")
  1542  	}
  1543  
  1544  	if err := passDefInAppAnnotation(defs, app); err != nil {
  1545  		return errors.Wrapf(err, "cannot pass definition to addon app's annotation")
  1546  	}
  1547  
  1548  	if h.dryRun {
  1549  		result, err := yaml.Marshal(app)
  1550  		if err != nil {
  1551  			return errors.Wrapf(err, "dry-run marshal app into yaml %s", app.Name)
  1552  		}
  1553  		h.dryRunBuff.Write(result)
  1554  		h.dryRunBuff.WriteString("\n")
  1555  	} else {
  1556  		updated, err := h.createOrUpdate(app)
  1557  		if err != nil {
  1558  			return err
  1559  		}
  1560  		if updated {
  1561  			h.installerRuntime["upgrade"] = true
  1562  		}
  1563  	}
  1564  
  1565  	auxiliaryOutputs = append(auxiliaryOutputs, defs...)
  1566  	auxiliaryOutputs = append(auxiliaryOutputs, templates...)
  1567  	auxiliaryOutputs = append(auxiliaryOutputs, schemas...)
  1568  	auxiliaryOutputs = append(auxiliaryOutputs, views...)
  1569  
  1570  	for _, o := range auxiliaryOutputs {
  1571  		// bind-component means the content is related with the component
  1572  		// if component not exists, the resources shouldn't be applied
  1573  		if !checkBondComponentExist(*o, *app) {
  1574  			continue
  1575  		}
  1576  		if h.dryRun {
  1577  			result, err := yaml.Marshal(o)
  1578  			if err != nil {
  1579  				return errors.Wrapf(err, "dry-run marshal auxiliary object into yaml %s", o.GetName())
  1580  			}
  1581  			h.dryRunBuff.WriteString("---\n")
  1582  			h.dryRunBuff.Write(result)
  1583  			h.dryRunBuff.WriteString("\n")
  1584  			continue
  1585  		}
  1586  		addOwner(o, app)
  1587  		err = h.apply.Apply(h.ctx, o, apply.DisableUpdateAnnotation())
  1588  		if err != nil {
  1589  			return err
  1590  		}
  1591  	}
  1592  
  1593  	if h.dryRun {
  1594  		fmt.Print(h.dryRunBuff.String())
  1595  		return nil
  1596  	}
  1597  
  1598  	if h.args != nil && len(h.args) > 0 {
  1599  		sec := RenderArgsSecret(addon, h.args)
  1600  		addOwner(sec, app)
  1601  		err = h.apply.Apply(h.ctx, sec, apply.DisableUpdateAnnotation())
  1602  		if err != nil {
  1603  			return err
  1604  		}
  1605  	} else {
  1606  		// delete addon args secret file
  1607  		deleteErr := deleteArgsSecret(h.ctx, h.cli, addon.Name)
  1608  		if deleteErr != nil {
  1609  			return deleteErr
  1610  		}
  1611  	}
  1612  	return nil
  1613  }
  1614  
  1615  func (h *Installer) renderNotes(addon *InstallPackage) (string, error) {
  1616  	if len(addon.Notes.Data) == 0 {
  1617  		return "", nil
  1618  	}
  1619  	r := addonCueTemplateRender{
  1620  		addon:     addon,
  1621  		inputArgs: h.args,
  1622  		contextInfo: map[string]interface{}{
  1623  			"installer": h.installerRuntime,
  1624  		},
  1625  	}
  1626  	contextFile, err := r.formatContext()
  1627  	if err != nil {
  1628  		return "", err
  1629  	}
  1630  	notesFile := contextFile + "\n" + addon.Notes.Data
  1631  	val, err := value.NewValue(notesFile, nil, "")
  1632  	if err != nil {
  1633  		return "", errors.Wrap(err, "build values for NOTES.cue")
  1634  	}
  1635  	notes, err := val.LookupValue(KeyWordNotes)
  1636  	if err != nil {
  1637  		return "", errors.Wrap(err, "look up notes in NOTES.cue")
  1638  	}
  1639  	notesStr, err := notes.CueValue().String()
  1640  	if err != nil {
  1641  		return "", errors.Wrap(err, "convert notes to string")
  1642  	}
  1643  	return notesStr, nil
  1644  }
  1645  
  1646  // this func will handle such two case
  1647  // 1. if last apply failed an workflow have suspend, this func will continue the workflow
  1648  // 2. restart the workflow, if the new cluster have been added in KubeVela
  1649  func (h *Installer) continueOrRestartWorkflow() error {
  1650  	if h.dryRun {
  1651  		return nil
  1652  	}
  1653  	app, err := FetchAddonRelatedApp(h.ctx, h.cli, h.addon.Name)
  1654  	if err != nil {
  1655  		return err
  1656  	}
  1657  
  1658  	switch {
  1659  	// this case means user add a new cluster and user want to restart workflow to dispatch addon resources to new cluster
  1660  	// re-apply app won't help app restart workflow
  1661  	case app.Status.Phase == common2.ApplicationRunning:
  1662  		// we can use retry on conflict here in CLI, because we want to update the status in this CLI operation.
  1663  		return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
  1664  			if err = h.cli.Get(h.ctx, client.ObjectKey{Namespace: app.Namespace, Name: app.Name}, app); err != nil {
  1665  				return
  1666  			}
  1667  			app.Status.Workflow = nil
  1668  			return h.cli.Status().Update(h.ctx, app)
  1669  		})
  1670  	// this case means addon last installation meet some error and workflow has been suspended by app controller
  1671  	// re-apply app won't help app workflow continue
  1672  	case app.Status.Workflow != nil && app.Status.Workflow.Suspend:
  1673  		// we can use retry on conflict here in CLI, because we want to update the status in this CLI operation.
  1674  		return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
  1675  			if err = h.cli.Get(h.ctx, client.ObjectKey{Namespace: app.Namespace, Name: app.Name}, app); err != nil {
  1676  				return
  1677  			}
  1678  			mergePatch := client.MergeFrom(app.DeepCopy())
  1679  			app.Status.Workflow.Suspend = false
  1680  			return h.cli.Status().Patch(h.ctx, app, mergePatch)
  1681  		})
  1682  	}
  1683  	return nil
  1684  }
  1685  
  1686  // getAddonVersionMeetSystemRequirement return the addon's latest version which meet the system requirements
  1687  func (h *Installer) getAddonVersionMeetSystemRequirement(addonName string) string {
  1688  	if h.r != nil && IsVersionRegistry(*h.r) {
  1689  		versionedRegistry := BuildVersionedRegistry(h.r.Name, h.r.Helm.URL, &common.HTTPOption{
  1690  			Username: h.r.Helm.Username,
  1691  			Password: h.r.Helm.Password,
  1692  		})
  1693  		versions, err := versionedRegistry.GetAddonAvailableVersion(addonName)
  1694  		if err != nil {
  1695  			return ""
  1696  		}
  1697  		for _, version := range versions {
  1698  			req := LoadSystemRequirements(version.Annotations)
  1699  			if checkAddonVersionMeetRequired(h.ctx, req, h.cli, h.dc) == nil {
  1700  				return version.Version
  1701  			}
  1702  		}
  1703  	}
  1704  	return ""
  1705  }
  1706  
  1707  func addOwner(child *unstructured.Unstructured, app *v1beta1.Application) {
  1708  	child.SetOwnerReferences(append(child.GetOwnerReferences(),
  1709  		*metav1.NewControllerRef(app, v1beta1.ApplicationKindVersionKind)))
  1710  }
  1711  
  1712  // determine app name, if app is already exist, use the application name
  1713  func determineAddonAppName(ctx context.Context, cli client.Client, addonName string) (string, error) {
  1714  	app, err := FetchAddonRelatedApp(ctx, cli, addonName)
  1715  	if err != nil {
  1716  		if !apierrors.IsNotFound(err) {
  1717  			return "", err
  1718  		}
  1719  		// if the app still not exist, use addon-{addonName}
  1720  		return addonutil.Addon2AppName(addonName), nil
  1721  	}
  1722  	return app.Name, nil
  1723  }
  1724  
  1725  // FetchAddonRelatedApp will fetch the addon related app, this func will use NamespacedName(vela-system, addon-addonName) to get app
  1726  // if not find will try to get 1.1 legacy addon related app by using NamespacedName(vela-system, `addonName`)
  1727  func FetchAddonRelatedApp(ctx context.Context, cli client.Client, addonName string) (*v1beta1.Application, error) {
  1728  	app := &v1beta1.Application{}
  1729  	if err := cli.Get(ctx, types2.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: addonutil.Addon2AppName(addonName)}, app); err != nil {
  1730  		if !apierrors.IsNotFound(err) {
  1731  			return nil, err
  1732  		}
  1733  		// for 1.1 addon app compatibility code
  1734  		if err := cli.Get(ctx, types2.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: addonName}, app); err != nil {
  1735  			return nil, err
  1736  		}
  1737  	}
  1738  	return app, nil
  1739  }
  1740  
  1741  // checkAddonVersionMeetRequired will check the version of cli/ux and kubevela-core-controller whether meet the addon requirement, if not will return an error
  1742  // please notice that this func is for check production environment which vela cli/ux or vela core is officalVersion
  1743  // if version is for test or debug eg: latest/commit-id/branch-name this func will return nil error
  1744  func checkAddonVersionMeetRequired(ctx context.Context, require *SystemRequirements, k8sClient client.Client, dc *discovery.DiscoveryClient) error {
  1745  	if require == nil {
  1746  		return nil
  1747  	}
  1748  
  1749  	// if not semver version, bypass check cli/ux. eg: {branch name/git commit id/UNKNOWN}
  1750  	if version2.IsOfficialKubeVelaVersion(version2.VelaVersion) {
  1751  		res, err := checkSemVer(version2.VelaVersion, require.VelaVersion)
  1752  		if err != nil {
  1753  			return err
  1754  		}
  1755  		if !res {
  1756  			return fmt.Errorf("vela cli/ux version: %s  require: %s", version2.VelaVersion, require.VelaVersion)
  1757  		}
  1758  	}
  1759  
  1760  	// check vela core controller version
  1761  	imageVersion, err := fetchVelaCoreImageTag(ctx, k8sClient)
  1762  	if err != nil {
  1763  		return err
  1764  	}
  1765  
  1766  	// if not semver version, bypass check vela-core.
  1767  	if version2.IsOfficialKubeVelaVersion(imageVersion) {
  1768  		res, err := checkSemVer(imageVersion, require.VelaVersion)
  1769  		if err != nil {
  1770  			return err
  1771  		}
  1772  		if !res {
  1773  			return fmt.Errorf("the vela core controller: %s require: %s", imageVersion, require.VelaVersion)
  1774  		}
  1775  	}
  1776  
  1777  	// discovery client is nil so bypass check kubernetes version
  1778  	if dc == nil {
  1779  		return nil
  1780  	}
  1781  
  1782  	k8sVersion, err := dc.ServerVersion()
  1783  	if err != nil {
  1784  		return err
  1785  	}
  1786  	// if not semver version, bypass check kubernetes version.
  1787  	if version2.IsOfficialKubeVelaVersion(k8sVersion.GitVersion) {
  1788  		res, err := checkSemVer(k8sVersion.GitVersion, require.KubernetesVersion)
  1789  		if err != nil {
  1790  			return err
  1791  		}
  1792  
  1793  		if !res {
  1794  			return fmt.Errorf("the kubernetes version %s require: %s", k8sVersion.GitVersion, require.KubernetesVersion)
  1795  		}
  1796  	}
  1797  
  1798  	return nil
  1799  }
  1800  
  1801  func checkSemVer(actual string, require string) (bool, error) {
  1802  	if len(require) == 0 {
  1803  		return true, nil
  1804  	}
  1805  	semVer := strings.TrimPrefix(actual, "v")
  1806  	l := strings.ReplaceAll(require, "v", " ")
  1807  	constraint, err := semver.NewConstraint(l)
  1808  	if err != nil {
  1809  		klog.Errorf("fail to new constraint: %s", err.Error())
  1810  		return false, err
  1811  	}
  1812  	v, err := semver.NewVersion(semVer)
  1813  	if err != nil {
  1814  		klog.Errorf("fail to new version %s: %s", semVer, err.Error())
  1815  		return false, err
  1816  	}
  1817  	if constraint.Check(v) {
  1818  		return true, nil
  1819  	}
  1820  	if strings.Contains(actual, "-") && !strings.Contains(require, "-") {
  1821  		semVer := strings.TrimPrefix(actual[:strings.Index(actual, "-")], "v") // nolint
  1822  		if strings.Contains(require, ">=") && require[strings.Index(require, "=")+1:] == semVer {
  1823  			// for case: `actual` is 1.5.0-beta.1 require is >=`1.5.0`
  1824  			return false, nil
  1825  		}
  1826  		v, err := semver.NewVersion(semVer)
  1827  		if err != nil {
  1828  			klog.Errorf("fail to new version %s: %s", semVer, err.Error())
  1829  			return false, err
  1830  		}
  1831  		if constraint.Check(v) {
  1832  			return true, nil
  1833  		}
  1834  	}
  1835  	return false, nil
  1836  }
  1837  
  1838  func fetchVelaCoreImageTag(ctx context.Context, k8sClient client.Client) (string, error) {
  1839  	deployList := &appsv1.DeploymentList{}
  1840  	if err := k8sClient.List(ctx, deployList, client.MatchingLabels{oam.LabelControllerName: oam.ApplicationControllerName}); err != nil {
  1841  		return "", err
  1842  	}
  1843  	deploy := appsv1.Deployment{}
  1844  	if len(deployList.Items) == 0 {
  1845  		// backward compatible logic old version which vela-core controller has no this label
  1846  		if err := k8sClient.Get(ctx, types2.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: types.KubeVelaControllerDeployment}, &deploy); err != nil {
  1847  			if apierrors.IsNotFound(err) {
  1848  				return "", errors.New("can't find a running KubeVela instance, please install it first")
  1849  			}
  1850  			return "", err
  1851  		}
  1852  	} else {
  1853  		deploy = deployList.Items[0]
  1854  	}
  1855  
  1856  	var tag string
  1857  	for _, c := range deploy.Spec.Template.Spec.Containers {
  1858  		if c.Name == types.DefaultKubeVelaReleaseName {
  1859  			l := strings.Split(c.Image, ":")
  1860  			if len(l) == 1 {
  1861  				// if tag is empty mean use latest image
  1862  				return "latest", nil
  1863  			}
  1864  			tag = l[1]
  1865  		}
  1866  	}
  1867  	return tag, nil
  1868  }
  1869  
  1870  // PackageAddon package vela addon directory into a helm chart compatible archive and return its absolute path
  1871  func PackageAddon(addonDictPath string) (string, error) {
  1872  	// save the Chart.yaml file in order to be compatible with helm chart
  1873  	err := MakeChartCompatible(addonDictPath, true)
  1874  	if err != nil {
  1875  		return "", err
  1876  	}
  1877  
  1878  	ch, err := loader.LoadDir(addonDictPath)
  1879  	if err != nil {
  1880  		return "", err
  1881  	}
  1882  
  1883  	dest, err := os.Getwd()
  1884  	if err != nil {
  1885  		return "", err
  1886  	}
  1887  	archive, err := chartutil.Save(ch, dest)
  1888  	if err != nil {
  1889  		return "", err
  1890  	}
  1891  	return archive, nil
  1892  }
  1893  
  1894  // GetAddonLegacyParameters get addon's legacy parameters, that is stored in Secret
  1895  func GetAddonLegacyParameters(ctx context.Context, k8sClient client.Client, addonName string) (map[string]interface{}, error) {
  1896  	var sec v1.Secret
  1897  	err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: addonutil.Addon2SecName(addonName)}, &sec)
  1898  	if err != nil {
  1899  		return nil, err
  1900  	}
  1901  	args, err := FetchArgsFromSecret(&sec)
  1902  	if err != nil {
  1903  		return nil, err
  1904  	}
  1905  	return args, nil
  1906  }
  1907  
  1908  // MergeAddonInstallArgs merge addon's legacy parameter and new input args
  1909  func MergeAddonInstallArgs(ctx context.Context, k8sClient client.Client, addonName string, args map[string]interface{}) (map[string]interface{}, error) {
  1910  	legacyParams, err := GetAddonLegacyParameters(ctx, k8sClient, addonName)
  1911  	if err != nil {
  1912  		if !apierrors.IsNotFound(err) {
  1913  			return nil, err
  1914  		}
  1915  		return args, nil
  1916  	}
  1917  
  1918  	if args == nil && legacyParams == nil {
  1919  		return args, nil
  1920  	}
  1921  
  1922  	r := make(map[string]interface{})
  1923  	if err := mergo.Merge(&r, legacyParams, mergo.WithOverride); err != nil {
  1924  		return nil, err
  1925  	}
  1926  
  1927  	if err := mergo.Merge(&r, args, mergo.WithOverride); err != nil {
  1928  		return nil, err
  1929  	}
  1930  	return r, nil
  1931  }