github.com/oam-dev/kubevela@v1.9.11/pkg/addon/helper.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  	"context"
    21  	"fmt"
    22  	"path/filepath"
    23  
    24  	"github.com/pkg/errors"
    25  	v1 "k8s.io/api/core/v1"
    26  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    27  	"k8s.io/client-go/discovery"
    28  	"k8s.io/client-go/rest"
    29  	"sigs.k8s.io/controller-runtime/pkg/client"
    30  
    31  	commontypes "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    32  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    33  	"github.com/oam-dev/kubevela/apis/types"
    34  	"github.com/oam-dev/kubevela/pkg/multicluster"
    35  	"github.com/oam-dev/kubevela/pkg/oam"
    36  	addonutil "github.com/oam-dev/kubevela/pkg/utils/addon"
    37  	"github.com/oam-dev/kubevela/pkg/utils/apply"
    38  	"github.com/oam-dev/kubevela/pkg/utils/common"
    39  )
    40  
    41  const (
    42  	// disabled indicates the addon is disabled
    43  	disabled = "disabled"
    44  	// enabled indicates the addon is enabled
    45  	enabled = "enabled"
    46  	// enabling indicates the addon is enabling
    47  	enabling = "enabling"
    48  	// disabling indicates the addon related app is deleting
    49  	disabling = "disabling"
    50  	// suspend indicates the addon related app is suspended
    51  	suspend = "suspend"
    52  )
    53  
    54  // EnableAddon will enable addon with dependency check, source is where addon from.
    55  func EnableAddon(ctx context.Context, name string, version string, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r Registry, args map[string]interface{}, cache *Cache, registries []Registry, opts ...InstallOption) (string, error) {
    56  	h := NewAddonInstaller(ctx, cli, discoveryClient, apply, config, &r, args, cache, registries, opts...)
    57  	pkg, err := h.loadInstallPackage(name, version)
    58  	if err != nil {
    59  		return "", err
    60  	}
    61  	if err := validateAddonPackage(pkg); err != nil {
    62  		return "", errors.Wrap(err, fmt.Sprintf("failed to enable addon: %s", name))
    63  	}
    64  	return h.enableAddon(pkg)
    65  }
    66  
    67  // DisableAddon will disable addon from cluster.
    68  func DisableAddon(ctx context.Context, cli client.Client, name string, config *rest.Config, force bool) error {
    69  	app, err := FetchAddonRelatedApp(ctx, cli, name)
    70  	// if app not exist, report error
    71  	if err != nil {
    72  		return err
    73  	}
    74  
    75  	if !force {
    76  		var usingAddonApp []v1beta1.Application
    77  		usingAddonApp, err = checkAddonHasBeenUsed(ctx, cli, name, *app, config)
    78  		if err != nil {
    79  			return err
    80  		}
    81  		if len(usingAddonApp) != 0 {
    82  			return errors.New(appsDependsOnAddonErrInfo(usingAddonApp))
    83  		}
    84  	}
    85  
    86  	if err := cli.Delete(ctx, app); err != nil {
    87  		return err
    88  	}
    89  	return nil
    90  }
    91  
    92  // EnableAddonByLocalDir enable an addon from local dir
    93  func EnableAddonByLocalDir(ctx context.Context, name string, dir string, cli client.Client, dc *discovery.DiscoveryClient, applicator apply.Applicator, config *rest.Config, args map[string]interface{}, opts ...InstallOption) (string, error) {
    94  	absDir, err := filepath.Abs(dir)
    95  	if err != nil {
    96  		return "", err
    97  	}
    98  	r := localReader{dir: absDir, name: name}
    99  	metas, err := r.ListAddonMeta()
   100  	if err != nil {
   101  		return "", err
   102  	}
   103  	meta := metas[r.name]
   104  	UIData, err := GetUIDataFromReader(r, &meta, UIMetaOptions)
   105  	if err != nil {
   106  		return "", err
   107  	}
   108  	pkg, err := GetInstallPackageFromReader(r, &meta, UIData)
   109  	if err != nil {
   110  		return "", err
   111  	}
   112  	if err := validateAddonPackage(pkg); err != nil {
   113  		return "", errors.Wrap(err, fmt.Sprintf("failed to enable addon by local dir: %s", dir))
   114  	}
   115  	h := NewAddonInstaller(ctx, cli, dc, applicator, config, &Registry{Name: LocalAddonRegistryName}, args, nil, nil, opts...)
   116  	needEnableAddonNames, err := h.checkDependency(pkg)
   117  	if err != nil {
   118  		return "", err
   119  	}
   120  	if len(needEnableAddonNames) > 0 {
   121  		return "", fmt.Errorf("you must first enable dependencies: %v", needEnableAddonNames)
   122  	}
   123  	return h.enableAddon(pkg)
   124  }
   125  
   126  // GetAddonStatus is general func for cli and apiServer get addon status
   127  func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status, error) {
   128  	var addonStatus Status
   129  
   130  	app, err := FetchAddonRelatedApp(ctx, cli, name)
   131  	if err != nil {
   132  		if apierrors.IsNotFound(err) {
   133  			addonStatus.AddonPhase = disabled
   134  			return addonStatus, nil
   135  		}
   136  		return addonStatus, err
   137  	}
   138  	labels := app.GetLabels()
   139  	addonStatus.AppStatus = &app.Status
   140  	addonStatus.InstalledVersion = labels[oam.LabelAddonVersion]
   141  	addonStatus.InstalledRegistry = labels[oam.LabelAddonRegistry]
   142  
   143  	var clusters = make(map[string]map[string]interface{})
   144  	for _, r := range app.Status.AppliedResources {
   145  		if r.Cluster == "" {
   146  			r.Cluster = multicluster.ClusterLocalName
   147  		}
   148  		// TODO(wonderflow): we should collect all the necessary information as observability, currently we only collect cluster name
   149  		clusters[r.Cluster] = make(map[string]interface{})
   150  	}
   151  	addonStatus.Clusters = clusters
   152  
   153  	if app.Status.Workflow != nil && app.Status.Workflow.Suspend {
   154  		addonStatus.AddonPhase = suspend
   155  		return addonStatus, nil
   156  	}
   157  
   158  	// Get addon parameters
   159  	var sec v1.Secret
   160  	err = cli.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: addonutil.Addon2SecName(name)}, &sec)
   161  	if err != nil {
   162  		// Not found error can be ignored. Others can't.
   163  		if !apierrors.IsNotFound(err) {
   164  			return addonStatus, err
   165  		}
   166  	} else {
   167  		// Although normally `else` is not preferred, we must use `else` here.
   168  		args, err := FetchArgsFromSecret(&sec)
   169  		if err != nil {
   170  			return addonStatus, err
   171  		}
   172  		addonStatus.Parameters = args
   173  	}
   174  
   175  	switch app.Status.Phase {
   176  	case commontypes.ApplicationRunning:
   177  		addonStatus.AddonPhase = enabled
   178  		return addonStatus, nil
   179  	case commontypes.ApplicationDeleting:
   180  		addonStatus.AddonPhase = disabling
   181  		return addonStatus, nil
   182  	default:
   183  		addonStatus.AddonPhase = enabling
   184  		return addonStatus, nil
   185  	}
   186  }
   187  
   188  // FindAddonPackagesDetailFromRegistry find addons' WholeInstallPackage from registries, empty registryName indicates matching all
   189  func FindAddonPackagesDetailFromRegistry(ctx context.Context, k8sClient client.Client, addonNames []string, registryNames []string) ([]*WholeAddonPackage, error) {
   190  	var addons []*WholeAddonPackage
   191  	var registries []Registry
   192  
   193  	if len(addonNames) == 0 {
   194  		return nil, fmt.Errorf("no addon name specified")
   195  	}
   196  	registryDataStore := NewRegistryDataStore(k8sClient)
   197  
   198  	// Find matched registries
   199  	if len(registryNames) == 0 {
   200  		// Empty registryNames will match all registries
   201  		regs, err := registryDataStore.ListRegistries(ctx)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  		registries = regs
   206  	} else {
   207  		// Only match specified registries
   208  		for _, registryName := range registryNames {
   209  			r, err := registryDataStore.GetRegistry(ctx, registryName)
   210  			if err != nil {
   211  				continue
   212  			}
   213  			registries = append(registries, r)
   214  		}
   215  	}
   216  
   217  	if len(registries) == 0 {
   218  		return nil, ErrRegistryNotExist
   219  	}
   220  
   221  	// Found addons, for deduplication purposes
   222  	foundAddons := make(map[string]bool)
   223  	merge := func(addon *WholeAddonPackage) {
   224  		if _, ok := foundAddons[addon.Name]; !ok {
   225  			foundAddons[addon.Name] = true
   226  		}
   227  		addons = append(addons, addon)
   228  	}
   229  
   230  	// Find matched addons in registries
   231  	for _, r := range registries {
   232  		if IsVersionRegistry(r) {
   233  			vr := BuildVersionedRegistry(r.Name, r.Helm.URL, &common.HTTPOption{
   234  				Username:        r.Helm.Username,
   235  				Password:        r.Helm.Password,
   236  				InsecureSkipTLS: r.Helm.InsecureSkipTLS,
   237  			})
   238  			for _, addonName := range addonNames {
   239  				wholePackage, err := vr.GetDetailedAddon(ctx, addonName, "")
   240  				if err != nil {
   241  					continue
   242  				}
   243  				merge(wholePackage)
   244  			}
   245  		} else {
   246  			meta, err := r.ListAddonMeta()
   247  			if err != nil {
   248  				continue
   249  			}
   250  
   251  			for _, addonName := range addonNames {
   252  				sourceMeta, ok := meta[addonName]
   253  				if !ok {
   254  					continue
   255  				}
   256  				uiData, err := r.GetUIData(&sourceMeta, UIMetaOptions)
   257  				if err != nil {
   258  					continue
   259  				}
   260  				installPackage, err := r.GetInstallPackage(&sourceMeta, uiData)
   261  				if err != nil {
   262  					continue
   263  				}
   264  				// Combine UIData and InstallPackage into WholeAddonPackage
   265  				wholePackage := &WholeAddonPackage{
   266  					InstallPackage:    *installPackage,
   267  					APISchema:         uiData.APISchema,
   268  					Detail:            uiData.Detail,
   269  					AvailableVersions: uiData.AvailableVersions,
   270  					RegistryName:      uiData.RegistryName,
   271  				}
   272  				merge(wholePackage)
   273  			}
   274  		}
   275  	}
   276  
   277  	if len(addons) == 0 {
   278  		return nil, ErrNotExist
   279  	}
   280  
   281  	return addons, nil
   282  }
   283  
   284  // Status contain addon phase and related app status
   285  type Status struct {
   286  	AddonPhase string
   287  	AppStatus  *commontypes.AppStatus
   288  	// the status of multiple clusters
   289  	Clusters         map[string]map[string]interface{} `json:"clusters,omitempty"`
   290  	InstalledVersion string
   291  	Parameters       map[string]interface{}
   292  	// Where the addon is from. Can be empty if not installed.
   293  	InstalledRegistry string
   294  }